Skip to Content

Implementation Thoughts

Once you have thought about program design, even down to your classes and their interfaces (public methods), you still have to write the code in the methods. What sort of good ideas are there to help with this? Below are a few thoughts.

Keep Code ASAP: As Simple As Possible

The first goal of coding is to keep your code as simple as possible for the problem that you are solving. As our textbook (Sethi) says, problems have instrinsic complexity that your code will have to reflect, but incidental complexity in your code is a bad thing, and you should not leave it there.

Rarely is your first working code solution the best. Don’t stop when your code finally works – now go back and simplify it so that it is better code.

Name Everything Properly, and Purposely Rename

Names of things in your code: classes, methods, variables, local variables, parameters, even file and directory names, are extremely important in the readability and understandability of your code and system.

So think about everything that needs a name, and name it appropriately.

Avoid abbreviations unless it is extremely common and is used in the problem domain.

Use simple names like i or j for indices, because we recognize them from math and they keep complex expressions simpler, but then don’t use these kinds of names for more important data.

Rename things in your code when you realize the first name you chose is not really appropriate. Modern IDEs and editors support variable renaming for a reason – it is good practice!

Use Private Methods to Split Too-Long Public Methods

Normally you design the public interface to your class as a set of public methods (remember, data should never be public!). Then you start coding those public methods. When the bodies of functions or methods get too long they can get hard to understand, and this is when we should take some code out of the method and put it in a separate function/method, and then call that one. These helper methods should be private (or at least not public) and should be conceptually coherent.

One good rule of thumb is: if you find it reasonably easy to give the new method a name, then it is conceptually coherent. If you cannot think up a name and are tempted to call it aPieceOfMyOtherMethod or something like that, it probably is not conceptually coherent.

When should you split a method? The answer is not as simple as X lines of code, because some code is simple and some is complex. Better ideas center around code complexity metrics such as Cyclomatic Complexity. Coverage tools like JaCoCo often calculate such metrics for you. Cyclomatic complexity calculates the number of regions the method’s control flow graph creates in a plane. Some organizations have coding rules such as “any method with a cyclomatic complexity greater than 8 must be split”. This is not a bad idea.

The short summary is: if your method looks complex, split it!

Use Early Return to Simplify Methods

Get your data checks and exceptional conditions out of the way early, and finish them by an early return from the method, then the rest of the method body can just concentrate on doing its purpose with valid data without being unneccessarily indented.

This kind of method is bad:

int badMethod(int x, int y)
{
    if (x > 0) {
        // do all
        // the method
        // computation
        return computedResult;
    } else {
        return -1;
    }
}

It should be rewritten with an early return, like this:

int goodMethod(int x, int y)
{
    if (x <= 0)
        return -1;
    // do all
    // the method
    // computation
    return computedResult;
}

More complex computations might have several separate data checks and early returns before the main purpose of the method begins.

Use Break and Continue to Simplify Loops

Just like early return, the break and continue keywords can greatly simplify code that is in loops, and can help remove unneccessary indentation (i.e., incidental complexity).

So instead of this:

   while (some-condition) {
      if (need-to-process-this-one) {
         // the main code
         // for this loop
      }
   }

It is better to do use continue, like this:

   while (some-condition) {
      if (NOT need-to-process-this-one) 
          continue;
      // the main code
      // for this loop
   }

And instead of this:

   while (some-condition) {
      if (NOT some-stopping-condition) {
         // the main code
         // for this loop
      }
   }

It is better to do use break, like this:

   while (some-condition) {
      if (some-stopping-condition)
          break;
      // the main code
      // for this loop
   }

Both of those improvements move the main code of the loop to the main level of the loop, rather than leaving it indented one level too many.

Use Assertions

Most programming languages have some form of assertion capability, either built-in or as an add-on package. You can just search the web for assertions in your favorite language, so this page is not going to describe each of them.

Essentially, they all let you declare that, at a particular point in your program your code assumes that some expression is true. For example, if I have a C function that takes a pointer argument ptr and my function uses that pointer without checking it, I should put as the first line in my function:

   assert(ptr != NULL);

Then when I compile and run my program (with assertions enabled), if the pointer is NULL at some point, the assertion will fail and I will get an “Assertion failed at line ##” message, instead of just a “Segmentation violation” message that doesn’t tell me where it happened.

Assertions let you put your assumptions about the validity of your data into your program, without embedding all of them into your actual code. The great thing about assertions is that you can enable or disable them (either at compile or runtime, depending on the language), and so you can use them heavily during testing but then compile them out for deployment, without ever changing the source code.

Use Logging

We have all debugged our code with print statements, but then when we get it working we probably deleted those statements. So we did all that work for nothing! And we all have experienced the case where we deleted some print statements and then had another bug and needed to put them back in!

We should learn that code is never assured to be bug free! So, even a production system should be able to log its activity if and when needed. Logging is absolutely necessary in most serious software deployment.

Think of logging as print statements on steroids. Typically, you would use some logging library or framework, and put logging statements in your code rather than print statements. Typically each logging statement (a function call) would have a formatted log message, a severity level, and perhaps a type. When the program is actually run, logging can be disabled, or enabled at different severity levels and/or for different types. The actual logged messages might be printed out, saved to a log file, or even transferred to a remote computer.

An example popular Java logging framework is Log4J. Many others exist for all programming languages, e.g., Apache Logging, and many tools exist for analyzing log files.

Use logging!