Skip to content

Instantly share code, notes, and snippets.

@LegalizeAdulthood
Last active February 7, 2018 22:41
Show Gist options
  • Save LegalizeAdulthood/3067cb51aef3a4002037add389319a9e to your computer and use it in GitHub Desktop.
Save LegalizeAdulthood/3067cb51aef3a4002037add389319a9e to your computer and use it in GitHub Desktop.
Give Your Old Code Some New Love

Case Study

  • Our case study is the open source fractal generator Iterated Dynamics, a fork of FRACTINT.
  • This is a 30 year old code base that started as a 16-bit MS-DOS program written in C and 16-bit x86 assembly.
  • The code base has contributions from many, many authors over the years.
  • Source formatting and identifier conventions are varied due to the large number of authors.
  • Source code organization has been tortured by the constraints of early MS-DOS programs operating in a limited amount of physical memory and the use of overlays.
  • Conditional compilation was sprinkled throughout to enable various debugging facilities or experiments in alternative implementations.
  • As C does not have inline functions, there are some places that heavily use preprocessor macros.
  • Some of the functions are exceedingly long*.
  • The program uses an inordinately large number of global variables*.
  • The code exhibits almost every code smell that you could have, except for those that require object-oriented programming.
  • The code uses global headers for data* and function* declarations, exacerbating compile times.
  • Links to the case study repository are shown with an asterisk (*).

Basics

  • Prioritize your debt; not every loan is at the same interest rate
    • Get rid of daily annoyances first; they make it painful to do any work in the code
    • Don't wake the dragon; if you don't need to change something now, defer it to later
  • Work carefully to ensure always forward progress
  • Use version control
    • Proceed in small steps; commits are free
    • One kind of change per commit; do not change too many things at once so mistakes can be easily reverted
    • Use branches to isolate and prototype changes
  • Stay focused on the task at hand; if you encounter other changes that should be made, add them to your To Do List and come back to them later
  • Leverage any existing automated tests
  • Consider creating automated regression tests before making risky changes

Infrastructure

  • Modernize your build with CMake
    • Use CMake 3.0 at least
    • Adhere to modern CMake best practices
    • Legacy code bases often have multiple build scripts to support different target environments (Makefile for unix, VC6 .dsp files for Windows, .xcodeproj for MacOS)
    • Replace these with a CMakeLists.txt* that generates project files for IDEs in a version-independent manner
  • Upgrade your compiler to the latest version, consider C++14 a minimum as of 2018.
  • Use continuous integration to ensure timely notification of problems
    • For open source projects use travis and AppVeyor
    • For commercial projects, consider using a hosted continuous integration service or install Jenkins
    • CI locks in forward progress

Small Annoyances

  • Format source files consistently; use an automated tool
    • Visual Studio can reformat code
    • ReSharper for C++ add-on provides more formatting options to Visual Studio
    • AStyle
    • clang-format
    • Commit style parameters (.astyle, .clang-format) to the source tree
    • Proceed in small steps* by working one file at a time
    • Some of these tools have bugs even though they are supposed to adjust only whitespace; compile frequently
    • Use a periodic job to keep source code consistently formatted (cron job, jenkins, etc.) if inconsistencies are likely to creep back into your code based on past team behavior
  • Name identifiers consistently
    • Use a tool to automatically rename them, or...
    • Rename the definition and Lean on the Compiler to find all references and rename them manually
    • Proceed in small steps*; commits are free
    • Watch out for inactive preprocessor blocks of code; some tools can't rename inside inactive alternatives
    • Use "Find in Files" to ensure all mentions of the old names are gone
  • Refactoring: Convert C to C++
    • Leverage C++'s improved static type checking and facilities for data abstraction
    • Keep exported functions and data as extern "C" if ABI compatibility is needed

Preprocessor Madness

Modernizing Code with Clang-Tidy

Analysis Tools

Refactoring Code

  • Refactor: v. To change the structure of existing code without changing the behavior
  • Modularize the "global header"*, a single header file that declares all functions and data and is included in every source file.
    1. Create a header file for a single source file.
    2. Move declarations exported from the single source file from the global header to the new header file.
    3. Lean on the compiler to identify all dependent source files that need to include the new header file.
    4. Repeat until all declarations have been removed from the global header.
    5. Remove the global header and all the #include directives of it.
  • Refactoring: Split Module
  • Refactoring: Guard Macro Body
  • Refactoring: Replace Integer With Boolean (example*)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment