Search This Blog

A Taxonomy of Code and Comments

Last week we had an excellent discussion in the C++ professionals group on LinkedIn (update: and now HN, as well) about my last post on trying to comment code less and make it more self-documenting. Thank you to everyone who contributed to the discussion. I really enjoyed reading everyone's perspective and debating the merits of commenting code. One commenter even pointed out the one line of code that I was still uncomfortable about in my example. I applaud such attention to detail when reading an article!

Forming Comment Camps


Over the course of the discussion I could see three distinct camps emerge that differed in what they thought of commenting based on the experiences they have had programming.
  • The No Comment camp had seen too many worthless or misleading comments in their day and recommended trying to use less comments and more self-documenting code. 
  • The Document! camp had found documentation comments extremely helpful when using APIs and stressed the importance of documenting interfaces and providing references for algorithms.
  • The Explain Intent camp had run into too much confusing and convoluted code in the past and either wished it had been more well explained in comments or were thankful that it had been, as the case may be.
All of these ideas have merit, and there is much overlap and nuance involved in the arguments for and against each one. However, those arguments lay mostly in the grey area between these camps where combinations of bad code and bad comments make things interesting and programmers' lives difficult.

My personal experience led me to the No Comment camp, as I showed with an example from the code base that I've been working on for the past year and a half. In it there were nine comments: Six of them were worthless, one was attached to code that I eliminated, one was a question about whether the following code was necessary, and one was the answer to that question stated poorly and in the wrong place. That's essentially 100% bad comments in a little bit more than 50 lines of code. Since I did move and reword the one poorly stated comment, I pruned the code of nearly 90% worthless comments and, I think, made it much more readable in the process.

Of course, this is a small section of code, but it is fairly representative of the code base I'm working on. I didn't have to look too hard to come up with an example. I grabbed the current code I was refactoring. Now that doesn't mean other code bases are the same. In fact, I am certain they are different, and that is where most of the differences in the opposing camps comes from. The rest could be chalked up to differences of opinion and personal style.

The Comment-Code Taxonomy


These three camps can be put into a larger collection of comment-code types that make up a taxonomy. Let's think of code as being either Good, Bad, or Ugly. Good code is clean, self-explanatory, and self-documenting with meaningful variable and function names. Bad code is buggy, wasteful, or under-performing; it's not right, and it needs work. Ugly code is confusing or convoluted; it's working, but it makes you want to tear out your hair - or your eyes.

Comments could also be categorized as Good, Bad, or Ugly, too. Good comments clearly explain the intent of the code and answer why the programmer chose to do things the way they did. They can also provide references and document interfaces for other programmers to easily use. Bad comments are the misleading or flat-out wrong comments that do more harm than good. Ugly comments are irrelevant or redundant because they restate what the code already says. We need a fourth category here for Nonexistent comments - pretty self-explanatory.

So this comment-code taxonomy can show where any particular piece of code falls within the landscape and how you could improve the code to get it to one of the three camps:



Good Code
Bad Code
Ugly Code
Good Comment
Document! camp
Fix the code
Explain Intent camp
Bad Comment
Fix the comment
Fix the code
and the comment
Fix the comment
and maybe the code
Ugly Comment
Remove the comment
Fix the code and
remove the comment
Maybe fix the code;
remove or improve the comment
Nonexistent Comment
No Comment camp
Fix the code
Fix the code or
add a comment

My previous post focused on the reasons to work towards good code without comments, but the landscape is much more vast than that. There are excellent reasons to make any one of the camps the goal for the code you're working on. For the Document! camp, if the code is an interface that needs to be documented for your users, or the ideas came from somewhere else and should be credited, then good comments should be written for that code. Header files should be well-documented, and they tend to be the main place for these types of comments. However, comments in the implementation code can be an indication that a function is needed there instead, and the comments can be moved to the header file along with the function declaration.

For the Explain Intent camp, there are numerous reasons why we would decide to put up with ugly code. The code base may be restricted to changes. We may be coding around a bug in a library. We may be working under time constraints that don't allow for significant code refactoring. Or we may not be able to find that clean, self-documenting way to express the code that would preclude a comment. In those cases the comment should be there and it should be good.

One last type of comment that falls somewhere in between ugly and nonexistent is the TODO comment. These comments are extremely useful for remembering what still needs to be done while you're in the middle of refactoring. I usually litter the code with TODO comments while initially working out what will be refactored and then make sure they're cleaned up at the end with a simple search through the code. They would be truly ugly if left in production code, so they should be nonexistent by the time you release.

Those are the special cases. As Don Norman addresses in The Design of Everyday Things, they may seem to be extremely frequent and are easy to remember because they are exceptional, but how often are we working on public interfaces or dealing with ugly code in frozen code bases? I'm sure some programmers do, but, even for them, not all the time. Otherwise, what are we doing spending all of our time staring at code we can't change? In all other cases - the common cases - I would still recommend doing what you can to make the code expressive enough to not need comments. Programming is an intricate puzzle, and we can use all the hints and guidance we can get when reading other people's code, or our own. But those hints don't need to be in ancillary comments when they can be directly imbedded in the code.

