The Transformation Priority Premise

Throughout the book, I’ve suggested that the next test you write is the one that grows your system by the smallest increment. If you strictly adhere to the TDD cycle, seeking to always demonstrate test failure before you move on, you will learn what it means to take too large a step. (See Getting Green on Red.) Following the last rule of simple design—minimize the total number of classes and methods (see Simple Design)—will also help by teaching you to avoid overdesigning your system and putting more code in place than need be. The third rule of TDD (The Three Rules of TDD) also tells you to put no more code in place than necessary to pass your tests.

Small steps are important, because larger steps will waste more time as your system grows. Create an overblown solution that far exceeds what a test demands, and you’ll likely need to overhaul a good chunk of code a few tests down the road.

Success with TDD requires the ability to grow a system by small increments. You will shorten your path to mastery by having the will to back up and try a different route when necessary. (You’ll need a good version control tool to support changing course.)

Another tool for determining your next test is Robert C. Martin’s Transformation Priority Premise (TPP), which proposes a priority list of transformations. Each transformation represents an alteration of code from specific to slightly more generic. Using the TPP, you choose a test that drives the highest-priority transformation against your existing code. The premise is that following the TPP allows you to grow your system using the smallest possible increments. Use the TPP, and you’ll avoid the test-driven weeds.

You can find the original list of priorities at http://web.archive.org/web/20130113152824/http://cleancoder.posterous.com/the-transformation-priority-premise. The priority list isn’t foolproof; it’s a premise, after all. Other subsequent blog posts have proposed slightly tweaked priority lists.

Meet the Transforms

The TPP likely sounds a bit complex to you. Working through an example is worth 1,024 words. We’ll step through the Soundex example from Chapter 2, Test-Driven Development: A First Example using the transform priority list (TPL) in its original posting order.

({}nil)

Replace no code with code that employs nil.

(nilconstant)

Replace nil with a constant.

(constantconstant+)

Replace a simple constant with a more complex constant.

(constantscalar)

Replace a constant with a variable or argument.

(statementstatements)

Add unconditional statements.

(unconditionalif)

Split the execution path.

(scalararray)

Replace a variable/argument with an array.

(arraycontainer)

Replace an array with a more complex container.

(statementrecursion)

Replace a statement with a recursive call.

(ifwhile)

Replace a conditional with a loop.

(expressionfunction)

Replace an expression with a function.

(variableassignment)

Replace the value of a variable.

(Source: http://en.wikipedia.org/wiki/Transformation_Priority_Premise.)

Given that we’ve already worked through Soundex, we’ll focus our discussion on the code transformations and leave other chatter about the code to a minimum. Our first test differs slightly; we’ll handle padding the zeros immediately.

tpp/1/SoundexTest.cpp
 
TEST(SoundexEncoding, AppendsZerosToWordForOneLetterWord) {
 
Soundex soundex;
 
auto​ encoded = soundex.encode(​"A"​);
 
 
CHECK_EQUAL(​"A000"​, encoded);
 
}

The failure to compile the test represents our first need for a transformation—from no code into code returning nil, the simplest possible transformation, topmost on the TPL. Passing the compile “test” requires an implementation for encode that returns a nil value...but leaves us with a failing unit test.

tpp/1/Soundex.h
 
class​ Soundex {
 
public​:
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
return​ nullptr;
 
}
 
};

We fix the failing unit test by transforming nil into a constant, the second transform on the list.

tpp/2/Soundex.h
 
class​ Soundex {
 
public​:
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
*
return​ ​"A000"​;
 
}
 
};

Triangulation

The last time we built Soundex, we drove out the hard-coded constant "A" as part of the refactoring step. We introduced a variable in order to eliminate the duplication of the string literal from test to production code. We also decided that eliminating the specific hard-coded value in production code would make it consistent with the general goal stated by the test name (RetainsSoleLetterOfOneLetterWord). In doing so, we were following the spirit of the TPP: to incrementally generalize the code. Each transform on the TPL represents a move from specific to slightly more generic.

This time around, we will drive out the hard-coding by using a technique known as triangulation, first described in Test Driven Development: By Example [Bec02]. Triangulation involves approaching the same behavior from a different angle, by adding a second test case.

tpp/3/SoundexTest.cpp
 
