pointer-image   20   Use It Before You Build It

 

“Go ahead and complete all of your library code. There’s plenty of time later to see what people think of it. Just throw the code over the wall for now. I’m sure it’s fine.”

images/devil.png

Many successful companies live by the slogan “Eat your own dog food.” In other words, to make your product the best it can be, you need to actively use it yourself.

Fortunately, we’re not in the dog-food business. But we are in the business of creating and calling APIs and using interfaces. That means you need to actually use your own interface before foisting it on the rest of the world. In fact, you need to use the interface that you’re designing before you even implement the code behind it. How is that possible?

Using the technique known as Test Driven Development (TDD), you write code only after writing a failing unit test for that code. The test always comes first. Usually, the test case fails either because the code under test doesn’t yet exist or because it doesn’t yet contain the necessary logic to allow the test to pass.

By writing the tests first, you’re looking at your code from the perspective of a user of the code, not of the implementer. And that makes a big difference; you’ll find that you can design more usable, consistent interfaces because you have to use them yourself.

In addition, writing tests before writing code helps eliminate overly complicated designs and lets you focus on really getting the job done. Consider the following example of writing a program that allows two users to play tic-tac-toe.

As you start to think about designing code for the game, you might think of classes such as TicTacToeBoard, Cell, Row, Column, Player, User, Peg, Score, and Rules. Let’s start with the TicTacToeBoard class, which represents the tic-tac-toe board itself (in terms of the core game logic, not the UI).

Here’s a possible first test for the TicTacToeBoard class, written in C# using the NUnit test framework. It creates a board and asserts that the game is not already finished.

 
[TestFixture]
 
public​ ​class​ TicTacToeTest
 
{
 
private​ TicTacToeBoard board;
 
[SetUp]
 
public​ ​void​ CreateBoard()
 
{
 
board = ​new​ TicTacToeBoard();
 
}
 
[Test]
 
public​ ​void​ TestCreateBoard()
 
{
 
Assert.IsNotNull(board);
 
Assert.IsFalse(board.GameOver);
 
}
 
}

The test fails because the class TicTacToeBoard doesn’t exist—you’ll get a compilation error. You’d be pretty surprised if it passed, wouldn’t you? That can happen—not often, but it does happen. Always make sure your tests fail before they pass in order to flush out potential bugs in the test. Let’s implement that class:

 
public​ ​class​ TicTacToeBoard {
 
public​ ​bool​ GameOver {
 
get​ {
 
return​ false;
 
}
 
}
 
}

In the GameOver property we’ll return false for now. In general, you want to write the least code necessary to get the test to pass. This is a kind of lie—you know that the code is incomplete. But that doesn’t matter, because later tests will force you to come back and add functionality.

What’s the next step? First you have to decide who’s going to start, so let’s set up the first player. We’ll start with a test for setting the first player:

 
[Test]
 
public​ ​void​ TestSetFirstPlayer() {
 
// what should go here?
 
}

At this point, the test is forcing you to make a decision. Before you can finish it, you have to decide how you’re going to represent players in the code and how to assign them to the board. Here’s one idea:

 
board.SetFirstPlayer(​new​ Player(​"Mark"​), ​"X"​);

This tells the board that the player Mark will be using the peg X.

While this will certainly work, do you really need the Player class or the first player’s name? Perhaps, later, you might need to keep track of who the winner is. But that’s not an issue right now. The YAGNI[20] (You Aren’t Gonna Need It) principle says that you should not implement a feature until something needs it. At this point, there is no force that indicates you need the Player class.

Remember, we haven’t written the SetFirstPlayer method in the TicTacToeBoard class and we haven’t written the Player class. We’re still just trying to write a test. So let’s assume the following code to set the first player:

 
board.SetFirstPlayer(​"X"​);

This conveys the notion that the first player’s peg is X. It’s also simpler than the first version. However, this version has an implicit risk: passing in an arbitrary character to SetFirstPlayer means you’ll have to add code that checks whether the parameter is either O or X, and you’ll need to work out what to do when it isn’t. So let’s simplify even further. We’ll have a simple flag to say whether the first player is an O or an X. Knowing that, we can now write our unit test:

 
[Test]
 
public​ ​void​ TestSetFirstPlayer() {
 
board.FirstPlayerPegIsX = true;
 
Assert.IsTrue(board.FirstPlayerPegIsX);
 
}

We can write the FirstPlayerPegIsX as a boolean property and set it to the desired value. This looks simple and easy to use as well—much easier than dealing with the complexity of using the Player class. Once the test is written, you can get it to pass by implementing the FirstPlayerPegIsX property in the TicTacToeBoard class.

See how we started out by having a whole Player class and ended up simply using a boolean value? This simplification came about by testing first, before writing the underlying code.

Now remember, the point isn’t to throw out good design practices and code everything as a large set of booleans! The point is to figure out what is the minimum amount of effort required to implement a given feature successfully. Overall, we programmers tend to err so much in the other direction—needlessly overcomplicating things—that it’s very useful to try to err in the other direction.

It’s easy to simplify code by eliminating classes that you haven’t written yet. By contrast, once you have written code, you may feel compelled to keep that code and continue working with it (even if it’s long past its expiration date).

When you design and develop object-oriented systems, you probably feel compelled to use objects. There’s a tendency to think that OO systems should be made of objects, and we sometimes force ourselves to create more and more classes of objects—whether they are really needed or not. Adding gratuitous code is always a bad idea.

TDD makes you go through the exercise of thinking about how you’ll use the code before you get a chance to write it (or at least before you go too far into the implementation). This forces you to think about usability and convenience and lets you arrive at a more pragmatic design.

And of course, design isn’t finished right at the beginning. You will continuously add tests, add code, and redesign the class over its lifetime (see Code in Increments, for more on this basic idea).

images/angel.png

Use it before you build it.

Use Test Driven Development as a design tool. It will lead you to a more pragmatic and simpler design.

What It Feels Like

It feels like you always have a concrete reason for writing code. You can concentrate on designing an interface without being overly distracted by implementation details.

Keeping Your Balance

  • Don’t get hung up on Test First vs. Test Before Checking Code In. Test First improves design, but you always have to Test Before Checking Code In.

  • Every design can be improved.

  • Unit tests may not be appropriate when you’re experimenting with an idea or prototyping. In the unfortunate case that the code does move forward into a real system, you’ll have to add the tests (but it’s almost always better to start over from scratch).

  • Unit tests alone don’t guarantee a better design, but they make it easier to create one.

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

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