Over on Slashdot, a discussion on programming prompted me to organize various thoughts I’ve had on programming. Having acquired several languages as a child (i.e. effortlessly) and going on to learn other languages (both natural and programming languages) as a teenager (i.e. through deliberate practice), I’ve always thought of coding on the same terms as natural languages: a means to express oneself and communicate information.
Programming language =~ natural language
Communication tool
Programming is much like writing in a natural language: both aim to clearly (and concisely) communicate information to another human being. Yes, even programs are meant for humans: they’re written for humans to read, understand, and modify; and are only executed by the computer as a side effect.
Just like novels shouldn’t include unnecessary elements in a story (see Chekhov’s gun), a coder should strive to keep his code concise because it means other coders will have less code to hold inside their head at any given time. A programming to quote to live by goes
Always code as if the person who will maintain your code is a maniac serial killer that knows where you live.
Aside from the disturbing fact that this quote is sometimes true (and other times might as well be), you’d be well served to follow the advice in this better-known quote:
I would have written a shorter letter, but I did not have the time.
— Blaise Pascal
Skill spectrum
Just like there is a world of difference between penning a novel and writing a comment on Youtube, designing and implementing a complex software system requires much more skill and knowledge than sticking to short “hello, world” code examples.
Good literature and programs both require their authors to have a good deal of “peripheral skills”. Simply being able to write grammatically correct sentences isn’t enough for a writer: he’ll need to master the nuances between synonyms, plot points, pacing, character development, etc. By the same token, each coder must understand the design of the overall system, how components fit together, and why [1]. But he’ll also need to know how to produce functional code that is maintainable, as well as adding to or altering existing code without breaking any functionality.
Producing maintainable code requires much more knowledge that it might seem at first glance: it requires making architectural choice adapted to the problem at hand, knowing when he’s dealing with a solved problem (whether the solution is to be found in the language’s API or in a textbook algorithm), and best practices. For example, one excellent way to facilitate code maintainability is with test-driven development: they participate in keeping the code clear and concise, and prevent regressions. But becoming proficient in TDD requires acquiring an additional skill set: what should be tested, how should it be tested, etc.
Learning a new language
First and foremost, never let schooling get in the way of your education. And never ever confuse the two.
It’s quite possible to acquire “fake competence“: simply consume media in the foreign language; read about the programming language. Why do I call this fake competence? Because although you’ll definitely learn new concepts that you can apply (new words, new API calls) you’re provided only with a sheltered context that doesn’t get you to actually think in the language.
Reading foreign language is much easier than speaking them, because it’s easier to recall the native equivalent of a foreign word than it is to recall a specific foreign word we want to place in a specific position in a foreign sentence (with foreign grammar). Similarly, it’s easier to see short code snippets (such as they appear in programming books) than it is to think up a solution to a problem and express the solution using a programming language.
So how do you acquire legitimate competence? You have to start producing. Much like you should start thinking “how would I say this?” you must start thinking “how would I implement this?”. In this process of knowledge interiorization, side projects are invaluable: they force you to apply your new skills in unchartered territory. Even though most good books teach new concepts by using an example application that gets developed throughout the book, I urge you to read books while applying the newly learned concepts to your existing projects, or event start a new toy project to be thrown out once you’re done with the book. When you’re staring at a blank screen and need to implement a concept you’ve read about, you’ll be much better at understanding the underlying concepts if they’re applied to a side project, because you’ll need to understand the principles behind the newly learned techniques before you’ll be able to apply them to your project. Don’t fall into the trap of skimming code in the book and thinking “this makes sense, I got it”. Unless you’ve had to use this information to solve a new problem, you won’t have interiorized it completely and won’t be able to recall it later.
If you don’t feel comfortable embarking on your own project, at least start with simple programming challenges, such as project Euler.
Above all, strive to understand and acquire programming concepts instead of mindlessly memorizing specific APIs. This will help you stand on the shoulders of giants by helping you tell the difference between
- a generic problem that must have been solved previously (whether through a method in the language’s API, or in a textbook algorithm) or that can be solved by analogy;
- a unique problem requiring a new solution. But think long and hard about this one, and avoid “not invented here” syndrome at all costs [2]. (Or you’ll end up on thedailywtf.com.)
The difference between understand programming concepts, and remembering a set of API calls is the same as understanding a mathematical phenomena and memorizing a formula: when the problem set is different, an understanding of the underlying phenomena will help you to solve the problem, whereas recalling a specific formula (not directly applicable to the case at hand) will be of no use.
In the next post, I’ll discuss how one can build on this basic understanding to achieve proficiency.
[1] even if he has so-called enterprise architects to provide him with design specifications
[2] that is not to say there is no value is reimplementing existing solutions. For example, you’ll learn an awful lot by programming a compiler, even though there are plenty that will produce better assembler code. The point is, your solution to a well understood problem might be novel and clever, but it’s probably better to keep it out of production: existing solutions will have been battle-tested and will withstand all known edge cases, which probably won’t be true for any solution that in help together with string and bubble gum…