|
| 1 | +# Look Out For Bugs |
| 2 | + |
| 3 | +One of my biggest mid-career shifts in how I write code was internalizing the idea from this post: |
| 4 | +[_Don't Write Bugs_](https://www.teamten.com/lawrence/programming/dont-write-bugs.html){.display} |
| 5 | + |
| 6 | +Historically, I approached coding with an iteration-focused mindset --- you write a draft version of |
| 7 | +a program, you set up some kind of a test to verify that it does what you want it to do, and then |
| 8 | +you just quickly iterate on your draft until the result passes all the checks. |
| 9 | + |
| 10 | +This was a great approach when I was only learning to code, as it allowed me to iterate past the |
| 11 | +things which were not relevant for me at that point, and focus on what matters. Who cares if it is |
| 12 | +`String args` or `String[] args` in the "паблик статик войд мэйн стринг а-эр-джи-эс", it's just some |
| 13 | +obscure magic spell anyway, and completely irrelevant to the maze-traversing thingy I am working on! |
| 14 | + |
| 15 | +Carrying over this approach past the learning phase was a mistake. As Lawrence points out, while you |
| 16 | +_can_ spend time chasing bugs in the freshly written code, it is possible to dramatically cut the |
| 17 | +amount of bugs you introduce in the first place, if you focus on optimizing that (and not just the |
| 18 | +iteration time). It felt (and still feels) like a superpower! |
| 19 | + |
| 20 | +But there's already a perfectly fine article about not making bugs, so I am not going to duplicate |
| 21 | +it. Instead, I want to share a related, but different super power: |
| 22 | + |
| 23 | +> You can find bugs by just reading code. |
| 24 | + |
| 25 | +I remember feeling this superpower for the first time. I was investigating various rope |
| 26 | +implementations, and, as a part of that, I looked at the `ImmutableText.java`, the implementation |
| 27 | +powering IntelliJ, very old and battle tested code. And, by just reading the code, I found a bug, |
| 28 | +[since fixed](https://github.com/JetBrains/intellij-community/commit/b16987177e6023cd971d22a503663b7d63691bb2). |
| 29 | +It wasn't hard, the original code is just 500 lines of verbose Java (yup, that's all that you need |
| 30 | +for a production rope). And I wasn't even _trying_ to find a bug, it just sort-of jumped out at me |
| 31 | +while I was trying to understand how the code works. |
| 32 | + |
| 33 | +That is, you can find some existing piece of software, carefully skim through implementation, and |
| 34 | +discover real problems that can be fixed. You can do this to _your_ software as well! By just |
| 35 | +re-reading a module you wrote last year, you might find subtle problems. |
| 36 | + |
| 37 | +I regularly discover TigreBeetle issues by just covering this or that topic on |
| 38 | +[IronBeetle](https://www.youtube.com/watch?v=hPUL8Xo6MJw&list=PL9eL-xg48OM3pnVqFSRyBFleHtBBw-nmZ): |
| 39 | +[bug discovered live](https://youtu.be/2_IJJZFMH2M?si=oNnqd8oCckXo8OLf&t=1691), |
| 40 | +[fixed](https://youtu.be/2_IJJZFMH2M?si=hluxJXQuK3XtDT3I&t=2090), |
| 41 | +[and PR merged](https://github.com/tigerbeetle/tigerbeetle/pull/3194). |
| 42 | + |
| 43 | +Here are some tips for getting better at this: |
| 44 | + |
| 45 | +The key is careful, slow reading. What you actually are doing is building the mental model of a |
| 46 | +program inside your head. Reading the source code is just an instrument for achieving that goal. I |
| 47 | +can't emphasize this enough: programming is all about building a precise understanding inside your |
| 48 | +mind, and then looking for the diff between your brain and what's in git. |
| 49 | + |
| 50 | +Don't dodge an opportunity to read more of the code. If you are reviewing a PR, don't review _just_ |
| 51 | +the diff, review the entire subsystem. When writing code, don't hesitate to stop and to probe and |
| 52 | +feel the context around. Go for `git blame` or `git log -S` to understand the historical "why" of |
| 53 | +the code. |
| 54 | + |
| 55 | +When reading, _mostly_ ignore the textual order, don't just read each source file top-down. Instead, |
| 56 | +use these two other frames: |
| 57 | + |
| 58 | +: Follow the control flow |
| 59 | + |
| 60 | + Start at `main` or subsystem equivalent, and use "goto definition" to follow an imaginary program |
| 61 | + counter. |
| 62 | + |
| 63 | +: Stare at the state |
| 64 | + |
| 65 | + Identify the key data structures and fields, and search for all all places where they are |
| 66 | + created and modified. |
| 67 | + |
| 68 | +You want to see a slice across space and time, state and control flow (c.f. |
| 69 | +[_Concurrent Expression Problem_](https://matklad.github.io/2021/04/26/concurrent-expression-problem.html)). |
| 70 | + |
| 71 | +Just earlier today I used the second trick to debug an issue for which I haven't got a repro. |
| 72 | +I identified |
| 73 | +`connection.peer = header_peer;`{.display} |
| 74 | +as the key assignment that was recently introduced, then [ctrl + f]{.kbd} for `connection.peer`, and |
| 75 | +that immediately revealed a gap in my mental model. Note how this was helped by the fact that the |
| 76 | +thing in question, `connection`, was always called that in the source code! If your language allows |
| 77 | +it, avoid `self`, use proper names. |
| 78 | + |
| 79 | +Identify and collect specific error-prone patterns or general smells in the code. In Zig, if there's |
| 80 | +an allocator and a `try` in the same scope, [you need to be very |
| 81 | +careful](https://matklad.github.io/2025/08/16/reserve-first.html). If there's an isolated tricky |
| 82 | +function, it's probably fine. If there's a tricky _interaction_ between functions, it is a smell, |
| 83 | +and some bugs are lurking there. |
| 84 | + |
| 85 | +--- |
| 86 | + |
| 87 | +Bottom line: reading the code is surprisingly efficient at proactively revealing problems. |
| 88 | +Create space for calm reading. When reading, find ways to build mental models quickly, this is not |
| 89 | +entirely trivial. |
0 commit comments