views:

532

answers:

7

I'm currently working in a piece of code where both logic and data access are present in the GUI classes. Obviously, I would like to improve on this situation.

The current current structure is basically:

  • Big ball of mud

The ultimate goal is to achieve a DDD-like structure:

  • DAL
  • Domain model
  • Service layer
  • Presentation model
  • GUI

So, how would you attack the problem?

  • Big bang
    • Define the structure for the final state and push code to its ultimate home.
  • Divide and conquer
    • Try to separate the big ball of mud in to two pieces. Repeat until done...
  • Strangling
A: 

Is a total rewrite an option? In my experience rewriting from scratch can often be more efficient than trying to clean up the existing mess. You cold still keep parts of the existing code but in a new context. And the same goes for the gui and the database if you have one. Rewrite from scratch and take with you what you can use.

Rune Grimstad
+1  A: 

Depends on whether you have to have always a working state, so that you can bug-fix and deploy whenever neccassary, then Devide and Conquer would be a good solution. If you can maintain the old code, while working on a new one (and have the disciplin to apply bug-fixes to both code-bases) a re-write may be a better solution.

Peter Miehle
+3  A: 

I'd never heard of the term 'Strangler Application' - I like it. Where possible this would always be a good approach, it certainly minimises risk and is quite pragmatic, chipping away at the big edifice piece by piece.

Where that doesn't work in my experience is where reasonably significant changes are needed right away - changes that will require a bit of refactoring (or a lot of hacking). In that situation I've often found the changes I needed to do were right at the heart of the big ball of mud and there was no option but getting dirty - even what should have been standard maintenance or minor enhancement changes were just horrible and a major refactor was the best option.

For those cases, I'd go with divide and conquer - the first goal I always aim for is testability, once you have that all the rest is so much easier. In fact, that is often one of the main drivers I have for refactoring away from the big ball of mud – that sort of code is often very nearly un-testable, hopefully there are example UI inputs and outputs, but sometimes even that is missing.

So when faced with code where everything is lumped into the UI I usually start by factoring discrete units of functionality into classes and methods, then pushing those parts of code down into a domain or service layer. Doing it bit by bit greatly reduces the chance of breaking something and makes it easier to pin-point where the breaking code was when things do go wrong.

Run whatever test cases you have available at the end of every change and make sure you are still meeting some sort of baseline.

If you write good unit tests as you go you can start reducing the scale of the problem and I've found that it soon becomes practical to take the strangler approach - with decent unit tests or at least the right framework to allow the writing of decent unit tests it becomes much more practical to gradually replace parts of functionality.

David Hall
+1  A: 

If by refactoring, you mean improving the code without modifying the functionality, I'd start by creating an automated regression testing base line. There are plenty of tools out there to help with this. I use TestComlete though there are good cheap alternatives.

Having established a regression test baseline, personally I would then go with divide and conquer, as it in my experience it is the most likely to succeed. Once you have a testing baseline, it matters less which approach you choose.

Shane MacLaughlin
A: 

Starting with a clean new architecture and moving the old peices of code into this new architecture piece by piece and refactoring it to suit the new arch would be a good option. I think a bottom up approach when moving the functions would be good.

Manoj
+3  A: 

Never attempt "Big Bang". It almost always blows in your face, since it's a high-risk, desperate measure when everything else has failed.

Divide and conquer: This works well ... if your world has only two sides. In real software, you have to conquer so many fronts at the same time, you can rarely afford to live in a black-white fantasy.

I guess I've been using something like "Strangling" for most of my career: Gradually morphing bad old code into shiny new code. Here is my recipe:

Start somewhere, it doesn't really matter where. Write a few unit tests to see how to the code really behaves. Find out how often it does what you think it does and how often it doesn't. Use your IDE to refactor the code so you can test it.

After the first day, make a guess whether you've started at the right place to take this monster apart. If so, go on. If not, find a new place and start over.

Advantages of this strategy: It works in small steps, so the risk can be kept in check and if something breaks, if has to be in the code you've been working on last week.

Disadvantage: It takes a whole lot of time and you will feel frustrated because often, progress will just seem so slow until the "knot" pops and suddenly, everything starts fall into place as if by magic.

Aaron Digulla
+1  A: 

For me it depends on the situation.

If it is a very small project I'd be tempted to just rewrite it from scratch...however you don't often have that luxury.

Failing that, I'd go for chipping away at it piece by piece. I'd write unit tests to verify the existing functionality and slowly use TDD to transform the code into an elegant and well designed system. Depending on how long this process is going to take it will probably start to look like the StranglerApplication you mentioned above.

BigBang is very risky as you have no easy way of verifying that the updated system does the same thing as the old one.

Divide and Conquer is less risky than BigBang...but if its a large enough system it can wind up being just as problematic as BigBang.

mezoid