I've been trying to find a kata or small project that will help show the process and the value of applying µObjects to a code base. I've written about how µObjects hit all the points of maintainable code - but I don't have a good code example.
What I'm going to do today is a "Pizza Shop" kata. I first saw this when it was presented by Mike Ibarra at a Seattle Software Craftsmanship Community meetup.
I don't remember who he got it from; but it was a former co-worker of his. This kata works well to show the power of TDD and emergent design. Particularly where they provide a safety net when there are changing requirements. A lot of kata's fail to get changing requirements in. It is hard to get that aspect in. Especially if we're looking to keep kata's short. Calling this a kata might be a strech; but it's something we use to practice; so kata it is.
With Kata Culture it's not the objective to get the problem done. The problem is the lie. We can all can code crap and get it working. Code to do what's been asked is easy. It's when change is asked for that code shows the quality. We want to write high quality software, we need to practice writing something and having changes come in.
When writing code; it's unlikely anyone will start with µObjects. You simply don't know what you need. You write the code procedurally and find the objects in it. Then extract the objects. As you extract more objects; refactor into different objects. µObjects have a very high emphasis on refactoring. It's how you keep the code clean and duplication down.
I'm gonna go through the kata doing TDD and emergent design and µObject-ifying when it's a good point to do so. Hopfully showing a lot of what I do on a daily basis at work using µObjects.
Let's start a pizza shop
There are a two pizza sizes
- Personal @ $9
- Family @ $18
There are two categories of toppings
- Regular @ 10% base pizza price
- Meat @ 15% base pizza price
These toppings are
- Regular: Mozzarella, Mushroom, Olive
- Meat: Pepperoni, Bacon, Ham
The Requirements
- Start a new Pizza
- Add Toppings to the Pizza
- Get Price of Pizza
- $9.90
- Get Description of Pizza
- "Personal pizza with Mushrooms, Mozzarella, and Pepperoni"
That's what we have for the first set. Let's start coding.
The Code
As I normally do; starting with a C# unit test project and adding in FluentAssertions. I have a project set up for this here. It helps at work when I want to spin up a new kata - just pull that down and launch.
I have an .EditorConfig file I like to use with projects; I've included it in this one.
This is a new project; the first thing I like to do is rename the default test file from UnitTest1.cs
. I start very high level. For this, I'll start with PizzaShopTests.cs
I'm going to start churning through some tests and see what emerges. While I'm going through this I am doing TDD. Taking very small steps. We can see in commit 0aa8a7b that I'm returning the hard coded string for the description.
It's really clear in commit 87cffa9 that we're doing a lot of non-µObject practices. It's this little of it that starts to hurt me looking at it. There are strings being bandied about. We're using a static method.
twitch
It hurts.
These need to be refactored; so I'll apply some of the techniques I've used previously.
Strings are getting replaced by an IText
interface. This is heavily based off of Cactoos but slowly implementing only what I need vs what I have tried to do; a full port (which failed).
With commit 8afee05 we see the addition of a lot of IText
objects for specific behavior. We also Added an IScalar<T>
for a boolean result. We do this instead of boolean operators as those are statics and not available for our "Everything is an interface" practice.
In commit e4a6d4b I change from using the interface IText
to an abstract class Text
. I'm doing this to get rid of the extension methods.
The extension method is how we hid inline new
in a work project. I've seen that. I know how it looks and how it evolves. I'm going to use this as an opporunity to see how the base class evolves a bit. commit 083b005 keeps the interface.
It's clear that our code won't support multiple toppings; but let's add price next. This should force toppings into a class; which will simplify adding multiple.
Price for a pizza and toppings was added in commit f1ecf96. Now to force some changes with multiple toppings.
Commit 4c3c99d has the price change to a single line and accounting for any number of toppigs.
We haven't introduced the meat topping type; which will drive some other changes. These changes for getting them into a list are going to be useful before moving onto different types of toppings.
The big reason this is useful is we limit the number of place there are distinctions. Right now we're down to just one spot; a .9
. This will change to be a variable acting on 9
. Which will be simply to update for new types.
So far - Not a fan of the abstract Text
class. I was trying a few ideas ... it's ... clumsy. While I'm not a huge fan of hiding the news via a static method... It's not as clumsy as this is looking.
I've added the description for multiple toppings in commit 2a1d940. I don't think it's great code. Not µObject. I'll let it ride for now; expecting it to clean up as some other refactors happen. If it doesn't; then we know to come back to it.
At commit e3386b8); we can do price and description for multiple toppings. The toppings haven't been objectified. Let's start having the two types of toppings.
With commit fe58628 the Topping
class and ITopping
interface have been added. There's a bit of a smell happening. We're violating encapsulation with the ITopping#Name
method. I recognize this; but I also think it'll clear up soon. This is still part of the description for multiple toppings; which mentioned above, smells. So; yep. That chunk of code is getting pretty rank.
There's really only one requirement left for the first phase of our Pizza Shop; Family size.
Let's get a second size into our Pizza.
Family pizza added in commit 30211d2. This was pretty straight forward. It's turned Pizza
into a base class. (I'm not very strict about making classes sealed).
Only took a few tests.
Requirements Complete
At this point all of our requirements are met. The code's pretty good. There's some fixing that can happen. Things like making a PersonalPizza
and Pizza
abstract. I'll take a few passes and make these kinda changes.
Commit dd194ad sees us create a PersonalPizza
and abstract the Pizza
.
Commit e644bb6 fixes an immutability bug.
Commit d372cbe Adds a class around the collection and behavior of toppings
Commit d33d98e Introducing the "Write Into" concept for improved encapsulation. For a brief background of coming up with the base idea, check out some fun langauge abuse.
Commit 3af71e9 Adds a Money
object. This is the simplified form of the Quantity and Conversion pattern. If we were doing this for real reallies - Totally use Quantity and Conversion. Which we do use at work..
This is all part of the µObject conversation. It's Single Responsibility to the extreme. It's not Topping
s responsibility to generate the string form of the price. We shove that into a money object. It's not anyone else's responsibility to know how to add money together. Just Money + Money
.
Commit 1ea4d41 sees Text
revert to IText
. All behavor was lost from the class.
Commit 722d3c7 takes us to a pretty solid code base.
Doing some final clean up with commit 0a4a469. I've added some TODO's around points I'd like to keep an eye on and update when possible.
Hmm... Looking at the requirements; I see I missed forcing specific toppings. That's OK. We'll get that in next time.