Programmers talk about “reasoning about code” all the time, but what does that actually mean? Is it really just pretending to be a CPU and working out exactly when your for loop ends?
Yes and no. Figuring out exactly what your loops are up to is part of it, but at a higher level it’s also about how your methods, objects, modules, functions all work together. Like this stackoverflow answer says, writing code isn’t just about cranking out something that compiles, you’ve also got to be able to figure out if it’s actually right, if it performs well enough, if it can be scaled, if it’s vulnerable to bad data (whether that’s malicious or accidental).
Sadly, tools can only help you so much with everything that comes after getting your code to compile. The compiler can tell you whether your code will run, but it can’t tell you if it does what you meant. Automated tests are a huge help with that, but those tests are code themselves and you also need to be able to reason about them to make sure you’re testing what you meant to test and that the test itself isn’t broken.
To figure out what your code is doing, you need to be able to read and understand it well enough that you can predict what it will do for any given input. That’s basically all “reasoning about code” is. If you can reason about your code, you can change it or add more features without breaking things or spending hours and hours cursing at your computer and howling “whyyyyy won’t this work?!”
So being able to reason about your code is really useful, but how do you do it? Practice, mostly. If you’re a beginner programmer you should not worry even a little bit about understanding a whole codebase or heck, even a whole class. Focus on one method or one loop or one if statement at a time. When you get enough practice, you’ll be able to understand those really quickly and you can move up a level to figuring out what a whole method does with different inputs or how two methods in the same class work together.
Oh, and it really helps if the code you’re trying to reason about is good code (that is, well organized, has good names, etc). Some code is next to impossible to reason about because it’s full of giant methods or the variable names aren’t helpful at all or, and this is one of the worst problems, variables change all the time in unpredictable ways. A “total” variable that changes value each time through a loop isn’t so bad, it’s clear what it’s for and why it changes. On the other hand a “total” variable that gets reused for completely different totals at different places in a method is a real problem. Humans can only hold so many things in their working memory at once and “wait where does total change again?” takes up most of them. If you have a terrible time reasoning about a certain piece of code, it’s entirely possible the problem is not you but the code.
In cases like that when I don’t have time to refactor the code so it’s easier to read I find it really helpful to make a lot of notes and draw myself a map through the code. If you want to be really helpful you can even type up your notes / make a diagram out of your map and add it to your developer documentation.
Reasoning about code can be hard to do and takes practice, but it’s not some sort of magic that only “real” programmers can do.