Skip to content

Instantly share code, notes, and snippets.

@npathai
Last active May 30, 2020 03:09
Show Gist options
  • Save npathai/6ddc56a3aa173e8481b28f7c4182bf9a to your computer and use it in GitHub Desktop.
Save npathai/6ddc56a3aa173e8481b28f7c4182bf9a to your computer and use it in GitHub Desktop.
Working Effectively With Legacy Code Book Notes

I don't have much time and I have to change it

Sprout Method

If a change is too difficult to make in existing method, then create a new method which performs additional work and call it from existing method. Unit test the new method, even if it requires to make it static and pass all dependencies in it.

Sprout Class

Similar to sprout method, but if creating instance of class is too difficult to do in limited time, or maybe it will require separating a lot of dependencies. Create another class to hold changes and use from original difficult to test class. Unit test sprouted class properly. This may seem like making the code worse, and sometimes is, but many times it helps in figuring out commanality between sprouted class and some new class in future and improves chances of carving out interfaces or resusable classes.

Wrap method

If new functionality is cleanly separable, then rather than adding more stuff to existing method, we can make original method as private and rename it to give apt name, make wrap method of same name and call two methods from it. One of them is new method and other is the one we made private.

Before

public void method() {
  ...
  ...
}

After

public void method() {
   renamedMethod();
   newMethod();
}

private void renamedMethod() {
  // was previously called method, but renamed and wrapped inside new wrap method
}

private void newMethod() {
  // New functionality implemented in this method
}

Wrap Class

Just like wrap method, Wrap class performs the additional functionality and delegates to original class to perform the old function. A decorator pattern implementation. A wrapper on top of existing class.

  1. Extract Interface refactoring
  2. Make existing class implement it and the new wrapping class as well.

It takes forever to make a change

Well maintained system

It might take a while to figure out how to make a change, but once we have figured out the change is relatively easy and we feel much more comfortable with system

Legacy System

It can take a long time to figure out how to make a change, and making the change is difficult also. We don't get any better understanding of the system. Sometimes no matter how much time we spent, it won't be enough to understand everything we need to make the change. Then we developers cross our fingers and hope for the best.

Lag time

Amount of time we have to wait after making the change to get real feedback. Imagine Mars rover and 14 min delay in communication.

How do I add a feature

Programming by difference

When faced with situation to add a new feature, rather than modifying existing method, we can extend original class and implement method there. This allows us to go from red to green quickly. After we have added the feature, we can figure out exactly how we want to integrate the feature.

Example of MailForwarder with anonymous and BCC features and MailConfiguration or MailingList

Inheritance when overused becomes really confusing. It is a useful technique but we must also make sure we don't violate Liskov Substitution Principle too often (principle of least surprise)

Let's say we hold a reference to concrete class X, so that clients may think it will behave like concrete object X. But in reality we may have initialized the reference with some concrete class Y, which may have behavior that may surprise the caller.

So rather than overriding the concrete methods from super class, we can make class abstract and subclass two variants from it. One with older functionality and other with new functionality.

Programming by Difference lets us introduce variations quickly in systems. When we do, we can use our tests to pin down the new behavior and move to more appropriate structures when we need to. Tests can make the move very rapid.

I can't get this class into a test harness

The case of irritating parameter

Sometimes when working with legacy code, creating instance of a class can be quiet challenging.

  • Can't be easily created
  • Constructor does real work and has side effects
  • Harness won't easily build with this class in it
  • Constructor does work that we need to sense
When we need to create an object in a test harness, often the best approach is to just try to do it

Create a construction test without any assertion (just new the class under test), and let the compiler tell you what needs to be done to create the object. Or let runtime errors tell you what you need to provide. Once object is constructed, then modify the test to a proper test

Extract Interface for objects that are irritating or altogether impossible to create and inject fake/mock objects

Extract interface and pass nulls wherever you can. If the parameter is used, then there will be NPE. Then we know that paramter is used, and we need to pass the argument.

The case of hidden dependency

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment