Dear Junior
In our letters on software development we have touched upon the idea several times, but I think it might be worth spelling it out - the idea to make implicit concepts explicit; something I see as one central message of Domain Driven Design applied to coding.
Let me pick that phrase apart for a moment. When coding we have a lot of concepts in mind, e g when writing code that handles people I might have a class namned Person. In this case the concept of person has an explicit representation in the code - i e an explicit concept.
A person might have a birth date, represented in code as a data-field Date dayofbirth in the Person class; another example of an explicit concept.
We might have business logic restrictions that are based on the age of the person, e g you have to be at least fifteen years to access some content. So there will be code calculating this.
void contentRequest() {
...
boolean access = ((new Date().getTime() - customer.dayofbirth.getTime()) / msPerYear) >= 15;
Again we have a concept represented in code, this time age. However, this time the representation is not explicit - there is nothing in the code saying "age". Age is an implicit concept.
There is nothing strange with having implicit concepts. In the short code snippet we have several implicit concepts: current point of time, point of time of customer's birth, and age-limit for content are just three obvious. This is not a problem. The programmer reading the code will intuitively re-construct the relevant concepts.
However, the design guideline from Domain Driven Design advice us to keep an eye open for important concepts - and if we find the represented implicitly, then make that implicit concept explicit.
Let us look at the code snippet again.
boolean access = ((new Date().getTime() - customer.dayofbirth.getTime()) / msPerYear) >= 15;
Of the implicit concepts dwelling in this like, which of them seem important enough to make explicit?
The concept of "current point of time"? Probably not.
The concept of "milliseconds since birth"? Nah, not that either.
The concept of "milliseconds since birth expressed as years, rounded downwards"? Hey! I know this! We already have a word for it - we call it "age"! And we talk about it all the time!
Well, that sounds important enough. Let us start with giving that value a name.
void contentRequest() {
...
By the way: this is where I really like the IntelliJ shortcuts "cmd-w" to widen the selection until the expression is selected, then "cmd-alt-v" for the refactoring to extract the selection as a variable - naming it "age" in the pop-up. It literally takes less than 10 seconds.
Now, we actually have made the implicit concept age into an explicit concept. Mission completed.
Well, not completely. We need to find the concept a good home where it can lead a good life. Right now it is stranded in the middle of some access computation. At least we can give it a method of its own.
Applying another of my favourite refactoring "Replace Temp with Query" yields this code.
void contentRequest() {
...
boolean access = customerAge(customer) >= 15;
class Person {
Date dayofbirth;
// ...
long ageAt(Date timepoint) {
return (timepoint.getTime() - dayofbirth.getTime()) / TimeUtil.msPerYear;
}
}
You might not believe me, but I remember the days of yore when you actually had to do all the involved steps manually; cut-n-paste the method body and header, change the list of parameters, update the code to use the fields of "this" instead of the argument, change all client calls (which might be several).
Now, we have finally ended up with a version I feel comfortable with. The concept "person" has now an conceptual attribute "age at" which has an explicit representation in code. In code we have "enhanced" our language of what we can talk about directly. So, when we discuss the age of a customer with business people, it is likely that we consistently mean the same thing - even if we for sure still can misunderstand each other.
As a side effect the code checking the access has become a lot clearer.
boolean access = customer.ageAt(new Date()) >= 15;
Let me pick that phrase apart for a moment. When coding we have a lot of concepts in mind, e g when writing code that handles people I might have a class namned Person. In this case the concept of person has an explicit representation in the code - i e an explicit concept.
A person might have a birth date, represented in code as a data-field Date dayofbirth in the Person class; another example of an explicit concept.
We might have business logic restrictions that are based on the age of the person, e g you have to be at least fifteen years to access some content. So there will be code calculating this.
void contentRequest() {
...
boolean access = ((new Date().getTime() - customer.dayofbirth.getTime()) / msPerYear) >= 15;
Again we have a concept represented in code, this time age. However, this time the representation is not explicit - there is nothing in the code saying "age". Age is an implicit concept.
There is nothing strange with having implicit concepts. In the short code snippet we have several implicit concepts: current point of time, point of time of customer's birth, and age-limit for content are just three obvious. This is not a problem. The programmer reading the code will intuitively re-construct the relevant concepts.
However, the design guideline from Domain Driven Design advice us to keep an eye open for important concepts - and if we find the represented implicitly, then make that implicit concept explicit.
Let us look at the code snippet again.
boolean access = ((new Date().getTime() - customer.dayofbirth.getTime()) / msPerYear) >= 15;
The concept of "current point of time"? Probably not.
The concept of "milliseconds since birth"? Nah, not that either.
The concept of "milliseconds since birth expressed as years, rounded downwards"? Hey! I know this! We already have a word for it - we call it "age"! And we talk about it all the time!
Well, that sounds important enough. Let us start with giving that value a name.
void contentRequest() {
...
long age = (new Date().getTime() - customer.dayofbirth.getTime()) / msPerYear;
boolean access = age >= 15;
Now, we actually have made the implicit concept age into an explicit concept. Mission completed.
Well, not completely. We need to find the concept a good home where it can lead a good life. Right now it is stranded in the middle of some access computation. At least we can give it a method of its own.
Applying another of my favourite refactoring "Replace Temp with Query" yields this code.
void contentRequest() {
...
boolean access = customerAge(customer) >= 15;
private long customerAge(Person customer) {
return (new Date().getTime() - customer.dayofbirth.getTime()) / msPerYear;
}
Better, but can be improved to make the concept clearer. If we talk about the "age of the customer" it will vary from time to time, and it might cause confusion: the age when the customer requested access to the content, the age when content was first accessed, when it was last accessed, the age now? Better to clarify.
Of course we could rename the method to "customerAgeNow". However, I do not feel comfortable having my concepts depend implicitly on external things like the system clock. I prefer to make those dependencies explicit. So we make the time-in-question a parameter.
void contentRequest() {
...
boolean access = customerAgeAt(customer, new Date()) >= 15;
private long customerAgeAt(Person customer, Date timepoint) {
return (timepoint.getTime() - customer.dayofbirth.getTime()) / msPerYear;
}
This design also improves testability drastically. The tests can now use explicit test-data, and need not rely on checking, or changing, the system clock.
Kudos once again to the lovely refactoring support of modern IDEs. Another less-than-10-seconds-refactoring: two cmd-W to select "new Date()", cmd-alt-P to extract as parameter, change name to "timepoint" in pop-up. Enter. Done.
By now it is pretty obvious that the method "customerAgeAt" suffers from feature envy. The most relevant data it operates upon is the Customer, but it resides somewhere else. We should consider moving it.
Should we move it, the Customer class would get a method "ageAt", taking the liberty to rename it slightly. That method certainly makes sense in this context, but does it so in other contexts? As we do not have the rest of the codebase at hand, we will just have to pretend that "ageAt" makes sense in other context - and might actually be useful there as well.
This is really one of my favourite refactoring: move method. Again extremely smooth thanks to modern IDEs. Literally two keystrokes (F6, Enter - in IntelliJ) for the move, and a few for renaming.
void contentRequest() {
// ...
boolean access = customer.ageAt(new Date()) >= 15;
Date dayofbirth;
// ...
long ageAt(Date timepoint) {
return (timepoint.getTime() - dayofbirth.getTime()) / TimeUtil.msPerYear;
}
}
Now, we have finally ended up with a version I feel comfortable with. The concept "person" has now an conceptual attribute "age at" which has an explicit representation in code. In code we have "enhanced" our language of what we can talk about directly. So, when we discuss the age of a customer with business people, it is likely that we consistently mean the same thing - even if we for sure still can misunderstand each other.
As a side effect the code checking the access has become a lot clearer.
boolean access = customer.ageAt(new Date()) >= 15;
This is a line of code that can be shown some person from the business side and explained by reading it out "access is granted if the customer's age at 'now' is at least fifteen years". Given that support, they can see by themselves - something that really builds trust.
The time invested was not huge. Coding-wise all refactorings took less then a minute together. The time to slowly realise that "age" is an important concept in this particular domain is not counted. However, that insight will have to dawn on the programmer sooner or later, and when that happens, that extra minute is well invested.
To make concepts explicit does not only apply to code, it can be fruitfully applied to other areas as well, e g capturing and writing requirements/specifications.
As we all stand on shoulders of gigants, I also must give credit where credit is due. The phrase "Make Implicit Concepts Explicit" I have picked up from Eric Evans, the thought leader of Domain Driven Design.
In conclusion: to make implicit concepts explicit help us to over time close the gap between the code and the understanding of the business domain. It is not uncommon to in the process find bugs or crucial misunderstandings, that then can be adressed in a proactive manner instead of popping up as nasty surprises later on.
Yours
Dan