First, a quick definition.
A design pattern is a fixed solution to a given software problem. For example, a singleton pattern is generally written as a single method which returns the single object, constructing it if it does not currently exist.
In the below I’m using the phrase programming idiom in the same sense we use the phrase to describe language: a more general way of expressing ourselves in code that conveys an idea or a feature. Unlike a design pattern, a programming idiom is not as rigid, but serves more as an organizing principle combined with a way of expressing ourselves in code.
Idioms may have a variety of ways of being expressed. And some “design patterns” are really programming idioms; ways to express ourselves in code that may have more than one possible solution.
(The word “idiom” may not be the best term, but I needed something to contrast from design pattern–which is used in a far more rigid way by most developers, and programming style, which seems to encompass something far more general. An idiom may express a way you solve a particular problem for a particular program which may not be repeatable or represent a general solution. How you clump statements into functions, for example.)
With that said, here are 12 principles for good idioms–good ways to reorganize or simplify your code. (With all due respect to Dieter Rams’ ten principles for good design.)
There are many ways to express the same idea in code, and unnecessary complexity just makes life harder on everyone.
All too often I have seen people use a design pattern or refactor code, but fail to collapse parts of the code which are repeated. A good idiom (such as a mechanism for making an API call) is one which collapses the repeated statements into a common method somewhere else.
This goes hand-in-hand with making your code concise, but also means you should not hide functionality in implicit side effects or move important functionality elsewhere where it is difficult to discover.
Readability of software extends beyond just making your intent clear. It also has to do with how you name your variables, how you format the code on the page, common naming conventions you may adopt in your code.
All too often we see programmers trying to make their code “concise” by making clever use of language constructs which obscure the meaning of their code. But software should not be a way we tell the world how smart we are or how clever we are in using (or abusing) certain language features. Instead, we should be restrained in how we use the tools we have at expressing the ideas we are trying to express.
A lot of software out there looks like it has been cut-and-pasted all over the source kit. Sometimes this cannot be helped thanks to constraints in time, but ideally all the software for a particular purpose should be kept together so it can be understood as a single component, and so it can be maintained as a single unit.
Some computer languages have interesting features which can help us solve some very difficult problems in a concise manner. Do not use them unless you absolutely have to.
In one case I saw code which used Objective C “swizzling”, which allows a reference to a method to be swapped in the background with a replacement method, to extend the functionality of UITableView on iOS. With the next version of the operating system, the software crashed hard. Worse, because the swizzling process affected the underlying class declaration, this created a difficult to diagnose crashing problem that affected all parts of the application. This, despite the fact that the functionality added by swizzling was only used on one screen.
The solution? Create a subclass and override the methods that needed to be altered using inheritance. Not as sexy, but the intent of the code was more clearly expressed, and the changes caused by swizzling was then isolated to a single screen where they could be dealt with in a more reasonable manner.
Beyond just building well-defined components, we should strive to keep code related in functionality together, as well as code that is related in execution. Components should be built out of methods grouped by related functionality; components should be assembled into collections of classes which represent similar functionality or similar features. And as much as possible the code which is executed as a consequence of an operation should be kept together with code that performs an operation.
The built-in debugger in most IDEs are fantastic; they allow us to quickly make changes and see the results of our changes in a few seconds. And yet we see developers unintentionally sabotage their IDEs with code that uses features (such as the Java proxy mechanism or Java introspection combined with serialization) which make it impossible for us to know how the code flows, where data for an operation was obtained, or the result of a computation.
And in the process of being clever, that developer has effectively tied our hands behind our back, making it impossible for us to determine how the code works by seeing it in action.
I once worked on a project written in Java, written using the Eclipse IDE, which could not be compiled within Eclipse, but only with the Unix make utility. Worse, it took an hour to build the product. Needless to say this severely limited developer productivity.
And while this is an extreme example, all too often I’ve seen solutions which require the setup of special tools or which require the incorporation of build steps which may break the IDE.
Sometimes, of course, you have no choice. Objective C and C++ both started as preprocessor programs which rewrote the code into C. But this should be far more rare than it is in practice.
Too often we get it in our minds to write the “general solution”, or the “extensible solution”, or solve problems we don’t have in the event we may need to solve them later. Don’t do this. Instead, just solve the problem you have today; if your code is well organized you’ll have plenty of time tomorrow to solve tomorrow’s problems.
More importantly, you cannot know what problems you’ll have tomorrow. So all you are doing is adding additional complexity and creating additional work for yourself when you don’t need to.
The way you organize your code should make it easier for a new developer to understand not only the intent of your code, but also understand how it works, how data flows through your program—and more importantly, where to fix bugs and add new features in a way which is natural to the code base.
Make everything as simple as possible, but not simpler. – Albert Einstein.