2017/10/07 - with periodic updates

Sean McAdam

I have spent a little time over the years thinking of what makes a good coder.  I have scribbled down some thoughts over the years, and in the past couple years I have been slowly compiling my "list of rules" into a document.  I have wanted to share this with several people over the years, and have not had it in a coherent format.  So here it is, now in a coherent format.

  1. Develop Good Habits Early (and stick to them)

    • Avoid shortcuts
    • Avoid quick fixes
    • Define and develop good procedures and and stick to them
    • If you are in a time crunch, being able to rely on tried and true procedures will greatly improve your performance
    • Always use peer review where possible
The first rule is derived from the notion that effective people generally have good habits.  If your work, or the performance of your work is rooted in good practices, your work should be efficient and effective.  One of the things that I generally rail against in the work place is the knee jerk reaction in the face of crisis to shortcut solutions.  You will have to fix the problem twice when you do that, or worse you forget that there was a problem, and the reason you had to fix the problem in the first place, and it occurs again.

  1. Design / Implement / Test / Documentation / Deployment / Debug / Maintenance

    • Code life cycle development should happen in that order (in theory)
    • Equal amounts of time should go to the beginning phases, while maintenance could run on and off for years
    • Design, design, design, you will thank me later. Work through all aspects of the project prior to writing any code. Document, Document, Document, you will thank yourself later, possibly years later
    • Down the road you may have to maintain what you wrote, or worse, what someone else wrote, and hopefully they documented it well.
These steps are well known, but so often dropped in the face of a tight deadline.  If you do not do proper design and preparation for your project, or application, you will pay for it in time later with debugging and other unintended consequences.

  1. Consistency and Neatness count

    • Pick a standard methodology to format your code, a consistent naming conventions for variables, constants, functions, classes, and source file naming
    • If you are on a team, have a team wide methodology in place, and enforce it
    • Stick to your methodology
    • Use automated tools to enforce your methodology. 
    • Integrate your automated formatting tools into the compiling process
    • Use automated documentation tools where possible
    • Document your code: What is expected to do, how can it fail, what does it do when it fails, what is required for it to work properly
    • Document your functions in-line, and what they are expected to do
    • Document your variables in-line and their purpose if they are non-obvious
    • Document complex blocks of code in-line 
    • Document your assumptions in-line 

Going back to #1, good habits are paramount for efficiency and effectiveness.  If your code is neat, clean and consistent, it will be less likely to have bugs, better understood by team members, and future maintainers.  

  1. Fail early, Fail often

    • Check everything (variable, classes, states, parameters) before using them, fail if they are out of bounds
    • Log failures, exceptions, and/or terminate your program when any unexpected data or conditions occur.  Log the location (file and line) where possible.
    • Verify all function arguments for sanity
      • For performance reasons you may wish to wrap your verifications in a boolean that allow you to toggle them on and off for debugging during development
      • You can put common verifications in their own function calls, such as “is this variable a specific type, class, value range, or a correctly formatted string?”
    • Verify all assumptions about variables, and system state, fail when the assumptions are not met
    • Assume that everything that can fail will fail at the most inconvenient possible time. Prepare for it!
    • If the language supports it, use exceptions, and catch them at predetermined points.
    • Become good friends with Murphy to better understand him
    • Keep in mind that Murphy was an optimist
This is one item I am adamant about, so often code is written like everything will go swimmingly. No notion of failure is taken into account.  There should be a bail out, logging opportunity at every point where an assumption is made.  If you expect failure, you can log it, capture it, and prepare for it.  If you are prepared your code will be easily diagnosable when you are faced with a problem down the road.

  1. Don’t Trust, Verify!

    • All input is suspect until proven otherwise.
    • All user input is extremely suspect and should be rigorously verified
    • All forms of input from the Internet should be considered hostile, and thoroughly scrutinized before acting on it.
    • Don’t even trust yourself, every function call that you make should be verified both in the function arguments, and the returned value(s)
Any input coming from a user, and especially the Internet, has to be validated, it has to be proven.  Going back to the previous rule, this is paramount.  Many programs will fail when unexpected data, formatting, special characters are input from external sources. The program can pass this data into the larger operational context of your system, causing problems downstream that may not surface for days, months, or years, making it very hard to track down the original problem.  Verify it, and if it fails (see #4).

  1. Use Revision Control Tools

    • CVS, Subversion, Git are tools that will let you version control your code.
    • Take a snapshot of your program or project at every major and minor milestone, daily, and prior to creating a branch for testing or further development
    • Keep your revision control system on a different platform, and/or back it up every night. (See the section on Murphy)
    • Integrate your automated revision control tools with the check in process
    • Integrate your automated formatting tools with the check in process
Version control can be a pain to setup, and hard to get used to using (See #1). But it can be imperative when it comes to debugging a system. Keeping a copy of all of the changes to a system, especially a large and complex system, will greatly effect your ability to help find the problem.

  1. Design logging and Debugging into your code from the beginning

    • Any system that takes more than a couple functions should have some debugging and logging designed in from the beginning.
    • Larger and more complex systems should have this designed in from the beginning
    • With a well designed debugging, and logging system your ability to rapidly diagnose and respond bugs will be greatly increased.
One of the largest problems dealing with a misbehaving system in production is gathering the needed data to figure out what is going wrong.  A properly designed logging system with debugging information will save you countless hours of pain and suffering when things go south.

  1. Debug before you “Deslug”

    • Avoid the tendency to start improving performance by making changes to design or structures, until you have shaken out all of the bugs by properly tested the system
    • If you have properly instrumented your code with debugging features, you can design in “deslugging” features along side of the debugging more easily at a later point.
There is always an overwhelming urge to make the system perform better, and correct design issues on the fly. This is because you can see these performance issues and design flaws as you deploy the software. The initial reaction is to correct the problem while you are deploying the software. Don't. This will only serve to confuse any problem diagnostics that will need to be explored. Fight one problem at a time, fix the original problems with out introducing new ones.  

  1. Use a Test Harness where possible

    • If a project takes you more than a week to write it is probably a good idea to apply a test harness to it to verify its functionality
    • Design your test scenarios in the beginning of the development cycle
    • There are anywhere from 3 to 10 bugs per 1000 lines of code depending on the skill and experience of the programer and/or the team. Find the bugs early, save some heartache
This one seems pretty obvious to me.  Test your code at every step of development.  Build it into the check in process (see #6).

  1. Use automated code generation where possible

    • Whenever possible design code to generate large repetitive blocks of code, or where there are large numbers of inputs (large database table structures as an example)
    • Make sure that there is proper debugging and logging built into the automated code, along with sanity check and verification of all parameters

This is my favorite rule. I love automated code generation. First understanding the problem domain, and then coding a solution can be a hard challenge to overcome.  You have to clearly define your requirements, and write code that will recreate the requirements faithfully. If you do it right, you could have major portions of your code recreated each time you compile, update, and recreate your application automatically. Changes to large portions of code can be managed with a simple re-generation of your code base.  If you do it right you can count on a portion of your code to be consistent, and stable.

So there it is, my Top 10 rules. Do I follow them all?  I try too.  If I work alone it is much easier to do it, but on a team, there is always so convincing that has to happen to get the whole team to use them.  I guess that is the nature of teams.