In some ways I don’t trust other coders but, to be fair, I don’t trust myself either. What I mean is, everyone makes mistakes, we should be tolerant of this and plan for it. It’s important to have steps in place to catch mistakes before they become problems. If someone makes a mistake I understand. If someone keeps making the same mistake I worry. That could be a problem with learning from mistakes or it could be the systems being used makes mistakes easier. If it’s my mistake I own up and try to learn a lesson.
Error tolerance and detection
Catching mistakes early is better than catching them late. Debugging tasks can take a long time even
if the fix is just a single code change. assert
functions can be used to detect unexpected situations
during testing. While it’s annoying to have code fail at an assert
and it’s usually much faster
to fix than if the program crashes at some random time afterwards. You might also be able to use
static_assert
to check a condition during the build process before even getting to testing.
An assert
doesn’t typically make it’s way into the final executable. However there can still be lots
of circumstances when you want to check expectations. This is at it’s most obvious when dealing
with user input, files and network traffic. Each of these should be read in expected formats.
However they may not due to error, corruption or malicious intent. It can be useful to treat other parts
of a codebase with some distrust as well. Your code may expect to receive a properly formatted record,
would it be that costly to check? I’ve created my own runtime_assert
in a similar situation and
found an out of bounds error as a result.
If possible write code such that can’t be used incorrectly. If you have an object that needs to be created and separately initialised then someone in the future can forget to initialise the object. If you have an object that is created and initialised in one step or is initialised automatically when it is first used then there is nothing that has to be remembered. If you make an API that is hard to use then it is likely to be used wrongly. Documenting the correct usage is great but may not get read until a bug is found.
Testing code
How many people out there expect their code to work first time? If I write a few hundred lines of code then normally expect to find some sort of mistake before it gets checked in to mainline. That might be a off-by-one error or it might be an aspect of the task that hadn’t fully been considered. I want to exercise all my code to see if it works rather than just assuming that it does. If something isn’t wrong then it can feel suspicious.
Recently I’ve been working with batch processing and rough testing involved running a script to test a typical example. A lot was going on in each batch so it would exercise most of the code. I tended to categorise tests into two types:
- My updated code should produce the same output as before. This is true for optimisation and refactoring tasks. If so I use an automatic check to see if the new output matches the old output.
- My updated code can produce a different output from before. This is true for bug fixing and new features. If so I have to check if the new output makes sense.
If you can make use of the first type of test it tends to be much faster. That means I can test more often and there is less code to search through to find the mistake. Even bug fixing and new features can take advantage of this. First use the simplest, slow, or certain solution. Once I’ve recorded that output it can be used for comparison purposes for a better, faster, or less certain solution.
Exercising most of the code with a single typical example was useful but there were edge cases. I could also use the same script to test multiple examples, even “the full set”. While small examples would take 30 seconds to run, large examples would take 5 minutes and the full set several hours. It was very useful to change between examples and to choose how many examples to run. I was able to run smaller tests when I thought my changes were safer and larger tests when I thought they were more likely to cause problems. Running a full set of tests could normally be left until I thought the task was finished, only running the test would show if the task was actually finished.
Most of the projects I’ve worked out haven’t started out with significant unit testing. However whenever unit testing has been used it has helped me catch mistakes and often gives code a better structure as well. In order to write unit tests it’s often necessary to break code up to separate functionality which tends to be better structurally as well. Ideally a single unit test should test a single aspects of the code. While this is a good goal I don’t worry to about it too much. It’s certainly easier to diagnose problems if you can write the tests that way. If you can’t write your tests that way then it’s still better to have the test rather than not have it. A good set of tests can give you the confidence to make changes. That means you can make you code faster, cleaner, more featured or all of them.
The developer should remember to run testing but, if at all possible, tests should run automatically. This could be every day or every night or whenever something is checked in. The important thing is that it’s regular does and doesn’t require the developer to remember. Forgetting to run the tests is another mistake that anyone can make.
Code reviews
I’ve had mostly positive experiences with code reviews but I don’t think this is universal. Ideally these happen promptly, help find mistakes in the code and spread knowledge in the team. It doesn’t help if they take ages, devolve into arguments or are just box ticking exercises.
To get the best out of code reviews I’d recommend:
- Have them promptly – For pull requests on GitHub I tried to respond within 24 hours. If you’re in the middle of something you may not need to respond immediately but plan breaks where you can respond. I found first thing in the morning or after lunch a good excuse to switch tasks and look at a PR.
- Try to understand the code – While this seems obvious, you should ideally be able to understand all the changes that are being made so you can give a real opinion on them. This can be made easier if code is written to be easily reviewed but that’s a non-trivial task and a topic for another post.
- Ask questions and make suggestions rather than demands – You might think you see a bug or know a better way of doing things but maybe there is a good reason for it.
- If it’s coded differently from how you would do it, that doesn’t mean it’s wrong – I think it’s good to to have consistent techniques used in a project but variation is still possible. Again, suggestions rather than demands.
- If you don’t have time to do a review them let the developer know – Better to let someone know that you’re busy and hopefully someone else has time.
On balance
Remember, mistakes will happen but they don’t have to make it in to your release.
Leave a Reply