Share Constants

One of the challenges in getting started arises when constructors set state independent of getters and setters. A typical example of this occurs when constructors use default values, common when you have multiple constructors and simpler ones call more complex ones (Listing 6-4).

Listing 6-4: Use of literal default values in constructor implementations

public class FixedThreadPool {
  private final int poolSize;

  public FixedThreadPool(int poolSize) {
    this.poolSize = poolSize;
  }

  public FixedThreadPool() {
    this(10);
  }

  public int getPoolSize() {
    return poolSize;
  }
}

Testing the first constructor is trivial. We pass a pool size and use the getter to verify it. We cannot use the getter and setter combination test because the poolSize attribute cannot be changed once it is set. But how do we test that the default constructor does its job correctly? An initial attempt at a test might look like Listing 6-5.

Listing 6-5: Testing a constructor that uses default values internally

@Test
public void testFixedThreadPool() {
  FixedThreadPool sut = new FixedThreadPool();

  int actualPoolSize = sut.getPoolSize();

  assertEquals(10, actualPoolSize);
}

This certainly gets the job done to start us off. What happens if the default pool size changes? As we have seen in previous examples, we get a maintainability benefit from coupling our test inputs to the expected values. The same is true with the “hidden” inputs that manifest with default values. Fortunately, we can easily refactor our code to make our defaults both visible and self-documenting. Review the refactored fragment of the original production code in Listing 6-6.

Listing 6-6: Refactored code to extract default values to constants

public class FixedThreadPool {
  public static final int DEFAULT_POOL_SIZE = 10;

  ...

  public FixedThreadPool() {
    this(DEFAULT_POOL_SIZE);
  }

  public int getPoolSize() {
    return poolSize;
  }
}

The declaration and usage of the DEFAULT_POOL_SIZE constant gives us a publicly accessible, intent-revealing name for the value used by the default constructor. Our test now becomes much more legible and less fragile when written as in Listing 6-7.

Listing 6-7: Testing the constructor using a default value constant

@Test
public void testFixedThreadPool() {
  FixedThreadPool sut = new FixedThreadPool();

  int actualPoolSize = sut.getPoolSize();

  assertEquals(FixedThreadPool.DEFAULT_POOL_SIZE,
      actualPoolSize);
}

The same technique can be used to expose string constants or even object constants and not just in constructors. Any fixed value or “magic number” that makes the software work is a candidate. You will often see such constants defined in well-defined libraries simply based on their convenience and readability, but they serve to enhance the testability of the software as well. This is a technique that is hard to abuse.3

3. However, like any good thing, it can be abused. The most egregious abuse I have seen of shared constants is the preallocated exception. In Java in particular, the stack trace for an exception is captured when the object is allocated. When it is preallocated as a static constant, the stack trace shows the static initialization context, not the context in which the exception is actually thrown. This can be very confusing when diagnosing a test failure.

Sometimes the form of constant values may differ. When there are multiple values that represent a fixed set, an enumeration is often used. Listing 6-8 shows a common idiom in C++ using nested anonymous enumerations.

Listing 6-8: Using nested anonymous enumerations in C++ for closely related constants

class 3DPoint {
  enum {
    X_INDEX = 0,
    Y_INDEX,
    Z_INDEX
  };

  ...
};

By default, the first member of the enum is assigned the value 0 automatically, but it is a common idiom to specify the initial value. Successive values increment by one from their predecessor.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.34.39