It’s time to show the customer how much you really care. Nagging bosses? Worried clients? Stakeholders that keep asking, “Will it be done on time?” No amount of well-designed code will please your customers; you’ve got to show them something working. And now that you’ve got a solid OO programming toolkit, it’s time to learn how you can prove to the customer that your software works. In this chapter, we learn about two ways to dive deeper into your software’s functionality, and give the customer that warm feeling in their chest that makes them say, Yes, you’re definitely the right developer for this job!
We’ve learned quite a bit so far, and our toolbox of analysis and design tools is getting pretty full. We even added some OO programming techniques in the last chapter:
All the tools and techniques you’ve been learning are terrific... but none of them matter if you don’t use them to produce great software that makes your customer happy.
And most of the time, your customer won’t care about all the OO principles and diagrams you create. They just want the software to work the way that it’s supposed to.
Joe: Yeah, maybe we shouldn’t have spent all this time on so many diagrams, and all this architecture stuff. We’ve got nothing to show Gary except a bunch of ovals with things like “Play Game” written inside them.
Frank: Come on guys, we’ve got a lot more done than that. It’s going to be simple to finish up the Board class, because we’ve already got a start on writing a lot of that functionality.
Jill: Well, sure, but that’s the only class we’ve written any code for. How are we supposed to show that to Gary?
Joe: Well, I guess we could write the Unit class pretty easily, since we did that class diagram. So it wouldn’t take a lot more time to write the code for that class.
Frank: Exactly. And, really, we know how to write all of these classes. We can just take each class, or even an entire package, and apply all those OO principles and analysis and design techniques to each chunk of functionality.
Jill: But we’ve got to work on functionality now. We don’t have time for a bunch more big-picture analysis and design.
Frank: But that’s just the thing, Jill: we don’t need to change what we’re doing, we just need to iterate deeper.
Joe: Iterate deeper? What does that mean?
Frank: It just means we keep doing analysis and design, but now on each individual part of Gary’s game system framework.
Jill: And as we build up the application, we’ll have lots of pieces working that we can show to Gary, right?
Joe: And we get to use all these tools we’ve got to make sure the software is well-designed, too, right?
Frank: Exactly. But first, we’ve got a choice to make...
You write great software iteratively.
Work on the big picture, and then iterate over pieces of the app until it’s complete.
When it comes to developing software, there is more than one way to iterate into specific parts of your application. You’ve got to take on smaller pieces of functionality, but there are two basic approaches to figuring out which small pieces to work on—and even what a “small piece” means in terms of your application.
You can choose to focus on specific features of the application. This approach is all about taking one piece of functionality that the customer wants, and working on that functionality until it’s complete.
Both approaches to iterating are driven by good requirements.
Feature driven development
...is when you pick a specific feature in your app, and plan, analyze, and develop that feature to completion.
Because requirements come from the customer, both approaches focus on delivering what the customer wants.
You can also choose to focus on specific flows through the application. This approach takes a complete path through the application, with a clear start and end, and implements that path in your code.
When you’re using feature driven development, you work on a single feature at a time, and then iterate, knocking off features one at a time until you’ve finished up the functionality of an application.
With use case driven development, you work on completing a single scenario through a use case. Then you take another scenario and work through it, until all of the use case’s scenarios are complete. Then you iterate to the next use case, until all your use cases are working.
There’s just one basic way to write code, isn’t there? Well, there are actually a ton of different ways to go about iterating deeper and finishing up parts of your application. Most of these different approaches fall into the two basic categories we’ve been looking at, though. So how do you decide which to use?
Feature driven development is more granular | Use case driven development is more “big picture” |
Works well when you have a lot of different features that don’t interconnect a whole lot. | Works well when your app has lots of processes and scenarios rather than individual pieces of functionality. |
Allows you to show the customer working code faster. | Allows you to show the customer bigger pieces of functionality at each stage of development. |
Is very functionality-driven. You’re not going to forget about any features using feature driven development. | Is very user-centric. You’ll code for all the different ways a user can use your system with use case driven development. |
Works particularly well on transactional systems, where the system is largely defined by lengthy, complicated processes. | Works particularly well on systems with lots of disconnected pieces of functionality. |
Since Gary’s losing patience, let’s go with feature driven development. We can take just a single feature and work it through to completion, and it shouldn’t take as much time as it would to write the code to support an entire use case.
Anytime you’ve got a customer impatient to see results, you should consider feature driven development, and starting with a feature you’ve already done some work on.
Once you’ve decided on a feature to start with, you’ve got to do some more analysis. Let’s start with what we had written down on the feature list:
Here’s what we’ve got so far... but this is still a pretty generic description of what we need to code.
We also have the start of a class diagram, from Chapter 7:
It looks like we’ve got everything we need to start coding, right? To help us make sure we haven’t forgotten anything, let’s go back to using some textual analysis.
We don’t have a use case to analyze, but we can revisit the vision statement for Gary’s games, and see if we’re covering everything that Gary wanted his units to do.
Compare the class diagram for Unit with this vision statement. Are there things missing from our class diagram?
What else might Gary expect to see when you say, “I’m done with writing code for the units in your framework?”
In our class diagram, all we’ve really figured out is how to represent the properties of a unit. But in Gary’s vision statement, he’s expecting his game system framework to support a lot more than just those game-specific properties.
This makes sense, because the key feature we were focusing on in Chapter 7 was not the entire Unit class, but just game-specific properties of a Unit.
Here are the things we came up with that Gary is expecting units in his framework to do:
Each unit should have properties, and game designers can add new properties to unit types in their own games.
Units have to be able to move from one tile on a board to another.
You should have some ideas about how to handle this from our work on a related key feature back in Chapter 7.
Units can be grouped together into armies.
We worked on supporting game-specific units, and how to store the properties of a Unit, back in Chapter 7. But Gary wants more than a class diagram before he’s convinced you’re getting any work done.
Your customers want to see something that makes sense to them
Your customers are used to seeing computer programs run on a computer. All those diagrams and lists may help you get on the same page with them in terms of requirements and what you’re supposed to build, but you’re going to need more than that before they think you’ve built anything useful.
You need to come up with some test scenarios that you can show to your customer, which will prove that your code works, and that it behaves like your customer expects it to.
Be careful... this “scenario” isn’t the same as the “scenario” we’ve been talking about in a use case scenario.
Test cases don’t have to be very complex; they just provide a way to show your customer that the functionality in your classes is working correctly.
For the properties of a unit, we can start out with a simple test scenario that creates a new Unit, and adds a property to the unit. We could just show our customer a running program that displays output like this:
We decided to test setting, and then changing, the value of a property. If the hitPoints property is set, for example, and then set again, getting the value of hitPoints should return the most recent value for that property:
For our third scenario, we decided to test what would happen when you tried to retrieve the value of a property that had never been set. Error conditions like this crop up all the time, and we don’t want our program crashing every time a game designer makes a small typo or mistake in their code. Here’s what we did to test this:
You should test your software for every possible usage you can think of. Be creative!
Don’t forget to test for incorrect usage of the software, too. You’ll catch errors early, and make your customers very happy.
Joe: What do you mean? We figured out that all properties in a Unit have a name and a value. So we decided to use a Map to store them all.
Frank: And game designers can add any new properties they want by just creating new property names, and sticking name/value pairs in the Map with the setProperty() method.
Jill: Right. But then, we also added a type property, since all units will have a type. And that’s something common for all units...
Joe: Sure. See, we did do the commonality analysis right.
Jill: ...but we also now know that units can be assembled into groups, like armies or fleets or whatever. So what happens if we have two units of the same type in the same group... how can we tell the difference between them?
Frank: You think we need some sort of ID, don’t you?
Jill: Yeah, maybe. Or at least a name... but even then, you can’t really prevent duplication with a name property, can you?
Joe: OK, but that still doesn’t mean we need to change our design. We can just add the ID property into our property Map. So we’ve got a nice, uniform way to access all those properties, using the getProperty() method.
Frank: He’s right, Jill. And since we encapsulate away the details about property names into the properties Map, we could even change from using an ID to a name, or something totally different, and code using the Unit class wouldn’t have to change much... you’d just need to use the new property name in getProperty(). That’s a pretty slick design!
Jill: But what about commonality? If ID is really common to all types of Unit, shouldn’t it be moved out of the Map, sort of like we did with the type property?
Joe: Whoa... encapsulation or commonality. That’s tough... it seems like we can’t do one without screwing up the other.
In this solution, all game designers can directly access the id, name, and weapons properties, instead of having to use getProperty() and work through the more generic properties Map.
The emphasis is on keeping the common properties of a Unit outside of the properties Map, and leaving properties that vary inside the properties Map.
Sam chose to emphasize the things that are common across all types of Units. But there are some negatives to Sam’s design, too:
We’re repeating ourselves
Now there are two different ways to access properties: through the getId(), getName(), and property-specific methods, and the getProperty() method. Two ways to access properties is almost certainly going to mean duplicate code somewhere.
Maintenance is a problem
When you see the potential for duplicate code, you’ll almost always find maintenance and flexibility issues, as well.
Now you’ve got property names, like id and name, hard-coded into the Unit class. If a game designer doesn’t want to use those, or wants to change them, it’s going to be a real hassle, and require changes to the Unit class. This is usually where encapsulation would help, and that leads us to Randy’s design choice...
This solution focuses on encapsulating all the properties for a Unit into the properties Map, and providing a standard interface—the getProperty() method—for accessing all properties. Even properties that apply to all units, like type and id, are accessed through the properties Map in this solution.
The emphasis is on encapsulation, and a flexible design. Even if the names of common properties change, the Unit class can stay the same, since no property names are hardcoded into the class itself.
Randy’s solution is more resistant to changes, and uses a lot more encapsulation, but there are tradeoffs with this design, as well. Here are a few of the downsides to Randy’s design:
We’re ignoring commonality Randy encapsulated all of the properties into the properties Map, but now there’s nothing to indicate that type, name, id, and weapons are intended to be properties common to all Unit types.
Lots of work at runtime getProperty() returns an Object, and you’re going to have to cast that into the right value type for each different property, all at runtime. That’s a lot of casting, and a lot of extra work that your code has to do at runtime, even for the properties that are common to all Unit types.
For Gary’s game system framework, let’s use Sam’s solution, which pulls the common properties of a Unit out into their own properties and methods, and leaves unit-specific properties in a separate Map.
We’ve got test scenarios we want to show Gary, and a design for the Unit class. The last thing we need to do before coding is to make sure our design for Unit will allow us to code a solution that passes all the tests.
It’s been two chapters in coming, but we’re finally ready to write the code for the Unit class. Here’s how we did it:
We’ve talked a lot about test cases, but so far, you haven’t seen how to actually write one. Let’s examine a test case up close, and see exactly what makes up a good test.
Each test case should have an ID and a name.
The names of your test cases should describe what is being tested. Test names with nothing but a number at the end aren’t nearly as helpful as names like testProperty() or testCreation(). You should also use a numeric ID, so you can easily list your tests cases out (something we’ll do on the next page).
Each test case should have one specific thing that it tests.
One piece of functionality may involve one method, two methods, or even multiple classes... but to start with, focus on very simple pieces of functionality, one at a time.
Each of your test cases should be atomic: each should test only one piece of functionality at time. This allows you to isolate exactly what piece of functionality might not be working in your application.
Each test case should have an input you supply.
You’re going to give the test case a value, or a set of values, that it uses as the test data. This data is usually then used to execute some specific piece of functionality or behavior.
Each test case should have an output that you expect.
This is what you want the program to output. So if you set type to “infantry”, and then call getType(), your expected output is “infantry”.
Given your input, what should the program, class, or method output? You’ll compare the actual output of the program with your expected output, and if they match, then you’ve got a successful test, and your software works.
Most test cases have a starting state.
There’s not much starting state for the Unit class. We do need to create a new Unit, but that’s about it.
Do you need to open a database connection, or create a certain object, or set some values before running your test? If so, that’s all part of the starting state of the test case, and needs to be handled before you run the actual test.
And now for some bonus credit...
We added three new test cases to our table, to handle the three properties common to all units that aren’t tested in UnitTester. You should be able to write three additional test methods based on this table. Did you figure these out on your own?
With a Unit class and a set of test cases, you’re ready to show Gary some working code, and prove that you’re on the right track to building his game system framework just the way he wants it. Let’s show him the test class running:
Let’s change the programming contract for the game system
When you’re writing software, you’re also creating a contract between that software and the people that use it. The contract details how the software will work when certain actions are taken—like requesting a non-existent property on a unit.
If the customer wants an action to result in different behavior, then you’re changing the contract. So if Gary’s framework should throw an exception when a non-existent property is queried, that’s fine; it just means that the contract between game designers and the framework has changed.
You probably didn’t notice, but we’ve been doing something called programming by contract in the Unit class so far. In the Unit class, if someone asks for a property that doesn’t exist, we’ve just returned null. We’ve been doing the same thing in getWeapons(); if the weapons list isn’t initialized, we just return null there, too:
When you return null, you’re trusting programmers to be able to deal with null return values. Programmers are basically saying that they’ve coded things well enough that they won’t ask for non-existent properties or weapons, so their code just doesn’t worry about getting null values back from the Unit class:
Back in Note, we were asked to stop returning null, and throw an exception instead. This really isn’t a big change to the contract; it just means that now game designers are going to have big problems if they ask for non-existent properties or weapons.
But what happens if you don’t think your code will be used correctly? Or if you think that certain actions are such a bad idea that you don’t want to let users deal with them in their own way? In these cases, you may want to consider defensive programming.
Suppose you were really worried that game designers using the Unit class, and asking for non-existent properties, were getting null values and not handling them properly. You might rewrite the getProperty() method like this:
I’m sure you’re great code and all, but I just don’t trust you. I could send you null, and you could totally blow up. So let’s just be safe, and I’ll send you a checked exception that you’ll have to catch, to make sure you don’t get a null value back and do something stupid with it.
Of course, when programmers use your code, they might not trust you either... they can program defensively as well. What if they don’t believe that you’ll only return non-null values from getProperty()? Then they’re going to protect their code, and use defensive programming, as well:
We’ve finally finished up unit properties, and can move on to the next item on our list:
Each unit should have properties, and game designers can add new properties to unit types in their own games.
Units have to be able to move from one tile on a board to another.
Units can be grouped together into armies.
This should sound pretty familiar... we already dealt with movement in Chapter 7:
We’ve been talking a lot about iterating deeper into your application, and at each stage, doing more analysis and design. So you’re taking each problem, and then breaking it up (either into use cases or features), and then solving a part of the problem, over and over. This is what we’ve been doing in this chapter: taking a single feature, and working on that feature until it’s complete.
But once you choose a single feature or use case, you can usually break that feature up into even smaller pieces of behavior. For example, a unit has properties and we have to deal with unit movement. And we also need to support groupings of units. So each of these individual pieces of behavior has to be dealt with.
Just like when you broke your app up and began to iterate, you’ll have to do more analysis and design at each step. Always make sure your earlier decisions make sense, and change or rework those decisions if they don’t.
Lots of times you’ll find that decisions you made earlier save you work down the line. In Gary’s system, we decided that game designers would deal with movement on their own. So now that we’re talking about how to handle unit movement, we can take that decision we made earlier, and apply it here. Since it still seems sensible—there’s no reason to change that decision—we can have game designers worry about handling unit movement, and move on to the next piece of behavior.
You should be able to write the code for UnitTester using the test case table shown here. If you want to see how your code compares with ours, visit http://www.headfirstlabs.com, click on Head First OOA&D, and look for “UnitGroup Test Cases.”