TEST(SoundexEncoding, AppendsZerosToWordForOneLetterWord) {
 
CHECK_EQUAL(​"A000"​, soundex.encode(​"A"​));
*
CHECK_EQUAL(​"B000"​, soundex.encode(​"B"​));
 
}

We could make the failing test pass by introducing an if statement: if the first letter of the word is A, return "A100"; otherwise, return "B100". Introducing that code would represent the transform (unconditionalif). We choose the higher-priority transform (constantscalar).

tpp/3/Soundex.h
 
class​ Soundex {
 
public​:
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
*
return​ word + ​"000"​;
 
}
 
};

Scanning the Test List

Where next? We want to grow the code using the highest-priority transformation possible. Let’s take a look at the list of remaining tests:

 
PadsWithZerosToEnsureThreeDigits
 
ReplacesConsonantsWithAppropriateDigits
 
ReplacesMultipleConsonantsWithDigits
 
LimitsLengthToFourCharacters
 
IgnoresVowelLikeLetters
 
IgnoresNonAlphabetics
 
CombinesDuplicateEncodings
 
UppercasesFirstLetter
 
IgnoresCaseWhenEncodingConsonants
 
CombinesDuplicateCodesWhen2ndLetterDuplicates1st
 
DoesNotCombineDuplicateEncodingsSeparatedByVowels

We currently have an unconditional statement consisting of an expression using a constant and a scalar. That shrinks the relevant portion of the TPL a bit, since we need not worry about transforms from {}, nil, array, and if.

Any code requiring the ability to take a substring, string length, or even uppercase a letter would require introducing a function call (perhaps; approaches not requiring functions might work if we’re creative in our thinking). For now we’ll avoid tests that seem to require these functions and seek instead something of higher priority.

It looks as if most tests that don’t require a function call are likely to require a conditional. Hard-coding specific constants only gets us so far before we require code that can make a decision. Let’s tackle a bit of encoding.

tpp/4/SoundexTest.cpp
 
TEST(SoundexEncoding, ReplacesConsonantsWithAppropriateDigits) {
 
CHECK_EQUAL(​"A100"​, soundex.encode(​"Ab"​));
 
}

We generalize our code using the (unconditionalif) transformation.

tpp/4/Soundex.h
 
class​ Soundex {
 
public​:
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
*
std::​string​ code(​""​);
*
code += word[0];
*
if​ (word[1])
*
code += ​"100"​;
*
else
*
code += ​"000"​;
*
return​ code;
 
}
 
};

When using the TPP, relax a little and avoid prematurely tightening up the code. You must still eliminate duplication and retain expressiveness, but leave simple if statements and while loops alone for a while. You may find that avoiding more complex forms (ternary operators and for loops, for example) makes it easier to spot better opportunities for good refactoring.

Our solution is a bit rote, which is not a problem. It’s not very clear and exhibits a bit of duplication. That’s a problem. We refactor.

tpp/5/Soundex.h
 
class​ Soundex {
 
public​:
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
std::​string​ code(​""​);
*
code += head(word) + encodeTail(word);
*
return​ zeroPad(code);
 
}
 
*
char​ head(​const​ std::​string​& word) ​const​ {
*
return​ word[0];
*
}
 
*
std::​string​ encodeTail(​const​ std::​string​& word) ​const​ {
*
if​ (word[1] == 0) ​return​ ​""​;
*
return​ ​"1"​;
*
}
 
*
std::​string​ zeroPad(​const​ std::​string​& code) ​const​ {
*
if​ (code[1] != 0)
*
return​ code + ​"00"​;
*
return​ code + ​"000"​;
*
}
 
};

The code in zeroPad still kind of stinks, doesn’t it? We make a second refactoring pass.

tpp/6/Soundex.h
 
std::​string​ zeroPad(​const​ std::​string​& code) ​const​ {
*
return​ code + (hasEncodedCharacters(code) ? ​"00"​ : ​"000"​);
 
}
 
*
bool​ hasEncodedCharacters(​const​ std::​string​& code) ​const​ {
*
return​ code[1] != 0;
*
}

What did I just say about fancy constructs like the ternary operator? It seemed to make sense here for eliminating a bit of code duplication. If it causes us any headaches, we’ll back up and eliminate it.

