The Transformation Priority Premise
Introduction
When following Test-driven development (TDD), you will make changes to your code. These operations that change the behavior of your code are called transformations. Transformations can be used to pass failing tests (for example, returning a constant to get a test to pass).
As the code goes through a series of transformations, it will go from being specific to being more generic. There are many different transformations you can use (listed below), and the Transformation Priority Premise (TPP) states that you should prefer the simpler transformations.
The point of following the TPP is to help you pass failing tests faster (thus staying true to the ideals of TDD). This works, because you focus on making smaller and simpler changes to your code. Focusing on the simplest change possible also helps you decide which test to write next.
Here are the transformations Uncle Bob outlines in his article on the TPP (in order of increasing complexity):
- ({} → nil) no code at all → code that employs nil
- (nil → constant)
- (constant → constant+) a simple constant to a more complex constant
- (constant → scalar) replacing a constant with a variable or an argument
- (statement → statements) adding more unconditional statements
- (unconditional → if) splitting the execution path
- (scalar → array)
- (array → container)
- (statement → recursion)
- (if → while)
- (expression → function) replacing an expression with a function or algorithm
- (variable → assignment) replacing the value of a variable
Just as a quick recap, the TPP states that you should prefer the simpler transformations, which would mean those near the top of this list.
TPP in Action: Minimax Algorithm
I recently was tasked with performing a kata, showing how I implemented the minimax algorithm in a Tic Tac Toe (TTT) app I wrote. I already had the algorithm ready and working, and all I had to do was to retrace the steps I took to build it out, and then practice them to be ready for the presentation.
However, I quickly ran into a problem. The way I had implemented minimax was by testing for initial conditions, passing those tests, writing tests that took a huge logical leap, and finally writing a large portion of code to get those tests to pass. I had not followed the tenets of TDD when implementing it. I basically wrote out the entire algorithm in pseudocode, typed it in, then debugged it to get it working. Now when I went back to try and demonstrate the implementation step by step, it was impossible, because I didn’t take it step-by-step in the first place.
Following the TPP helped me break the implementation down into smaller steps, to the point where when I inserted my recursive call, I actually ended up with fewer lines of code (1 less), as opposed to 11 more lines (shown in the example below).
Although the TPP did help me get started, it really helped me pull through when I was developing the recursive case. Therefore, I would like to focus on that aspect for my example.
Recursion, one step at a time
Just as a point of reference, here is the complete Minimax class:
As you can see, if I implemented the recursive call in 1 step, adding it all to the else
part of the if
statement, it would require 11 lines of code (not including whitespace or the line with the }
). This is basically how I implemented it the first time through.
To demonstrate the main logical steps it took to implement the recursive call, I must first trace the code back to a reasonable starting point. The code below is at the point where it just gets the current score (from the viewpoint of the player) and returns it (*note that the move is still hard-coded at this point):
Now, at this point, I would like to allow the player to get it’s next move, then return it. This would satisfy the transformation (constant → scalar) for the move
value in the hash. However, to still keep the default hard-coded move for when the player has no moves left, I need to add in a conditional. So going from (unconditional → if) is my best option here. This results in:
(*From here on out, I’ll only show the minimax_move_and_score
method, as the rest of the class stays the same, expect for one require
statement added in later).
Now I need to update the current_score
in the else
case, because it doesn’t change based on the next_move
that is added to the board. Although I can’t go as simple as (constant → scalar) for this step, I can do the next best transformation: adding more unconditional statements, or (statement → statements). *Note: I know I am not showing the tests, but the explanations that I am giving for how I transformed the code summarize the gist of the tests that drove the implementation. The code with more unconditional statements is now:
The next test was for if the player had 2 positions to move to, and had to choose the move that resulted in the highest score. This is the step with the most code added, but you will see that all the lines serve a single purpose:
It is important to note here that although 8 lines of code were added, they only perform 1 function: to pick the move from the possible_moves
array that results in the highest score. Also, at this point, recursion still isn’t used yet (although it is the next step).
Now all that is needed is a way to switch back and forth between players, which requires us to make the recursive leap of faith. We will finally employ the (statement → recursion) transformation:
Finally! We’ve reached the point where the bulk of the method is built out (there is still 1 bug though, which is to get the minimum score if it’s the other player’s turn). Besides that, and some refactoring, the method is finished. As you can see now, the actual recursive call was added only when it was absolutely necessary, with smaller intermediate steps being taken to build out the method. Now this class is one step closer to being ready for a kata.
Takeaways
One of my key takeaways from learning about and applying the TPP is that it really can make the difference between being able to implement something, and not being able to implement it. This might seem obvious, but I spent a while examining my initial minimax implementation, not really having a clue where or how to start. Using TPP I was able to find a starting point, then take small, methodical steps to work towards the final solution. This improved my test suite as well, making each test cover just a small piece of functionality.
Another takeaway is that the list of transformations can be an effective guide when doing TDD. No matter which transformation you think is the best, it is usually helpful to check the list and make sure that none of the transformations higher up on the list can be employed. If you are able to use a simpler transformation, it will help you pass the test faster, making your TDD cycles tighter.
A final takeaway is that picking the right transformation to implement can help you think of the next test to write, which can move you along in your development process. If you have an idea of the next smallest step you can take in your implementation, then you can look for a test that only covers that particular functionality.
In the end, TDD is an art. However, the TPP can be a valuable tool to help you get over your “writer’s block”.
- Martin, Robert C. “The Transformation Priority Premise.” https://8thlight.com/blog/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html