Thinking about Programming

Running software and system maintenance

Whenever we get confused, we must be able to see where we are going in order to know what action to take. We must know what we are trying to achieve.

Part of The Programmers’ Stone — return to the main guide for the full series and chapter index.

What is Software Engineering For?

We are software engineers. Why? What is software engineering for? What do software engineers do? We still hear curious answers. Some people talk as if the job is to “follow the procedures” or “transliterate a requirement.”

Software engineering exists to ensure the programs our customers need are running on their computers, and keep running. That means our programs must do the right things. They must be robust. Sometimes we must know for certain that they are robust, and sometimes we need to be able to justify that confidence.

The necessary programs must be running tomorrow as well. That usually means our programs today must be maintainable. We must do our work cost-effectively, or we will not get the chance to write the programs in the first place. Our delivery must be timely.

We use all our inventiveness and the experience contained within our discipline to attain these goals. Methodologies, standards, tools and languages are intended to assist us in attaining them.

We do nothing for the sake of it.

Software Engineering is Distributed Programming

The traditional view of the workplace is that the team is doing a job, and the individual is a part of this effort. But we can also draw a system boundary around the programming team and notice that it does nothing that an individual programmer could not do. Requirement elicitation, design, implementation, test, management, review, build, archive, and configuration management must all be performed even by a single programmer doing a small job.

So we can see software engineering activities as the distribution of what a single individual could do quite effectively and responsibly in “potter mode” in a study.

We distribute programming for the same reasons we distribute any kind of processing: availability, parallelism and specialisation.

This way of looking at things brings insights. We must select the divisions between tasks intelligently. Sometimes we can benefit from putting two tasks with one person. Sometimes separating tasks brings benefits, especially when independence protects thinking.

We must make sure communication between distributed tasks is efficient. That means agreeing a protocol and bearing each other’s needs in mind. Anything you would need in your mind when you have completed one task and are about to embark on another, your colleague needs too. Output is useless if it does not tell the next person what they will need to do the next bit.

The final insight here is that the “black box” of individual insight still exists in the team. The flow of information is not a linear series of transforms. It is fan-in of issues to a designer and fan-out of solutions. The insight of the designer has not been distributed.

What is Programming?

To understand software engineering we must understand a programmer. Let us allow a programmer to specify the requirement, and examine a scenario that ends in the construction of the simplest possible program: a single bit program.

Ada is sitting in a room.
In the evening the room becomes dark.
Ada turns on the light.

That is the fundamental act of programming. There is a problem domain (the room), which is dynamic (gets dark). There is order to the dynamic problem domain (it will be dark until morning), permitting analysis. There is a system that can operate within the problem domain (the light), and it has semantics (the switch state).

There is a desire (that the room shall remain bright), and there is an insight (that the operation of the switch will fulfil the desire).

It is worth pointing out what we mean by a “programmer.” Someone typing the same invoicing system yet again might not be doing much programming at all. A project manager using a spreadsheet to gain an intuitive understanding of when the budget will be squeezed and what the key drivers are, most certainly is.

Programming is a Mapper’s Game

We have a reasonable description of what programmers actually do. Two key words are “desire” and “insight.” These are difficult to discuss sensibly using workplace language that concentrates only on supposedly objective phenomena.

To get anywhere with programming we must be free to discuss and improve subjective phenomena, and leave objective metrics to resultants such as bug reports.

First, desire. In the example above, Ada likely did not begin with a clear desire for greater light. Her environment became non-optimal. She had to seek a clear description of exactly what she wanted. This clarification of desire is often nested, and proceeds in tandem with design.

Next, insight. This is the moment of recognition when we see that the interaction of the problem and the desire can be fulfilled by a given use of the semantics. It is intellectually challenging because the “pieces” you are fitting can change shape as you work.

There is a pattern here that relates computer programming to every other creative art. We can speak of Problem, Semantics and Desire. Problem and Semantics are domain specific. The act of Seeing is shared across creative disciplines.

Programming is a mapper’s game.

General Tips on Mapping

