Going back to the original post:
> In short, because we wrote simple code in the beginning and didn't think
deeply enough about
> what the needs were, a significant amount of redesign ends up being required,
resulting in a
> lengthy turnaround time for implementing a simple feature. The nice part is
that our tests tell
> us what stuff needs to change. The lousy part is that so many things need to
change. This
> seems to be a frequent occurrence here where something comes up requiring a
day's detour
> to redesign something (and everything around it) that previously seemed just
fine.
>
> I'm thinking the solution is BDUF, but that's a bad word here. Is there a
better solution?
Let's forget about "simple" code (which sounds like it was
"simplistic", which is different.)
You have legacy code. Legacy code will make adding new features incur
unpredictable
amounts of work because the existing code is not well-factored. To
quote Joe Rainsberger:
"In systems with high technical debt, the cost of repaying that
technical debt dominates the cost of a story."
To make the code more well-factored (paying down the technical debt),
whenever you need to integrate a new feature into it, you should pay
close attention to code smells in both the new code and the old code
and consider refactoring to deal with each smell as you recognize it.
You can do refactorings in small safe steps (even in C++) manually.
Very closely follow the instructions in Fowler's book on Refactoring
until you learn them by heart. Eclipse with gcc has a few refactorings
that actually work: Extract Method and Rename. Rename understands
scope, so it is safer than search-and-replace. Extract Method and the
other refactorings in Ecipse might be buggy, so be careful when you
use them. For things like changing a function signature, "lean on the
compiler" to show where changes have to be made.
You also need tests to make sure the refactorings are not damaging the
existing features. Feather's book on working with legacy code has lots
of techniques for adding tests to legacy code.
On a higher level, code smells are violations of good design
principles. For example, the Single Responsibility Principle (SRP)
says there should one purpose for every class / method / module. There
are principles about coupling and cohesion and managing dependencies,
etc. It's often easier to detect a code smell than it is to apply
these abstract principles. "Large Class" and "Large Method" are
remedied by "Extract Class" and "Extract Method/Move Method", though
knowing SRP helps in deciding what parts of a class or method should
be extracted.
Perhaps the most important design principle is "Tell, don't ask": keep
functionality and data together.... bad code often has the
functionality in one place, and gets the data it needs from other
places, creating problems with dependencies and lack of locality --
symptomized by "adding a new feature requires changing lots of code".
The code smells "Shotgun Surgery", "Feature Envy", "Long Parameter
List" are applicable here.
Getting fast feedback will allow more refactoring, which will
(eventually) allow faster development of new features. Try to get
parallel builds happening (distributed compilation). Try to get
smaller source files and smaller header files. Reduce the complexity
of header files - use forward declarations, avoid inline code, try to
keep only one class per header file / source file. Using the "pimpl"
idiom widely can decrease compile time by 10%, but it can also
disguise the "Large Class" and "Feature Envy" code smells.
The advantage of refactoring instead of rewriting, is that you always
have working code. If your manual and automated tests are good, then
you should be able to ship the code, even if it is a half-way state
between a bad design and a good design.
I wrote an article on refactoring legacy C++ code here:
http://www.stickyminds.com/BetterSoftware/magazine.asp?fn=cifea&id=75
I'm also one of the authors of Industrial Logic's elearning
(particularly the C++ content). We have albums and workshops available
in C++ on Code Smells, Refactoring, TDD and microtesting legacy code.
--
C. Keith Ray, IXP Coach, Industrial Logic, Inc.
http://industriallogic.com    866-540-8336 (toll free)
Groove with our Agile Greatest Hits: http://www.industriallogic.com/elearning/
http://agilesolutionspace.blogspot.com/