Besides, if the code is so difficult to understand that a comment is necessary, what makes you think that the comment will be any more understandable than the code? I suppose it could happen, but I've actually never seen Ugly code with Good comments. Ugly code and Ugly comments both betray a lack of understanding, and they tend to stay together, if there are comments at all. If you've managed to express the intent adequately in a comment, many times the way to make the code better becomes blatantly obvious. That knowledge should be rolled back into the code instead of left hanging in a comment.

Make no mistake, writing good comments is hard - probably as hard as writing good code - because in both cases you have to clearly understand what you are trying to do and how you are expressing that in code. But, why spend the time writing good comments when you could spend that time writing better code? It will improve both your programming skills and your code comprehension skills. It will stretch your abilities, and in the process, your mind, so that thinking in code becomes more natural over time. Self-documenting code should always be the goal. Comments are the exception when we fail to attain it.

Don't Comment Your Code - Write Better Code

Update: Somehow, I managed to remove the middle section of this post and most of the code example when I was updating labels, or adding a link, or something. It read like nonsense because of that, but I've rewritten the middle section to the best of my recollection. If you were confused before, it should make more sense now.

As I've gained programming experience, I've noticed a significant change in how I write code. I tend to write less and less comments, and the nature of them has changed. Where I used to explain what the code did and how it worked, I now leave those explanations up to the code itself. I try to more directly express what the code is doing, and in the rare cases where that is not sufficient, I may put a few words in a comment to explain why the code is doing what it's doing. If I find comments answering 'what' or 'how', I take that as an indicator that the code is not written well enough. Then I refactor the code to make the comment redundant and eliminate it.

I do this refactoring primarily because I hardly ever read comments. When I'm trying to understand a block of code, I focus on the code because that is what gets executed. The comments are a distraction, and I don't trust them. They very easily get out of sync with the code, and then they lead you astray instead of aiding your understanding.

I've come to think of comments like a writing device that I find more than mildly annoying - footnotes and end notes. I understand their use for citing references, but when an author feels the need to add explanations and anecdotes in footnotes that should have been in the main text, all it does is break up the flow of the text. If the footnotes were so important that they had to be included, they should have been integrated better with the main text. If they don't fit in the main text, then they should have been cut out completely.

The same reasoning applies to comments. Footnotes should not be a substitute for better writing, and comments should not be a substitute for better code. And like writing, better coding involves making the code more directly express what the programmer intends. This is not an easy thing to do, and it can take many drafts to reach a version of the code that does an adequate job of expressing its purpose. This process is a form of optimization that improves readability instead of performance, yet it is just as important as performance optimization because confusing code is a minefield of potential bugs and performance losses.

An Example of Bad Code Badly Commented


To make my reasoning a bit more concrete, here is an example of one particularly messy method that I refactored recently that illustrates how I go about making code more clear. Keep in mind that some months prior I had already done a fair amount of work assigning better variable and method names, but the code still went through significant changes before reaching a clear and concise result. As the method name implies, it runs a filter over a set of samples:

void CFilter::Run(void) {
   // Update stage 0 after EDMA writes into the Stage 0 buffer
   _rgStageInfo[0].SetWriteIndex(_pWrFirstStage);

   // Run filter stages
   int i = 0;
   int rgixFirOutput[MAX_ADCS] = {0};
   for( i = 0; i < _cStages; i++ ) {
       // Determine if there are enough Samples to produce at least 2 results
       int cResultPairs = _rgStageInfo[i].NumResultPairs();

       if ( 0 == cResultPairs ) break;

       bool fIsInternalStage = i+1 < _cStages;
       int dixFirOutput = 1;
       if (!fIsInternalStage) {
           if (_fDownConverting) dixFirOutput = 2;
       }

       // process all available samples in this stage
       for( ; cResultPairs > 0; cResultPairs-- ) {
           if ((i == 0) && _fDownConverting) {
               _rgStageInfo[i].DownConvert(_centerFrequency, _cChans);
           )

           int *pixFirOutput = _rgixFirOutput;
           Sample *pFirOutput = _rgFirOutput;
           Sample *pFirOutputAlt = _rgFirOutput + 1;
           if (fIsInternalStage) {
               GetFirOutputIndexes(rgixFirOutput, i+1);
               pixFirOutput = rgixFirOutput;
               pFirOutput = _rgStageInfo[i+1].GetBuffer();
               pFirOutputAlt = _rgStageInfo[i+1].GetBufferAlt();
           }

           _rgStageInfo[i].CalculateResultPair(fIsInternalStage, _cChans, 
                                               pixFirOutput, dixFirOutput, 
                                               pFirOutput, pFirOutputAlt);
       } // for (cResultPairs)
    } // for (all Stages)

    // Is this the final stage?
    if ( i == _cStages ) { 
       // Reset EDMA src addr to simulate a 4-word linear buffer src
       // then trigger EDMA to move filtered results to vInput buffers
       if ( _csHoldoff ) {
           _csHoldoff--;
       } else if (_fDownConverting) {
           CFft::EdmaTriggerAll(_mid, _rgStageInfo[_cStages].GetBuffer(),
                                _rgixFirOutput, _fDownConverting);
       } else {
           // This should be unecessary since the parameter set will reload itself?
           EdmaSetSrc(_hEdmaFirOut, _rgStageInfo[_cStages].GetBuffer());
           EdmaSetChannel(_hEdmaFirOut);
       }
   }
}

