Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring for DI on large projects

I work on a large scale platform project supporting around 10 products that use our code.

So far, all of the products have been using the full functionality of our platform:
- Retrieval of configuration data from a database
- Remote file system access
- Security authorization
- Base logic (the thing we are paid to offer)

For a new product we've been asked to support a smaller subset of functionality without the infrastructure the platforms bring along. Our architecture is old (start of coding from 2005 or so) but reasonably solid.

We're confident we can do that using DI on our existing classes, but the estimated times to do so range from 5 to 70 weeks depending who you talk to.

There's a lot of articles out there that tell you how to do DI, but I coulnd't find any that tell you how to refactor for DI in the most efficient way? Are there tools that do this rather than having to go through 30.000 lines of code and having to hit CTRL+R for extacting interfaces and adding them to construcors too many times? (we have resharper if that helps) If not, what do you find is the ideal workflow to quickly achieve this?

like image 980
user1970723 Avatar asked Jan 11 '13 17:01

user1970723


People also ask

How long should refactoring take?

In practice, most times you look at your code and you're happy with it or there's a small refactor, like extracting a method, which takes a minute or two of your time. On rare occasions, a significant refactoring becomes apparent and takes up 20 – 30 minutes of your time.

What is the goal of refactoring?

The goal of refactoring is to improve internal code by making many small changes without altering the code's external behavior. Computer programmers and software developers refactor code to improve the design, structure and implementation of software. Refactoring improves code readability and reduces complexities.


Video Answer


2 Answers

I'm assuming that you're looking to use an IoC tool like StructureMap, Funq, Ninject, etc.

In that case, the work of refactoring really starts with updating your entry points (or Composition Roots) in the codebase. This could have a large impact, especially if you're making pervasive use of statics and managing lifetime of your objects (e.g. caching, lazy loads). Once you have an IoC tool in place and it wires the object graphs, you can start to spread out your usage of DI and enjoy the benefits.

I would focus on settings-like dependencies first (which should be simple value objects) and start making resolution calls with your IoC tool. Next, look to create Factory classes and inject those to manage lifetime of your objects. It will feel like you are going backwards (and slow) until you reach the crest where most of your objects are using DI, and corollarily SRP - from there it should be downhill. Once you have better separation of concerns, the flexibility of your codebase and the speed at which you can make changes will dramatically increase.

Word of caution: don't let yourselves be fooled into thinking sprinkling a "Service Locator" everywhere is your panacea, it's actually a DI antipattern. I think you will need to use this at first but then you should finish the DI work with either constructor or setter injections and remove the Service Locator.

like image 86
Brett Veenstra Avatar answered Nov 15 '22 19:11

Brett Veenstra


Thanks for all the replies. We’re almost a year further now and I think I can mostly answer my own question.

We of course converted only the parts of our platform that were to be reused in the new product, as lasseeskildsen points out. Since this was only a partial conversion of the code base, we went with the DIY approach to dependency injection.

Our focus was making these parts available without bringing along unwanted dependencies, not to enable unit testing of them. This makes a difference in how you approach the problem. There are no real design changes in this case.

The work involved is mundane, hence the question of how to do so quickly or even automatically. The answer is it cannot be automated, but using some keyboard shortcuts and resharper it can be done quite fast. For me this is the optimal flow:

  1. We work across multiple solutions. We created a temporary “master” solution that contains all projects in all solution files. Though refactoring tools are not always smart enough to pick up the difference between binary and project references, this will at least make them work partially across multiple solutions.

  2. Create a list of all dependencies you need to cut. Group them by function. In most cases we were able to tackle multiple related dependencies at once.

  3. You’ll be making many small code changes across many files. This task is best done by a single developer, or two at most to avoid having to constantly merge your changes.

  4. Get rid of singletons first: After converting them away from this pattern, extract an interface (resharper -> refactor -> extract interface) Delete the singleton accessor to get a list of build errors. On to step 6.

  5. For getting rid of other references: a. Extract interface as above. b. Comment out the original implementation. This gets you a list of build errors.

  6. Resharper becomes a big help now:

    • Alt + shift + pg down/up quickly navigates broken references.
    • If multiple references share a common base class, navigate to its constructor and hit ctrl + r + s (“change method signature”) to add the new interface to the constructor. Resharper 8 offers you an option to “resolve by call tree”, meaning you can make inheriting classes have their signature changed automatically. This is a very neat feature (new in version 8 it seems).
    • In the constructor body assign the injected interface to a non-existing property. Hit alt + enter to select “create property”, move it to where it needs to be, and you’re done. Uncomment code from 5b.
  7. Test! Rinse and repeat.

To make use of these classes in the original solution without major code changes, we created overloaded constructors that retrieve their dependencies through a service locator, as Brett Veenstra mentions. This may be an anti-pattern, but works for this scenario. It won’t be removed until all code supports DI.

We converted about a quarter of our code to DI like this in about 2-3 weeks (1.5 persons). One year further, and we are now switching all our code to DI. This is a different situation as the focus shifts to unit testability. I think the general steps above will still work, but this requires some actual design changes to enforce SOC.

like image 43
user1970723 Avatar answered Nov 15 '22 21:11

user1970723