Today I was asked to come up with a set of guidelines to help a junior developer. The goal was to create a short bullet point list, of high level design ideas that can be applied to any kind of programming language and technology.
Here is the list I came up with.
First, I would like to emphasize the facts that these rules are not set in stone. They constitute good guidelines but any developer should exercise its own judgment and discretion in using them. There are times where these points will be conflict between each others or when they cannot be achieved in a reasonable amount of time. If these rules were absolutes, then they would likely be enforced by the compiler or code analysis tools. But that’s not the case. It means that for every single rule, there must exist at least one legitimate reason for a developer to differ from it.
- Use object oriented principles, such as encapsulation and the principle of least knowledge, instead of relying on global states, functions, and sharing too much information.
- Aim for the simplest solution that can possibly work and yet remain maintainable.
- Aim for loose coupling between objects. Try to minimize the surface of contact and interactions between objects, as much as possible.
- Use design patterns when they make sense, and if you do, stick to well-known naming conventions.
- Do not plan for future features in your design. These features may never ship. Stick to what is needed to complete your goal.
- Try re-using patterns that already exist in the code, rather than introducing new, slightly different patterns even if they are equivalents.
- Less code is always better. But less code does not mean cryptic code without any comments.
- Make the code as predictable and as obvious as possible, almost to the point of boredom. You are not the intended audience of your own code. You write code for other reasonably competent programmers to read, and they should almost be able to “guess” what comes after each line.
- Make the relationships between different parts of the code as explicit as possible. Use signal/slots, the observer design pattern, event notifications, or any such communication techniques to make relationships between objects more explicit. Avoid hidden flag variables. Avoid too much tight coupling that disseminate the relationships between objects.
- Avoid side effects. Your code should do what it says on the tin. Nothing more. It should be obvious whether a method modifies the internal state of an object, or not.
- Avoid code duplication. There should not be any copy/pasted code anywhere. If your code needs to do similar and yet slightly different tasks, then it should be refactored in such a way that the similar code is written once but used multiple times. You should refactor existing code when you identify or create new duplicates.
- Handle errors properly, and make it explicit what happens when errors occur.
- Some errors should be thrown and handled by the caller, because you cannot decide what to do.
- Some errors are non-critical and should be logged, and remain invisible to the end-user.
- Some errors are critical and will have an impact on the user. In the worst case the program should exit.
- Use design by contract. Not religiously so. But use assertions for pre-conditions and post-conditions to detect problems. If you have a tricky algorithm, you should also use invariants. This is useful for detecting programming errors, i.e. errors that should never really happen in real production code. This also makes it obvious when you break or use existing code incorrectly.
- Provide just enough information in the logs. Too much information will pollute the logs. Not enough will make certain problems impossible to debug without extra logging. Try to think what you would like to know, if the feature your wrote broke.
- Use comments judiciously. In particular, you should document everything that would not be obvious for the intended audience. Focus on explaining workarounds, hacks, side effects or particularly tricky algorithms. Comments should also be used to provide good documentation about the role of top-level classes and the effect of the most complicated methods, and what happens when they fail.
- Follow the relevant coding guidelines. Even if no formal guidelines exist, try to stick to the same style as the file you edit. A particular emphasis should be given into naming things properly.