Did your eyes glaze over? It's fairly confusing, and frankly, ugly code. There is so much going on here that doesn't have much to do with running the filter stages, and the comments are not at all helpful. But before getting into that, I should briefly explain the Hungarian notation being used. I use a variation of Apps Hungarian Notation to prefix variable names with information about the variables in shorthand. I was skeptical of Hungarian notation at first, but I've found that using prefixes that are actually meaningful in the context of the application are quite helpful for naming and understanding variables. Once you get used to the prefixes used in a given app, you no longer have to waste much time thinking of good variable names because, most of the time, they quickly come to mind. A lot of these conventions are the same across applications, but some are specific. Here are the ones that are relevant to this code:

'_' = member of a class
'c' = a count of something
'd' = a difference between two things (i.e. an offset)
's' = a data sample, specific to this DSP app
'f' = a boolean flag
'p' = a pointer
'h' = a handle to a system resource
'rg' = a range (i.e. an array)
'ix' = an index

These prefixes can be stacked, so 'cs' is a count of samples and 'rgix' is a range of indexes. Not all variables will lend themselves to this notation, but those variables normally have a specific purpose that's easily named. For example, _centerFrequency doesn't have a prefix except for the member designator, but it doesn't need one because it's clearly the center frequency that the sample stream is being down converted to.

An Intermediate Step on the Way to Something Better


Getting back to the problems with the code, the first line of the method shows the main problem with this whole piece of code. It is too detailed for the level of meaning that this code should convey. The method is running a filter, so it should clearly show how it runs the filter, not muck around with setting the write index of the first stage. The write index was optimized out, which I'll get to later, so this line and its accompanying comment were removed.

The next improvement comes from knowing that this code is only called when a pair of samples are available for processing, and only one pair of samples is ever available when the method is called. These constraints are not apparent in the comments. In fact, the comments lead the programmer to believe that any number of samples could be ready, even zero, but that is not the case. Moving the test for available samples to the end of the for loop and checking if enough samples have been accumulated to run the next stage makes more sense. Oh, and that redundant comment? Gone.

Let's move on to the inner for loop. As I said, this method is only called when two results will be generated for the first stage. Additionally, each subsequent stage can only generate a maximum of two results as well. That means the inner for loop will only run once. It's useless! And so is the redundant comment preceding it. They'll get axed.

Finally, there is a lot going on with a couple of FIR output buffers that is mucking up the inner for loop. All of those xxFirOutput variables are rather confusing. They are used to keep track of the stage buffers and the channel indexes within the stage buffers so that filter results get moved to the right place after each stage is processed. Instead of having this method keep track of all of this stuff, the stages themselves should keep track of it, and they should each have a pointer to the next stage so that they can coordinate the movement of their filter results. Moving the buffer handling code into the stage class simplifies the moved code, and reduces this Run() method to its fundamental operations:

void CFilter::Run(void) {
   // Run filter stages
   int i = 0;
   for( i = 0; i < _cStages; i++ ) {
       if ((i == 0) && _fDownConverting) {
           _rgStageInfo[i].DownConvert(_centerFrequency, _cChans);
       }


       _rgStageInfo[i].CalculateResultPair(_cChans);


       // TODO integrate this better and possibly use a while loop
       if ((i+1 < _cStages) && _rgStageInfo[i+1].DecSamplesUntilResult()) break;
   } // for (all Stages)


   // Is this the final stage?
   if ( i == _cStages ) {
       // Reset EDMA src addr to simulate a 4-word linear buffer src
       // then trigger EDMA to move filtered results to vInput buffers
       if ( _csHoldoff ) {
           _csHoldoff--;
       } else if (_fDownConverting) {
           CFft::EdmaTriggerAll(_mid, _rgStageInfo[_cStages].GetBuffer(),
                                _rgixFirOutput, _fDownConverting);
       } else {
           // This should be unecessary since the parameter set will reload itself?
           EdmaSetSrc(_hEdmaFirOut, _rgStageInfo[_cStages].GetBuffer());
           EdmaSetChannel(_hEdmaFirOut);
       }
   }
}


