Most advice about programming is about writing code, but we rarely ever consciously consider how to improve at reading code. One way to do that is to read more code.
If you read really good code, and I mean really read it, like a book, and absorb it, then you will improve so much.
This kind of improvement is the most impactful, because you also spend most of your time as a programmer reading code than writing new code.
Also, if you become good at reading code, then you don't need documentation.
Personally, I find it to be even better to step through code using a debugger.
I think just reading down the text of a set of code files like a book has pretty limited utility. (Though better than not reading code at all!) It's like reading one of those choose-your-own-adventure books from start to end.
Starting from an entry point and then digging into methods from there is better, but being able to inspect the runtime state and get a sense for the data layout makes the experience so much richer.
> but being able to inspect the runtime state and get a sense for the data layout makes the experience so much richer.
That's kind of the problem and why it's a crutch. If you get better at reading code and reasoning about it without needing to use a debugger, it'll make you even more productive for when you actually do need to reach for it. The opposite is not true.
Being able to read code deeply in the setting of code review and point out structural issues without needing to execute that code or strap on a debugger is an enormous superpower, and one of the biggest skills that differentiates an early-career or mid-level engineer from a senior engineer in those that I've hired and managed. Over time, you do a lot more reading code than writing it if your codebase does its job successfully.
I don't think it's possible to put too fine a point on this because it seems to be a weirdly and stupefyingly common point of view among programmers:
This perspective makes absolutely no sense.
This is like saying that the exercises in mathematical textbooks are a crutch because you'll never be able to read formulas if you interact with the ones in the book via the exercises given. This is the exact opposite of the case! The exercises are there to force you to "step through" the technical details in the text, to actively build intuition for the dynamics. If there were a way to spin up a math debugger to literally step through the details as you work through the exercises, that would be amazing. Educators would kill for that capability! And we already have it, essentially for free. But then out of some kind of misplaced sense of purity, tons of people have concluded that it's bad to use this incredible capability.
This is just bad pedagogy.
Edit to add: I feel like I didn't state this plainly enough: The way to learn things from textbooks is not to just read a bunch of different text, it is to interact with the concepts covered by texts. That's why textbooks all have exercises, and why educational institutions always ask students to do those exercises (and more). The advice to "read a lot of code" is like saying "read a lot of textbooks". But that's not good enough. You need to run the code, ask questions of it, test assumptions out; this is just like doing exercises in a textbook, and a debugger is a superpower for doing that active interrogation, while reading the text.
I think it's common to react to an alternate approach with defensiveness and assume it doesn't make sense, because it calls into question one's sense of identity and strategic competency.
I never said to "read a lot of code" analogously to "read a lot of textbooks." I actually explicitly called out the practical context of code review, which is where you really learn and develop the muscle of code reading in a design critique setting.
You cannot get good at code review without getting very good at reading code and structurally reasoning about it. You get good at code review by doing it, and in particular doing it with other engineers who are very good at it and who can teach you how to do it. There are a lot of ways to do this that are essentially free (open source contribution is a great one); and of course, it's a mandatory part of what you'll need to do on the job as a professional engineer at any reputable shop.
Get good enough at giving and receiving quality code reviews and you'll find yourself reaching for your debugger less and less. That's because you'll be able to reason about code /in your head/. You can see how common and edge paths would evaluate without needing to execute it, because you've built the muscle to technically analyze and discuss it as it stands.
Go without building that muscle, and you'll lack the foundations that it builds for you, and you'll always rely on the crutch to make up for it. For what it's worth, this is what well designed software engineering interviews are designed to test for -- your ability to reason about and effectively work with code without being dependent on tools.
If you've never seen an experienced senior or principal engineer who uses zero tools solve problems 10x faster than you can because of their ability to do what I just mentioned, this is going to sound foreign. But if you ever develop the inclination to develop into such an engineer, you'll have to do what I just described.
You and I agree on this being an important "muscle" to build.
But this thread is not about people using this muscle, it's about people developing it afresh and further exercising it. What you described with senior engineers doing code reviews and such is analogous to a ballet dancer on stage performing, but I'm talking about doing a thousand pliés in a practice studio. The stage doesn't have a barre or a mirror, but the studio does, and it would be foolish not to use those useful tools when exercising.
I'm not totally sure if we're just talking about two different things - use vs. exercise - but a plain reading of your comments makes me think that you think the best way to improve one's structural reasoning (or "mental debugger" as another commenter put it nicely) skills is to just read more code. But I'm sorry, that's just not right. It is much better for building a good mental model of how code executes to simultaneously inspect and interrogate it as you're reading. It isn't necessary, it's just better.
A crutch is just not the right analogy. Rather, it's an aid that makes exercise more effective so that you're stronger when it comes time for game day.
Or, to come at this from another angle: Surely you agree that just reading code is not actually helpful for developing the reasoning ability you're talking about in your comment. Wouldn't you agree that novices need to run the code they read, to see what it does? Otherwise they are guessing , and as novices, they are almost certainly guessing wrong. Without anything to check their guesses against, they are likely to just assume they're right and internalize that likely-wrong mental model. I certainly had to painstakingly undo a lot of my incorrect mental model from bad guesses early on. The more that can be avoided, the better!
If you agree with that, which I suspect you do, then I'd put it to you that a debugger is just an efficient and effective tool to better aid in this process of guessing what code does and then checking those guesses, in order to further develop and internalize a correct mental model of how it works. It's like if you could run a unit test surgically exercising exactly the code path you're interested in, exactly up to your breakpoint, and assert on exactly the program state you're guessing about to confirm or deny your hypothesis. But you can do it interactively, without needing to write test code. It's an amazing capability and learning aid! It's very bad advice to tell people not to use it as they are learning.
> I'm not totally sure if we're just talking about two different things - use vs. exercise - but a plain reading of your comments makes me think that you think the best way to improve one's structural reasoning (or "mental debugger" as another commenter put it nicely) skills is to just read more code.
I've already said twice, both in the original comment and in the follow up, that my point is about applied code reading in the context of code review, not in a vacuum. The first time you decided to respond to a made up point which I never made, I clarified it. Now that you're doing it a second time, it's clear you're arguing in bad faith and putting words in my mouth.
I wonder why you keep on arguing against a strawman point that I never made rather than actually responding to my argument. Is it because you are incapable of understanding the point I'm making? Perhaps. Is it because you're deliberately ignoring it out of cognitive dissonance as you know it would weaken your argument, and you'd have to admit that you're arguing for something which is the wrong strategy? I hope not, but that's my best guess.
The rest of your comment is impractical. If a novice is struggling with understanding and working with a piece of code, do you really think the best way for novices to learn is to "figure it out" with a debugger, or do you think they should ask for help through code review or pair programming with someone else working with them on the project?
You seem to be arguing under the assumption that software engineering is a solo sport. I am arguing that software engineering is a team sport and those who treat it like a solo sport tend to underperform and plateau in their skill development.
I stand by what I said. Debuggers are a crutch. Crutches can be useful but you shouldn't rely on them. Learn how to reason about code in the abstract (which you learn to do by learning fundamentals of computer science and applying them in a team context with others who can) and you won't need it because you can compile the AST of the codebase in your head. Moreover, this skill is MOST critical to develop when you're early in your career because it's when you have the neuroelasticity to learn new ways of reasoning about and problem solving with code. I was forced to do this by early career engineering leader mentors and while painful and humbling, I am very thankful for it because it put in place the practical fundamentals for me to architect large, performant, scalable and revenue generating systems from scratch with very little issues.
If someone thinks that's impossible to learn whether late in their career or early in their career, that speaks more to their shortcomings than to an issue in the technique. For what it's worth, many of the strong early career engineers I've worked with (1-2 years experience max) have also been able to do this, and often times even better than many mid to late career engineers. As you may expect, these strong early career engineers end up running circles around their more experienced counterparts pretty quickly.
> I've already said twice, both in the original comment and in the follow up, that my point is about applied code reading in the context of code review, not in a vacuum.
Ok, but that's not what this thread was about before you replied. The OP literally starts with the words "advice for new software devs". So I assumed that, rather than just being off-topic, you must be using that as an analogy for what we were actually talking about.
The reason I keep coming back to the topic of the thread is that it is the topic of the thread. It honestly just took me awhile to figure out that you actually did just want to grind an axe on a totally different point.
> If a novice is struggling with understanding and working with a piece of code, do you really think the best way for novices to learn is to "figure it out" with a debugger
Um, yes, definitely. Or at least, it is one extremely valuable tool they should learn how to use effectively. Another very valuable genre of tool that has emerged very recently is things like chatgpt. And then there's good old fashioned reading of code, books, documentation, articles, etc.; those are useful, but the interactivity of a debugger or chat assistant is better.
> or do you think they should ask for help through code review or pair programming with someone else working with them on the project
For one thing, "novice" does not only mean "entry level employee", it also means students. But both students and people early in their career should be taking advantage of both high-touch 1:1 support like code reviews and pair programming, when possible, and also muddling through things on their own, guessing wrong and figuring out what the right answer is and why their guess was wrong. It is not possible when learning and deepening a new skill to have an expert available for individualized attention at all times. Individualized attention from an expert human probably is indeed the best way to learn and develop skills, but it doesn't scale.
> If someone thinks that's impossible to learn ...
Who said that? It is obviously possible to learn things while ideologically rejecting readily available tools that can help you learn more efficiently and effectively, it's just extremely foolish.
For instance, a tool that seems to have been readily available to you, which you seem to have used effectively, was the support of an experienced mentor. It would have been very foolish of you to reject that. But by your logic, using an experienced mentor to help you develop a new skill is a crutch that you must reject because you won't be able to run all your code reviews by a mentor later in your career. But of course you can see why that's stupid. But it's the same thing you're saying about debuggers. "Don't use a tool to learn because then you won't be able to function without it later". It's just not how learning works.
Any tool that helps a novice build a more accurate mental model of a subject is a benefit, not a crutch. Holding an ideology in opposition to such tools is foolishness.
> Individualized attention from an expert human probably is indeed the best way to learn and develop skills, but it doesn't scale.
> Any tool that helps a novice build a more accurate mental model of a subject is a benefit, not a crutch. Holding an ideology in opposition to such tools is foolishness.
It looks like you really don't have a substantial argument or credible results to show for why your proposed approach is better, so I'll refute this one: no, any tool that helps a novice build a more accurate model is not necessarily a benefit. It's the same reason that you don't ride a bicycle with training wheels forever, and the same reason singers who can't sing without auto-tune atrophy in their development.
I'm glad you're at least conceding the value of individualized attention from an expert human being the best way to learn and develop skills, but I disagree with you that it doesn't scale. In fact, I'll refute it most strongly -- it's the only thing that /does/ scale.
If you are a novice engineer and you want to progress as fast as possible, start doing real code review as soon as you can. As I mentioned earlier, you don't need to get that from being gainfully employed -- you can and will pick up all of these skills in open source contributions. Start contributing and you'll get feedback. Work on the feedback and you'll start learning what code review is all about in practice. Learn what code review is all about in practice and you'll stop fumbling around in the dark with a debugger.
You only have so many hours in the day. Spend it on what really matters, and spend it on the most important muscles. Do your deadlifts and squats rather than ab crunches.
Hilariously, you and I seem to entirely agree about the point I have by far the strongest conviction on, which is that passively reading code is not the best way to level up skills.
You think people should be taking advantage of human support to learn by actively doing, and I think they should be taking advantage of both human support, if available, and also whatever computer tools are at their disposal independently.
The only reason I started arguing with you is that this prideful machismo ideology of disdain for useful tools that is common among programmers has become a giant pet peeve of mine, over the years since I held that foolish ideology myself. I'm not arguing with you, I'm arguing with myself a decade ago, when I could have avoided a lot of silliness.
> You only have so many hours in the day. Spend it on what really matters, and spend it on the most important muscles.
I also only have so many hours in the day, frankly not nearly enough as it is to do my work and take care of my family, and I don't want to spend any of them trying to figure out how to mentor people who send broken code for review without bothering to first figure out why it doesn't work how they expected and how to fix it. That is not a valuable use of anyone's time; there are plenty of tools available to off-load this to computers, without taking advantage of someone else's "only so many hours in the day".
What I am interested in mentoring people on in a code review or pairing session are the interesting non-mechanical facets of the craft. I love discussing that stuff. But please don't come to me with "I don't understand why this data structure holds this value at this point in the program"; you can figure that out yourself and learn something on your own in the process.
It looks like we are both arguing with ourselves from a decade ago, so at least we share that common ground :)
Candidly, I don't know how constructive this conversation is at this point; given that you've mentioned "I also only have so many hours in the day, frankly not nearly enough as it is to do my work and take care of my family, and I don't want to spend any of them trying to figure out how to mentor people who send broken code for review without bothering to first figure out why it doesn't work how they expected and how to fix it" I find it hard to believe that being one of the best builders in the world is really your goal or that coding is anything more than a vocation for you rather than a calling. And there's nothing wrong with that, but it's going to mean we will simply talk past each other having two completely different conversations.
However, my advice is for those who are not content to be good enough at writing software to maintain a stable job in tech until retirement, but for those who want to go above and beyond that, and stop at nothing to be the best of the best and reap the rewards, internal fulfillment, and sense of purpose that comes with that. Realistically that isn't and it shouldn't be most the goal of most early stage engineers in the field -- it's an expensive goal that even if attained only will make a very specific kind of person (like myself) fulfilled. Your advice is likely good for the former category, while mine would be poor.
For those in the latter category, I'll contextualize my advice based on the this very well written blog post by Dan Luu about the difference between p95 proficiency and p99 mastery. You can sum up my argument as stating that overreliance on debuggers (and other tools such as IDEs) is absolutely something that you see in p95s that you don't see in p99s. P99s can utilize debuggers, IDEs and other tools but it doesn't materially affect how quickly they can execute because of how well trained, fast and accurate their "mental math" flavored problem solving ability already is [1]. If you're curious, please take a look through the linked blog post and see how much you agree with it.
It's true that a debugger is easier to use, but it's limited in that you can't bring it all the places that you can bring your eyes. Also, as one improves at reading code a develops a mental debugger that can be brought anywhere your eyes can.
> it's limited in that you can't bring it all the places that you can bring your eyes.
Why can't you?
> Also, as one improves at reading code a develops a mental debugger that can be brought anywhere your eyes can.
This is definitely true, but that "mental debugger" is susceptible to incorrect assumptions about the runtime state at the point of execution. If this weren't the case, far fewer bugs would be written in the first place, as this mismatch about assumed vs. actual runtime state is where most bugs emerge.
Honestly not trying to be rude, but that would be because of how eyes work. The moment you do anything that goes beyond looking at code, is the moment you've met that limitation I described above.
The other part you brought is totally valid, it's just all about tradeoffs. If you prefer using a debugger in situations where I would read the code, then more power to you.
Actually, it's funny, because the original article talked about the One True Way (or whatever his exact wording was), and how we all go through a phase where we discover that for ourselves. I think this thread in a way has been an expression of that concept. Anyways, thanks for your thoughts, I'll remember this next time I'm struggling with the ol' brain debugger ;-)
What do you mean by "because of how eyes work"? When using a debugger, one's eyes mostly remain on the code... I feel like I must just be truly missing what you're trying to say here.
I like your call-out of the "one true way" section. What I was thinking in that section is that my experience, having now traversed a number of "one true ways", is that: 1. Very nearly all tools and techniques are truly useful in some way in some circumstance, and 2. Not all "one true ways" are created equal; some tools and techniques are more useful than others and better stand the test of time.
In my view, using a debugger to understand the runtime state while reading and exercising code (especially when it is unfamiliar) is one of those techniques that is broadly useful and never out of date. But that's not a totalizing view, it isn't the "one true way", obviously nobody only reads code while running it within a debugger, nor should they! But dismissing its utility for this use case of understanding code more deeply is, in my view, just odd.
Usually, the best code to read is something that you already need to understand. So, if there's a library you use at work, for example.
Also, for every language there's a really important project written in that language, like SQLite if you want to learn C, or finagle if you want to learn Scala.
If you read really good code, and I mean really read it, like a book, and absorb it, then you will improve so much.
This kind of improvement is the most impactful, because you also spend most of your time as a programmer reading code than writing new code.
Also, if you become good at reading code, then you don't need documentation.