I've been programming for over 30 years from machines that seem puny today (Z80 and 6502 based) to the latest kit using languages that range from BASIC, assembly language, C, C++ through Tcl, Perl, Lisp, ML, occam to arc, Ruby, Go and more.



The following is a list of things I've learnt.



0. Programming is a craft not science or engineering



Programming is much closer to a craft than a science or engineering discipline. It's a combination of skill and experience expressed through tools. The craftsman chooses specific tools (and sometimes makes their own) and learns to use them to create.



To my mind that's a craft. I think the best programmers are closer to watchmakers than bridge builders or physicists. Sure, it looks like it's science or engineering because of the application of logic and mathematics, but at its core it's taking tools in your hands (almost) and crafting something.



Given that it's a craft then it's not hard to see that experience matters, tools matter, intuition matters.



1. Honesty is the best policy



When writing code it's sometimes tempting to try stuff to see what works and get a program working without truly understanding what's happening. The classic example of this is an API call you decide to insert because, magically, it makes a bug go away; or a printf that's inserted that causes a program to stop crashing.



Both are examples of personal dishonesty. You have to ask yourself: "Do I understand why my program is doing X?". If you do not you'll run into trouble later on. It's the programmer's responsibility to know what's going on, because the computer will do precisely what it's told not what you wish it would do.



Honesty requires rigor. You have to be rigorous about ensuring that you know what your program does and why.



2. Simplify, simplify, simplify



Tony Hoare said: "There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult."



Simplify, refactor, delete.



I'd rephrase Hoare's maxim as "Inside every large, complex program is a small, elegant program that does the same thing, correctly".



Related to this is the 'small pieces loosely joined' philosophy. It's better to structure a program in small parts that communicate than to create some gigantic monolith. This is partly what has made UNIX successful.



3. Debuggers are sometimes a crutch, profilers are not



I almost never use a debugger. I make sure my programs produce log output and I make sure to know what my programs do. Most times I can figure out what's wrong with my code from the log file without recourse to a debugger.



The reason I don't use a debugger much is I think it leads to lazy thinking. Many people when faced with a bug reach for the debugger and dive into setting breakpoints and examining memory or variable values. It's easy to become enamored with such a powerful tool, but a little bit of thinking tends to go a long way. And if your program is so complex that you need a debugger you might need to go back to #2.



(Aside: having said all that, one of the programmers I most respect, John Ousterhout, seemed to spend all day in the Windows debugger).



On the other hand, profilers are essential if you need to understand performance. You'll never cease to be amazed what a profiler will tell you.



4. Code duplication will bite you



Don't Repeat Yourself. Do everything just once in your code.



This is related to #2, but is a special case. Even a simple piece of code that's duplicated will lead to trouble later when you 'fix' one version and forget about the other one.



5. Be promiscuous with languages



Some people get obsessed with a specific language and have to do everything in it. This is a mistake. There is not single greatest language for all tasks.



The key thing is to know which language in your toolbox you'll use for which problem. And it's best to have lots of tools. Try out different languages, build things in them.



For example, perhaps you'll not use Python or ML very much but you'll have played with list comprehensions and seen their power. Or you'll dabble in Go and will have seen how it handles concurrency. Or you'll have used Perl and seen the power of really flexible string handling. Or you'll have used PHP to quickly build a dynamic web page.



I hate language wars. They're basically for losers because you're arguing about the wrong thing. For example, in my hands PHP is a disaster, in the hands of others people make it sing. Similar things can be said about C++.



6. It's easier to grow software than build it



This is related to #2. Start small and grow out. If you are attacking a problem then it's easier to grow from a small part of the problem that you've tackled (perhaps having stubbed out or simulated missing parts) than to design a massive architecture up front.



When you create a massive architecture from the start you (a) get it wrong and (b) have created a Byzantine maze that you'll find hard to change. If, on the other hand, you work from small pieces that communicate with each other, refactoring will be easier when you realize you got it wrong from the start.



The root of this is that you never know what the truly correct architecture will look like. That's because it's very rare to know what the external stimuli of your program will be like. You may think that you know, say, the pattern of arriving TCP traffic that your mail server will handle, or the number of recipients, or you may not have heard of spam. Something will come along from outside to mess up your assumptions and if your assumptions have been cast into a large, interlocked, complex program you are in serious trouble.



7. Learn the layers



I think that having an understanding of what's happening in a program from the CPU up to the language you are using is important. It's important to understand the layers (be it in C understanding the code it's compiled to, or in Java understanding the JVM and how it operates).



It helps enormously when dealing with performance problems and also with debugging. On one memorable occasion I recall a customer sending my company a screenshot of a Windows 2000 crash that showed the state of a small bit of memory and the registers. Knowing the version of the program he had we were able to identify a null pointer problem and its root cause just from that report.



8. I'm not young enough to know everything



I've still got plenty to learn. There are languages I've barely touched and would like to (Erlang, Clojure). There are languages I dabble in but don't know well (JavaScript) and there are ideas that I barely understand (monads).



PS It's been pointed out that I haven't mentioned testing. I should have added that I do think that test suites are important for any code that's likely to be around for a while. Perhaps when I've been programming for another 30 years I'll have an answer to the question "Do unit tests improve software?". I've written code with and without extensive unit tests and I still don't quite to know the answer, although I lean towards unit tests make a difference.