That already looks much better. Now it's much more clear what the method is doing. For every filter stage, it does an optional down conversion if it's the first stage, it calculates a result pair, and it checks if enough samples have accumulated in the next stage to run it as well. If not, it breaks out of the for loop. If all stages have run, then the results are passed on to the next step of the process. But wait, look back at that down conversion code. It only runs in the first stage and the first stage is guaranteed to run, so it can be moved above the for loop. Also, that first comment isn't saying anything useful so we can get rid of it.

Next, notice that TODO comment? That is one kind of comment I don't hesitate to put in my code. It's there to remind me to go back to something that I might forget, but it is temporary. As soon as I finish the TODO task, I remove the comment. In this case the DecSamplesUntilResult() call can be moved inside the CalculateResultPair() call so that the latter call returns true if the next stage has enough samples to run. Then to convert the for loop to a while loop, we can use a pointer to the current filter stage instead of array accesses and put the CalculateResultPair() call inside the while loop condition. Then all we have to do inside the while loop is increment the stage info pointer to the next stage.

Finally, did you notice the question in the last comment? That's been there for quite a while, and I finally got around to answering it. The answer was actually right there in the previous comment, but it's not very clear. The reason why the DMA source needs to be reset is because the destination size is bigger than the source size, and if it wasn't reset, the DMA source pointer would keep incrementing until the destination was full - right past the end of the buffer. The relevant comment was moved and reworded. The redundant comment before the test for the final stage was removed.

Better Code With a Single Relevant Comment


Now look at how much cleaner the final code is:

void CFilter::Run(void) {
   SStageInfo *pStageInfo = _rgStageInfo;

   if (_fDownConverting) pStageInfo->DownConvert(_centerFrequency, _cChans);

   while ( (pStageInfo != &_rgStageInfo[_cStages]) &&
           pStageInfo->CalculateResultPairForNextStage(_cChans) ) {
       ++pStageInfo;
   }

   if ( pStageInfo == &_rgStageInfo[_cStages] ) {
       if ( _csHoldoff ) {
           _csHoldoff--;
       } else if (_fDownConverting) {
           CFft::EdmaTriggerAll(_mid, pStageInfo->GetBuffer(),
                                _rgixFirOutput, _fDownConverting);
       } else {
           // Reset EDMA source address to simulate
           // a FIFO source to a larger sink buffer.
           EdmaSetSrc(_hEdmaFirOut, pStageInfo->GetBuffer());
           EdmaSetChannel(_hEdmaFirOut);
       }
   }
}

The code pretty much stands on its own now and clearly shows its intentions. The only surviving comment explains why the DMA source is getting reset because that's normally an odd thing to do. When I have to come back to this code six months from now, I'll be able to easily see what it does. I can read the code without having to slog through irrelevant or redundant comments or tedious details that should be handled at a lower level. Instead of becoming mired in byzantine logic, I can get on with the task at hand because the code's intent will be obvious. That is the goal of well-written code.


Follow Up: A Taxonomy of Code and Comments

Beware: Premature Optimization Can Happen at Any Time

I'll be the first to admit that I love optimization. No matter what type of code I'm writing, my mind will be constantly formulating and experimenting with alternative ways of achieving the design goals in the most efficient way possible and weighing the trade-offs. I would have a hard time choosing between optimizing and debugging as my favorite programming tasks. I know. That probably makes me weird, but honestly, I'm okay with that. Optimizing and debugging involve a kind of problem solving that I find extremely enjoyable during the process and satisfying when completed well.

Optimization can take many forms. Over the years I've learned to focus on the ones that yield more bang for the buck - architectural, data structure, algorithm, and readability optimizations - and avoid those that are more trouble than they're worth. These troublesome optimizations can generally be classified as premature optimizations and micro-optimizations - categories that are not mutually exclusive. It is all too common to see micro-optimizations that are done prematurely.

Before we go any farther, some definitions are in order. Premature optimization is simply optimization that is done before it's known to be necessary, i.e. before you have actually measured the time consumed by the particular piece of code you want to optimize and found it to be a little CPU piggy relative to the rest of your program. Micro-optimization is twiddling with small code sections to try to beat the compiler at its job without making significant gains in performance, i.e. moving the deck chairs around on the Titanic.

Avoiding these types of optimization does not give you the right to be sloppy. You should still be picking appropriate data structures and algorithms for the job at hand. Joe Duffy has a great writeup on ways that premature optimization has been used as an excuse for bad programming choices. Don't be yet another example of that.

Why Not to Optimize


There are a number of great reasons to avoid these bad optimizations, the most obvious being that you are likely wasting your time. If you are optimizing code that doesn't have timing constraints or already has good enough performance, those optimizations are worthless. If your optimizations are getting optimized out by the compiler or the compiler would have done the same thing anyway, your hard work would be all for naught. Instead of fighting on the compiler's turf, you could be spending your time optimizing at a higher level in an area of your program that will matter. Measure first, then optimize only where you need to.

