1

Very short introduction (this is quite a context-heavy question):

I'm a 17 year old programmer, doing some projects, usually netting around 20 files of 200 lines each. I usually don't program very low-level, more high-level with battery included API's like Python + pygame and Lua + WoW API. Nevertheless I've written quite some code in the lower levels of the computer too (mostly C/C++).

Now, I read a lot of programmer discussion and a common returning argument is preventing pesky bugs, for example in "reusing variable names". I always nodded and thought that it was a valid argument, but just now I wondered, how valid is it?

To be honest, after thinking for a while, I figured I have no idea what they mean with pesky bugs. We all heard stories about phenomenal impossible-to-debug bugs, we all have spent useless evenings on that one annoying bug, but apart from a few cases I have never been busy with a programming-related bug for longer than a few hours.

Though on the other hand, the projects I work(ed) on aren't huge 10 million line projects like the linux kernel, and are quite simple... scripts. I have a good understanding of them (or at least my part in collaborations) and are not very error-prone.

So I'm wondering, do these pesky bugs occur exponentially more as the amount of code increases, or ...?

7
  • 2
    few hours... I get mad when ONE bug takes me an hour. Most bugs take me minutes (i use the call stack) and i pretty much avoid global variables to avoid 'state' bugs. BTW have you tried C#? Do you like it?
    – user2528
    Commented Oct 22, 2011 at 7:00
  • Those few hours are very rare cases. Indeed most bugs take from 10 second to a few minutes. I haven't tried C#, but nevertheless I don't like it (I both use Linux and Windows intensely, and I am disgusted by Mono).
    – orlp
    Commented Oct 22, 2011 at 9:09
  • 1
    If you are talking about Should I reuse variables? then keep in mind that reusing variables is something quite different from reusing variable names.
    – user
    Commented Oct 22, 2011 at 16:42
  • ah well i really like statically compiled languages and .NET surprises me on features and functionality. Thus C# became my language of choice by i did mention it solely because visual studios has great debugging features. (and that you mention C++ which is what i learned before C#)
    – user2528
    Commented Oct 22, 2011 at 17:25
  • 1
    Scale that trivial 20*200 program to 1000 files*5000SLOC each, make it poorly written and 20+ years old, and all those things that you read about as best practice make more sense than saving a couple of thumps on the keyboard. (BTW - reusing variables a common problem). Most of the pesky bugs are introduced by later enhancements where the original author did something 'dodgy' and the second author did not notice it.
    – mattnz
    Commented Feb 25, 2015 at 22:49

4 Answers 4

7

Pesky bugs do increase in occurrence when the number of lines of code goes up.

  1. Unless you do good OOD and define plenty of abstractions (which is very possible but not practiced by everyone), increasing the amount of code will make the complexity of that code go up. Every time you modify it, you will need to remember everything you did before AND now you'll have more info to store in your head.
  2. Increased amount of code means that certain parts of your program may not be touched for months. In a few years from now you might even realize that some products run with code that hasn't been touched by anyone for years. But if you have to modify it, you need to remember (or figure out) how it works.
  3. When LOC goes up, you start having more than one person on a project. Now the other person needs to remember what the original author was thinking. If you don't, maybe you'll call a function that has some bad concequences. Try remembering something you haven't written in the first place.
  4. As your code base increases and so does functionality of your app, you start using more third party APIs (well, you should anyway). As more and more ends up dependent on third-party building blocks, sometimes bugs will surface because integration between your code and theirs may not always be smooth. Maybe their side is buggy, maybe you misunderstood the documentation.

All good practices, that you've been reading about and nodding, are there to take the memory element as much as possible out of the equation. When the code is clear to read and understand. When it flows logically and there's no hidden/secret meanings and shortcuts, there's less chance of screwing up an existing application.

To give you an example, in my first job, when I was working on the code that was creating tape archives, I found a line of code at the end of one of the functions, ReadTapeDirectory(). I knew what that method did, I worked with it before and I just assumed it was some copy/paste that someone forgot to remove. I deleted the line and weeks later when someone went to work with tapes, we realized that all archives were completely empty. Come to find out ReadTapeDirectory() was moving the tape position to the proper spot, and without it the entire archival process was broken. Obviously, if a previous developer followed good practices advice that you read over and over, he would've named the function something like ResetTapePosition() and then in the spirit of DRY principle refactored ReadTapeDirectory() into smaller pieces. As you start working with other people, you'll be amazed how many of these examples (unclear naming with easy fix; not corrupted tapes) you will run into on daily basis.

