I got this coffee mug from Santa 🎅
It's pretty funny.
I chuckle every time I use it when programming.
The hilarious coffee mug code:
while (working)
{
coffee.drink();
work.execute();
if (coffee == "empty")
{
if (coffeepot == "empty")
coffeepot.brew();
coffee.refill();
}
}
While the code reads like English, we could improve it.
Give It a Go!
Try refactoring the code before reading my solution.
Why?
It has to do with how our brains work:
Attempting to answer a question before we know how to, helps us remember the answer better, even if our attempt was unsuccessful!
It is a crucial principle for rapid learning and improved knowledge retention.
Let's Refactor!
To make the example work in C#, I've had to modify the code:
- I've put the code snippet into a
CoffeeDrinker
class'sDrink()
method, and - I've introduced a string
State
property onCoffee
andCoffeepot
to compare to the"empty"
state in the conditional checks.
I wanted to refactor this code safely.
To that effect, I've created unit tests and implemented a small working system modelling Coffee
, Coffeepot
, and Work
objects.
Let's refactor the coffee mug code!
First Improvement
Both if
conditional expressions check for coffee 'emptiness' via direct string comparison.
We will make the code more robust by hiding this implementation detail.
I've replaced those checks with Boolean properties on the Coffee
and Coffeepot
objects:
It's already much nicer.
This improved code is no longer concerned with how we check for emptiness.
That's an important point since we can change the underlying emptiness check, and the code inside the if
conditional won't be affected.
Here's the appropriate clean code guideline:
Separate what the code does from how it does it.
Understanding the distinction (what vs how) is a master software engineering competency.
Second Improvement
Next, the Working
variable in the while
loop seems out of place. Having a 'working' indicator on the Work
object would feel more natural.
Consequently, I've moved the logic of Working
into an IsInProgress
property on the Work
class. I felt if (Work.IsInProgress)
read like natural language while if (Work.Working)
was understandable, but it didn't sound right.
Third Improvement
The code has a logic problem.
The calls to Coffee.Drink()
and Work.Execute()
would be more effective at the end of the loop.
Why?
Two reasons:
- Refilling the coffee mug after drinking from it will waste coffee when we exit the loop after refilling! Refilling before drinking would fix the problem.
- Drinking coffee before checking whether we have coffee (or not) could mean trying to drink from an empty mug! Definitely less than ideal for a coffee-powered programmer! The solution is the same as in the previous point: We should check whether coffee is available before drinking. And if not, refill from the coffee pot.
That's solid code.
We could leave it like this.
However, for additional simplicity, I prefer to go one further.
Fourth Improvement
When I reflected on the latest version of the coffee mug code, I suddenly realised that several lines of coffee preparation are required before we get to the essential: Drinking coffee and executing work.
Therefore, I suggest we extract the less important coffee preparation detail into a helper method, PrepareCoffee()
.
To reduce nesting, I've also inverted the first if conditional into a guard clause: If we have coffee, return early and continue drinking and working.
And so the final version of CoffeeDrinker.Drink()
becomes:
It's a short and straightforward method without complexity: While there is work, prepare coffee, drink it and do the work.
So easy to understand.
One doesn't need to think to comprehend what's going on.
Here's another helpful rule:
Aim to write code so simple your readers won't have to think to understand it.
While this code is clearer than the original, we did sacrifice the joke.
Conclusion
The core lessons from this post are:
-
Learning Retention Principle: Attempting to answer a question before we know how to, helps us remember the answer better. So, always attempt a problem first. Look up the answer later. This simple change in strategy will enormously impact your understanding and retention of material.
-
Abstraction & Encapsulation: Consider separating what the code does from how it does it. Don't worry if you still need to learn what this means. As you program, reflect on this point frequently, and you'll soon see.
-
Excessive Simplicity: Aim to write code so simple your readers won't need to think to understand it. Encapsulate complexity and hide it away.
Note: I performed the coffee mug code refactoring with a unit test safety net. Check out my Patreon for access to the complete source code.