When you optimize, you are committing to potentially more complicated code for the sake of potentially more performance. If the optimizations are done at a high level, that commitment is probably fine. The mental overhead of the optimizations is integrated into the architecture of the program, and so it is manageable and possibly simplifies the design instead of complicating it.

If the optimizations are at a low level, you are at risk of competing with the compiler or the libraries and frameworks or even the hardware you're using. After all, processors do all kinds of optimizations including branch prediction, out-of-order execution, and memory caching, and they are changing and improving all the time. Every time you upgrade your programming environment, you will have to remeasure your optimized code to make sure it still performs well. And besides, are your users all using the same environment that you are? Even changes to other parts of your program could change the assumptions that made the optimization work and nullify the performance gains. Any changes, both within and outside your control, could make the optimization obsolete, so it will have to constantly be tested and verified. Trust me, you want to avoid that rabbit hole.

Finally, you should be optimizing for readability over performance whenever you can. That may seem a little harsh, but in reality, readable code begets performant code. I can't begin to count the number of times I refactored a complicated section of code to be more readable and only when I was finished simplifying did I see another way to refactor it to make it faster and use less memory. Often times I could see that the original code was trying to be a performance optimization, but the complexity was getting in the way. It wasn't until I made the code comprehensible that I could make it performant, and without fail the newer, faster code was much easier to read and smaller. That's a win-win-win. The bottom line is that in almost all cases, you should optimize first for readability.

When Measured Optimization Becomes Premature


Even though I try to follow these guidelines to the best of my ability, I still get caught by premature optimization sometimes. Last week was one of those times, and it brought out another reason to resist the urge to optimize until you are sure of how the program is working. But before getting into the code, here's a little background to hopefully make better sense of the example.

I'm writing embedded C++ code for a real-time application running on a TI DSP processor. A big part of what makes the real-time data processing possible for this application is the DMA (direct memory access) controller. The application has a number of memory buffers to stage the data so the processor can do calculations efficiently on a contiguous block of data before it's shuttled off to the next staging buffer. The DMA controller takes care of moving the data to the processor's internal memory and back out to external memory so that the processor is free to do program control and computation.

This DMA controller is packed with features to automate different types of memory transfers and kick off transfers from external events, but one of the more basic features is the ability to send a set of commands to the controller to initiate an immediate block transfer. The controller will go off and move the data and then interrupt the processor when it's done. Great!

The issue that I was dealing with was that sometimes there was nothing to do while waiting for the controller to move the data, so the processor had to wait for it to finish. It seemed like the controller was taking a fair amount of time, and I wanted to see if a plain old memcpy() call would be faster. Here's the little program I used to compare them:

void main() {
    int cs = 2000;
    Sample *pSrc = new Sample[cs];
    for (int i = 0; i < cs; i++) pSrc[i] = i;
    Sample *pDst = new Sample[cs];


    int cTests = 10;
    for (int j = 0; j <= cTests; j++) {
       unsigned cb = (j+1)*cs/cTests*sizeof(Sample);
       unsigned tStart = CLK_gethtime();
       int id = DatCopy(pSrc, pDst, cb);
       DatWait(id);
       unsigned tDatCopy = CLK_gethtime();


       memcpy(pDst, pSrc, cb);
       unsigned tMemcpy = CLK_gethtime();


       UTL_logDebug2("Test %d copying %d bytes", j+1, cb);
       UTL_logDebug2("  DatCopy: %d, memcpy: %d", 
          tDatCopy - tStart, tMemcpy - tDatCopy);
    }
}

The DatCopy() function call sets up the DMA transfer of cb bytes from pSrc to pDst, and then the DatWait() call waits until the transfer is complete. The CLK_gethtime()call is a special function that returns a count of the number of processor clock cycles since reset, and the UTL_logDebug2() calls are special print statements that log the formatted strings to a memory buffer that can be viewed with an emulator.

The real application doesn't copy more than 4000 bytes at a time, so this loop measures the amount of time that the DMA and memcpy() take to copy from 400 to 4000 bytes. Here are the results I got:

Data Copy Comparison graph

So according to this measurement, memcpy() is always significantly faster than DatCopy(), and I should pretty much always use memcpy()unless there is some other computation that can be done after kicking off the transfer to hide the latency in DatCopy(), right? That's what I thought, too. It seemed pretty straightforward, and I figured it would be an easy performance gain. I was about to go change all my uses of DatCopy()-DatWait() to memcpy(), but I had a nagging feeling that this couldn't be right.

The Premature Optimization Bug


Because of the memory architecture of this processor, memcpy() has to use the DMA controller to do its copying. All memory operations go through the DMA controller, but memcpy() was doing the transfer in small pieces under program control while DatCopy() should have been using the controller directly to transfer the data in one big block. It shouldn't be possible for memcpy() to be faster. Indeed, it isn't.

There was a bug in the DatCopy() code. The problem was that DMA transfers are set up by default to link to a null transfer that tells the controller to do nothing. The null transfer can be replaced by another DMA transfer so that when one transfer completes, it starts the next transfer automatically. However, if the null transfer is left there, then the DMA controller flags it as a missed transfer and takes its sweet time getting back to tell the processor that it's finished.

