
Before We Begin
Programming always brings a certain thought to mind: all the interesting, worthwhile problems have already been solved by those who came before, and what we do is build on top of what they left behind. The patterns and practices we casually invoke by name were forged in the field through the hard-won experience of our predecessors.
Yet when you look closely at the problems they solved and the software they built, much of it has since disappeared.
Countless programs have been written, but most no longer run. Some were replaced by complete rewrites; others outlived the environments needed to run them; still others survive only as source code, preserved in open-source archives like specimens in amber.
Software is a peculiar kind of artifact. Even after it is finished, it keeps being revised throughout its working life, and as time passes it gradually loses the form it had when it was first made.
If you replace the planks of an old ship one by one with new ones, can you still call it the same ship once every last piece has been swapped out?
This question, known as the Ship of Theseus, applies just as directly to software. Rewrite the code line by line and eventually nothing from the original remains, yet we still call it the same program.
So what exactly are we building? If a program can be called the same program even when none of the original code survives, then what we create is perhaps not the code itself, but something that allows the code to keep being changed.
The Software Industry and Its Crisis

(October 1968, NATO Science Committee — image source: Software Technology Group1)
Let us look for a starting point. Different people choose different ones, but I will begin with the Software Engineering Conference of 1968.
Software has always carried the word "crisis" close beside it. Software engineers may well be professionals whose job is, in part, to sound the alarm.
In October 1968, the NATO Science Committee convened a conference in Garmisch, Germany, to discuss the problems of software development. The name attached to that conference, "Software Engineering," was not a reference to an established discipline but a demand: a demand that building software, like other branches of engineering, required theoretical foundations and practical rigor.
Why did the crisis arise? Hardware performance was climbing rapidly, but the methods for building software could not keep pace. As things once thought impossible became feasible, people demanded larger and more complex systems; schedules slipped, budgets overran, and the bigger a program grew, the harder it became to keep its errors under control.
Does any of this sound familiar? The history of software might fairly be described as one long running struggle with schedule delays, cost overruns, and the problem of controlling complexity.
But should we confine this crisis to a problem of an era when technology was simply insufficient? Or would the problem disappear once technology advanced far enough?
What is interesting is that computer scientists of the time looked at this problem in quite different ways. Some debated the limits of complexity that humans could handle and pondered how programs ought to be decomposed, while others asked why the act of building programs was necessary in the first place.
So what did they think software actually was?
Let us begin with Edsger W. Dijkstra, who called for a "Humble Programmer" in his 1972 Turing Award lecture.
He was the one who, four years after the 1968 conference, confronted head-on the questions that conference had raised.

(Edsger W. Dijkstra)
1972: Dijkstra — Building Within the Bounds of Human Understanding
As a result of a long sequence of coincidences I entered the programming profession officially on the first spring morning of 1952 and as far as I have been able to trace, I was the first Dutchman to do so in my country. In retrospect the most amazing thing was the slowness with which, at least in my part of the world, the programming profession emerged, a slowness which is now hard to believe. But I am grateful for two vivid recollections from that period that establish that slowness beyond any doubt.
The Humble Programmer - 1972
Edsger W. Dijkstra's The Humble Programmer was his 1972 Turing Award lecture, published that October in Communications of the ACM. The humility it calls for refers to the attitude of a programmer who acknowledges the limits of their own cognitive capacity. The argument is that we should abandon the expectation that a sufficiently intelligent person can keep even the most complex program entirely in their head, and instead seek ways to structure programs so that they can be understood and reasoned about.2
The original text is a remarkably engaging read even today, and the following passage stands out as the most memorable of all.
The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague.
Telling a programmer to avoid clever tricks seems strange at first glance, because we tend to think of a great programmer as someone who solves complex problems with short, elegant code.
What, then, was Dijkstra pointing at? Not a lack of intelligence, but the limits inherent in human cognitive capacity itself.
So why did Dijkstra make the size of the skull a central concern? Computers were growing faster and capable of processing ever more instructions, but the human brain that must understand those programs was not advancing at the same pace. The solution, then, lies not in infinitely expanding human capacity but in building programs that can be understood within our existing, bounded capacity.
If a single short line of code carries more conceptual content, that is compression. But if it creates a puzzle the reader must solve, it is not compression but waste. The mere fact of writing the same computation with fewer symbols cannot distinguish the two. This is why Dijkstra flatly denies any conceptual relevance in the question "Can I write this with fewer symbols?" The conciseness he speaks of is not a count of symbols but conceptual clarity that lets the reader transparently follow the flow of logic.
Of course there are people who enjoy code golf, but that is, in any case, far from the kind of conciseness Dijkstra had in mind.
Let us bring back to mind the Ship of Theseus we discussed at the start. A program is continually revised over time, and the code originally written may disappear. Yet if the person who writes new code can understand the program's behavior and explain its correctness, there remains at least a chance of carrying on where the previous program left off. But what if the system is so densely compressed that it is extremely difficult to interpret?
Then maintenance becomes impossible. No matter how understandable a program is made, it will eventually need to be modified as time passes. New features are added, requirements change, and existing designs become obsolete as they no longer fit their environment.
So, going one step beyond simply making a program understandable, is there a way to preserve that understandability as much as possible even as the program changes?
To explore this question, let us turn to a paper published in 1972 by David Parnas.

