The art of writing was invented, I suppose, so that we could communicate with the future, i.e. record the past and present, and in the process create history. Software programs, on the other hand, are written for the purpose of defining the future. One that is meant to be interpreted by machines. The tapping of a keypad turns a blank page into a blueprint. It starts with one file, and can quickly grow larger. Combined, several blueprints become packages. They contain little logical engravings of our expressed intentions. Aggregated, these are translated into binary compatible entities, ready to be executed by little machines living within larger, more complex ones.
The code that we write is a set of instructions. A map to be followed, if you will. And as such, it is up to us, as programmers, to uncover the roads that it has and the places or dead ends it can lead to. This work is of exploratory nature, and requires one to develop an exploratory mindset. I recall the confusion, and questioning that puzzled me the very first time I wrote code. Driven by curiosity, seeing the first variables being added on screen, and the first messages printed on filled me with far more possibilities and wonder than I had imagined. This was followed by a mental expansion, as I went from basics to more intriguing concepts, like I/O, and networking. They introduced the ideas of permanent state and communication. With each new concept, new doors of possibilities opened in my mind. With them came even more questions, and wonder. I imagined. I tried. And eventually, created. Writing good software requires quite a bit of digging, I must say. And getting it wrong the first time around is more common than we usually think.
Given time, and a little experience, we introduce methodology to the chaos. Patterns, architectural styles, and paradigms come into play. Then, our own styles, preferences, and mechanisms. Before we know it, we are not just fluent in an idiom, but we become fluent in the art of becoming fluent with a new idiom. It can be a programming language, a framework, a library, or designing a particular type of application. The best way I can describe it is as a pure trial-and-error process where we attempt to implement ideas, and evolve them in the process. But we are not done once the blueprint is written down. We also need to test it, and in the process debug it; deconstruct our assumptions, follow each relevant trail forward to validate what it is against what it ought to be.
This process is not only logical, but it is creative. It is engineering. It's how I was thought to do it with gdb, before I was introduced to an IDE. It is, indeed, nothing short of detective work. You see, we could close off a 10km area from a crime scene, talk to every person that had been in it for the past 7 days, and we'd still have no guarantee of getting any answers. The only thing we'd be doing is wasting time, and resources on a wild hunt. The smart way to go about it is to begin with the clues that we're given. Try and understand what each one of them means, independently, and what story they tells us collectively. Similarly, when debugging, we have to go back the beginning, find the critical points, and trace the steps one at a time until we get to the truth. This is, in fact, a search for the truth. And to find our beginning, we need not look further than the clues we're given by our stacktrace.
One need not look at a design as a piece of artwork to find creativity in it. It is the expression of our formal approach to the problem. It is where we can see how our minds have come to understand the problem, as it is described in natural terms, and how we have come to translate it into a detailed specification that is going to be followed by the logical machines. We want it to be correct, robust, and as efficient as possible, just as any mechanical tool ought to be. But our code is just the design. It's our creative solution. Conceptually, there is nothing inherently right or wrong about a design. This is only determined when we bring our problem face to face with it, and pose the question: is it doing what we wanted it to do? Often enough, we can't get an answer by glimpse alone. Our ability to see what we want to see can hinder us here. We need to code, measure, and iterate continuously. And then, code, measure, and iterate some more. The path to the truth requires hard evidence.
So, let us not simply wonder why our code is failing; let's instead go and search for the answer. Pick up a good programming book. One that tells you that you'll get it wrong on the way to getting it right, because that is the way we humans learn: by trial and error. Learn from your mistakes. Learn to investigate, formulate hypotheses to your problems, and figure out ways to validate or eliminate them.
Programming is imagining. It is contemplating, designing, working, and re-working. It is not linear, nor is it straightforward. And it's not magic either. It is, rather, the art of solving puzzles while constructing them.