We add a second assert to drive in encoding for a second consonant. In terms of an implementation, we could introduce a second if statement, but that would only introduce a duplicative construct and not generalize the code. We seek the next highest transform that applies: (scalararray).

tpp/7/SoundexTest.cpp
 
TEST(SoundexEncoding, ReplacesConsonantsWithAppropriateDigits) {
 
CHECK_EQUAL(​"A100"​, soundex.encode(​"Ab"​));
*
CHECK_EQUAL(​"A200"​, soundex.encode(​"Ac"​));
 
}
tpp/7/Soundex.h
 
class​ Soundex {
 
public​:
*
Soundex() {
*
codes_[​'b'​] = ​"1"​;
*
codes_[​'c'​] = ​"2"​;
*
}
 
// ...
 
std::​string​ encodeTail(​const​ std::​string​& word) ​const​ {
 
if​ (word[1] == 0) ​return​ ​""​;
*
return​ codes_[​static_cast​<size_t>(word[1])];
 
}
 
// ...
*
private​:
*
std::​string​ codes_[128];
 
};

We complete the consonant list and do a little bit of refactoring for expressiveness.

tpp/8/Soundex.h
 
class​ Soundex {
 
public​:
 
Soundex() {
*
initializeCodeMap();
 
}
 
 
void​ initializeCodeMap() {
 
codes_[​'b'​] = codes_[​'f'​] = codes_[​'p'​] = codes_ [​'v'​] = ​"1"​;
 
codes_[​'c'​] = codes_[​'g'​] = codes_[​'j'​] = codes_ [​'k'​] =
 
codes_[​'q'​] = codes_[​'s'​] = codes_[​'x'​] = codes_[​'z'​] = ​"2"​;
 
codes_[​'d'​] = codes_[​'t'​] = ​"3"​;
 
codes_[​'l'​] = ​"4"​;
 
codes_[​'m'​] = codes_[​'n'​] = ​"5"​;
 
codes_[​'r'​] = ​"6"​;
 
}
 
// ...
 
 
std::​string​ encodeTail(​const​ std::​string​& word) ​const​ {
 
if​ (word[1] == 0) ​return​ ​""​;
*
return​ codeFor(word[1]);
 
}
 
*
std::​string​ codeFor(​char​ c) ​const​ {
*
return​ codes_[​static_cast​<size_t>(c)];
*
}
 
 
// ...
 
};

We scan the list of remaining tests again. Most still appear to require introducing a function call. Higher than a function call in priority, though, are two transformations that support looping, one via a while loop and the other via recursion. One test—ReplacesMultipleConsonantsWithDigits—appears to demand a looping solution.

Later versions of the TPL contain some priority variations. We’re using the original version, which promotes transformations to a recursive solution over transformations to looping solutions. The emphasis has been a topic of debate. In a functional language, such as Erlang or Clojure, you want a recursive solution. In C++, the choice is up to you. You may want to compare the performance of a recursive solution to its iterative counterpart.

We’ll stick with the original TPL ordering and find out where that takes us.

Growing out the code with the TPP has resulted in a solution where each step of the way so far has been a small, incremental change. We haven’t required any major overhauls to existing code. Introducing a recursive solution is no different.

tpp/9/SoundexTest.cpp
 
TEST(SoundexEncoding, ReplacesMultipleConsonantsWithDigits) {
 
CHECK_EQUAL(​"A234"​, soundex.encode(​"Acdl"​));
 
}
tpp/9/Soundex.h
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
std::​string​ code(​""​);
*
code += head(word);
*
encodeTail(word, code);
 
return​ zeroPad(code);
 
}
 
// ...
*
void​ encodeTail(​const​ std::​string​& word, std::​string​& code) ​const​ {
 
if​ (word[1] == 0) ​return​;
*
code += codeFor(word[1]);
*
encodeTail(tail(word), code);
 
}
*
std::​string​ tail(​const​ std::​string​& word) ​const​ {
*
return​ word.substr(1);
*
}

There are a couple problems. First, it doesn’t work. We need to adjust the number of zeros that get padded to the encoding. Second, hey! That’s a function call to substr, and we chose an increment that introduced recursion because it was higher than one requiring a function call.

