Everyone knows what programming is: staring at code while sipping something caffeinated and occasionally cursing at the screen, right? Jokes aside, there are great articles like Paul Ford’s “What is Code” already out there that provide an explanation of the functional basics and politics of coding, and give good visibility into the human side of this craft. But what the heck is it actually, as an activity, day to day? How would one dissect the actual process of programming as performed by a person? How do we see it, ourselves, as programmers?
There are two sides to it. We can start by articulating the goal of spending time on programming. And then we can introspect into the actual mechanics of fulfilling that goal.
A developer’s responsibility is to make software code exist. Specs, time and comfortable office space go in, working software comes out. This is a severe simplification, obviously, that ignores the iterative and incremental nature of real-life software projects, as well as complexities of multi-person team contribution; but on some scale, however small, that’s what happens. There are also additional conditions to meet, like documentation, ease of maintenance, etc — but that’s effectively just part of the spec. Whoever is the developer’s “customer” just wants a working deliverable — a deployable and executable program.
The program runs on a computer (duh), or several. It might interact with some infrastructure to make things happen — databases, external APIs, the programming language environment itself. And, of course, it implements some kind of real-world concepts — social activity of humans, boring but incredibly important billing matters, airplane instruments, etc. The latter is the domain of the software. A programmer, then, has to know both: the domain, to know what to code and understand the spec details, and the infrastructure, because it has idiosyncrasies and caveats of its own.
The core of programmer activity is connecting these dots. If client needs XYZ, and I know that ABC leads to XYZ, I will do ABC. Rinse and repeat.
And here is how we spend a huge chunk of programming time: mentally stepping through code over and over. We see machine instructions and visualize how the computer would act on them, to ensure that we wrote the right thing. Every coder is constantly running an imaginary CPU in their brain. We write something, and then check it against spec. Write some more, check some more. If the code matches expectations, we move on; otherwise, we fix it and try again. This is, of course, in tandem with actually running the code, testing it. But the human replay of code is constantly happening, sometimes even as we type.
Mentally stepping through code is obviously necessary; however, it is also as onerous and error-prone as it sounds. That is part of why programming is so draining sometimes — a lot of concentration is involved. Aptitude and practice make it easier, but the squishy biology of our brains quickly runs out of room to handle all the complexity of real code.
This is why automated testing is so important: a real computer will be more reliable at re-checking code for breakage (regressions) as we make changes. And this is why abstraction is so important and practical: it is a process of zooming out of complex behaviours to be able to fit them into our “top of mind” and run them on that imaginary mental CPU. As we reduce the amount of moving parts to keep track of and minimize the side-effects of executed steps, we get less of a headache when keeping up with what the code does.
And at this point we get to the other essential description of programming: modelling reality.
As we delve deeper into any project, we build an understanding of what a user might expect to see from our program. That understanding is a model — using known requirements as data points to anticipate other, implied expectations, and to get ready for future requirements that may come in as time goes on. Just like scientists try to model the universe and its behaviour as a set of predictable rules, we model user and customer expectations, their business, their interaction with our software. It is not code, it’s in our heads: just our knowledge that then guides programming work.
Computer program abstraction nicely complements that. Instead of looking at code as a set of “if this then that” instructions, we can see it as a documentation of our model of the business reality. The more literally we encode our understanding of the world via software concepts, the more streamlined and self-documenting the code becomes. There are various labels for doing that: e.g. domain-driven design or semantic naming (and yes, the M in MVC stands for “model” as a machine-readable expression of our knowledge of the domain). But it is not always easy to keep code semantic, especially for new and poorly-understood parts of the domain, and not always worth it. Either way, the real model still always exists — in our heads — regardless of how stringent we get about mapping it to code.
From this perspective, programming as an activity can be seen as progressively refining the known model of business reality and implementing it in code — resembling research and discovery more than engineering and construction. That is still just a metaphor, of course, but it makes it clear how agile processes have such a natural fit with the incremental and iterative nature of modelling.
Writing programs is a functional skill, trained and guided over time. The above description is an incredible over-simplification, but it helps demystify that function, and shows the very directed reasoning behind some of the most common programming techniques. Programming is hard, but it’s not rocket science; the know-how involved exists for very deterministic and practical reasons. It all comes back to knowing the human behind the code.