Packers have a proceduralised culture that provides behavioural tramlines for just about everything. It can be so complete you do not even notice it until you solve a problem effectively by a method that is not on the list.

Mappers do not have a general cultural context to learn from, so they are often self-taught. Here are observations gathered from people who map for a living.

Problem Quake

After you have been telling yourself about what your customer needs to accomplish, chasing elements of the problem and their relationships, the whole thing can suddenly collapse into something much simpler. We rarely get it quite right in that sudden moment. Be ready to shift your new understanding around and make the most of the aftershocks. This is a good time to express your understanding to colleagues and let them look afresh.

Incremental vs Catastrophic Change

Sudden realisations come when they are ready, and we can optimise conditions to produce them. They are exhilarating, convincing, and sometimes wrong. When you get them, check them against everything you know and try to break them. On the other hand, you can often reduce complexity by chunking the problem and moving lumps around. Do not be embarrassed about thinking crudely at first.

Boundaries

Focus on boundaries. There are things you care about, things that affect things you care about, and things you do not care about. If you can find your boundaries, your problem is well defined and you can start to solve it. If you cannot, you might need more data, another conversation, or an explicitly stated assumption.

Explore Permutations

When you have a green duck, a pink lion and a green lion, ask yourself where the pink duck has got to. Understanding trivial and impossible permutations can lead to greater overall understanding.

Work Backwards

We all know how to solve mazes in children’s puzzle books. Backward reasoning is often the same trick applied to real work.

Plate Spinning

You can tell your unconscious mapping faculty is running because of a fidgety, uncomfortable, even grouchy feeling. When it eases off, it is your call. If you want results, take a quick tour around your problem from a couple of different perspectives and the fidgetiness will come back.

Ease Off

There is no point pushing harder when you can get no further. Switch to rest mode. Obtain sensory stimulation and leave your surroundings. You can recover mental energy quickly if you stop when you know you are stuck.

Break Loops

Effective background thinking feels different to stale looping. When you are stale, you need new empirical input. Get more data. Talk to someone. If it is a buggy program, instrument it heavily and read the output slowly. If it is a complex set of asynchronous events, write them out by hand and force attention onto one event at a time.

Fault to Swapping

When you are paralysed trying to optimise a sequence too big to fit in your head, do any useful job and then look again. Progress breaks logjam.

Duvet Stuffing

Turn the cover inside out, put your arms into it, grab the far corners, then grab the duvet corners and shake. A bit of practice and you can do a king size in under 30 seconds.

Mapping and the Process

The purpose of software engineering is ensuring that the programs our customers need are running on their computers. Software engineering is distributed programming. From this perspective, a process is a protocol for communicating with colleagues through time and space. It tells successors where to find information they will need. It provides common points where we can compare like with like and discuss what we have varied.

The process is not a prescriptive meta-program for making other programs. We think within its structure, but we must interpret the process definition in the light of the actual problem. Abdicating interpretation simply selects an arbitrary interpretation, and then people end up arguing about paperwork for features that do not even exist in their system.

Angels, Dragons and the Philosophers’ Stone

Our ancestors were as smart as we are. Understanding some puzzles from antiquity as the thinking of past mappers is useful because it shows what unaided human intellect is capable of.

Infinity was broken into different kinds. Conceptual infinity is easy to name. Potential infinity is an instruction like “keep counting forever.” Actual infinity would require an infinite collection of things to exist. Our ancestors argued that an infinite collection of ordinary objects would take infinite space, so it cannot exist in our universe. The remaining possibility is an infinite number of things with no size, like angels on the head of a pin.

Today, physics still wrestles with related questions of continuity and discreteness at different scales.

Dragons, in the old stories, gathered together what we now call tectonic phenomena into one explanatory bucket. The symbol preceded the real model, and served until the deeper structure became known.

Alchemy is often misunderstood as a crude hunt for wealth. In the older tradition, it can be read as a journey of operations that ends where it begins, but changes the operator’s perception of the world. The operator is transformed. Alchemy is mapping.

Practical intelligence also appears in historical engineering. Cathedrals were built without modern mathematics, but many structures approach optimal solutions. Builders relied on accumulated experience and the human ability to “feel” structure.

Creative hacking and responsible engineering are not contradictions. They are different axes. You can stretch your faculties while still fulfilling obligations to colleagues and users.

Literary Criticism and Design Patterns

There is a difference between intentionality and action. A writer intends to show a villain’s cruelty and does so by arranging scenes. A programmer intends to signal state and does so by flipping bits.

Whenever we can distinguish intent from mechanism, we can evaluate effectiveness and learn about both. In literature this is criticism. In programming it is review, refactoring, and testing informed by failure reports.

Clear structure lets comments explain purpose rather than apologise for confusion. At a useful level of granularity, comment and code align naturally. This motivates the design of functions and objects that can be described succinctly.

Patterns capture reusable structure that sits above basic control flow. Studying them helps programmers discuss style and architecture without reducing everything to personal preference.

Readable code is not indulgence. It reduces maintenance cost and defect rate. Beauty in code tends to follow function and conceptual clarity.

When tool or language disputes become “religious wars,” the way out is to compare options against objectives and context. Experienced people often converge when the real objective is made explicit.

Cognitive Atoms

In any task that requires understanding, we will find at least one “cognitive atom.” It is a part of the problem that can only be adequately treated by loading its elements into the mind of a single mapper and working until the structure is seen.

Project planners gain control by recognising these atoms early. There is a relationship between architecture and the atoms it contains. The architect must use intuition to identify solvable but unsolved problems, because no one wants to build an architecture that is not implementable.

It is easy to keep breaking problems into smaller tasks until you reach code-level activity, and then discover chaos. The real complexity did not disappear. It was squeezed into fragile interfaces, performance problems, and awkward APIs until it reappears around the whole system.

By definition, you cannot plan a cognitive atom as a sequence of certain subgoals. It must be entered as a single task and its duration estimated. Once it “fissions” into clarity, detailed task lists become possible. Projects that pretend to Gantt everything on day one usually reveal production-line thinking rather than intelligent management of uncertainty.

The Quality Plateau

When you form your own mental map of a problem domain and attempt to simplify it, the question becomes when to stop. This applies at every level of design. Often there is a deep solution that is simpler than anything else and manifestly minimal.

The original text presented a long worked example from a particular Win32 threading context to illustrate simplification by removing unnecessary state, nesting, duplication and comments that repeat the code. That material is now dated, and this page will be followed by a modern, separate worked example.

For now, keep the principle: when you squeeze complexity out, you reduce places for bugs to hide. A simpler structure tends to make the behaviour more transparent, and transparency is what allows verification.

Knowledge, Not KLOCS

Programmers are expensive. The value they produce is not the visible volume of code they type. It is the understanding embodied in a system that works and can be maintained.

Organisations that count “output” as lines of code tend to punish simplification and reward bloat. Intelligent organisations want the most understanding and the least source code they can achieve.

Good Composition and Exponential Benefits

A useful definition of good composition is that if any element were missing or changed, the whole would be changed. In design, as in art or logic, structure matters because each element’s role is defined in relation to the rest.

Functionality accumulates. Once in place, it becomes background, and teams become entangled in legacy structures. Deadlines accelerate this entanglement, but deadlines are not an excuse for surrendering clarity.

Cleanup work has leverage. Days after cleanup count more than days before cleanup, because a clean system allows faster progress and fewer mistakes. The warning is to estimate cleanup realistically. The nastier the tangle, the bigger the multiplier, and the greater the risk you will not finish.

Complexity also collapses exponentially. A cleaner algorithm yields a smaller implementation. Less code is easier to understand, test and change. Fewer bugs produce fewer deltas. Fewer deltas require fewer tests. Late-stage chaos often comes from too much patching and too little clarity.

The skunkworks approach can sometimes be an effective risk tool. It trades exhaustive process for experienced judgement, accepts uncertainty, and aims to regain leverage through understanding. It can fail, but when it succeeds it can simplify what would otherwise become a long maintenance burden.

Originally written in the late 1990s and refreshed for publication in 2026. Modern companion pages for each section will expand the examples and update the technical references.