For anyone looking for a realistic example of intention revealing naming, I hope this requirement I encountered will help you out. Here you’ll see 3 versions of the requirement in code, each one an improvement on the previous.
Background
I do some work for an online pharmacy. Most of the products the pharmacy sells online are available to be bought by anyone, however, there is a small set of prescription products for sale that are only available to certain customers.
A product can belong to any number of categories. A category may be designated as a “prescription category”. If a product belongs to a prescription category then the product is a prescription product.
For every product we track the quantity in stock. If a product sells out then an “out of stock” message is shown to the customer and the “add to cart” link is removed.
Here’s an idea of the view code I started with:
if (product.getQuantity() < 1) {
"Sorry this product is out of stock"
} else {
"<a href=...>Add to cart</a>"
}
New business requirement
If a prescription product sells out, then it can still be ordered by the customer. The customer does not need to know that the product is out of stock as the pharmacy can now get it shipped to them in time.
1st attempt at introducing requirement
We use the existing Product method “belongsToPrescriptionCategory()” to determine if this is a prescription product.
if (product.getQuantity() < 1
&& !product.belongsToPrescriptionCategory()) {
"Sorry this product is out of stock"
} else {
"<a href=...>Add to cart</a>"
}
belongsToPrescriptionCategory() loops through each category the product belongs to and if it finds a prescription category then it returns true. If none of its categories are prescription ones then false is returned.
1st attempt summary: Its kind of clear what is going on here, but it could be clearer. Worse is the potential for duplication. If you need to perform this check elsewhere in the view then your code will become ugly. You’re aiming for clear and elegant, hence your code ought to read like a story.
2nd attempt
So we want the code to read like a story, how about this?
if (product.isOutOfStock()
&& !product.requiresPrescription()) {
"Sorry this product is out of stock"
} else {
"<a href=...>Add to cart</a>"
}
2 new methods have been introduced to Product:
isOutOfStock() {
return getQuantity() < 1;
}
requiresPrescription() {
return belongsToPrescriptionCategory();
}
This last method demonstrates a useful technique that I rely upon: wrapping an existing method call with an intention revealing name (‘requiresPrescription’ is the intention revealing name). Anyone reading the code for the first time will understand what is going on quicker than if I hadn’t done this. Its also a help to me when I come back to read this code for the first time in a few months and have completely forgotten the purpose and reasons behind it.
2nd attempt summary: The code is no fairytale but it is an easier to read story. We still have potential for needless duplication by having those 2 method calls in the if expression.
Final attempt
if (product.showInShopAsOutOfStock()) {
"Sorry this product is out of stock"
} else {
"<a href=...>Add to cart</a>"
}
Product’s new methods:
showInShopAsOutOfStock() {
return isOutOfStock() && showableInShopAsOutOfStock();
}
showableInShopAsOutOfStock() {
return !neverShowInShopAsOutOfStock();
}
neverShowInShopAsOutOfStock() {
return requiresPrescription();
}
3rd attempt summary: We’ve removed the excessive code from the view layer and have ended up with code who’s intention you know immediately the first time you read it.
That method name “showInShopAsOutOfStock()” tells you exactly what the purpose of that view code is. If you need to know the business reasons behind why we show something as out of stock in the shop then you go and take a look at the new methods in the Product class. Business reasons do not need to be explicit everywhere you want to test whether to show a product as out of stock in the shop.
Summary
Intention revealing names make your code clear, they remove duplication, and make it quicker to add new requirements. For example, suppose another new requirement came in that said I had to make it so all products that cost over £1000 should never be shown in the shop as out of stock, my first attempt would have me add to the neverShowInShopAsOutOfStock() method:
neverShowInShopAsOutOfStock() {
return requiresPrescription() || getPriceInPounds() > 1000;
}
Simple right? Remember long descriptive method names are a widely accepted practice and you may find it more fun than writing lots of comments to explain your code. Paragraphs of comments can go stale quickly, descriptive method names tend to not.