Working Effectively With Legacy Code — Mechanics of Change(Part II: Chapter 1)

This is Part II: Chapter 1of the Working Effectively With Legacy Code series. If you haven’t read the previous Part I: Chapter 5.

PART II: Changing Software

Chapter 1: I Don’t Have Much Time and I Have to Change It.

It Happens Someplace Every DayYour boss comes in. He says, “Clients are clamoring for this feature. Can we get it done today?”“I don’t know.”
You look around. Are there tests in place? No. You ask, “How bad do you need it?”
You know that you can make the changes inline in all 10 places where you need to change things, and it will be done by 5:00. This is an emergency right? We’re going to fix this tomorrow, aren’t we?Remember, code is your house, and you have to live in it.

Most often we live in a world where we are constantly asked to deliver more often and efficiently. In such circumstances, we tend to lose quality. There is a loss of quality when we need to deliver in quantity.

The only way to deliver quantity with quality is to go slow with writing tests as we deliver the work. It might feel we are going slow at the beginning, but ultimately it will make the work go faster since there will be fewer bugs and we can debug the application easier than before.

How to Change, When you don’t have much time?

In many cases, we can write fresh code even though we don’t have much time. There are several techniques we can use to write fresh code.

  • Sprout Method.
  • Sprout Class.
  • Wrap Method.
  • Wrap Class.

We need to use them with consideration. We will add a fresh code, which has test cases. But, we can’t easily test its code usage. Use caution.

Sprout Method

This method can be used when we need to add a new feature to a system and it can be formulated completely as new code. In such, case we can write a new method and can easily write a test for the new method.

Consider the following snippet, we need to verify that none of the new entries are already in transactionBundle before we post their dates and add them.

public class TransactionGate {public void postEntries(List entries) {
List entriesToAdd = new LinkedList();
for (Iterator it = entries.iterator(); it.hasNext(); ) {
entry.postDate();
entriesToAdd.add(entry);
}
transactionBundle.getListManager().add(entriesToAdd); }
}

A simple addition as below might be sufficient but that will be pretty invasive since there won’t be any separation between the new code and old code.

