Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

[flagged]


> type systems, invariant checks

Yes, something strange happens in large systems, where it's better to assume they work the way they are supposed to, rather than deal with how they (currently) work in reality.

It's common in industry for (often very productive) people to claim the "code is the source of truth", and just make things work as bluntly as possible. Sprinkling in special cases and workarounds as needed. For smaller systems that might even be the right way about it.

For larger systems, there will always be bugs, and the only way for the number of bugs to tend to zero is for everyone to have the same set of strong assumptions about how the system is supposed to behave. Continuously depending on those assumptions, and building more and more on them will eventually reveal the most consequential bugs, and fixing them will be more straightforward. Once they are fixed, everything assuming the correct behavior is also fixed.

In large systems, it is worse to build something that works, but depends on broken behavior than to build something that doesn't work, but depends on correct behavior. In the second case you basically added an invariant check by building a feature. It's a virtuous process.


This comment is a nugget of gold - I hadn't thought about it in those terms before but it makes total sense. Thank you!


One thing worth pointing out here is you that when reading you rarely will actually find the bug that you set out to, undoubtedly you'll notice others. Because you're reading the code with a mindset of "what awkward conditions failed to be handled appropriately such that xyz could happen".

It is also valuable to both form a hypothesis of how you think the code works, and then measure in the debugger how it actually works. Once you understand how these differ, it can be helpful in restructuring the code so it's structure better reflects it's behavior.

Time spent reading code is almost never fruitless.


In my experience there's one approach, which might not necessarily prevent bugs, but helps to reduce their numbers, and does not require much effort. I'm trying to use it whenever possible.

1. Code defensively, but don't spend too much time on handling error conditions. Abort as early as possible. Keep enough information to locate error later. Log relevant data. For example just put `Objects.requireNonNull` for public arguments which must not be null. If they're null, exception will be thrown which should abort current operation. Exception stacktrace will include enough information to pinpoint the bug location and fix it later.

2. Monitor for these messages and act accordingly. My rule of thumb: zero stack traces in logs. Stacktrace is sign of bug and should be handled one way or another.

With bug prevention, it's important to stay reasonable, there's only so much time in the world and business people usually don't want to pay 10x to eliminate 50% bugs. And handling theoretical error conditions also adds to the complexity of codebase and might actually hurt its maintainability.


The jumping off point given in the lede of the post—<https://www.teamten.com/lawrence/programming/dont-write-bugs...>—ends with this:

>If you want a single piece of advice to reduce your bug count, it’s this: Re-read your code frequently. After writing a few lines of code (3 to 6 lines, a short block within a function), re-read them. That habit will save you more time than any other simple change you can make.

So, more focused on a ground-up, de novo thing as opposed to inheriting or joining a large project. Different models of "code" and different strokes for different folks, I guess, but the big takeaway I like from that initial piece is:

>I spent the next two years keeping a log of my bugs, both compile-time errors and run-time errors, and modified my coding to avoid the common ones.

It was a different era, but I feel like the act of manually recording specific bugs probably helps ingrain them better and help you avoid them in the future. Tooling has come a long way, so maybe it's less relevant, but it's not a bad thing to think about.

In the end, a lot of learning isn't learning per se, but rather learning where the issues are going to be, so you know when to be careful or check something out.


I've also noticed that a strong type system and things like immutability have worked tremendously well for minimizing the amount bugs. They can't necessarily help with business rules but a compiler can definitely clear out all the "stupid" bugs.


Dealing with a 15-year old legacy codebase with strong types: awful but manageable. Without: not a chance.


I've identified serious bugs that were lurking in large legacy codebases for years with this approach. Whenever I read code I always try to find the gaps between "what was the author(s) trying to do?" and "what does this code actually do?" There's often quite a lot of space between those two, and almost always that space is where you find the bugs.


> 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.

Yes, yes, why bother reading your code at all? After all, eventually 15 years will pass whether you do anything or not!

I think if you read it while it's 500 lines, you'll see a way to make it 400. Maybe 100 lines. Maybe shorter. As this happens you get more and more confident that these 50 lines are in fact correct, and they do everything that 500 lines you started with do, and you'll stop touching it.

Then, you've got only 1,5m lines of code after 15 years, and it's all code that works: that you don't have to touch. Isn't that great?

Comparing that to the 15m lines of code that doesn't work, that nobody read, just helps make the case for reading.

> What actually prevents bugs at scale is boring stuff: type systems, invariant checks, automated property testing, and code reviews that focus on contracts instead of line-by-line navel gazing.

Nonsense. The most widely-deployed software with the lowest bug-count is written in C. Type systems could not have done anything to improve that.

> sure, but treating it as some kind of superpower is survivorship bias

That's the kind of bias we want: Programs that run for 15 years without changing are the ones we think are probably correct. Programs that run for 15 years with lots of people trying to poke at them are ones we can have even more confidence in.


| > What actually prevents bugs at scale is boring stuff: type systems...

| Nonsense. The most widely-deployed software with the lowest bug-count is written in C. Type systems could not have done anything to improve that.

C is statically and fairly strongly typed. Hard to tell if you're arguing for or against the statement you're responding to.


You can definitely use this approach for large projects. No matter how big, at some point you are just reading a function or file. You don't need to read every single file to find bugs.

This can be combined with a more strategic approach like: https://mitchellh.com/writing/contributing-to-complex-projec...


> What actually prevents bugs at scale is boring stuff:

There is a layer above this: To understand, really, what are the requirements and to check if are delivered. You can have perfect code that do nothing of consequence. Is the equivalent of `this function is not used by anything` but more macro.

But of course, the problem is to decipher the code, where what you say helps a ton.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: