1. Write self documenting code
Self-documenting code means using meaningful names for classes, variables and functions. This means that a function name will tell the user what the function does. A class name will tell the user what the class is responsible for.
For example:
- If a function is going to change the state of an object, ie the status, then it should be called SetStatus or UpdateStatus – and it should update the Status only – nothing else
- If a function is getting a value, ie the status, then it should be called GetStatus – and it should not update any state within the class
- If some code does two things – then these two things should be split into two functions – each with a name that is meaningful
- Complex code should be put into a function – ie flags &= ~Flags.Blah – this should be put into a function called UnsetFlag
- Ambigous code should be put into a function
- If a class does two things – then it may be better to use two classes with inheritence
2. High cohesion
This is also covered by ‘write self documenting code’ – but is important enough to be mentioned twice. High cohesion means each class and function does, or represents, only one thing.
Often a function will start simple and end up complex, so continuous refactoring is important. Even if the code in the function is not going to be used anywhere else – each block of code in the function that does something can be moved into another function – and the function name should describe what that function does. This will make the code very easy to read and to maintain.
This also applies to nested ifs and loops. Don’t allow a function to go deeper than 1 or 2 nested ifs or loops. If you have a fucntion called UpdateStatuses, which loops through each status to update – then the loop can call the UpdateStatus function to update each status..
3. Loose coupling
Loose coupling is correlated to high cohesion and means that each class should be as self-contained as possible. By default – every function, property and member should be private – and should only be made public if and when it is needed to be used by another class.
For example – if you refactor your UpdateStatuses function to make an UpdateStatus function – you may think this could be a useful function by itself – and it possibly is. But later, when someone wants to change the behavour of the UpdateStatuses function – they may need to change the UpdateStatus function. If the UpdateStatus function is private – then the coder can easily check within the class to see how the changes will impact the functions calling UpdateStatus. This is easy. If the function is public – then the coder has to check what other classes are calling the public function. Maybe there are none, or maybe there is a class in another solution that calls it and will now be broken because of the change to UpdateStatus. This either creates a lot more work to make the small change – or it reduces the robustness of the app – and requires a lot of testing.
4. No magic
‘Magic’ refers to stuff that works because it works because it works.
The obvious one is magic numbers. Its always obvious to the coder that width+20 is simply taking into account the extra size of the scrollbar – but when the width of the scrollbar changes – or when scrollbars are turned-off, or someone simply wants to add in an extra 2px for the width of the window border its very easy to break this code. Even if its only used in one place – create a constant to store it.
Magic also refers to anything that isn’t blatently obvious in your code or in the development environment. If to build the app you have to know that the references need to come from a different location etc etc – this is magic and makes the app hard to maintain.
5. Comments
This is a given – of course all developers add meaningful comments to their code. Its also important that we can use VS to export the class documentation – so please make sure the following comments are always added using the /// system in VS:
- Class description
- Function description
Also – each block of code and anything that contains a decision of some sort should have a short comment using //.
6. Readability
How your code looks is important and while everyone has their own style – consistency is important. At Calcium, we use:
- Use proper indenting with 3 spaces per tab
- Member variables are above properties, which are above functions
- All control blocks use { }
- { and } are on a new line
- Separate operators and keywords with spaces, ie: if (name != null)
7. Check-in comments
Every check-in must refer to an issue id. By ‘every’ – this means ‘every’ – ie all, ie 100%. This is deliberately intended to make sure that every change to the code belongs to an issue in our tracking system. This means if you find a bug – you need to record it before you fix it. This means we know exactly what changes have been made in a release so we can test accordingly.
8. Keep it clean
This goes further than having easy to read code – it refers to everything else – ie the code you no longer want. While debugging – you’re likely to have several versions of the code until the find the right one. When you’re done – cleanup the code you don’t want. Basically, a class (and a project) should have the following cleaned up before going into production:
- All unused references removed
- All unused imports removed
- All old code (including the stuff that’s commented out) removed
- All old functions removed
- All old comments removed
- All old forms removed
- Etc
9. Object orientated code
Obviously (I hope) the main design is all OO – so we’re working in an OO environment – but we’re always making small design decisions as we code and need to keep the conceptual OO design in mind always. Things to keep in mind while making these descisions are:
Who has knowledge of what? If your class doesn’t know about another class yet – think carefully before introducing a dependency. Maybe the other class already knows about your class, or there might be a controller class that is more suited to perform the action
Who is responsible for what? Your class is responsible for storing and acting on its data. Its data should be encapsulated from the outside – so make sure strangers aren’t playing with your private bits using your properties.
10. Minimise testing
We all hate testing and we all suck at testing – really! So, the answer is – minimise the amount of testing that is necessary. If a function is being called from 20 different places – and you add a 21st place – which needs slightly different behaviour – do you modify the function, or override or copy the function? If the other 20 places do want this new functionality – then that’s good – but you have to test 20 places or write some unit tests. If the other 20 places don’t want the change – then you can write another function and avoid all that testing.
