Reviewing code is a skill Code review serves multiple purposes beyond finding defects, including education, maintaining norms, gatekeeping, and accident prevention, according to research on Google developers and a 2013 study. The author argues that reviewing code is a skill that can be taught and improved, and that it remains valuable even as LLMs are claimed to be better at finding bugs. Reviewing code is a skill Intended audience: Software developers interested in getting better at developing software. Initially, I meant this post more for junior developers, but some parts are more applicable for tech leads etc. So if the post appears a bit muddled, please accept this apology from my inner Pascal, “Sorry, I did not have the time to break it up into two posts.” In developer communities, there has been a lot of discussion on code review, especially over the course of 2025-2026. For example, you may have seen claims like:Not necessarily all from the same people. - “Code review is the bottleneck” - “Mandatory pre-merge code review is for low-trust environments; you should push directly to main” - “Code review doesn’t find bugs” - “Code review is not for X, it’s for Y” - “LLMs are better than humans at reviewing code” - “LLM code reviews are much better than humans at finding edge cases/bugs” - “You should stop looking at the code; you should be doing XYZ instead” and so on. Out of this, zooming in on “Code review is not for X, it’s for Y” for a moment, the research has the following to say: By coding our interview data, we identified four key themes for what Google developers expect from code reviews: education, maintaining norms, gatekeeping, and accident prevention. Education regards either teaching or learning from a code review and is in line with the initial reasons for introducing code review; norms refer to an organization preference for a discretionary choice e.g., formatting or API usage patterns ; gatekeeping concerns the establishment and maintenance of boundaries around source code, design choices or another artifact; and accidents refer to the introduction of bugs, defects or other quality related issues. Similarly, Expectations, outcomes, and challenges of modern code review https://d1wqtxts1xzle7.cloudfront.net/123087856/icse2013-libre.pdf?1748877378=&response-content-disposition=inline%3B+filename%3DExpectations outcomes and challenges of.pdf&Expires=1786290544&Signature=R5O-pbRUD~ZBlCXj7MKPrQovHnP94AzTDdRaHH7OMLzSz-rElLhqbwYRJrN76q2ooGx6cRtl5laCF1fMf-rp3~CuR9vSOY2zWA20TuZCrQueownjjFMsMwMk16RgdsSgAPxtA~TXCOD06HqAo~wYeETeJW-l9gbRgbaFI2caM7pxbZM3eY2TvPQi54EAeymv3OyIylije4xUtlaoa9b7bwSfxSHwvxkaS2alYSqVu3HO1iZMTTQs9djOzEV3J8yGoJ26-PFhhwSUG1HIawGy6iGgOPur5Ue-EMWoLa85l5zGzh0jBdyJcf7VAaDIbU9wC4K~yXh9GzMocHpXV0YHDw &Key-Pair-Id=APKAJLOHF5GGSLRBV4ZA 2013 points out: Our study reveals that while finding defects remains the main motivation for review, reviews are less about defects than expected and instead provide additional benefits such as knowledge transfer, increased team awareness, and creation of alternative solutions to problems. Moreover, we find that code and change understanding is the key aspect of code reviewing and that developers employ a wide range of mechanisms to meet their understanding needs, most of which are not met by current tools So at least, hopefully we can agree that code review serves many purposes. I will get to the other points in a bit. But before that, I want to articulate a framing which I haven’t seen much elsewhere. Namely, that reviewing code is a skill . Specifically, I posit that: - It’s possible to get better at reviewing code. By “better”, I mean across all of the purposes above: catching bugs, catching design issues, increasing one’s awareness of what’s going on, and understanding the code. - It’s possible to teach someone to get better at reviewing code. - By virtue of it being a fairly modern skill, we don’t quite know where the human skill ceiling lies e.g. what is the Pareto frontier in terms of speed vs quality? . - If you’re a software developer, and if you believe that people will continue to be involved in the development and maintenance of programs for the foreseeable future, then it’s valuable to get better at reviewing code. First, I will give three small examples taken from the past few weeks of work of me finding bugs when reviewing code. I’ve specifically chosen bugs for discussion because they’re relatively unambiguous. Next, I will provide some of my own historical background related to code review, and some arguments in favor of the core thesis. After that, I discuss some ideas for experimenting with and improving code review. Finally, I discuss the aforementioned oft-repeated memes around code review and how they hold up to scrutiny with the thesis in mind. Let’s get started. A tale of three almost-introduced bugs A bunch of details have been elided in the course of explaining the different examples below, in an attempt to make them digestible. It may be valuable to keep an eye on stray thoughts of the form, “well, that seems like a code smell, no wonder you almost had that bug” or “duh, this could’ve been avoided with XYZ”. In two out of the three cases, the person who wrote the PR had experience with the surrounding code. The other thing to note is that LLM reviews with a mixture of high-end coding models around Jun 2026 were run for all of the PRs described below. They did not catch the issues that I caught. It may be helpful to consult this handy table from Lorin Hochstein’s Traditional versus resilience engineering views https://surfingcomplexity.blog/2026/08/02/traditional-versus-resilience-engineering-views/ :The post is short and recommended reading. The table below is a subset of the one in the original post. | Traditional view focuses on | Resilience engineering view focuses on | |---|---| | objectives | production pressure | | reducing complexity | navigating complexity | | root cause | interaction of multiple factors | | human variability as liability | human variability as asset | One way to read the cases below is to try to think about both sides of the table as you read them. Writing some git configuration We use our own devboxesDisposable VMs used for software development. at $WORK which run on EC2 instances. The logic for starting things has two subprocesses: One background process, to initialize state which isn’t needed right away. This process may or may not have finished by the time the user starts using the devbox. One foreground process, which needs some extra data from the user’s laptop, and blocks the user until it’s done. Only after this process finishes can the user start using the devbox. To reduce latency, we’ve been working on moving more operations to the background process. In this spirit, one of my colleagues created a PR to move some of the modifications to the global ~/.gitconfig In the interest of performance and consistency, we want to manage some aspects of people’s Git configurations. Yes, I know Nix exists, I use it for one of my own servers. No, we don’t use Nix at work. You’re keeping a check on your intrusive thoughts, right? from the foreground process to the background process. So when a git config command needs to modify ~/.gitconfig , it first acquires an exclusive file lock on ~/.gitconfig.lock . This prevents concurrent modifications from other cooperating processes, such as other git config calls. When I saw the PR, I remembered we’d hit non-determinism in the devbox setup, where git’s behavior of fast-failure on lock acquisition failure led to flakiness during startup due to a similar problem of concurrent writes. Just retrying on top would’ve still led to non-determinism, so my colleague added a cross-process dependency edge using an existing mechanism, where the write would only happen after the background process finished. Then I pointed out that we’d actually tried that earlier, but we’d gotten rid of that almost immediately, due to the increase in end-to-end latency because now a subpart of the foreground process would have to wait for the whole background process to finish . In the end, since we also had some file modifications which couldn’t quite go through git config directly,Due to needing to reconcile DO NOT EDIT blocks. we converged on a solution which had a separate flock operation with its own .lock file . This allowed for 1 having retries with backoff 2 doing multiple modifications under the same flock without other intervening writes 3 doing direct writes without worrying about concurrent writers. To display progress or not to display progress There’s a periodic CI job which does some processing and uploads a tarball to an AWS S3 bucket. Turns out, the aws CLI displays progress by default. Presumably, this is meant to help with debugging issues, as well as provide reassurance about progress when using the CLI directly in the terminal. When this colleague changed the logic to upload to 4 buckets from 1 bucket to speed up downloads from other regions,AWS buckets belong to a specific region. the CI job started failing, due to the log file exceeding the limit of 10MB. Turns out, the aws CLI logs a line for every 256 KB uploaded. This corresponds to tens of thousands of log lines when the upload is 10GB+. Multiplying the log lines by 4 tipped the job over the 10MB limit. The colleague submitted a PR to change the invocations to use --no-progress to unbork the CI job. At the time, my first thought was, “Hmm, is this the only option? Maybe there’s a way to show fewer progress updates? If the job fails mid-upload, it’d be nice to have some progress information to make it clearer when the job failed relative to the start of the upload.” So I looked up the docs for the aws CLI, and saw a flag --progress-seconds