Chapter 4: The Seam Model

Chapter 4: The Seam Model

Note of Working Effectively with Legacy Code

A seam is a place where you can alter behavior in your program without editing in that place.

That definition caught me; as a foreigner, I had no idea what the writer was talking about. And I don't have enough experience with the famous C++ language, so even though I looked through the example that the writer showed me, I still didn’t get it.

Anyway, Let's look at what we can understand first.

A Huge Sheet of Text

The writer talked about the experience of coding on an old machine; even though I think it is interesting, I am still being thankful for borning in better times. And then he talked about the Reuse, but not too much.

Seams

Considering that it is difficult for me to understand the C++ language the writer wrote, I decided to write an example by myself.

class CAsyncSslRec {
    run() {
        this.doSomethingElse();
        this.PostReceiveError('type', 'errorCode');
    }

    doSomethingElse() {
        console.log('Do something else');
    }

    PostReceiveError(type: string, errorCode: string) {
        console.log('Fire the nuclear weapons!');
    }
}

Now we want to test the CAsyncSslRec class. But here is the problem: we don't know what will happen if we directly call the PostReceiveError Method.

I mean, who knows? It may even fire a nuclear boom.

But how should we proceed to finish our tests? Here comes the Seam.

According to the writer, we can replace the Method, which may fire nuclear weapons with the Tiny little One, which won't harm anything.

Here it is:

class TestingCAsyncSslRec extends CAsyncSslRec {
    PostReceiveError(type: string, errorCode: string) {
        console.log('Fire a toy gun');
    }
}

const test = new TestingCAsyncSslRec();
test.run();

// result: 
//          "Do something else"
//          "Fire a toy gun"

We leveraged the power of Inherit, and we overrode the nuclear Method. We are so strong, and that feels good.

By the way, the writer calls this Seam Object Seam

If we can replace behavior at seams, we can selectively exclude dependencies in our tests.

Seam Types

Preprocessing Seams

I barely know anything about the famous C++ or C preprocessor, so I think we should just let this alone.

This one uses the power of IDE or some magic script to replace the link of the actual module with the fake one during the compilation. You should also configure the variety of environment files to indicate that you are in the Testing environment.

Object Seams

This one is my favorite one. I have talked about it a lot above; now, let's dig deeper.

interface Cell {
    Recalculate: () => void;
}

class FormulaCell implements Cell {
    Recalculate () {
        // well, do something here.
    }
}

class CustomSpreadsheet {
    public buildMartSheet() {
        const cell: Cell = new FormulaCell();
        cell.Recalculate();
    }

    public buildMartSheetInBetterWay(cell: Cell) {
        cell.Recalculate();
    }
}

In the buildMartSheet Method, there is no seam for us to exploit cause the dependency is too strong and too explicit; we will lose the stage of playing tricks with this method.

Without changing the source code, the Recalculate method of the FormulaCell class will always be called. So let's see the buildMartSheetInBetterWay Method, yeah, a better way.

We trust the test to shape our source code, which is fantastic. Now, we can send our toy cell with a toy Recalculate Method to proceed with our tests.

And all of this will leave us with a better structure for the features which keep changing.

Like the writer told us:

When you get used to seeing code in terms of seams, it is easier to see how to test things and to see how to structure new code to make testing easier