Since all of these DatCopy() transfers are one-offs, they shouldn't link to another transfer. They should return immediately. Once I figured this out and flipped a controller bit to make the transfers static instead of linked, I got this instead:

Corrected Data Copy Comparison graph

Ah, the world is right again. DatCopy() is generally faster than memcpy(), as it should be, except for transfers less than about 1000 bytes because of the time needed to set up a DMA transfer. There's only one place in the application where the transfers were guaranteed to be that small and could improve throughput if they were faster, so that's the only place where I put in the memcpy() optimization. As a bonus, all the other transfers sped up because the DMA setup bug in DatCopy() was fixed.

If I hadn't listened to that nagging doubt, I would have changed the code in dozens of places in the mistaken belief that I was improving performance, when in fact I was papering over a performance bug. Don't fall victim to that kind of hastiness. If something doesn't seem right, think it through and make sure you're not optimizing prematurely. You could be trying to do the compiler's job. You could be committing to code maintenance that isn't worth it. You could be unnecessarily complicating your code. Or you could be ignoring bugs that are preventing real performance gains. Do your homework instead, and only do the optimizations that matter.

People Tend to Do What They Know, So Learn What You Don't

People will do what they know. It's inescapable. When the spotlight is on, and you are expected to perform, most people are going to go with a tried and true response, not an untested experiment with unknown risks. In the context of engineering, people will tend to use designs, architectures, and tools that they've used before. It's entirely natural and rational, and in most cases going with a known design will yield predictable results. But that is not always the case.

Sometimes adhering to what you know can lead to some prickly design traps. Probably the worst of these is NIH (not invented here) syndrome. In its strongest form, this disease presents itself as a team's complete inability to accept design components or subsystems from anyone else, even within the same company. Everything must be designed and implemented in-house from the ground up. In weaker forms the team may use some standard libraries and frameworks, but the default state of their design process is still roll-your-own. NIH normally becomes a problem only if the entire design team is afflicted. If a minority of designers have this tendency, they can usually be overruled or have the impact of their decisions minimized. But if company culture is saturated with this mindset, teams will have an awful lot of work to do.

Another design trap born of only doing what you know is forcing a solution to fit the problem. If the design warrants a solution that doesn't exist in your repertoire, and you don't take the time to properly understand the problem and research its design space, you may end up trying to use a design that is ill-suited to the problem or solves an entirely different problem. This is the classic case of trying to fit a square peg into a round hole. It can't be done without significantly distorting the peg or the hole or both, and the result ain't gonna be pretty.

Possibly the most common trap is plain old suboptimal design. You may know a decent solution that fits the problem, but a better one exists that makes the design more elegant or more efficient or more flexible. The better solution could be an algorithm, architecture, or framework that you put off learning or are not yet aware of. You end up using the suboptimal design because it's what you know, and it can happen at any level of design.

All of these traps have something in common. They can be disarmed with the will to learn what you don't know. If you adhere to only those concepts you already know, it is easy to fall into one or more of these traps, but if you are determined to research the problem at hand, deepen your knowledge of the application domain, or experiment with new tools on the side, you can overcome the pitfalls and make better design decisions.

Let's examine each of the traps listed above in reverse. To avoid suboptimal design, the simplest thing you can do is take a look at what others have already done to solve the problem you're working on. It is almost a certainty that someone else has already run into the same problem, solved it, and published the solution for others to use.  It's almost as likely that multiple solutions are out there for you to choose from, if you use some creativity.

Leonardo da Vinci invented armored vehicles, helicopters, and hang gliders in the 15th century, long before engineers built functional versions of them. He worked out a lot of the main design problems before the materials and manufacturing processes even existed to make his inventions a reality. If he could do that centuries ahead of time, it's probably safe to assume that almost all problems have already been solved in some context. Most innovation today is the refinement of those solutions or the application of those solutions to new fields. Your problem is probably not so unique that nobody has come across it, yet. Spend a little time looking around before diving into a solution that may be suboptimal.

Forcing solutions to fit the problem can be thought of as a more extreme case of suboptimal design. To avoid this pitfall, try learning more about the problem domain first. The more you know about a domain, the more you come to appreciate its subtleties and nuances, and the more likely you will realize how it is different from things you have done in the past.

For example, on the surface, system modeling and real-time signal analysis seem to have a lot in common. They utilize many of the same DSP concepts, but the details are what set them apart. In a real-time system, a lot of effort is put into streamlining calculations with multiple stages of computation separated by memory buffers to relax timing constraints. In contrast, a system model is attempting to accurately model a real system on different hardware while making simplifying assumptions to speed up the execution of the model. Even though they may be performing the same calculations, the different constraints of the two problem domains will make the solutions' architectures drastically different. Even within each of those domains, the constraints of the specific problem being solved is important, and slight changes to the requirements can have a ripple effect on the rest of the system. Learn more about the problem domain to avoid the misapplication of an old solution to a new and different problem.

