Software Development Philosophy
I write programs from users. I write code for developers.
Programs
Well-designed programs typically have the following characteristics:
- They are intuitive to users. They represent concepts in ways that users already understand. They do not force users to learn new concepts to accomplish things they already know how to do. The gestures supported by Apple’s touch UI are an excellent example of intuitive design. People who have never seen an iPad before can start using one almost immediately.
- They are reliable and consistent. They don’t fail or behave in unexpected ways.
- They are no more complex than they need to be. They allow users to perform common tasks easily, while still making less common tasks possible.
Code
“The purpose of code,” said a colleague at a recent meeting, “is to describe to other humans what your application is doing.”
In a well-written program, the code is the documentation. Variable names describe the variable’s purpose in the current context. Method names and function names describe what the methods or functions do.
I write code that is descriptive and communicative. Every unit of code should describe what it does, and inline documentation should describe how the code fits into the overall program.
“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.” — An old quote attributed to various people.
General Design Practices
I generally avoid tightly coupling the basic components of a program, so that components can be rewritten and swapped out at will, without affecting other parts of the system. To accomplish this, components must have clearly defined interfaces and responsibilities. They must be able to communicate without any assumptions about each others’ internal state, internal operations, or side effects. Service-oriented architectures enforce this kind of decoupling, and languages like Clojure do everything they can to encourage it.
As far as possible, I try to write short, deterministic functions and methods that have no side-effects. I typically write code and unit tests at the same time. If I cannot write a clear, simple test for a unit of code, I’ll rewrite the code so that it’s easily testable. I won’t put code into production if I can’t verify its behavior.
Testing
I won’t write anything without tests. Unit tests are essential to ensure that an application’s fundamental building blocks work as expected. Integration tests ensure components work together. End-to-end tests ensure that entire workflows behave predictably and lead to expected outcomes.
A good test suite helps catch regressions and prevents problems from seeping into production releases. The best way to learn the value of testing is to work on projects that have none. Deal with those consequences for a while, and you’ll never skimp on tests again.