The TPP, a premise and work in progress, isn’t a panacea that will solve all your coding challenges. It’s also not a hard set of rules. The goal of the TPL is to help you seek the next smallest increment. In our case, the recursive solution demonstrated itself to be a good incremental step, and that’s what’s important. That we had to introduce a function call to implement the solution seems an acceptable bending of the priority rules. (Recursive solutions dealing with collections—a string is but a collection of characters—typically require a function that extracts the tail of the collection. It’s often the best approach.)

Fixing the padding problem can be done in a few ways. One is to initialize either a counter or a string representing the zeros to be padded and then decrement it each time an encoded character gets appended in encodeTail. However, that requires an assignment statement, which is lower on the priority list. Simpler, and higher on the TPL, is to update the zeroPad function to take into account the length of the code.

tpp/10/Soundex.h
 
const​ ​static​ size_t MaxCodeLength{4};
 
std::​string​ zeroPad(​const​ std::​string​& code) ​const​ {
*
return​ code + std::​string​(MaxCodeLength - code.length(), ​'0'​);
 
}

We realize that encodeTail can be “backed up” to operate on the first (0th) element of the word if we call it with the tail of the word (instead of with the complete word). We make the change, which allows us to make a small additional refactoring to increase the expressiveness of the code.

tpp/11/Soundex.h
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
std::​string​ code(1, head(word));
 
encode(tail(word), code);
 
return​ zeroPad(code);
 
}
 
void​ encode(​const​ std::​string​& word, std::​string​& code) ​const​ {
 
if​ (word.empty()) ​return​;
 
code += codeFor(head(word));
 
encode(tail(word), code);
 
}
 
const​ ​static​ size_t MaxCodeLength{4};
 
std::​string​ zeroPad(​const​ std::​string​& code) ​const​ {
*
return​ code + std::​string​(MaxCodeLength - code.length(), ​'0'​);
 
}

We love having the tests to allow us to make sure our change works!

Our core algorithm is tight and clear. As with the last time we built Soundex, the encode function clearly states the policy for encoding a word. Let’s see whether we can knock out the rest of the tests. We choose IgnoresVowelLikeLetters. It seems like it would require introduction of only an if statement.

tpp/12/SoundexTest.cpp
 
TEST(SoundexEncoding, IgnoresVowelLikeLetters) {
 
CHECK_EQUAL(​"B234"​, soundex.encode(​"BAaEeIiOoUuHhYycdl"​));
 
}

The test passes with no code changes! As always, we want to think about why (see Getting Green on Red), but it’s of less concern now. If we’re strictly adhering to the TPP, the premise is that it’s guiding us to incorporate the smallest possible increments. Tests that pass prematurely are thus unlikely to represent building too much code.

The test passes because the codes_ lookup array returns null for any elements not contained. Appending null results in no change to the code. Moving on, we discover that IgnoresNonAlphabetics passes for the same reason.

We introduce CombinesDuplicateEncodings.

tpp/13/SoundexTest.cpp
 
TEST(SoundexEncoding, CombinesDuplicateEncodings) {
 
CHECK_EQUAL(soundex.codeFor(​'f'​), soundex.codeFor(​'b'​));
 
CHECK_EQUAL(soundex.codeFor(​'g'​), soundex.codeFor(​'c'​));
 
CHECK_EQUAL(soundex.codeFor(​'t'​), soundex.codeFor(​'d'​));
 
CHECK_EQUAL(​"A123"​, soundex.encode(​"Abfcgdt"​));
 
}

Our test run dies with an std::length_error. A quick look at the backtrace (using gdb under Linux) indicates that the problem is in the zeroPad function. If the code ends up being more than three characters, zeroPad attempts to construct a string with a negative number of ’0’ characters.

Does that mean we should switch our focus to LimitsLengthToFourCharacters? The TPP suggests no, since it requires introducing a length function call, while CombinesDuplicateEncodings should require only a conditional statement. However, the exception means we are not seeing a direct failure of our test, since the code can’t run to completion. We decide to seek a failing test first and make a note to return and try the alternate route if we get into trouble. We disable the test and tackle LimitsLengthToFourCharacters instead.

tpp/14/SoundexTest.cpp
 
TEST(SoundexEncoding, LimitsLengthToFourCharacters) {
 
CHECK_EQUAL(4u, soundex.encode(​"Dcdlb"​).length());
 
}

A small change incorporating (expressionfunction) passes the test.

