Dataslope logoDataslope

The Software Crisis

How procedural software became unmanageable at scale, and why the industry started looking for a new way to organize code

Before we look at a single line of Java, we have to answer a question that almost nobody asks: why does object-oriented programming exist at all? It was not handed down from on high. It is the answer to a very specific, very painful problem that the software industry ran into in the 1960s.

A short history of code getting bigger

In the early days of computing, programs were tiny. A complete program might be a few hundred lines of FORTRAN or assembly, written by one person, doing one thing — compute a ballistic trajectory, sort a payroll file, control a thermostat. Code at that size is easy to keep in your head.

Then computers got cheaper and faster, and the things we asked them to do got dramatically bigger.

By the late 1960s a single program could be hundreds of thousands of lines long, written by dozens of people over years, modified long after the original authors had left. The tools of the previous decade — global variables, GOTO statements, subroutines that all shared the same data — simply did not scale to that size.

In 1968, at a NATO conference in Garmisch, Germany, attendees gave this problem a name: the software crisis. Projects were running years late, budgets were exploding, and the resulting software was frequently late, slow, full of bugs, and almost impossible to change safely.

Where the term comes from

The phrase "software crisis" was used at the 1968 NATO Software Engineering Conference. The same conference is widely credited with introducing the term "software engineering" itself — the idea that building large systems was a discipline, not just typing.

What was so painful about procedural code?

The dominant style at the time is sometimes called procedural programming: a program is a list of procedures (functions) that share access to data, often via global variables. Let's look at a miniature version of what that felt like.

Code Block
Java 8 (Update 492)

Now imagine a real bank, with thousands of customers, dozens of procedures, and many programmers. Several painful patterns emerge:

  • Data has no owner. Any function in the program can mutate customerBalance. If a bug puts it in a bad state, the culprit could be anywhere.
  • Invariants are not protected. Nothing prevents withdraw from making the balance negative. The rule "balance must be ≥ 0" is not enforced by the data — it has to be remembered by every author of every function.
  • Procedures and data are scattered. There is no single place to look to find "everything about a customer."
  • Change cascades. Adding a new field to a customer means searching the entire codebase for places that read or write customer data. Miss one, and you have a bug.
  • Reuse is hard. To use the "customer logic" in a different program, you would have to lift out a tangle of globals and procedures that all reference each other.

A picture of the mess

It helps to visualize what a procedural system looks like as it grows.

Every procedure points at every piece of data. There are no walls. This is the diagram a senior engineer would draw on a whiteboard right before saying "...and that is why we cannot ship the new feature on time."

Symptoms the crisis produced

By the early 1970s, the industry had a vocabulary for the symptoms:

SymptomWhat it looked like in practice
Spaghetti codeTangled GOTOs and shared state; you couldn't trace one feature without reading the whole program
BrittlenessA small change in one place broke five things far away
Defect explosionMore code → more bugs, and the bugs were harder to localize
Long lead timesAdding "one more field" took weeks because nobody knew what it would break
Knowledge lossWhen the original author left, the system became read-only

What would a fix have to look like?

Stand back and the requirements for any solution become clear:

  1. Data should have an owner — code that touches a piece of data should be small, named, and findable.
  2. Invariants should be enforced by the type itself, not by every caller's discipline.
  3. Reusable units should be packageable, so you can lift "customer" out of one program and drop it into another.
  4. The structure of the code should mirror the structure of the problem, so a new engineer can navigate it by intuition.
  5. Large systems should compose out of smaller systems, not exist as one giant ball of procedures.

That list is the seed of object-oriented programming. An object will turn out to be exactly the thing that bundles data with the code that owns it, enforces its own invariants, can be packaged, mirrors something in the real world, and composes with other objects.

But before we get there, the field needed someone to actually invent that idea. That happens in the next page.

Test your understanding

QuestionSelect one

In a procedural system that uses lots of global variables, why are bugs especially hard to track down?

Because the compiler refuses to type-check global state

Because any function in the program can modify the data, so the culprit could be anywhere

Because global variables are stored on the heap instead of the stack

Because procedural languages have no if statements

QuestionSelect one

Which best describes what the term software crisis (NATO 1968) referred to?

A shortage of computers in industry

A specific virus that destroyed mainframe systems

A pattern of large software projects being late, over budget, buggy, and hard to change

The first appearance of open-source licensing disputes

QuestionSelect one

Which property would a solution to the software crisis most need?

Faster CPUs so procedural code runs quickly

A single global database that all functions share

Units of code that own their data, enforce their own rules, and can be composed into larger systems

More clever GOTO statements

On this page