12 comments

[ 3.3 ms ] story [ 33.7 ms ] thread
Does this person also identify performance issues by reading the code? This is completely impractical.
Why is "public static void ..." written in Cyrillic here? I guess this might be a joke?
In Writing Solid Code[0], Steve Maguire recommends stepping through every line of code, in a symbolic debugger.

Sounds crazy, but I usually end up doing that, anyway, as I work.

Another tip that has helped me, is to add code documentation to inline code, after it’s written (I generally add some, but not much inline, as I write it. Most of my initial documentation is headerdoc). The process of reading the code, helps cement its functionality into my head, and I also find bugs, just like he mentions.

[0] https://writingsolidcode.com/

I agree with the idea of not making bugs in the first place. Overall I think this piece is great and includes good suggestions. However, personally I think the best weapon to avoid writing bugs is making them impossible in the first place, ala "Making invalid state unpresentable".

Interestingly there's a post from the last day arguing that "Making invalid state unpresentable" is harmful[0], which I don't think I agree with. My experience is that bugs hide in crevices created by having invalid states remain representable and are often caused by the increased cognitive load of not having small reasoning scopes. In terms of reading code to find bugs, having fewer valid states and fewer intersections of valid state makes this easier. With well-define and constrained interfaces you can reason about more code because you need to keep fewer facts in your head.

electric_muse's point in a sibling comment "The whole “just read the code carefully and you’ll find bugs” thing works fine on a 500-line rope implementation. Try that on a million-line distributed system with 15 years of history and a dozen half-baked abstractions layered on top of each other. You won’t build a neat mental model, you’ll get lost in indirection." is a good case study in this too. Having poorly scoped state boundaries means this reasoning is hard, here too making invalid states unpresentable and interfaces constrained helps.

0: https://news.ycombinator.com/item?id=45164444

Extremely funny post.

The author doesn't grasp how much of what they've written amounts to flexing their own outlier intelligence; they must sincerely believe the average programmer is capable of juggling a complex 500 line program in their heads.

I do what the author does all the time, every day. But then, I work mostly on my own; and I've spent decades learning how to structure my code so as to minimize the amount that has to be "live" in my head at any give moment, and so that I can quickly rebuild that mental model on re-reading.
Isn't this basically what a debugger gives you? You say "follow the control flow" and "track state," but those are exactly what I do when stepping through code with invariants and watchpoints. The only real difference I see is that reading doesn't require a reproducible example, while debugging does. Otherwise, the habits seem nearly identical.
I once found a bug in code that was read to me over the phone while I sat in an airport waiting for a flight. So I agree that constructing a model of the program in your head is the key, and you can use various interfaces for that. Some are more optimal than others. When I first started learning to write programs we very often debugged from printed listings for example. They rolled up nicely but random access was very slow.
I think I'm having a hard time understanding the value of this piece. Is carefully reading the code you're writing/working on not the default? How on Earth do you write code without understanding what it does?
This article really resonated with me. I've been trying to teach this way of thinking to juniors, but with mixed results. They tend to just whack at their code until it stops crashing, while I can often spot logic errors in a minute of reading. I don't think it's that hard, just a different mindset.

There's a well-known quote: "Make the program so simple, there are obviously no errors. Or make it so complicated, there are no obvious errors." A large application may not be considered "simple" but we can minimize errors by making it a sequence of small bug-free commits, each one so simple that there are obviously no errors. I first learned this as "micro-commits", but others call it "stacked diffs" or similar.

I think that's a really crucial part of this "read the code carefully" idea: it works best if the code is made readable first. Small readable diffs. Small self-contained subsystems. Because obviously a million-line pile of spaghetti does not lend itself to "read carefully".

Type systems certainly help, but there is no silver bullet. In this context, I think of type systems a bit like AI: they can improve productivity, but they should not be used as a crutch to avoid reading, reasoning, and building a mental model of the code.

I keep reading articles about how you need to fit mental models of code in your head with analogies to spatial maps. This is not how your brain processes these. You have a spatial center mapping 3D objects to literally mini-3d-models encoded in neurons. You can grasp some(!) code with this if the structure is similar to what you code yourself, but most of the code in larger bases is a ruleset like your countries taxcode - it will only fit in your language processing center and need a lot of working memory.

Now some people might be able to fit more than millers number 7+-2 there and juggle concepts with 20 interconnected entities, but this is mostly done by people having this as their main work / business logic.

These articles mix up same-form dimensional mapping like audio or visual to distinct data, it's similar to why its easy to replicate audio and images, but not olfactory / smell. Your nose picks up millions of different molecules and each receptor locks onto a certain one.

Thinking you can find general rules here is exactly why LLMs seem to work but can never be inductive - they map similarities in higher dimensional space, not reasoning. And the same mix up happens here: You map this code to a space that feels home to you, but it will not apply to reading another purpose software outside your field, a different process pipeline, language or form.

If your assumption would be correct all humans needed to train is reading assembly and then magically all bugs will resolve!

Maybe if you want to understand code with both hemispheres map it to a graph, but trying to make strategies from spatial recognition work for code is like trying to make sense of your traffic law rules by length of paragraphs.