tpp/14/Soundex.h
 
void​ encode(​const​ std::​string​& word, std::​string​& code) ​const​ {
*
if​ (word.empty() || isFull(code)) ​return​;
 
code += codeFor(head(word));
 
encode(tail(word), code);
 
}
*
bool​ isFull(std::​string​& code) ​const​ {
*
return​ code.length() == MaxCodeLength;
*
}

We reintroduce CombinesDuplicateEncodings. It runs cleanly and fails as expected. A solution involves passing the head of the word to the recursive encode function as a basis for comparison to the current digit to be added. We try it with a name hastily chosen to avoid conflict with the head function name.

tpp/15/Soundex.h
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
std::​string​ code(1, head(word));
*
encode(tail(word), code, head(word));
 
return​ zeroPad(code);
 
}
 
void​ encode(​const​ std::​string​& word, std::​string​& code,
*
char​ H) ​const​ {
 
if​ (word.empty() || isFull(code)) ​return​;
*
std::​string​ digit = codeFor(head(word));
*
if​ (digit != codeFor(H))
*
code += codeFor(head(word));
*
encode(tail(word), code, head(word));
 
}

Our solution demands refactoring. Since we use both head and tail parts of the word to be encoded in encode, we simplify its signature by passing in the entire word. We also choose a nonconflicting name and add a helper function to clarify what’s going on. (Every line in encode changed, so none of its lines is highlighted, nor is the new function isSameEncodingAsLast.)

tpp/16/Soundex.h
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
std::​string​ code(1, head(word));
*
encode(word, code);
 
return​ zeroPad(code);
 
}
 
 
void​ encode(​const​ std::​string​& word, std::​string​& code) ​const​ {
 
auto​ tailToEncode = tail(word);
 
if​ (tailToEncode.empty() || isFull(code)) ​return​;
 
 
auto​ digit = codeFor(head(tailToEncode));
 
if​ (isSameEncodingAsLast(digit, word))
 
code += digit;
 
encode(tailToEncode, code);
 
}
 
 
bool​ isSameEncodingAsLast(
 
const​ std::​string​& digit,
 
const​ std::​string​& word) ​const​ {
 
return​ digit != codeFor(head(word));
 
}

A similar test, CombinesDuplicateCodesWhen2ndLetterDuplicates1st, should require roughly the same transformation. (We specify the assertion’s expected value as starting with a lowercase letter, since we haven’t yet tackled the concern of uppercasing the first letter in a Soundex encoding.)

tpp/17/SoundexTest.cpp
 
TEST(SoundexEncoding, CombinesDuplicateCodesWhen2ndLetterDuplicates1st) {
 
CHECK_EQUAL(​"b230"​, soundex.encode(​"bbcd"​));
 
}

The test passes because we already pass the entire encoding to the recursive encode function. Another similar test passes, too.

tpp/18/SoundexTest.cpp
 
TEST(SoundexEncoding, DoesNotCombineDuplicateEncodingsSeparatedByVowels) {
 
CHECK_EQUAL(​"J110"​, soundex.encode(​"Jbob"​));
 
}

We’re a little nervous about all these tests passing, so we think of another scenario to allay our concerns. It passes. Sheesh.

tpp/18/SoundexTest.cpp
 
TEST(SoundexEncoding, CombinesMultipleDuplicateEncodings) {
 
CHECK_EQUAL(​"J100"​, soundex.encode(​"Jbbb"​));
 
}

(There is one more possible alternate scenario, based on the fact that H and W might be treated differently, depending upon who you talk to. Since we ignored this potential difference last time we built Soundex, we’ll keep things simple and ignore it again.)

We tackle uppercasing the first letter, which also requires we update the expectation in CombinesDuplicateCodesWhen2ndLetterDuplicates1st.

tpp/19/SoundexTest.cpp
 
TEST(SoundexEncoding, CombinesDuplicateCodesWhen2ndLetterDuplicates1st) {
 
CHECK_EQUAL(​"B230"​, soundex.encode(​"bbcd"​));
 
}
 
 
TEST(SoundexEncoding, UppercasesFirstLetter) {
 
CHECK_EQUAL(​"A"​, soundex.encode(​"abcd"​).substr(0, 1));
 
}