And now we arrive back at NIH syndrome. Things get more complex here because simply learning more about the application domain or researching solutions to the problem at hand will not dissuade someone from writing their own solution. There is a certain comfort and familiarity in writing your own code that is difficult to overcome. You know the code inside out because you wrote it. You can debug it and fix it if there are problems. And let's face it, your code is a gold-plated work of art while every other programmer's code is crap, right?

Let's discuss this a little in the context of an example from the video game industry. When embarking on a new game design, one of the basic choices that needs to be made is whether to license a game engine or build your own. If you build your own, you own it for better or worse. It's your own code, and you have total control of it and full knowledge of how it works. But you have to start at square one and it's going to take time to develop. If instead you license an existing game engine, you can start with known working code that hopefully comes close to your needs. You have to pay for it, but having a working engine can be a huge asset. And depending on the support you can get, you could have a robust platform with critical help for developing your game.

So how do you choose? Well, even if you license the engine, you're probably going to be changing it quite a bit and writing lots of code. You're going to learn a lot about that engine by the time you're done. Then on the next game project, it will be something you already know - another tool in your programming toolbox. If it was acquired with less time and effort than a home-built engine, that's a big win. If it came from a blockbuster game, it's already been tested and proven in the wild, another big win.

I'm not saying that licensing game engines is always the way to go. Sometimes existing engines don't fit the requirements or they're too expensive. The details of each project are important. The point is that the decision should be made using careful analysis of the project's requirements and constraints, not dictated by a team suffering from NIH syndrome. Knowledge of multiple game engines helps tremendously in that decision. The same is true for any software project when deciding whether to write your own code or use existing libraries and frameworks. The deeper your knowledge about what's already out there, and I mean functional knowledge that comes from having already used the tools under consideration, the more informed you are in making that decision. Knowledge is power.

Because people tend to do what they know, you need to keep learning what you don't know to make better design choices. The pursuit of knowledge is never-ending. Get comfortable with that. Every tool you can add to your programmer's toolbox is an asset that you may find useful at any time, and you want to keep freshening and maintaining your toolbox, lest you be caught by a problem for which you have no ready solution. And the best tools in your toolbox? An inquisitive mind and a will to learn.

6 Ways that Programming is Like Juggling



In my continuing effort to draw connections between programming and every other activity in my life, I thought I would tackle the similarities I see between programming and juggling. I find that juggling is a pretty fun way to kill some time when I don't have the time to do anything more involved. It's relatively easy to find three or more relatively small round objects that I can try to keep in the air.

Since I have a couple of small children, I can generally look down at my feet and find a multitude of toss-able things readily available on my very own floor. Building blocks, plastic eggs, wooden fruit - it's all fair game, and entertaining for the kids as well. I once tried using a couple of medicine balls, but that didn't work out so well. Balls that heavy tend to screw up your tosses, and boy do your arms get tired fast. I suppose they would be good for training to juggle chainsaws, though.

Juggling has some nice advantages beyond wasting time. It improves your hand-eye coordination, reflexes, and spacial awareness. It's a neat party trick, for kids at least, and looks pretty impressive once you get a few tricks beyond the basic three-ball juggle under your belt. Okay, enough about the virtues of juggling. How does it relate at all to programming?

You have to keep multiple balls in the air. And when those balls are in the air, there is a lot going on. Your eyes are spotting which ball to catch next. Your hands are adjusting to where the balls are going to land. You're launching the ball you just caught so that hand is free for the next ball that's already on its way down. Each of your hands and your eyes are acting independently, but they are still connected through the paths of the balls and acting synchronously. At no point can you be aware of everything that's happening at once. Your hands have to know what to do and react without conscious thought.

With programming, you are juggling with your mind instead of your hands, and you have to keep numerous variables, functions, and processes in the air at the same time. The bigger the program, the more that's potentially going on at once. If you had to consciously think about everything, you would have a hard time making progress. You have to depend on your mental reflexes to retrieve the necessary information at the right time for the particular code you're writing. If you have to constantly jump around the code to remember which variables do what and where that other function is called and what were the side effects, you're dropping the balls instead of keeping them all in the air.

Consistency and accuracy are your two best friends. When learning to juggle, one of the first things you have to train yourself to do is throw the balls exactly the same height and distance every time. If you watch a good juggler, the balls or rings or clubs will all travel along exactly the same path as if they are wired together. When they deviate from that path, the juggler is either inserting a quick trick into the juggle, or he is transitioning into a different juggle that will become the new path.

A routine is made up of a series of transitions from one steady state to another with slight deviations thrown in to surprise the audience. The absolute consistency and accuracy of every single throw allows the juggler to think at a higher level about the next part of the act instead of how to throw and catch each ball as it's happening. That ability to step back from the basic mechanics and let the subconscious take over allows a smooth, dynamic performance to happen.

