Connascence of Value: a different approach

Recently I wrote a series of posts in which I attempted to drive a TDD episode purely from the point of view of connascence. But as I now read over the articles again, it strikes me that I made some automatic choices. I explicitly called out my design choices in some places, while elsewhere I silently used experience to choose the next step. So here, I want to take another look at the very first step I took.

You will recall that in making the first test pass I introduced some CoV (Connascence of Value), because the test and the Checkout class both know the magic number 50:

cov1

I fixed that CoV by passing the magic value as a parameter, following the Dependency Inversion Principle. But there are plenty of alternatives. Instead of using the DIP, I could directly convert the CoV into CoN (Connascence of Name) by introducing a constant in the Checkout class:

public class CheckoutTests {
  @Test
  public void basicPrices() {
    Checkout checkout = new Checkout();
    checkout.scan("A");
    assertEquals(Checkout.ITEM_PRICE, checkout.currentBalance());
  }
}
public class Checkout {
  public static final int ITEM_PRICE = 50;

  public void scan(String sku) { }

  public int currentBalance() {
    return ITEM_PRICE;
  }
}

(This isn’t something I would do in real life, but let’s just see where it leads, for curiosity’s sake.)

That was a really simple step, and it completely fixes the CoV: CoN is level 1 on the connascence scale, and is thus the weakest kind of coupling I could have. Note that I can no longer use random values in the test; I wonder where that fact will lead…

As before, I am now left with CoM (Connascence of Meaning), because both the test and the Checkout know that monetary values are represented using an int. I fix that in the same way as I did last time, by introducing a Money class:

public class Checkout {
  public static final Money ITEM_PRICE = Money.fromPence(50);
  //...
}

Next, I recycle the test to make it a little more complex, just as before:

@Test
public void basicPrices() {
  Checkout checkout = new Checkout();
  checkout.scan("A").scan("B");
  assertEquals(Checkout.ITEM_PRICE.add(Money.fromPence(30)), checkout.currentBalance());
}

As before, I have to add a few useful things to Money to get this to compile; all good. But this time around, it seems I have more choices when it comes to getting the test to pass. That’s because I fixed the CoV with a constant, thus imposing less structure on the Checkout.

For the sake of similarity I will do the same as last time (even though I know it causes problems later). So I calculate the running balance inside scan():

public class Checkout {
  static final Money ITEM_PRICE = Money.fromPence(50);
  private Money balance;

  public Checkout scan(String sku) {
    if (sku == "A")
      balance = ITEM_PRICE;
    else
      balance = balance.add(Money.fromPence(30));
    return this;
  }

  public Money currentBalance() {
    return balance;
  }
}

Yikes! I now have CoV due to the 30, and also CoV due to the “A”. Which should I fix first? And does it matter? Let’s find out…

First, I will add another constant to hold the price of a “B”:

@Test
public void basicPrices() {
  Checkout checkout = new Checkout();
  checkout.scan("A").scan("B");
  assertEquals(Checkout.PRICE_OF_A.add(Checkout.PRICE_OF_B), checkout.currentBalance());
}

That doesn’t change much, but those constant names are interesting. I feel they are tied to the SKU strings somehow, and that makes me uncomfortable. Then I see it: I have now introduced some new CoV, because the name PRICE_OF_A depends on the value of the string “A”. If either changes, the code will not break, but my head will. All of which means that the introduction of the constant for the price of B didn’t really fix the CoV; it just moved it around a bit!

I guess that means I have to fix the CoV of the SKU names in order to make any progress. Let’s just take a moment to look at the extent of this coupling:

cov

The SKU name is known in three places, each of which is coupled to both of the other two. So in order to fix this CoV, I have to move that value into exactly one place.

As always, I have choices. What would you do?

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s