The habits or practices you adopt as a programmer influence your productivity and the quality of your output. The consequences of these practices can be good or bad, intended or unintended. After a brief stop off at the practice Test-Driven Development, I will go into another one: using a debugger to single-step your code as part of normal development and not just when you’re debugging.
Single-stepping your code in this way isn’t my idea. I first read about it in the excellent book Code Complete by Steve McConnell. I read the first edition in 1993 and there’s a second edition from 2004.
Test-driven development is a practice that has good and bad effects on code and the development process. The good effects are what’s intended; the bad ones are unintended but can happen nonetheless. The TL;DR version:
- Write a test first (fighting the programmer’s normal tendency which is to write code first). As there’s no code around yet to make the test pass, the test fails.
- Write the simplest possible bit of code that will make the test pass.
- Tidy up or refactor the code, so that the test still passes but the code hangs together better.
What are the effects of TDD?
- Good effects
- It forces you to think about how to test your code – not all code is easily tested
- It makes you write tests, rather than letting you promise yourself “I’ll do that later” but then later never arrives;
- As a result, the code has a decent level of test coverage, which is generally a very good thing.
- Bad effects
- Instead of “I’ll do that later” applying to testing, it could apply to refactoring. Tests passing or failing is much more visible and more easily measured then how much your code needs refactoring. Human nature and the pressure of deadlines invite you to move on to do the next urgent and visible thing and skip the refactoring. As a result, you have a set of passing tests but behind them is code that is otherwise of poor quality. It will likely be harder to understand, harder to change in the future etc. than if you’d refactored it.
- It can lead to reductionist thinking, as the thing that leads to code being created is a test. In my experience this is always a unit test, that tests only a small bit of the system’s behaviour, rather than an integration or system test that will test more of it. It encourages your brain to go down a series of rabbit holes into detail, rather than letting the big picture part of your brain get enough say in what you are doing.
I’m not going to comment further on the advantages and disadvantages of TDD, other than to say it has good and bad points, and whether you can have the good parts without the bad parts is likely to depend on your situation. It’s here as an example of a practice that influences the way that development happens, that is related to testing. Also, it’s worth pointing out that single-stepping your code is independent of whether you do TDD or not.
By single-stepping I mean using a debugger to execute your program completely under your control. You can think of your program as being like a film. A film is made up of a series of frames, and your program is made up of lines of code. You normally move from one frame / line to the next as quickly as possible, which gives an impression of flow and movement.
A debugger lets you hold the program on a given line for as long as you want and then advance to the next one only when you’re ready. It lets you examine the state of the world (variables and their values) before it changes due to the next line. Unlike a film, which always goes from one frame to the next in a linear and predictable way, code can jump about, skip lines altogether etc. The debugger will indicate which line it’s about to execute, and you will be able to see this jump about as your code is executed – bouncing around loops, skipping code based on if statements, jumping into and out of methods etc.
You can also set breakpoints on individual lines of code. These let you tell the debugger to advance through the code at almost normal speed until you get to a line with a breakpoint and then stop – they are just a short-hand alternative to clicking repeatedly on the button to move to the next line. Breakpoints can optionally have a condition so that the debugger will ignore the breakpoint until the condition is true. That means you can, for instance, let the program run quickly without manual intervention until it gets to the 14th time a particular line is executed by its enclosing loop.
Debuggers exist for many, if not all, common languages:
- ETL tools like SSIS
Problems with single-stepping
In some cases, single-stepping is weird or hard, and so my proposal will apply less to you. If, behind the scenes, your compiler is doing magic on your behalf (such that there is a looser relationship than normal between the higher-level code you write and the lower-level code that is executed) then the flow of execution can jump around in unexpected ways.
Single-stepping means that your code will not run at its normal speed – if you are dealing with time-critical code then this may fail or work differently from normal.
Code that runs in parallel is generally a world of pain best avoided if possible, and single-stepping is part of that pain.
When to single-step
Everything so far is likely to be familiar to programmers who have ever tried to track down the cause of a bug. Sometimes the normal outputs of your program are enough to diagnose a bug’s cause, but sometimes you need to break out the debugger to see what’s going wrong.
What I’m suggesting is that you single-step your code routinely, as part of development. You should single-step every line of every method that you either write or change. If you have changed 1 line in a 20-line method, you need to single-step all 20 lines.
Effects of single-stepping
You get a proper understanding of your code. Having lots of green in your test results is great, but you don’t know with confidence why the tests are green. Hopefully the code is correct, and the test is an experiment set up properly to demonstrate that. However, maybe your code is wrong, and the test doesn’t exercise it in the right way to show that. Seeing the execution jump from line to line, seeing variables go from uninitialized to having a value or seeing their values change – that’s the truth.
Single-stepping code that someone else had written previously and you are altering helps you get a proper understanding of the code. It helps to spread knowledge around your team.
While single-stepping is useful and enlightening, it’s not most people’s idea of fun. Knowing that you have to single-step every line of every method involved in a change encourages you to minimise the number of lines touched by that change. If you have a large and sprawling method that does several jobs, it will take more single-stepping and you will have a bigger chance of having to single-step it – if any of its jobs changes then it will need single-stepping.
Therefore, your own self-interest and laziness will encourage you to write methods that do a single job (have a single reason to change) and are as short as possible. You can take brevity too far (crunching several lines into a heavily nested set of operations on one line, for instance) but in general these are good forces to have in play.
If this sounds familiar, it’s the S of SOLID – the Single Responsibility Principle coined by Robert Martin. Single-stepping routinely will help you produce code that follows the Single Responsibility Principle. Or, if you prefer something more classically Computer Science-y: your code will have higher cohesion.
Single-stepping your code takes non-zero time, so in the short term adding it to your To Do list will delay when that list is complete. However, you are much more likely to nip bugs in the bud, when they are still fresh and very easily fixed. If you let them grow and fester, they will take longer to fix. If you consider your To Do list again, adding the fixing of a random bug caused 6 months ago will probably delay you more than single-stepping would have done 6 months ago. The cost of single-stepping is likely to be much smaller than its benefits in bug fixing avoided.
Single-stepping is a tool available to developers but not to testers, who often see the code as a black box. If you’re a developer, a debugger makes it easier for you to take full advantage of your white box relationship with the code. You can use this tool to bolster your value to your organisation.
In a previous job, where I was the leader of a team of programmers who looked after a back-end data cruncher, a colleague who lead a user interface team heard that I had encouraged my team to single-step code in this way. I still remember the compliment he paid me with a smile on his face, over 15 years later: “You’re really anal about quality, aren’t you?” What would you like your colleagues to think about you, and what are you doing about it?
2 thoughts on “Single-step your code before you hit bugs”
If you have unit tests and system tests (UI automation) what is the area this is taking, and why can’t logging replace debugging for gathering use-case data?
I think that logging can be an alternative to single-stepping for some cases, however:
1. sometimes you see variables that you don’t expect to change then change their value – it would be hard to log everything all the time, just in case
2. debugging + break points gives you two modes – quick vs. slow and detailed. Doing the same with logging is possible up to a point, but more work, and you don’t have as much flexibility in switching between modes. You’re limited to the granularity of how you have chosen your logging labels.
3. with logging you are one step removed – you need to infer what the code’s doing from the log output, rather than directly seeing the point of execution jump about
4. the feedback from the discipline of single-stepping everything you might have touched is key – it encourages cohesive code.
I often single-step my code via unit tests. I.e. in Visual Studio I say Debug Test rather than Run Test. So unit tests and single stepping aren’t necessarily an either/or, they can be a both/and.