Reviewing code requires reading
67 points by hauleth
67 points by hauleth
The point of doing review is to diffuse responsibility.
I don't often read a piece where a single line of reasoning annoys me so greatly.
If you merge code to main and prod breaks it's on you. Not me. Not the rest of the team. You are taken as a professional. It is your signature on that work. That's your code and your responsibility. If you had a P.E. license for civil engineering and stamped the plans to a bridge that later killed 20 people that's your work and your responsibility. The team's responsibility as developers and more importantly stewards of the codebase is to make it so that neither your code nor anyone else's is capable of breaking prod.
There is a difference and I don't believe it's a subtle one.
If you merge code to main and prod breaks it's on you.
I don't think this is a healthy mindset or culture. Code merging to main means someone else reviewed it, reasoned through the changes, maybe had a conversation about designs, went through iterations of changes, and ultimately approved it. No one builds the Eiffel Tower alone by their own means and skills. Blaming individuals in your workplace is only going to create resentment and a toxic culture. If it's a pattern of behavior, that's an HR problem.
It's not about assigning blame for the purpose of punishment. Every team should have a culture that promotes a blameless post mortem for all unforeseen issues that negatively impact company services. The issue I take with the reasoning he presented is that it is an out for personal accountability. There's no place in this industry where relying on teammates is a weakness but I think we should avoid any line of logic that gets us closer to saying "that wasn't my fault, other people didn't catch it either".
It's not about people, it's about the software development and vetting process. If you follow the process and things break, then the problem is with the process! Maybe the process issue was letting you participate in the first place, that's always a possibility.
Agree. But also. I've worked on teams where there was effectively no QA and guess how many bugs developers pushed out. A handful.
I've worked on teams with dedicated QA folks and guess how many bugs the developers would have pushed out (if QA hadn't caught it). A lot more than a handful!
I don't care whether it's code reviews, QA, "the process", or whatever. People should not assume that they can put less effort into their craft because somebody (or something) else will catch or prevent their mistakes. It promotes laziness and churn.
Reviewers can help but they're never going to be perfect and I don't think it's reasonable to require reviewers to manually run and QA every change because the author should have already done that.
Agreed 100%.
What's interesting is the split after 6 hours: 12/27 votes. One out of three puts pressure on an individual rather than a group.
This is concerning at best, even with all the biases and primers factored in we should understand the team mechanics and ethics of our craft better. John Nash is disappointed rn.
I’m pretty sure both of these takes are wrong because they’re focused on blame.
The point of doing review is to diffuse responsibility.
If you merge code to main and prod breaks it's on you. Not me. Not the rest of the team. You are taken as a professional. It is your signature on that work. That's your code and your responsibility.
I expect a professional team to review major incidents with something resembling a blameless postmortem, which is to say they examine the system as a whole and look for opportunities to improve it to minimize the risk of the incident happening again. Even if the incident really can be traced back to the actions of a single engineer, there’s a whole lot of systems around that engineer (education, hiring, static analysis, test scripts, instrumentation, and yes, code reviews) that could usually bear some improvement. If a codebase and the systems that support it are a tower of Jenga blocks, sure, it makes sense for the developer who drew the last block before it fell to take the lead in cleaning up the mess. But if the rest of the team doesn’t jump in and help, there’s probably a much bigger problem, which is that people are more motivated by fear of blame than they are by a desire to improve the system.
The real purpose of a code review as I see it is to improve code quality and cohesiveness through a process that should engender a culture of teamwork and shared values. If you have someone new to a team, reviewing their code is the best way to bring them into that culture. If you need to extend or maintain something someone else wrote, it’s a lot easier to do so when you’ve already read it and had an opportunity to ask questions and leave suggestions.
That equates to having no reviews. What is the point of a review if the reviewer bäres zero responsibilities? At that point he is useless. Why even having a review?
I would say difusing responsibility is a consequence. The point is to catch errors that are easy to make as a single purpose but unlikely to be missed by two or more people.
I say this as a person that holds the unpopular opinion that reviews are way overused in software and everyone should stop using them as a replacement for engineering. Which they will never be.
Isn't blink saying the reviewer has all responsibility? "Stamped the plans"? That's usually the senior architect in the bureau who does it.
It also annoyed me but for a different reason as you are saying. The point of doing a code review, in a healthy organization, should be that it seen as one of many processes that together are responsible for good quality process. Not just a singular gateway that is responsible for something going to production. In fact, in many cases it shouldn't even be the last gateway depending on the complexity of the landscape, integration with other applications, etc. But, unfortunately for many teams and companies the QA process after code is merged is something that might as well not exist anymore.
Anyway, I am a firm believer of everyone being responsible who is involved in the process. However not to diffuse responsibility but because quality should be a integral part of the process before code is even written. Basic boring stuff like three amigo sessions (or whatever method used to get various disciplines involved in talking about spec and requirements), test driven development methods, basic static code analysis tooling integrated in IDEs and various checkpoints, QA/Test automation expertise other than developers themselves integrated in teams and much more.
I know that for many people this is not a reality they ever encounter. But if we are making the comparison to civil engineering. The developer shouldn't be both the engineer and construction worker for the bridge in many cases. Even if they are, in civil engineering there also never is one person who is responsible as the practices I mentioned are very much part of the process there as well. An engineer needs to document everything, make all the necessary calculations for load bearing, have others re-check those calculations, then often get approvals from relevant government bodies who will double check various things. Then during construction there are also many more points where things are audited, checked, approved, etc. Even for simpler construction like that of a single family house this is still the case. Plans need to be drawn up, someone needs to sign of based on calculations, permits need to be given based on those plans and at various points in the process inspections need to be done.
Yes, there are people who have more responsibility than other but overall there never is a single person who has the only responsibility. In fact whenever there is a engineering disaster you often see that it has been a failure in the overall process where at multiple points things should have been stopped but were not.
We like to compare ourselves in software development with civil engineering. But if we do that we need to realize that our processes very often are just not on the same level at all. At least not as far as constructing bridges goes. In the best case scenario we often find ourselves at the level of housing developers who build the cheapest possible houses and try to work around a lot of the legally requirements.
If someone wants to remove that group responsibility and instead tell you to "approve without reading" then why ask me to push the button manually at all? If you want to pay someone for pushing a button without thinking [...]
I'm not saying I agree with the post this is discussing, but I think equating "without reading the code" to "without thinking" is not quite right. Consider the hypothetical case where:
In such a world, would you still care as much about reading the code line-by-line? Perhaps not. E.g. today most people write SQL without verifying that the query plan matches exactly what they want.
As I read the original post, my interpretation of it was "you articulate this idealized world where you're actually comfortable shipping code without literally reading the code" and then "okay, so how do we smoothly interpolate between now and that ideal world."
Now, one can disagree about how far we are today as an industry, or in the context of a specific company/codebase. But I think if you were to take the thought experiment of imagining the idealized world seriously, I think, most people would be able to come up with some criteria.
(Also, I fully understand and sympathize with why you might jump to "without thinking" based on industry experience, given current trends. But I think it's important to recognize that this is not what Charity's post is saying.)
OP's point is not about catching problems in the PR. OP is specifically talking about two things: building familiarity with the codebase (which does require reading the code, no matter how good the tests are), and creating accountability. I think there's benefit to having responsibility and knowledge of the codebase diffused, so that if Alice checks in code that Bob reviews and then goes on vacation, Bob can feel knowledgeable enough and responsible enough to fix anything or add any new features that are needed while Alice is away. If Bob is just rubber stamping a proof of correctness, then he will not be well equipped to work on that part of the codebase in the future. He might as well not have been involved in the commit process because it has not increased his knowledge or sense of ownership.
To me, this description is just what a typical compiler is. Except that a compiler is deterministic, unlike an LLM that generates (potentially dubious) tests. We already have a program that will transform human-written instructions/code into machine-readable code (or an IL), and it has guarantees about how generated output relates to the input, such that we can trust the output without the need to examine it. Though some people read the outputs of compilers for the purpose of optimization (Godbolt springs to mind), there isn't much need for it otherwise.
Attempts to do this at a different level of abstraction that is non-deterministic might look reasonable, but they are only pretending to provide the correctness guarantees that a compiler does, and they are going to run into issues.
So to me the whole debate about LLMs is just a symptom of a more fundamental problem, that of the existing (deterministic) programming languages not being expressive enough and not having effective enough tooling. I don't believe LLMs will be the solution to this problem, even if they feel like they solve it. They are adding another layer of indirection and performance penalty on top of a foundation that is too restrictive. They are expensive, buggy "pseudo-compilers" running on top of interpreters, running on top of compiled machine code.
This is why I believe they are a technological dead-end. They may be a way towards short-term profits for companies, but I don't believe they will improve software, or humans' relationship to writing or using software. To me, software is more than a technology used to ship products.
But that is completely missing a point.
It doesn't.
We did the same let go with C hiding assembler. Man, it was hard for the generation I followed. There were bugs in compilers, horrible perf drops, debugging was a nightmare. At first.
The point is: having a strong QA process (starting with good language fit, through linters, codemod rules, up to e2e tests and instrumented CD) is the new C that endures that the most of the code complexity added goes in the right direction.
This is where the team effort goes these days, this is how responsibility diffusion works with autonomous coding agents.
Open for discussion, though.
The thing is, that from compiler we expect that it will be deterministic. That gives us debuggable environment, and while debugging isn't always fun, it is doable. With LLMs and agentic coding that is not a case. Ok, you get some code, but you still need to review it, you cannot assume that it does exactly what you told it, as you can do with compilers. Compilers have rules and LLMs do not, LLMs have only suggestions.
And the point IMHO isn't about "whether this piece of code works or not", it is also about "can we maintain this code", "can we debug this code", and "can we reason about this code". If you are expecting generated code to work in happy path, then yeah, LLMs are quite good about it. That is why I think, that LLMs can fit a case, for ad-hoc, write-only, temporary tools, that will be run against validated data, and will be thrown away immediately afterwards. But if that is code, that someone (potentially future me) need to go back in some future, then IMHO review is needed. Meaningful review, not just "merge without reading".
Thanks for the detailed response. I feel you 100%.
It must really sound crazy, but your arguments are exactly the same as my older teacher back in 2000 gave us when arguing with a younger teacher. And at times it gave off vibes of a holy war. So, yeah.
I'll touch on the maintainability and predictability.
Maintainability. When I code in asm, I break my code into readable, concise, well commented routines. Debugger shows me the original code as opcodes map 1:1 back to asm. I can easily step through the code. With C/Pascal the disassembler is a mess, debugging optimized code is a nightmare, the compiler is [was for real] buggy and it never uses the latest arc hot instructions because it's dum.
Predictability. I just change two vars NAMES and the output is different [very old sorting issue], I'm adding A COMMENT and my code breaks [yeah, some tricky overflow bug in an edge case] let alone the opted code inlined and inlined changes 99% of the binary all the way. And boy it bloats the binary, now it's almost a 50K binary, how do I read the asm code of all of this!!!
So, WDYT?
It helps to think about different use cases. Pushing new javascript for the UI of an internal application? Yeah it's fine I don't think I have to look at the CSS.