I think there are two skills which are crucial for a programmer: the capacity for elegant mathematical thinking, and a keen understanding of the characteristics of computing technology. In this post I would like to elaborate on the nature of each of these skills and share my personal methods for keeping them sharp and relevant.

Mathematical thinking involves two processes: the creative process of inventing and defining abstract concepts, and the the deductive process of reasoning in terms of these concepts. Mathematical thinking is involved in the design of programming interfaces, the decomposition of systems into units, the development of correctness arguments, etc. Good mathematical designs have recognizable characteristics like: clearly articulated assumptions and inferences, well defined interfaces, neatly factored units, minimal use of special cases, etc.

Edsger Dijkstra, one of the great founding fathers of modern computing science, spent most of his career developing methods to teach students the art of clean, effective, elegant, mathematical thinking. He believed that elegant designs were crucial for developing computing systems that actually worked. He believed that simplicity was a prerequisite to reliability. He believed that mathematical elegance in design was the difference between success and failure. His EWD Archives have several essays on this topic which are worth checking out.

These beliefs led him to insist that universities teach aspiring programmers the art of effective thinking first and foremost. He developed a system of mathematics which he used to hone the thinking skills of his students.

I don't think any programmer who develops real technology would disagree with the positions taken above. All seasoned programmers know that if the abstract design is not elegant, then the real product has very little chance of succeeding. Platforms with poorly designed APIs generally don't catch on. The simplest solutions generally become the most popular. The original Google search algorithm (PDF) hinged on extremely elegant mathematics, and look what they did with it!

Yet Dijkstra has more or less been ignored by the main stream technology industry even though he has been trying to fundamentally improve the state of the art. I think the reason is that he has tended to focus exclusively on the mathematical aspects of programming while by and large ignoring the fact that our software actually has to be run on machines.

The crux is that these machines have characteristics which we have to factor into our designs. By ignoring the existence of these machines, we tend to deal with very precise mathematical worlds which scarcely resemble the the uncertainties of the fast-changing world of modern technology. Ignoring this world is to ignore half the challenge of programming!

This brings me to the second major skill of a programmer, viz. an intimate knowledge of computing technology. All effective programmers must know how the machines work. They must know the characteristics of modern operating systems and of modern networks. For instance, they must be keenly aware of engineering realities like the wildly differing characteristics of in-memory vs. on-disk vs. cross-network data accesses, and must factor these realities into the architecture of the systems they build.

Without this kind of knowledge, they simply willl not be able to design solutions that perform well on modern computers no matter how well developed they are in a mathematical sense. Conversely, a skilled technologist who is a shoddy thinker will tend to create elaborate systems which eventually crumble under the weight of their own complexity. I think we can agree that neither outcome is desirable.

I believe that developing as a programmer means that one must be developing in both these dimensions. One must constantly be pushing the limits of ones capacity for mathematical thought. One must be constantly absorbing new facts about computing technology and making them a part of ones everyday vocabulary.

You know you are developing mathematically if you can solve problems today which you could not solve a year ago, or if can solve them orders of magnitude better today than you could a year ago, or if you can understand solutions today which you struggled with a year ago. I have a three pronged attack to improve my mathematical abilities. All three begin with regular attempts at solving mathematical problems. From there, they diverge as follows:

  1. If I fail to solve the problem, I refrain from seeking out a solution. Rather, I put away the problem and revisit it at a later stage.
  2. If the solution was easy, I try to come up with multiple solutions and critique them all. Getting third-party critique on these solutions is also invaluable.
  3. Finally, if I have revisited a problem many times and not had success, I try to seek out out multiple solutions and try to articulate why I could not arrive at those solutions myself.

All these three methods put me in touch with my thought processes in different ways. I think this exposure builds awareness and leads to growth.

As for developing ones facility with technology, I think the best way is to hack away with new technology regularly. I tend to get into phases where I hack like mad to get a new product with a new technology working. I fill the gaps in my knowledge about that technology as a I go. After the project is done I try to note down the major things I learnt about the technologies I worked with and comment on what I did right and what I did wrong. One thing which is very important is that one must dig deep into the technologies one is hacking with. Having known gaps in ones knowledge greatly dilutes the efficacy of the exercise.

For instance, last year I wrote a web application in JavaScript and PHP (using YUI3). I had never written dynamic web apps before and working with event driven programming was totally new to me. I was lost for a while. But I dug deep, hacked away, and constantly refined my conceptions of the JavaScript programming language and model. I kept a list of things I could not understand about the way things worked and checked off those questions as time progressed. After a few weeks, I had a hang of totally new programming paradigm and a totally new language, and a product to boot.

I feel that engaging in these kinds of exercises expands ones horizons as a programmer by giving one a much larger canvas to play with. One develops the capacity to solve more difficult problems as well as the capacity to use an ever increasing array of technology more effectively. In other words, this kind of practice allows one to build better software products more efficiently. And for me, that is the ultimate measure of development as a programmer.

There are many more skills which are highly relevant to a programmer. And even for the two written about in this post, there are many more techniques and approaches one can use for their sustained growth and development. I plan on writing about these topics in future posts since they are of great interest to me. So stay tuned, and thanks for reading!