What you could learn from ‘Code Complete’ by Steve McConnell (2004, 853 pages)

In an age where Netflix and Spotify are held up as paragons of software development, what can a fourteen-year-old Microsoft book tell you about building software?  Actually, quite a lot.

The Agile framework (manifesto and principles) and Agile practices (Scrum, XP) are useful precisely because they are lightweight and easy to remember.  Where should you go for more detailed and specific advice? 

Code Complete (2nd edition) is an excellent resource for detailed advice, principles and checklists.  

The top advice in this book is:

  • Software use should determine what you build and how you build it.  For example, you will develop the control software for a nuclear reactor in a different way to how you would make Candy Crush (p4)
  • Each of the standard programming languages has different origins, advantages and disadvantages (p63-66)
  • Software design involves (p76):
    • Tradeoffs and priorities (speed of response or throughput, quality of product or time to market)
    • Restrictions (resources are limit, restrict options early)
    • Non-deterministic creation (i.e., three people will design three different system for the same requirements)
    • Heuristic processes (engineers will use their experience, but ideas must be tested, what works in one context will not work in another)
    • Emergent design (as new information and knowledge is gained, or circumstances change, so too should the design)
  • Managing complexity is the most critical technical topic in software development (p78).  Unnecessary complexity is created by choosing an overly complicated answer to a simple problem or choosing the wrong solution to a complex problem.  The way to solve this is:
    • Minimize complexity each person has to deal with
    • Do not over engineer.
  • There are several desirable design characteristics that are relatively universal (p80):
    • Minimal complexity
    • Ease of maintenance
    • Loose coupling (minimal connections between different parts, use abstraction, encapsulation and information hiding)
    • Extensibility (make changes, i.e., refactoring, without having to rebuild the whole thing)
    • Reusability
    • High fan-in
    • Low fan-out
    • Portability (smoothly move to any environment)
    • Leanness (no extra parts, not gold plated)
    • Stratification (complexity abstracted into different layers)
    • Standard techniques (easy to learn, understand and fix)
  • Identify, separate and isolate items that are likely to change often. Independent deployables mean that you can change and refactor them easily.  Code that is likely to change often is:
    • Business rules
    • Hardware dependencies
    • Input and output
    • Non-standard language features
    • Difficult design and construction areas
    • Status variables
  • Design patterns reduce complexity, reduce errors and provide options (see table p104 for popular patterns)
  • Defensive programming will save time and improve the user experience (but decide which parts you keep on production)
    • Protect your program from invalid inputs
    • Handle errors in the right way (null value or next piece of data) and log
    • Exception handling (only use for genuinely exceptional conditions)
    • Prevent damage from errors spreading (cascading)
    • Use debug aids (now very common!)
  • Software quality has several characteristics, and while some of these are mutually reinforcing, some are antagonistic (see below)IMG_3221.jpg
  • The best way to increase productivity and quality is to reduce time spent re-working code.  The idea is to measure twice, cut once and find defects as early as possible.  Tools for doing reducing defects are outlined below.                              IMG_3222.jpg
  • Testing is essential:
    • Testing at each step usually finds <50% errors, testing steps together finds <60% of errors, so you need to do both (this is a strong argument for integrating early and often)
    • Good tests are better than more tests
    • Black-box (no knowledge of inner workings is needed) is generally preferable to glass-box testing (you can see whats inside) or
    • Test each requirement, and plan the test when you write the requirement
    • Write test cases first, unless you are doing very new/novel/exploratory work (the most recent Agile Toolkit podcast suggests that you should not commit code without writing and pass a test)
    • See my review of How Google Tests software for a different and more contemporary view on testing
  • Use the scientific method to debug and fix defects:
    • Gather data through repeatable experiment
    • Form a hypothesis
    • Experiment to prove/disprove the hypothesis
    • Repeat as needed
  • Refactor all the time, only create technical debt to meet a life or death deadline, and then pay it off (technical debt accumulates interest – it gets worse over time).  The best way to refactor code is to:
    • Save the code
    • Keep refactors small
    • One refactor at a time
    • List the steps you plan to take
    • Record any additional work needed for later (don’t get sucked into a never-ending rabbit hole)
    • Make frequent checkpoints
    • Use compiler warnings
    • Retest
    • Add the test cases and/or refactor tests
  • Do not tune as you code.  Once basic functionality works, then identify the bottlenecks (20% code causing issues) and fix those.
  • As team size increase, other activities (planning, communication, management, testing) grow at an exponential rate – so keep teams and projects small (this is very much supported elsewhere, use teams of 7-11)
  • Projects never make up time, they only fall further behind.  You need to fix the underlying causes of productivity (see Accelerate for an up-to-date analysis).  There are many factors that improve or harm productivity:                             IMG_3225.jpg
  • Be careful about religious issues (IDE choice, language, tools).  Jez Humble in his new book Accelerate suggests allowing people to use the right tool for the job significantly and consistently outweighs any benefits from standardisation.
  • Integrate incrementally, rather than in a phased (‘big bang’) approach.  Build one piece at a time, in its simplest form, and then build the next piece. This makes errors easier to locate, easier to build, and you realise value earlier (this is central to the Agile approach)
  • Build daily and smoke test.  Tests should be:
    • Kept current
    • Automated
    • Be applied before the code is integrated
    • Break the build if they fail
  • Use code libraries to minimise work, and have higher quality code
  • It is about people.  Only 30% of the average programmers time is spent alone, and only 15% of communication is with a computer (85% with other people)
  • Write code for people first, computers second

Despite its age, Code Complete (is an essential read for all software engineers and product leaders.  Read this book to find helpful advice, checklist and guidance on building software.

You can buy Code Complete from Amazon UK here. All proceeds go to the site upkeep, and any extra goes to veterans charities.

The book contains some fantastic checklists:

  • Requirements (functional, non-functional, quality, completeness) p42
  • Architecture (topics, quality) p54
  • Major construction practices (coding, teamwork, QA, tools) p69
  • Design in construction (practices, goals) p122
  • High-quality routines p185
  • Defensive programming (general, exceptions, security) p211
  • Pseudocode programming process p233
  • General considerations in using Data p257
  • Fundamental data (integers, floating-points, char & strings, boolean, enumerated, names constants, arrays) p316
  • Unusual data types p343
  • Straight-line code p353
  • Using conditionals p365
  • Loops p388
  • Unusual control structures p410
  • Table-driven methods p429
  • Control structure issues p459
  • Quality Assurance plan p476
  • Pair programming p484
  • Inspections p491
  • Test cases p532
  • Debugging (find defects, syntax errors, fix defects, general approach) p559
  • Reasons to refactor p570
  • Refactoring (data, statement, routine, class, system) p577
  • Refactoring safely p584
  • Code tuning strategies (overall performance) p607
  • Code tuning techniques (speed and size) p642
  • Configuration management p669
  • Integration (strategy, daily tests) p707
  • Programming tools p724
  • Layout p773
  • Documentation p780
  • Good commenting p816