The same principles free a programmer to think at a higher level. If you consistently use the same design patterns and conventions when you're programming, you can do more forward thinking. You can get to this state of mind more easily by adopting standard naming conventions, method and class structures, and coding style. Once you are thinking less about what to name things, what you did name things, and how to structure code, you can relieve your mind to concentrate on achieving your goals. Increasing your accuracy frees up even more time that you were spending correcting compiler errors and simple coding bugs, leaving more time for higher level thinking and design.

Juggling requires efficiency of motion. It's quite tiring at first. It seems like you have to move your arms and hands in a dozen different directions, as if they need to be everywhere at once. After a few minutes all of your muscles get pretty worn out. As you get better, you figure out how to combine separate movements into more fluid motions that conserve energy and use the momentum of the balls to your advantage. As you move your hand to catch a falling ball, you let your wrist relax at just the right moment to load it like a spring and then bring your forearm in slightly and relaunch it to your other hand with a light flick of your wrist. You make less course corrections and all of your movements get smaller and more focused. Eventually you have all of the time in the world to catch the next ball, and you conserve your energy to keep juggling for much longer stretches of time.

In programming, efficiency of motion would apply directly to typing skills. That much is obvious, but let's cast our net a little wider. Efficiency of motion also applies to the entire programming environment. Take the time to learn the shortcuts of the operating system, IDE, and editor you're using. The more efficiently you can move around your programming environment, the faster you'll be able to program, and the more mental energy you'll have to put towards programming better. Whenever you find that you're making a course correction by interrupting your coding flow to accomplish some task, take a little extra time to see if there's another way to do it that will flow more smoothly. Eventually you'll be able to dance around your programming environment much more efficiently, spending less time wrestling with the environment and more time writing great software.

It is hard to learn new tricks. Once you have the basic three-ball juggle down, you'll want to expand your repertoire. Maybe you want to learn to juggle two balls in one hand or sneak a throw behind your back. You'll quickly find that every new trick is practically as hard to get right as the original three-ball juggle. Your hands have no idea what to do, and you have to force them to move where you want them to go because they will no longer do anything right on their own. Suddenly, juggling takes all of your mental focus again. More than in any other activity I can think of, in juggling it is difficult to learn new tricks.

Sticking with the same old tricks is boring, though, so there's quite a bit of motivation to challenge yourself and learn new ones. Learning new programming tricks can also be difficult, but also necessary. If you don't take the time to learn new languages and frameworks, your skills will stagnate and programming could become quite tedious. Overcoming the difficulty of expanding your bag of tricks is well worth it. Don't let the fear of temporary awkwardness stop you. You'll get past the initial pain of learning something new, and you'll be a stronger programmer when you're done.

Juggling has a natural progression. Start out passing one ball back and forth between your hands. Then add a second. Once you're comfortable with that, add the third. Then try juggling two balls in one hand, then two in the other hand, and then try two in each hand at the same time. Once you can juggle four at once, add a fifth. You can also branch out into bounce juggling or juggling clubs or passing with a partner. I'm drastically oversimplifying, of course, but the point is that there is a definite progression to getting better at juggling.

I don't believe that learning to program has a natural progression. For every programmer, there is likely a different path to getting where they are. But there are parts of programming that do have progressions. When you're trying to refactor a section of code, even if it is a great big tear-up and redesign, there is likely a natural progression that will get you from the current code to the desired code through a series of smaller, simpler steps that maintain working code. If you can find that progression, you can make your life much easier because you'll know everything is working at each step of the way instead of doing a major overhaul and finding that nothing works at the end. Take the time to find that natural progression.

Improvements come suddenly and without warning. When I was first learning to juggle three balls, I would keep a running count of how many throws I successfully caught. It was slow going at first, and I felt like I was stuck at six throws for a long time. Then all of a sudden I could do 15 throws, and I was stuck there for days of practicing. Then I jumped to 25 throws without consciously doing anything differently. Once I took the step beyond 25 throws, I could basically juggle three balls as long as I wanted, and I stopped counting and moved on to other tricks. None of these advances were gradual. It was as if the motions had suddenly sunk into my muscle memory, or some subtle change improved my efficiency of motion, and I took a big leap forward in skill.

I noted that when learning the guitar, techniques or pieces of music that were once difficult could become easier without noticing. Juggling isn't like that because the feedback is immediate and obvious. You have an absolute reference to always compare against: are you keeping everything in the air, or not? Programming can have absolute references like that, too. Can you suddenly write 100 lines of code without making any syntax errors? Or maybe you've done a large refactoring without introducing any new bugs. Or you've become so proficient with a new framework that you can keep coding without having to look things up in the documentation. These kinds of improvements will all be immediately noticeable.

Experiencing these leaps in programming skill is quite satisfying. Something clicks, and suddenly you can do things that you previously only admired in more experienced programmers. Keep practicing, work for those improvements, and your efforts will be rewarded.