6

We have a program written in C++ that is mostly procedural, but we do use some C++ containers from the standard library (vector, map, list, etc). We are constantly making changes to this code, so I wouldn't call it a stagnant piece of legacy code that we can just wrap up.

There are a lot of issues with this code making it harder and harder for us to make changes, but I see the three biggest issues being:

  1. Many of the functions do more (way more) than one thing
  2. We violate the DRY principle left and right
  3. We have global variables and global state up the wazoo.

I was thinking we should attack areas 1 and 2 first. Along the way, we can "de-globalize" our smaller functions from the bottom up by passing in information that is currently global as parameters to the lower level functions from the higher level functions and then concentrate on figuring out how to removing the need for global variables as much as possible.

I just finished reading Code Complete 2 and The Pragmatic Programmer, and I learned a lot, but I am feeling overwhelmed. I would like to implement unit testing, change from a procedural to OO approach, automate testing, use a better logging system, fully validate all input, implement better error handling and many other things, but I know if we start all this at once, we would screw ourselves. I am thinking the three I listed are the most important to start with.

Any suggestions are welcome.

We are a team of two programmers mostly with experience with in-house scripting. It is going to be hard to justify taking the time to refactor, especially if we can't bill the time to a client. Believe it or not, this project has been successful enough to keep us busy full time and also keep several consultants busy using it for client work.

Jeremy
  • 4,791
  • 1
  • 25
  • 40
oob
  • 171
  • 4
  • 1
    maybe one justification is the future time savings and that could be argued when you have recently spent X amount of time getting something done by estimating it would have been X/2 had you done some simple refactoring... i'm sure you could have thought of this too - just an initial thought. – Aaron Anodide Jun 06 '12 at 00:54
  • 1
    Absolutely recommended reading: *[Working Effectively with Legacy Code](http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052)*. – Péter Török Jun 06 '12 at 07:28

3 Answers3

9

My advice would be to be very very careful not to break something that works!

Look on this as a long term incremental project, improve code in the area that is relevant to a current change request and as far as possible leave the rest alone.

Don't get rid of global's just because they are global. Some data is naturally global -- time of day, run number, business date, connection status, shutdown requested etc.

The DRY principal applies mostly to development, once the code has been repeated it makes very little difference until you need to make a major change to the repeated code. So leave this stuff alone until it requires a major change.

Many functions that do more than one thing. -- welcome to the real world. Business requirments are never as simple as the ones you see in the text books, and, are often an amalgam of complex interdependent functionality. If you see a case where a function can be split cleanly into two separate simpler functions then jump on it, but, don't expect to find too many.

James Anderson
  • 18,049
  • 1
  • 42
  • 72
  • 2
    +1 for "improve code in the area that is relevant to a current change request" instead of "start at #1, 2 or 3" – Doc Brown Jun 06 '12 at 05:58
7

I'd start with problem #3 first. It's great to refactor your functions to avoid DRY and SRP, but when you have to refactor them again to deal with getting rid of the globals, it's not much point. If you simply pass an int to every function in your program, it's really no different to real global state. You need to create solid interfaces and encapsulation. In addition, it's pretty hard to refactor any function that has a global dependency.

The trick here is going to be, isolate a group of related functions, and refactor them until they're high quality, and then go again on another group. The key here is to break their interfaces up so that you can, in future, refactor them individually if you need to.

As for better error handling, correct use of classes and exceptions will bring that automatically, as it were.

In addition, I would create a new design for the program from scratch, and refactor towards it, so that the end result is not a mess of modules that are all individually coded well but make no sense as a group.

Refactoring can be best expressed as paying off technical debt which was accrued when the program was first created- in other words, in the future, you are going to have to do this work anyway to implement some feature or fix some bug. In the long run, you are saving time and money.

DeadMG
  • 36,794
  • 8
  • 70
  • 139
  • 2
    Nice answer - I do beleive a better way to think of refactoring is speculative investment rather than paying off technical debt. The reason is that it make you justify it on business grounds rather than tecnical grounds. – mattnz Jun 06 '12 at 04:23
  • I like your suggestion to think of it as working towards a completely new rendition of the system. At some point, you whip the cover off Version 2 and throw away the parts of Version 1 that are no longer needed. Either no one will be the wiser (except you) or, you trumpet it as a brilliant move to get ahead of the curve, depending on who is listening. –  Apr 07 '17 at 14:01
6

First, try to get rid of all warnings in the compilation of your program. Make sure you compile with something like the -Wall flag. Getting rid of warnings is a commonly overlooked problem that needs attention.

Next, try to get your hands on a C++ static analyzer. Running the static analyzer on your code will produce a list of problems with your code, and a lot of the time the problems will be ordered by severity. Try to solve the most serious violations first, and use the static analyzer to guide your refactoring.

Oleksi
  • 11,874
  • 2
  • 53
  • 54