![]() |
Sponsored Link •
|
Summary
A simple tool which makes refactoring safer.
Advertisement
|
In my last blog, I described sensing variables and how you can use them to write tests for gnarly long methods when you want to refactor. As I mentioned, it’s a useful technique, but it is a pain to set up. A few weeks ago, I wrote a little tool which makes the process much easier. It’s called Vise and it uses the metaphor of a vise, a clamp that you use to hold down mechanical parts when you are working on them.
Here’s how it works...
// Exhibit A – Big Gnarly Method public class RPRequest { ... public int process(int level, RPPacket packet) { if (...) { if (...) { ... } else { ... bar_args[1] += list.size(); packet.add(new Subpacket(list, arrivalTime)); if (packet.calcSize() > 2) bar_args[1] += 2; } } else { int reqLine = -1; bar_args[0] = packet.calcSize(reqLine); ... } } }
We’d like to do some refactoring inside this method, but we know that some of the things that we want to do (changing the order of statements, etc) could affect the contents of the bar_args
array if we make a mistake. Sadly, we don’t have access to the bar_args
array; it’s used internally in the method and inaccessible to us. What can we do? Well, we can use sensing variables, but there is another alternative. We can use Vise.
Essentially, Vise is a tool that helps you record a set of values that occur in your code and then use those values as a behavioral invariant. Once the values are recorded, if they change the next time the code executes, Vise throws an exception.
Here’s how we can use Vise to clamp down the code above. The first thing we do is go back to our code and add a series of calls to a static method named grip
on a class called Vise
:
import vise.tool.*; public class RPRequest { ... public int process(int level, RPPacket packet) { if (...) { if (...) { ... } else { ... bar_args[1] += list.size(); Vise.grip(bar_args[1]); packet.add(new Subpacket(list, arrivalTime)); if (packet.calcSize() > 2) bar_args[1] += 2; Vise.grip(bar_args[1]); } } else { int reqLine = -1; bar_args[0] = packet.calcSize(reqLine); Vise.grip(bar_args[0]); ... } } }
Next, we write a series of tests which call the process()
method with various inputs:
// Note that the test class inherits from ViseTest.. public class RPRequest extends ViseTest { public void testProcessSimpleRequest() { final int BASE_LEVEL = 0; new RPRequest().process(BASE_LEVEL, new RequestPacket(“local”)); } public void testProcessLoginRequest() { final int TARGET_LEVEL = 1; new RPRequest().process(TARGET_LEVEL, new LoginPacket(“mike”, “default”)); } ... }
Then we execute the tests, and they all pass.
They are pretty silly looking tests, aren’t they? They don’t have any assertions. Well, here’s why: The first time that you execute a test method that calls grip()
, Vise checks to see if there is a vise file for that method on your system. If there isn’t, it creates a file and then takes each value that you pass to grip and saves it to that file. If the file already exists, Vise will open it and read the next value from it each time grip
is called. If the file value matches the gripped value, all is fine. If there is a difference, Vise throws an exception that indicates that the behavior of your code has changed since you recorded those values.
The algorithm for using Vise is simple:
When I’ve used it, I’ve used it to convince myself that I’m preserving behavior as I start to get code under test. It makes the work easier.
Vise has a few more ease of use features. There is a method on Vise
called Vise.inspect()
which throws an exception that shows you all of the values that have been gripped in the current test method. When the exception is caught by JUnit, it gives you a nice listing of values. This can be very handy when you are trying to figure out whether particular tests are exercising a particular chunk of code. Vise also has a method named Vise.release()
which deletes the persisted file for each method. In practice, however, I’ve found it easier just to keep a window open on my desktop which shows all of the vise files for a set of tests. When I want to re-record, I just delete one or more of the files.
Is Vise ideal? No. I think it would be cool to have an IDE plug-in that does this. I can imagine going into my IDE, selecting a set of expressions across my code base, and then associating them with a set of tests. Once I’ve associated them I should be able to use some set of buttons on my IDE to clamp down the current behavior or release it. With that I could always know whether I’m inadvertently changing behavior I care about.
You can get a copy of the Vise code in a zip here. It contains a readme file, but be forewarned: the tool hasn't seen extensive use. Giovanni Asproni and I are piecing together a version for C++. Please let me know if you give the Java version a spin and find it useful (or not).
Have an opinion? Readers have already posted 11 comments about this weblog entry. Why not add yours?
If you'd like to be notified whenever Michael Feathers adds a new entry to his weblog, subscribe to his RSS feed.
![]() | Michael has been active in the XP community for the past five years, balancing his time between working with, training, and coaching various teams around the world. Prior to joining Object Mentor, Michael designed a proprietary programming language and wrote a compiler for it, he also designed a large multi-platform class library and a framework for instrumentation control. When he isn't engaged with a team, he spends most of this time investigating ways of altering design over time in codebases. |
Sponsored Links
|