if (!transactionBundle.getListManager().hasEntry(entry) {                 
entry.postDate();
entriesToAdd.add(entry);
}

It might look a simple addition, but we are making the code a little muddier. We mingled two operations: date posting and duplicate entry detection.

Could we have done this in a different way?

Yes, we can have the duplicate removal method as a separate method.

List uniqueEntries(List entries) {                                                
List result = new ArrayList();
for (Iterator it = entries.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
if (!transactionBundle.getListManager().hasEntry(entry) {
result.add(entry);
}
}
return result;
}

We can test this method and go back and call this method in the origin code. The resulting code would look like below.

public class TransactionGate {public void postEntries(List entries) {
List entriesToAdd = uniqueEntries(entries);
for (Iterator it = entries.iterator(); it.hasNext(); ) {
entry.postDate();
entriesToAdd.add(entry);
}

transactionBundle.getListManager().add(entriesToAdd); }
}

Advantages and Disadvantages of Sprout Method

  • With Sprout Method, we are using a clear separated new code from old code with a test one.
  • The downside of Sprout Method is that we are just adding some new functionality without making the old class any better.

Sprout Class

This method is useful when you can’t create objects of a particular class in a test harness in a reasonable time because of some tangled dependency situations.

The tangled dependency can be because its hard to instantiate the class, or they have hidden dependencies and to need to untangle the dependency we need to do a lot of invasive refactoring. Because of these Sprout Method technique doesn’t work too.

In such a case we can create another class to hold our changes and use it from the source class.

Consider the following class QuarterlyReportGenerator; which is huge and has a lot of dependencies tangled, but we need to make sure generate() will add a header row for the HTML table it’s producing.

class QuarterlyReportGenerator {
.....

void generate() {
Result results = database.queryResults(beginDate, endDate);
String pageText;
pageText += "<html><head><title> Quarterly Report </title></head><body><table>";
if (results.size() != 0) {
for (int i = results.begin(); i != results.end(); ++i) {
pageText += "<tr>";
pageText += "<td>" + i -> department + "</td>";
pageText += "<td>" + i -> manager + "</td>";
char buffer[ 128];
sprintf(buffer, "<td>$%d</td>", i -> netProfit / 100);
pageText += string(buffer);
sprintf(buffer, "<td>$%d</td>", i -> operatingExpense / 100);
pageText += string(buffer);
pageText += "</tr>";
}
}
}
......
}

Now, suppose we needed to add a header row for the HTML table, we can create a new class called TableHeaderProducer as below, considering QuarterlyReportGenerator class is huge and it would take time to break it.

class TableHeaderProducer {

void makeHeader() {
return "<tr><td>Department</td><td>Manager</td> < td > Profit </td ><td > Expenses </td >";
}

}

Now, we can just create an instance of this class and call it directly to makeHeader.

TableHeaderProducer producer = new TableHeaderProducer();
pageText += producer.makeHader();

Advantages and Disadvantages of Sprout Class

  • It allows us to move forward with more confidence without invasive changes.
  • The key disadvantage of Sprout Class is the conceptual complexity of implementing it.

Wrap Method

Adding behavior to existing methods is easy to do, but often it isn’t the right thing to do. Some behavior can be together, but some behavior needs to be separate, so we need to consider when and how to add behavior to the existing method.

Consider, the following method pay(). Let’s suppose a requirement comes along, that every time we pay an employee, we have to log the payment somewhere.

public class Employee {
...

public void pay() {
Money amount = new Money();
for (Iterator it = timecards.iterator(); it.hasNext(); ) {
Timecard card = (Timecard) it.next();
if (payPeriod.contains(date)) {
amount.add(card.getHours() * payRate);
}
}
payDispatcher.pay(this, date, amount);
}
...
}

We can use the wrap method to add behavior to the existing method as follows. This is one form of Wrap Method where we create a method with the name of the original method and have it delegate to our old code.

public class Employee {
private void dispatchPayment() {
Money amount = new Money();
for (Iterator it = timecards.iterator(); it.hasNext(); ) {
Timecard card = (Timecard) it.next();
if (payPeriod.contains(date)) {
amount.add(card.getHours() * payRate);
}
}
payDispatcher.pay(this, date, amount);
}

public void pay() {
logPayment();
dispatchPayment();
}

private void logPayment() {
...
}
}

Another form of Wrap Method is that we can create a new method that no one calls yet. This method is to be consumed to add the new logging feature.

public class Employee {
public void makeLoggedPayment() {
logPayment();
pay();
}

public void pay() {
...
}

private void logPayment() {
...
}
}

Advantages and Disadvantages of Wrap Method

  • Wrap method is better as they don’t increase the size of existing methods.
  • Wrap method explicitly makes the new functionality independent of existing functionality.
  • The primary disadvantage of Wrap Method is that it can lead to poor names.

Wrap Class

This is the class-level companion of Wrap Method. Wrap class uses another wrapper class to add behavior to the existing class. We do this by Extracting Interface and have the wrapper class implement that interface. This style is also referred to as the Decorator Pattern.

class Employee {
public void pay() {
Money amount = new Money();
for (Iterator it = timecards.iterator(); it.hasNext(); ) {
Timecard card = (Timecard) it.next();
if (payPeriod.contains(date)) {
amount.add(card.getHours() * payRate);
}
}
payDispatcher.pay(this, date, amount);
}
...
}

Here, in this case, we extract an interface called Employee which implements the pay() method and the implementing class hold on to the employee instance does the logging when pay() method is invoked.

class LoggingEmployee extends Employee {
public LoggingEmployee(Employee e) {
employee = e;
}

public void pay() {
logPayment();
employee.pay();
}

private void logPayment() {
...
}
...
}

Advantages and disadvantages of Wrap Class

  • Implementing Wrap Class might be hard to chew upon at the beginning since large code bases are pretty hard to work.

Note: The difference between Wrap Method and Sprout Method is pretty trivial. We use Sprout Method when we choose to write a new method and call it from an existing method, and we use Wrap Method when we chose to rename a method and replace it with a new one that does the new work and calls the old one.

TLDR; These are few techniques that we can use to make changes without getting exisiting classes under test, but beaware there are pros and cons to these techniques.

Previous Part I: Chapter 5:: Tools

PART II: Chapter 2:: It Takes Forever to Make a Change.

Coming Soon

References:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store