Imagine all the stuff that's probably in MS office, for which the reason is totally lost. There were reasons, but they're not really retrievable.
Yes and no, I think. Codebases represent a collection of point-in-time decisions, and each one of those decisions had a reason. There's always a 'why' even if it's not captured.
> Imagine all the stuff that's probably in MS office, for which the reason is totally lost. There were reasons, but they're not really retrievable.
True - and that's Really Bad. That means you're stuck not able to improve or change certain things, because you don't know why they were done that way - and when you have a userbase the size of Office, you can't just say 'f it' and break things - at least not lightly, as a code-level decision.
1. Green field+0 years: This particular portion of the system was designed in this way as a cargo cult.
2. Green field+2 years: Because that portion of the system was designed in that way, we had to modify this other portion of the system to achieve X.
3. Green field+3 years: Because of the change in 2, and this new constraint Y, we had to make this other change in a third portion of the system.
5. Green field+(3 to N) years: repeat steps 2 and 3.
6. Green field+N+M years: Wow 5 (or previous 6) has created a mess and exposed a serious architectural flaw from step 1 or previous 6. All of the logic in the various iterations of 2+3 are refactored as best as possible.
So, the "why" basically grounds out in "the refactoring we could afford to do in order to correct a mess that resulted from a cargo cult or previous refactoring of even older cargo cult".
Is that "why" functionally helpful? Usually not for me -- in either case, I can't really assume a lot and I have to proceed with laborious "just watch what the code does" debugging/logging that I would've done without the "why?".
But this is probably a ymmv situation.
Which, of course, has two negative implications. Not just one.
Of course every once in a while you see the super helpful commit log "fix bugs". Some of those are mine and I've even burned myself in the past.
I think there might be ways to mostly sidestep this, though. Docs that look more like changelogs than "docs." Store them reverse chronologically so someone new can either look back at recent history or try to jump in to when a particular feature was originally added. Lean in to there being a series of "whys" for the code.
If a developer doesn't check for a null value, are you really going to head to the documentation to find out why? Is the segfault really by-design? It might sound facetious, but that's the 90% of bugs: not specifically segfaults, but trivial mistakes, invalid values and logic problems. Only a debugger can show you the steps and conditions involved that lead to that invalid value. Sometimes, through debugging, you find a problem that does span multiple layers or components.
If something spans multiple layers or components then that's a design problem. When you are solving a bug like this you have to "debug" at a much higher abstraction level. You shouldn't use a debugger for this (as you've pointed out); you should use a team of peers (documentation alone is not the correct tool). That team might rely on documentation, but would have to keep in mind that documentation rots over time.
If you're solving the former with the processes of the latter, I'm surprised you get anything done. Documentation won't tell you where an invalid value originates from or why. Only extremely verbose logging or debugging can do that.
Yes. I often write code in a way such that no null is checked. But if I do so, I always document it properly and my reasoning why, if the function is used properly, no null will be passed.
This way, you can be sure that if a null is dereferenced, not my function is the source of the bug, but a function that does not obey the pre-invariants that my function imposes on its parameters.
So you know that you are not supposed to "fix" this bug by introducing a null check to my function, but instead fix the bug in the other function that calls mine.
if (value == null) print "Must try harder"
?
No. Static analysis can and is actually what is used most of the time. Either with an automated tool, or with your brain.
Of course debugging is also needed, but the right mix between the two is needed, and you are actually doing it even if you use a debugger a lot (at least I hope so, otherwise you are probably not fixing your bugs correctly way too often).
If you're lucky, you've got the commit messages and associated bug/work tracker items that explain where the software comes from. I agree there's no substitute for planning documents, but those don't always survive confrontation with the enemy.
But now I'm working on nuts and bolts cybersecurity stuff for a tech giant. Stuff like public key infrastructure. Even if I know why things are done, stepping through someone else's code with expected behaviors, pathological cases, and error cases has been extremely helpful because there are so many more subtleties to worry about.
Yes, ability to explore and understand the project via debugger is a great and useful skill.
However, if it takes debugger to understand that project, then, I'd say, it is a badly written and poorly documented project.