(David L. Parnas)
2. 1972: Parnas - Centering on Decisions That Will Change, Not on the Order of Processing
In December of that same year, David L. Parnas published "On the Criteria To Be Used in Decomposing Systems into Modules." This paper goes beyond simply saying it is good to divide a program into modules, and shows that the results differ depending on what criteria you use to divide them.3
Every module in the second decomposition is characterized by its knowledge of a design decision which it hides from all others. Its interface or definition was chosen to reveal as little as possible about its inner workings.
As his example, he compares two ways of dividing a KWIC (Key Word in Context) index system into modules. One approach assigns a module to each processing step, such as input, circular shift, sorting, and output. The other hides design decisions that are likely to change, such as the storage format, inside a single module, and requires other modules to interact only through that module's interface.
In the second approach, module boundaries do not necessarily coincide with execution stages. Parnas characterized each module in the second decomposition as a "design decision hidden from other modules," and this criterion later came to be widely known as information hiding.
What is hidden here is the internal decisions that other modules have no need to know. If you change the storage representation and code that was coupled to that representation breaks in all directions, then even if the code is split across multiple files, it remains, from the perspective of change, essentially one monolithic piece.
The advantage demonstrated by the second decomposition of KWIC lies in the ability to confine the scope of modifications within the module that hides the storage representation.
This is, in a sense, Parnas's answer to the question left open in the preceding section. To preserve understanding even as a program changes, you must draw boundaries such that understanding any one part requires no knowledge of another part's internals. When examining the structure, you simultaneously ask which things are likely to change, which module hides that decision, and how far the ripple of a change is permitted to travel.
Parnas's Information Hiding is precisely a method for coping with this uncertainty. The goal is to prevent a change in a design decision from propagating throughout the entire program.
But let's think about this practically. We are not omniscient. We don't know what will happen even a minute from now. Speaking for myself, I had no idea a client was about to terminate our contract until thirty seconds before they delivered the news.
Parnas proposed hiding design decisions that are difficult or likely to change inside a module. But who decides what is likely to change?
One developer might believe the database is likely to be swapped out, while another thinks business rules will change frequently. And though seniority usually settles the debate, the point is that two developers can both follow the same principle of Information Hiding and still draw their module boundaries in completely different places.
Ultimately, what to hide and where to draw the boundaries is shaped by the designer's experience, knowledge, and expectations about the future, in other words, by the programmer's worldview.
What we call good design implicitly contains hypotheses about a future that has not yet occurred.
Let's return to the Ship of Theseus analogy. When we must repair a plank under uncertainty, we look for ways to avoid disturbing the keel, the ship's backbone. (For those unfamiliar with keels: watch One Piece, specifically the Water Seven arc. Anyone who doesn't shed a tear watching it is truly emotionally parched.) Parnas's Information Hiding closely resembles that approach. It draws a boundary between the planks and the keel so that replacing a plank does not shake the keel.
Yet let's push the question a bit deeper. Up to this point, we have been thinking about how to understand a program and how to prepare for change.
But why must software keep changing in the first place?
Why must the Ship of Theseus be continually repaired?
There were people who examined this question by studying how real software actually changes over time: Meir M. Lehman and László Belady.

(Meir M. Lehman)

(László Belady)
3. 1976: Lehman and Belady — How Does a Program Change?
László A. Belady was originally an operating systems person. In 1966, while researching page replacement for virtual memory at IBM, he proposed that when memory is full, the optimal strategy is to evict the page that will go unused for the longest time in the future. To this day, operating systems textbooks cover this as the OPT, MIN, or Bélády algorithm.
The interesting thing is that this algorithm cannot actually be implemented in practice, because it requires knowing in advance exactly which pages will be used and when. He was someone who assumed knowledge of the future in order to establish a theoretical optimal baseline, and there is something quietly striking about the fact that this same person joined forces with Meir M. Lehman to study the question of how software actually changes as time passes.
The two tracked how IBM's massive operating system OS/360 grew bloated and transformed across successive releases over several years, and published "A Model of Large Program Development" in 1976. Lehman then extended this research further, laying out the principles of software evolution in his 1980 paper "Programs, Life Cycles, and Laws of Software Evolution."45
Earlier, through Parnas, we arrived at the conclusion: "hide the parts likely to change behind a module interface." Lehman and Belady, however, step back and ask a more fundamental question. This is where the real shift in thinking begins.
But why can't software simply stay put? Why must it keep changing? Unlike a machine, its components don't wear down from friction and require repair.
In his 1980 paper, Lehman explains this seemingly endless cycle of modification that we all commonly experience, as follows.
"The installation of the program together with its associated system... change the very nature of the problem to be solved. The program has become a part of the world it models, it is embedded in it."
"Differences between data derived from observation and from computation may cause changes in the world view... It is the perception of users, analysts and/or programmers that has changed."
Programs, Life Cycles, and Laws of Software Evolution - 1980
These words lay bare the problems of modern programming in full view. We are still having conversations that our predecessors had sorted out more than fifty years ago. The interesting and rewarding work always falls to those who came before; the husks are left for those who follow. That is programming.
In any case, Lehman divided programs into three types: S-type, P-type, and E-type. Programs where the job is done once the mathematical specification of inputs and outputs is satisfied (S-type), or programs like a chess engine that operate under fixed rules but seek approximate solutions due to practical constraints (P-type), are rarely shaken when the world changes. The truly troublesome kind is the program that directly intervenes in real-world business processes and people's daily lives, namely the E-type (Embedded) program.5
Why do E-type programs require constant repair? Is it because we misunderstood the requirements from the start? Because we arrived at the wrong answer?
More precisely, it is because the moment a program enters the world, the world changes as a result of it.
Suppose you deliver a product search and recommendation algorithm to a mid-sized online retailer. At first, consumers will clearly find what they want more easily, and sellers, seeing their sales rise, will be thoroughly satisfied.
But as the recommendation algorithm runs, sellers listing products on the shopping platform begin stuffing their product titles with all manner of irrelevant keywords to boost views (keyword stuffing). Some of the cleverer sellers even write programs to do it! (And naturally, those programs usually come through me. I build shopping platform recommendation algorithms, but I also sell programs that can exploit those very algorithms. I supply software to both sides.)
The existing algorithm will then still surface these keyword-stuffed products as highly relevant results, and your client will call you up and say:
"Our recommendation accuracy has dropped, and all these strange products keep showing up — what happened?"
Did the program break? No. The program worked so well that it triggered profit-seeking behavior among sellers, disrupted consumers' browsing habits, and, as a result, reshaped the market's dynamics. Now the existing algorithm no longer fits the market it helped change.
Think back again to the Ship of Theseus. What is the ship's purpose? To sail. It normally sails in coastal waters, but a particularly high-performing ship might lead people to venture into rough open seas they never dared before, loading it with heavier cargo along the way, which is precisely why new planks must constantly be added. The ship expanded the range of sailing, and that expanded range forces the ship to be rebuilt in turn.
That is what makes the Laws of Software Evolution that Lehman formulated so remarkable.
"A program that is actually used must keep changing, or it will become progressively less useful (Continuing Change)."
"Unless deliberate effort is made to reduce complexity, a program's structure becomes increasingly more complex as it evolves (Increasing Complexity)."
We said to keep programs simple enough to fit inside a human skull; someone proposed defining things called modules to hide design decisions within them. Yet what Lehman and Belady observed is that no matter how well a program is structured or how carefully decisions are hidden, as long as a program lives and breathes in the real world, waves of change never stop rolling in and complexity inevitably surges.
At this point, one very fundamental doubt begins to surface. Could the reason that building and maintaining programs is such grueling work be that we have, from the very beginning, gotten the framework of how we think and the language we use fundamentally wrong?
Someone picked a fight with the very practice of listing and patching instructions one line at a time, in the order a machine executes them. That person was John Backus, who took the stage to accept the Turing Award in 1977.

(John Backus)
4. 1978: Backus — Thinking in Compositions of Computation Instead of Flows of Commands
Programmers are, at their core, a deeply dissatisfied breed. The history of programming is perhaps a history of fighting, perhaps a war against ideas. Each one is in the grip of their own private worldview, trying to bend the machine called a computer to their will.
Among them, John Backus stands out as peculiar. So worn down by the need to code in ways that conform to how machines operate, He set out to challenge the very way programmers express computation.
John Backus's "Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs" was published in August 1978, based on his 1977 Turing Award lecture. It is a piece in which the man who led the development of FORTRAN turns a critical eye on the familiar expressive conventions of imperative programming.6

a store, and a connecting tube that can transmit a single word between the CPU and the store (and send an address to the store). I propose to call this tube the *von Neumann bottleneck*. The task of a program is to change the contents of the store in some major way; when one considers that this task must be accomplished entirely by pumping single words back and forth through the von Neumann bottleneck, the reason for its name becomes clear.
What exactly did he criticize? His target was the way machines operate, reading and writing values in memory, and how that operational model comes to dominate the very way programmers think about programs. He called the conduit that shuttles one word at a time between the CPU and memory the "von Neumann bottleneck," and he argued that imperative languages, which assign values to variables one by one, import that same bottleneck into programmers' habits of thought.6
The core of the FP system he proposed as an alternative is combining 'What' instead of commanding 'How'. Functions are chained together in fixed combining forms, and the properties of those combinations are treated like an algebra in mathematics.
The combining forms include composition, construction, condition, apply-to-all, and insert (roughly analogous to today's fold), among others. Rather than tracing what was assigned to each variable in sequence, the goal is to describe how computations are connected and which transformations preserve meaning.
If Dijkstra acknowledged how small the human skull is and insisted that programs must be comprehensible within it, Backus went one step further. "Why should humans have to explain programs by tracing the machine's state changes one by one in the first place?" Where Dijkstra called for the 'Humble Programmer,' John Backus was, in effect, demanding that the machine (and the language) be humble before the human.
His FP system did not, of course, become the standard programming paradigm of today. Yet his way of thinking is deeply embedded throughout many of today's popular languages. Functional tools like map, filter, and reduce, which modern languages proudly offer, are now closer to defaults than options. This is how the staple questions of senior programmer interviews came to exist.
Let us return to the Ship of Theseus. (I know, I know, you're tired of this analogy, but what can I do, I made up my mind to use it all the way to the end.)
If Lehman and Belady described the inevitable fate of having to keep replacing the ship's planks as the environment changes, Backus was the one who questioned the very structure of the hammer and saw (the language) used to replace those planks.
If we must repair the Ship of Theseus again, we now face a very practical question: "How much does it cost to replace these planks? And how great is the risk that this ship will sink in the middle of its voyage?"
Lehman revealed that software is fated to evolve, but that process of evolution came with enormous cost and risk. In the late 1970s, as a series of large-scale software projects run by the U.S. Department of Defense (DoD) ended in failure one after another, the center of gravity in software engineering shifted from 'how to write it well' to 'how to predict and control it.'
The person who threw himself directly at this problem was Barry Boehm.

(Barry Boehm)
5. 1981: Boehm — Viewing Technical Choices Through an Economic Lens
Personally, I tend to think of Barry Boehm as the patron saint of the freelance programmer. No matter how brilliant a design may be, you cannot build it without money. No matter how good the language you use, if you miss a deadline your client will be angry.
Theoretical correctness matters to a programmer, but one must also meet the cost and schedule constraints of the person who is paying.
Barry W. Boehm's Software Engineering Economics, published in 1981, introduced COCOMO (COnstructive COst MOdel), a framework for estimating the effort, cost, and schedule of software development. 7

(Figure: Nominal development effort and schedule estimation formulas by COCOMO project type. Source: Barry W. Boehm, original text p.128, Table V.)
He estimated software schedules in a variety of ways, but the part I found most interesting personally was his division of software into three types, each with its own formula for estimating the effort required for development.
Organic refers to a development environment that is relatively familiar and has few constraints; Semidetached falls in between; and Embedded describes a development type that must conform to strict hardware or operational environment constraints. (Note that this classification is entirely different from the E-type (Embedded) programs Lehman described earlier, so be careful not to confuse the two.)
COCOMO 81 consists of three basic models, which are classified as follows.
The Basic model estimates effort using only project size (KDSI) and project type.
The Intermediate model multiplies that estimate by an Effort Adjustment Factor (EAF) that incorporates 15 product, hardware, personnel, and project attributes.
The Detailed model applies the same EAF separately to each development phase, such as design, implementation, and integration, rather than once across the entire project. Moving from Basic to Detailed requires more information and produces increasingly precise estimates.
Memorizing every formula is beside the point, and since producing a cost estimate is the most critical skill for a freelance programmer, let's walk through a calculation using the Intermediate model as an example.
KDSI represents the number of delivered source instructions in thousands, and E is the development effort measured in man-months. The coefficients a and b vary depending on the project type.
KDSI represents the number of delivered source instructions in thousands, and E is the development effort measured in man-months. The coefficients a and b vary depending on the project type.
For example, suppose you are writing approximately 10,000 source instructions for an Embedded project.
This gives an estimated development effort of roughly 44 man-months. Of course, 44 man-months does not mean the project takes 44 months; it is the total workload converted into units of one person working for one month.
There is a well-known misconception surrounding this unit: the idea that if 44 man-months are needed, assigning 44 people will finish the project in one month.
In 1975, Frederick Brooks addressed this assumption head-on in The Mythical Man-Month. According to Brooks, adding more people to a project does not speed it up; it slows it down. Bringing new people up to speed takes time, and as headcount grows, the number of communication paths increases close to the square of the number of people involved. In that book, Brooks wrote that "adding manpower to a late software project makes it later," a statement that would later be widely cited as Brooks's Law. Programmers are not interchangeable parts whose output scales linearly when you plug more of them in, even if clients generally assume otherwise.
Here, though, a fundamental problem arises: how can you accurately calculate the cost of a program that has not yet been built?
Boehm was well aware of this problem.

(Figure. Accuracy of software cost estimation by development phase. Barry W. Boehm, Software Engineering Economics, 1981, p.311, Figure 21-1.)
On page 311 of Software Engineering Economics (1981), a figure illustrates the uncertainty in cost estimation across development phases, showing that in the early stages of development, actual costs can range anywhere from one-quarter to four times the original estimate. As requirements are clarified, designs are elaborated, and technical problems are resolved, that range gradually narrows.
This figure would later become widely known under the name "Cone of Uncertainty."
There is, however, one simple trap lurking here.
Uncertainty can only be reduced by actually investigating, testing, and uncovering faulty assumptions. If you keep developing without even knowing what you don't know, you will eventually pay the price for it.
This same concern carries into Boehm's 1988 paper, "A Spiral Model of Software Development and Enhancement."
Commonly called the spiral model, there is no need to memorize the complex diagram or every development phase. There is just one key idea to take away.
Determine what is most risky, and decide what to do next in order to reduce that risk.
Rather than ignoring risk in order to move development forward, the act of identifying and reducing risk is itself treated as part of development.
Take one example: a payment system. The time a user waits after pressing the payment button must be short. If even three seconds pass, the user will press it again, and pressing it twice means a duplicate charge. Customers are like fish. Give them time to think, and they will try to escape. A slow payment experience gives the fish time to think. You must never let a customer stop and weigh whether the purchase is worth it.
On the other hand, once payment is approved, the fish is already caught. Settlement, notifications, and accounting updates can take seconds or even minutes without causing an immediate problem. This is why a common design pattern separates payment approval into a synchronous process while handling everything else asynchronously. It is a principle frequently taught when building payment systems in Korea.
In other words, the key is distinguishing between tasks the user must wait for and tasks that can proceed in the background.
Let us return to the Ship of Theseus. The shipowner says there are only two weeks until the next voyage, and hands over $1,000 for repairs.
But the deck creaks, the sail is torn, and it looks like there may be a crack in the keel as well.
What should be fixed first?
The sail is visible, so it can be repaired easily. The deck just needs its planks replaced. But the crack in the keel has not yet been confirmed. The ship would have to be torn apart to find out, and if there really is a crack, the ship could sink.
What if the shipwright decided to repair the sail first? The work would proceed smoothly, and the progress rate would climb.
But what if the keel snaps the day after setting sail?
From the perspective of risk-driven development as proposed by Boehm, the uncertainties that could cause the greatest loss upon failure must be investigated before the parts that are simply easiest to fix right now.
The Ship of Theseus has a budget and a deadline, and there is not always enough money and time to eliminate every risk.
So who, exactly, decided what needed to be addressed immediately and what could wait?
As you work on software, there are parts you need to speed up, parts you intentionally leave slow, and parts you shouldn't mess with.
At one place I worked, there was code in an API that was never supposed to be deleted. When I deleted it, things ran faster. But I was called in by the client and reprimanded for removing that API. Why? Because that API had been deliberately slowed down to reduce server load.
The problem was that this fact was written only in the manual, not in any code comments. And of course, the manual was never handed over to me, because the client confused the API version and gave me an older one by mistake.
In any case, we often see situations where the code remains but the reason the code was written does not.
The file was preserved and it ran fine. But the knowledge needed to handle that code safely had vanished. I could read the code, but I did not understand it.
In 1985, Peter Naur gave this distinction a name. He proposed viewing a program as a theory.

(Peter Naur)
6. 1985: Naur — Does the understanding needed to change a program survive alongside it?
Naur is probably the scientist most beloved by programming-minded professionals on Hacker News and similar communities. He gets invoked every time there is a mass layoff of programmers, and in the age of AI, he is perhaps the most frequently cited scientist of all.
Peter Naur's "Programming as Theory Building" was published in 1985. The theory he describes is not limited to mathematical propositions or formal specifications; it refers to the understanding that explains how a program addresses real-world problems and why it takes the form it does, while also enabling developers to respond to new change requests.8

The death of a program happens when the programmer team possessing its theory is dissolved. A dead program may continue to be used for execution in a computer and to produce useful results. The actual state of death becomes visible when demands for modifications of the program cannot be intelligently answered. Revival of a program is the rebuilding of its theory by a new programmer team.
— Peter Naur, *Programming as Theory Building* (1985)
The "theory" he speaks of here should not be understood as confined to mathematical propositions or formal specifications. The "theory" Naur has in mind is the 'human understanding' that explains how a program addresses real-world problems and why it takes the form it does, while also enabling responses to new change requests. Even if two people share the same source code, they do not necessarily share the understanding of why a particular exception was introduced under a certain condition, or why two seemingly similar results were left unabstracted.8
When the team that holds this theory disbands, the program dies. Execution continues and the code remains, but the people who could safely modify it are gone. (This is why Naur gets invoked every time a mass layoff makes the news on Hacker News.) He therefore held that it is impossible to fully reconstruct a program's original theory from documentation alone. Passing the theory on to new developers requires close collaboration with existing ones, and he argued that any attempt by a new team to revive a program whose theory has already been lost should be undertaken only under exceptional circumstances.
Let us return to the Ship of Theseus. What if all the carpenters who replaced the ship's planks were gone, leaving only the blueprints (documentation) behind?
The ship still floats and can still carry cargo. But if a new route must be charted or the hull needs to be modified, can the ship really continue to serve its purpose?
Once there is no one left to explain why a particular carpenter built the mast small for a specific route, or why the stern was designed in that particular shape, it becomes impossible to achieve the ship's original purpose of carrying people from one place to another.
A new carpenter can look at the blueprints and replace planks, but cannot know whether those changes are compatible with the original design intent. The ship has not died. Yet the continuity of the knowledge that once understood it has been severed.
The death of a program that Naur described is much the same.
And yet software always carries problems within it.
Suppose all the carpenters who hold Naur's theory are still present. They know why the hull was built the way it was, they have the blueprints, and they understand which planks need replacing.
Does that make fixing the ship easy?
It will certainly be better than before. At least the ship is less likely to be ruined by carpenters who don't understand why it was built that way. But what if a new route must be opened? Climate change has made the seas rougher, the shipowner's greed demands more cargo, and the economics of the business push the owner to demand faster arrivals at the destination.
The fact that the carpenters perfectly understand the existing ship does not automatically give them answers about how to reconcile these demands with the changing environment.
In the end, we face two distinct problems.
One is whether a program can be understood at all, and the other is how difficult the problem itself is, even once you do understand it.
In 1986, Frederick Brooks proposed yet another criterion for distinguishing between these two problems.

(Frederick Phillips Brooks Jr.)
7. 1986: Brooks and Distinguishing the Difficulty of the Problem Itself
If Naur emphasized the importance of people and understanding by speaking of the 'death of a program,' we are now prompted to ask a question.
"Can't we create something that solves all this complex and painful software business in one fell swoop?"
In 1986, Frederick P. Brooks Jr. offered a starkly cold answer to that question in his paper No Silver Bullet: Essence and Accidents of Software Engineering. There is no 'silver bullet,' no single future technology or management technique that will dramatically boost productivity.9
There is no single development, in either technology or management technique, which by itself promises even one order-of-magnitude improvement within a decade in productivity, in reliability, in simplicity.
Within the next decade, no single development in technology or management technique alone promises a tenfold improvement in productivity, reliability, or simplicity.
Frederick P. Brooks Jr., No Silver Bullet (1986)
Why? Brooks divides the difficulties of software into Accidental difficulty and Essential difficulty.
Accidental difficulties are problems that arise from awkward syntax or outdated tools; they can be solved when better languages and tools come along. Essential difficulties, however, are inherent properties of software itself and cannot be solved by any tool.
He identifies four essential difficulties.
Complexity, Conformity (the need to conform to interfaces and conventions set by others), Changeability (the requirement to keep changing), and Invisibility (the inability to visualize software in space).
The 'relentless pressure for change' that Lehman observed earlier is, in Brooks's view, simply an inescapable fate that software cannot avoid.
Brooks therefore proceeds to examine, one by one, the means people commonly believe will make software development easier, attaching critical commentary to each. It becomes abundantly clear that programmers are not the type to take others' opinions at face value, and among these critiques, the part I found most amusing is this one.
Artificial Intelligence, that is, AI. The AI of 1986 was quite different from what we know today, but apparently there was a widespread expectation that advances in artificial intelligence would dramatically improve software productivity and quality. And Brooks answered that expectation in just three words.

I do not.
— Frederick P. Brooks Jr., *No Silver Bullet* (1986)
What on earth does this man believe in?
Setting aside the fact that the definition of AI at the time was different from today's, he explains what the truly hard part of software development is, as follows.

The hard thing about building software is deciding what to say, not saying it.
In the age of AI, these words resonate more deeply than ever.
Let us return to the Ship of Theseus.
Now the carpenters have a machine that crafts the ship's planks for them: a machine that reads blueprints, identifies worn-out components, and even proposes designs for a new hull.
Does that mean carpenters are no longer needed?
People Who Build Things That Disappear

Science is what we understand well enough to explain to a computer. Art is everything else we do.
— Donald Knuth
Up to this point, we have surveyed a small slice of the intellectual history of Software Engineering.
There will naturally be objections. Where did Donald Knuth go? What about Barbara Liskov? Alan Kay? Martin Fowler, Kent Beck? There are countless great computer scientists who do not appear in this post.
In my defense, if I had tried to bring all of them in to explain the Ship of Theseus, I would have ended up running a shipyard.
Come to think of it, Brooks, whom I introduced earlier, had already given us the answer. Adding more people to a delayed project only makes it later. The same turned out to be true for this manuscript. Every time I added one more scholar, the finish line moved further away. I never expected to live out Brooks's Law quite so literally.
What truly matters here is not memorizing the names of these scholars, but recognizing that they were people deeply dissatisfied with the world as they found it, each carrying their own distinct worldview and mental model.
As Knuth observed, the moment we fully understand a problem well enough to explain it to a computer, that task will be automated and will disappear.
What people did by hand until yesterday becomes today's program, and then it is gone.
So what is the "everything else" that remains after all those tasks have been fully explained to a computer, turned into code, and made to disappear? It is the map inside the programmer's head, the mental model through which they look at the world and decompose problems.
The stories of the pioneers we have explored are, in the end, each a struggle over their own mental model. Dijkstra saw clarity scaled to the size of the skull; Parnas saw boundaries that contain change; Lehman saw the co-evolution in which environment and program continuously reshape each other. Backus saw the algebraic composition of functions in place of state mutation; Boehm saw the balancing of cost against risk; Naur and Brooks saw the human understanding (Theory) that lies beyond the code, and the essential nature of the problem itself.
We can now answer the Ship of Theseus question posed at the beginning. If not a single line of the original code remains and everything has been replaced from scratch, why can we still call it the same program?
Because what we actually produced was never the code written in a text file; it was the programmer's mental model. The worldview inside the mind, which defines what sea this ship must cross, why that particular API was deliberately made slow, and which parts are replaceable planks versus the keel that must never be touched, is the true asset of software.
In the end, programmers are people who build machines that ceaselessly eliminate their own work, while forging a mental model that can never ultimately be handed over to the machine. And they are fighters who willingly clash with others to prove that their mental model is the most correct one in the world.
And sometimes I think the pioneers solved too many of the interesting problems. But, as always, new problems will emerge. Countless problems have appeared and disappeared.
The history of software has been a history of making things that disappear. We wrapped machine code in high-level languages, confined recurring business logic inside the structures we call frameworks, and continually transferred difficult complexity elsewhere; through all of this, we have never stopped reducing the cognitive burden on human beings.
In the age of AI, the very mode of creation has fundamentally changed.
People still write code sometimes, but a growing number now solve problems by conversing with AI instead. Where previously one had to map a mental model into code, the machine now takes charge of that mapping process.
We are transforming from people who write code into people who explain to a machine what should be built. Is this the right path? Or will things swing back toward writing code again? There is no way to know the precise answer.
Even today, people on the internet are locked in a passionate battle: those who insist AI produces nothing but garbage against those who claim AI lets them build things of greater complexity than ever before.
Who will win is anyone's guess. What is certain, however, is that when this battle is over, someone will appear and, just as the grandmothers and grandfathers of several decades ago did, establish the mental model for a new era.
I always long for new figures. My homepage is beginning to look like a retirement home for programming legends. Most of the posts are filled with people who are now grandmothers and grandfathers. I would like to hang a photo of someone younger on the wall and look up to them. I am someone who loves riding the wave of the moment, so I am ready to proclaim, "I recognized this person early on" and offer my admiration. I cannot remain director of the retirement home forever, can I?
Footnotes
- Photo from the Software Technology Group ↩
- Edsger W. Dijkstra, The Humble Programmer, EWD340, Communications of the ACM 15(10), 1972, pp. 859–866. Transcription from the author's archive at the University of Texas. Correctness proofs and the simultaneous construction of programs are discussed in the third argument of the lecture. Scan of published version. ↩
- David L. Parnas, On the Criteria To Be Used in Decomposing Systems into Modules, Communications of the ACM 15(12), 1972, pp. 1053–1058. University repository transcription. DOI. ↩
- L. A. Belady, M. M. Lehman, A Model of Large Program Development, IBM Systems Journal 15(3), 1976, pp. 225–252. The bibliographic information and abstract for this entry were verified against the publisher's records; the specific descriptions from an evolutionary perspective were cross-checked against the original 1980 paper cited below. DOI. ↩
- Meir M. Lehman, Programs, Life Cycles, and Laws of Software Evolution, Proceedings of the IEEE 68(9), 1980, pp. 1060–1076. The feedback loop of Embedded (E-type) programs is discussed on pp. 1062–1063. The paper's classification scheme distinguishes S-type programs (where the specification is the criterion of correctness), P-type programs (which produce approximate solutions to real-world problems and are judged by whether the results are useful), and E-type programs (which are embedded in reality and change that reality). Five laws of software evolution are identified: Continuing Change (a program in use must change or it becomes progressively less useful), Increasing Complexity (without active effort to reduce it, the structure grows more complex), Self-Regulation of Large Program Evolution, Conservation of Organizational Stability (the work rate remains roughly constant), and Conservation of Familiarity (the amount changed per release remains roughly constant). In later papers the laws were expanded to eight and their formulations were revised. DOI. ↩
- John Backus, Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs, Communications of the ACM 21(8), 1978, pp. 613–641. The 1977 Turing Award lecture and the specific FP system it introduces were read as distinct contributions. DOI. ↩
- Barry W. Boehm, Software Engineering Economics, Prentice-Hall, 1981. ISBN 9780138221225. Publication information was verified against the publisher's bibliography; the year and estimation ranges for COCOMO were confirmed via the research center's COCOMO 81 guide. The Basic COCOMO effort estimation formulas (in person-months) are: organic 2.4·KDSI^1.05, semidetached 3.0·KDSI^1.12, and embedded 3.6·KDSI^1.20. The Intermediate model differs starting from the base effort coefficients, using 3.2·KDSI^1.05, 3.0·KDSI^1.12, and 2.8·KDSI^1.20 for the same project types respectively, and multiplying those values by the product of 15 cost-driver multipliers drawn from product, hardware, personnel, and project attributes. The Detailed model further incorporates phase-specific multipliers and work breakdown. Accordingly, the difference among the three models cannot be described simply as applying cost multipliers on top of Basic's computed values. Here, KDSI refers to thousands of delivered source instructions, and should not be equated with an arbitrary count of physical lines in a file or CPU instruction counts. The Intermediate coefficients and calculation procedure can also be verified in Table V and the body text of the 1984 paper cited below. The figure showing estimation error ranges by phase appears in the 1981 book. ↩
- Peter Naur, Programming as Theory Building, Microprocessing and Microprogramming 15(5), 1985, pp. 253–261. The reprinted version linked here was used for Naur's paper text itself; the editorial commentary surrounding it was not treated as statements by the author. The death and revival of programs, and the limits of recovering a theory through documentation alone, are discussed on pp. 400–401 of the reprinted volume. DOI90032-8). ↩
- Frederick P. Brooks Jr., No Silver Bullet: Essence and Accidents of Software Engineering, University of North Carolina, TR86-020, September 1986. First presented in Information Processing 86: Proceedings of the IFIP 10th World Computer Congress (ed. H.-J. Kugler, Elsevier/North-Holland, 1986, Dublin, September 1–5, 1986). Conference proceedings bibliography. Distinguished from the version published in Computer 20(4), April 1987. 1987 publication DOI. ↩