The transformation, though simple, involves a function call. (We could code the logic of ::toupper ourselves, but I don’t think that’s what the TPP wants us to do.) You’ve seen our implementation of upper, so it’s not shown here.

tpp/19/Soundex.h
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
*
std::​string​ code(1, toupper(head(word)));
 
encode(word, code);
 
return​ zeroPad(code);
 
}

Hmm. The last test prods a thought. What if the first letter is already uppercased in the input and the second letter is the same but in lowercase?

tpp/20/SoundexTest.cpp
 
TEST(SoundexEncoding, IgnoresCaseWhenEncodingConsonants) {
 
CHECK_EQUAL(soundex.encode(​"BCDL"​), soundex.encode(​"bcdl"​));
 
}

Aha! Failure, and a quick resolution.

tpp/20/Soundex.h
 
std::​string​ codeFor(​char​ c) ​const​ {
*
return​ codes_[​static_cast​<size_t>(lower(c))];
 
}

Are we done? We didn’t add PadsWithZerosToEnsureThreeDigits. It does pass immediately, but we choose to add it for documentation purposes.

tpp/21/SoundexTest.cpp
 
TEST(ASoundexEncoding, PadsWithZerosToEnsureThreeDigits) {
 
CHECK_EQUAL(​"I000"​, soundex.encode(​"I"​));
 
}

The second time around is always a bit easier, but our success this time is due more to using the TPP than with our knowledge of the problem. We made a few judgment calls along the way and might not have even followed the TPP to the letter, but our end result speaks for itself.

Here’s the core algorithm from our TPP-test-driven solution:

tpp/21/Soundex.h
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
std::​string​ code(1, toupper(head(word)));
 
encode(word, code);
 
return​ zeroPad(code);
 
}
 
 
void​ encode(​const​ std::​string​& word, std::​string​& code) ​const​ {
 
auto​ tailToEncode = tail(word);
 
if​ (tailToEncode.empty() || isFull(code)) ​return​;
 
 
auto​ digit = codeFor(head(tailToEncode));
 
if​ (isSameEncodingAsLast(digit, word))
 
code += digit;
 
 
encode(tailToEncode, code);
 
}

And here’s the core algorithm from our non-TPP-test-driven solution:

c2/40/Soundex.h
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
return​ stringutil::zeroPad(
 
stringutil::upperFront(stringutil::head(word)) +
 
stringutil::tail(encodedDigits(word)),
 
MaxCodeLength);
 
}
 
 
std::​string​ encodedDigits(​const​ std::​string​& word) ​const​ {
 
std::​string​ encoding;
 
encodeHead(encoding, word);
 
encodeTail(encoding, word);
 
return​ encoding;
 
}
 
 
void​ encodeHead(std::​string​& encoding, ​const​ std::​string​& word) ​const​ {
 
encoding += encodedDigit(word.front());
 
}
 
 
void​ encodeTail(std::​string​& encoding, ​const​ std::​string​& word) ​const​ {
 
for​ (​auto​ i = 1u; i < word.length(); i++)
 
if​ (!isComplete(encoding))
 
encodeLetter(encoding, word[i], word[i - 1]);
 
}
 
void​ encodeLetter(std::​string​& encoding, ​char​ letter, ​char​ lastLetter) ​const​ {
 
auto​ digit = encodedDigit(letter);
 
if​ (digit != NotADigit &&
 
(digit != lastDigit(encoding) || charutil::isVowel(lastLetter)))
 
encoding += digit;
 
}

I know which version I’d like to maintain. Not only did the TPP generate the simpler algorithm, it required less effort along the way.

The Transformation Priority Premise remains a premise, albeit a darn good one. The more I apply it, the happier I am with the outcome. But it’s an advanced topic, one that is likely more palatable given a solid basis of understanding of how TDD plays out without it.

The TPP also demands a bit more up-front thought for each pass through the TDD cycle. You must think about a few things.

  • What implementation of the current test has a higher priority? Are there more-creative approaches that you could dream up?

  • Does another test have a higher priority than the one that seems to be begging to go next?

  • Just what are the other tests in the mix? Maintaining a test list (Test Lists) becomes almost essential to employing the TPP.

TDD without the disciplined add-on of the TPP works well enough, as you’ve seen throughout this book. But adopt the TPP, and you’ll do more than survive—you’ll thrive.

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

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