So is it that important to make sure you always use your variables once? No, it's not. But neither are so many of other best practices (well... some are actually kinda important). But my point is that you develop a skill of writing clear code and when you put all these things together, it makes a huge difference between readable/extendable code and a can of worms in which it takes 3 days just to add one menu item. And at the end of the day, all the good practices in the world still don't eliminate those pesky bugs, they just minimize the chances of them happening.

7

In my experience, I have definitely encountered several bugs that have taken weeks or even months to get to the bottom of. Some types of bugs only pop up in debug vs. release or 32bit vs 64bit modes, and others rely are multi-threading race conditions that only appear at seemingly random times. I'd say generally lack of memory safety, shared-memory concurrency, and pervasive shared mutable state are the main culprits. Large numbers of interacting components, especially third-party ones, also worsen the problem. That's why people are so religious about so-called "decoupled" and "composable" software designs - if each component works and can be tested independently, then the number of possible interactions (and thus the number of possible bugs) increases linearly with the size of the software, rather than exponentially.

One of my favorites was a bug that only appeared to some users, only in released versions of the software: occasionally when scrolling the mouse in a specific grid component, there would be a mysterious unhandled "ArithmeticOverflowException".

We figured it out months later - the developers would run/code/debug the program in 32bit mode, to provide edit-and-continue in Visual Studio. The bug would only occur in 64bit mode, and only when someone scrolled the mouse extremely fast on that particular grid...

It turned out that grid component (3rd party) was looking at a certain window message to handle scroll wheel events, and casting the 64bit integer to a 32bit one because most window messages stored nothing important in the upper 32 bits. If a user scrolled very fast, to avoid flooding the message pump with lots of individual "SCROLL" messages, Windows would "batch" the messages and use part of the upper 32bits of the message to record the number of scroll wheel clicks per batch. Casting that to a 32bit integer resulted in an overflow. Since this was in a 3rd party component, we only figured out the cause months later when doing some message-pump hackery of our own and hitting the same exception.

So that's one bug from my own experience that definitely qualifies as pesky.

3
  • I love this msg hackery typecast story story. Sounds annoying tho :(
    – user2528
    Commented Oct 22, 2011 at 7:05
  • +1 for bugs that can take a very long time to hunt down. In a complex piece of software, excluding bugs that are trivial once encountered (the few-minutes-to-be-fixed cases), it's very easy to end up with bugs that can take days or more to fix if you are unlucky. And then when you look at the commit diff, you wonder what the heck you did for those days...
    – user
    Commented Oct 22, 2011 at 16:39
  • Even though this answer was the top-voted one I didn't accept it because it mostly was an example, and not the marking of pitfalls I was looking for.
    – orlp
    Commented Oct 28, 2011 at 16:56
3

So I'm wondering, do these pesky bugs occur exponentially more as the amount of code increases, or ...?

I'd argue that the number of "pesky" bugs (heisenbugs, etc.) does indeed have some correlation to sheer amount of code. But I believe KLOC is only a rough metric for what is really at issue, and that is complexity and interactions. The more variables you have, the more state you have, and the more branch points you have, the more complex your code is. The more various parts of your code interact with other parts, or interact with external systems, the more complex things are. The more complex things are, the more opportunities you have for those "weird" bugs to sneak in.

When you mix in concurrency, shared state, and an external system or two, even a fairly short program can be very complex and can manifest difficult-to-diagnose behavior.

Read up on things like Cyclomatic Complexity and software Correctness for more on this topic.

2
  • 1
    I really like the ABC metric: c2.com/cgi/wiki?AbcMetric
    – sylvanaar
    Commented Oct 22, 2011 at 3:35
  • 1
    +1: Increasing SLOC (for the same complexity) often decreases insidious defects. extreme e.g. "if (a+b-c > Epslon || b-d+a < PI || z==a & b==z)" is almost always better rewritten in 10-20 lines - Adding braces would not help this case....
    – mattnz
    Commented Feb 25, 2015 at 22:53
0

I would say it's not so much the size of the code as the number of programmers working on it. Code I recently wrote myself is almost always fairly easy to debug. It's interactions between code you wrote and code you didn't that cause the most issues. That's why so many best practices are focused on making your code simple and robust so other programmers (or yourself a year from now) won't accidentally misuse it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.