There are a lot of differences between marketable-product code and hobbyist code. This isn't really subject to debate: the hobbyist will write quick and dirty applications for himself and maybe a few of his friends. When feature requests come in, the hobbyist will typically just hack them on. It's a method that breeds a lot of bugs since, instead of taking the time to refactor the codebase to accommodate the new functionality at the right level of abstraction, the hobbyist will break every rule in the book in order to get it done faster.

That's what I did the very first time I added Markov chaining to KevBot. The result was sloppy, slow and unreliable. That's why I took the time later to make several improvements to the codebase to do it properly. Not that KevBot is anywhere near marketable-product-level code, but its architecture is at least not wrong for its functionality.

For a software product to be marketable its code really ought to be organized in the simplest manner possible, for many reasons: multiple programmers need to understand the software the same way (the more complex the organization, the more likely two different programmers will understand something differently), simpler code that is broken into proper abstractions is easier to prove correct and bug-free and, perhaps most importantly, bugs become isolated when code is loosely coupled and simple.

That last point gives us a method for determining whether software is properly abstracted or whether it's quick and dirty code. We need only look at its bugs. Software that was hacked together by the whims of its author will have bugs that, from the point of view of the end user, won't make any sense. Bugs have a certain sanity to them when code is properly isolated, such as a bug in one piece of functionality affects only that functionality, or software does something strange in certain conditions but a user need only ponder it for a moment and realize that it's a simple mistake to make, such as software written from an unclear specification (which happens far too often).

I'm guilty of insane bugs. Back when I wrote my first math parser for KevBot I did a very sloppy job. I ended up using regular expressions to parse non-regular arithmetic expressions which was a terrible mistake. My sloppiness came to be known through its bugs. Each of "a", "p" and "e" were considered arithmetic operators or constants, so when KevBot saw a word like "ape" it would be accepted by the math parser and would therefore not be processed as a normal word. Since my parser originally worked right-to-left (and due to many other peculiarities) KevBot ultimately would output "2.718281828459045". That made no sense and really betrayed the fact that I had done a very bad job organizing my code, so I rewrote the parser using a deterministic pushdown automaton instead. I've been doing my taxes with it ever since.

In short, insane bugs are a symptom of code that doesn't make sense on an organizational level: the bug itself could just be a simple typo or misapplication of algorithms but it results in non-intuitive behaviour that the end user experiences. They are preventable as the programmer could have taken the time (in practice, it doesn't take a huge amount of time to do this for small codebases) to design the software in such a way that bugs, if they exist, will be isolated and sane.

You can't prevent bugs from showing up in your code. The programmer that created a design flaw that converted his simple bug into an insane bug would probably have made the same mistake in a well designed application. But in a well designed application the bug wouldn't manifest itself across multiple systems and might even be forgivable depending on the type of application it is.

One of the fundamental design principles in software engineering is keep it simple, stupid. The simpler something is, the better. We really don't have any excuse for letting our design bugs leak out onto the end user's screen. There is usually a trade between time and complexity (much as there is a trade between time and space) so we programmers just need a bit of time to evaluate and re-organize our code to prevent design bugs.

While it's certainly in a programmer's nature to write software quickly to get things done, it's important to re-evaluate how code is organized to make sure it's still sane. As codebases grow they tend to get more and more complex with tighter and tighter coupling between components. At the same time as programmers have been working on solving various problems, they tend to get ideas of how to best solve them in a more permanent manner. They are by definition intimate with the problems they need to solve and should be able to come up with great solutions for them.

Insane bugs are the best indication that something is horribly wrong with the way your code is organized. The next time you get a bug complaint that makes no sense, whether it's hobby or marketable code, don't just rush a hack in to solve it. Think about the design and how it could be changed to isolate the bug. You'll end up not only fixing the reported bug, but possibly other unseen bugs and future bugs as well. Your future self will thank you for it.