Coding in Prose
Writing consistent and unambiguous code
Some say the world will end in fire,
Some say in ice.
From what I’ve tasted of desire
I hold with those who favour fire.
But if it had to perish twice,
I think I know enough of hate
To say that for destruction ice
Is also great
And would suffice.Fire & Ice
In 1920, Robert Frost authored this classic poem. It eerily captures the post-war uncertainty through its manner of writing. Although penned in the context of war, the poem evokes a different feeling in a modern reader who has never experienced war (which is most of us, if not all).
The power to elicit different meanings despite the difference in context is what makes poetry an immortal form of literature.
Imagine however, the same power applied in software. A piece of code that:
* means 1 thing in the past, means another in the present (inconsistent)
* is interpreted in multiple ways by multiple readers (ambiguous)
Sounds like a nightmare, no?
As software engineers, what we really aim to write is a prose. Merriam-Webster defines prose as:
the ordinary language people use in speaking or writing
Unlike poems — which convey experience through imagination, a prose is a straightforward form of literature. It is pragmatic and focuses on delivering information.
Like a prose, a piece of code must:
* mean 1 thing, be it in the past or present (consistent)
* be interpreted in 1 way by multiple readers (unambiguous)
Throughout the rest of the article, we’ll inspect various pieces of code and match it with our criteria for a prose.
Example: Bank Account
Number
Let’s consider writing a software for a bank. An Account
is identified by a unique number. In code, this can be easily represented as:
When read by a teammate who writes code, the above is fairly straightforward. However, remember that software is not invented out of nothing. It always comes from a business need — in our case, the bank.
When shown to a bank manager, this code may elicit the following questions:
- what is
number
? (❌ ambiguous) - also, our account numbers are always 10 digits, not 8. (❌ inconsistent)
The first point complains about number
. A number
is a technical term — a datatype implying that math operations are possible. For example:
- 1+1 = 2
- 100/3 = 33.3333
- 9*11 = 99
- 12.5–2.5 = 10
Going back to our so-called “account number”, it begs the question:
Does it make sense to add account numbers
1111111
and2451234
? subtract? multiply? divide?
Simply put — it does not make sense. “Account number” is a distinct term used in banking, and is not the same as the math/programming term number
. Like a good prose, we make this explicit:
Now that ambiguity is taken care of, let’s tackle the inconsistency issue.
The 10-digit restriction is a business rule that may or may not change. When the code was first written, it may be the case that the length was only 8 digits — and that the growing number of Account
s led to the new 10-digit length.
Let’s turn this into prose:
The above snippet encapsulates the “10-digit restriction” business rule inside the factory method, from
. This ensures that in the application, there will never be an AccountNumber
that violates this constraint — as expected by the bank.
Through these refactoring steps, the resulting code now fit our criteria for a prose more closely:
- The technical term
number
is now hidden, and is instead explicitly called asAccountNumber
(✅ unambiguous) - A future maintainer knows immediately that an
AccountNumber
can only have 10 digits, despite not being able to talk to the bank manager in the past (✅ consistent)
Withdrawal
An Account
is something you can withdraw money from. A naive representation of this in code could be:
The withdraw
method is represented as:
Like in the previous example, it breaks our criteria for a prose:
* what is double
? (❌ ambiguous)
* What does currency look like? Is $
valid? (❌ inconsistent)
The key to refactoring this into a prose is by looking at the original sentence we used to define an Account
:
An
Account
is something you can withdraw money from.
A Money
is not a double
and a string
. It is a distinct concept of the domain, and therefore must be made explicit.
And finally, the method declaration now looks like:
Notice how the new withdraw
method reads like “withdraw an amount of Money” — exactly like a prose. All rules associated with money is encapsulated in Money
, instead of the withdraw
method.
Through these refactoring steps, the resulting code now fit our criteria for a prose more closely:
- The technical terms
double
andstring
are now hidden, and is instead explicitly calledMoney
(✅ unambiguous) - A future maintainer no longer needs to care on low-level details such as allowing
$
vsUSD
, decimal places per currency, and the like (✅ consistent)
Balance
From an owner’s perspective, an Account
’s balance is the total withdraw-able amount remaining. This can be represented as:
There’s nothing inherently wrong with this Account
model. It is consistent, as it allows future maintainers to understand the intent of code.
The problem is ambiguity — not between engineers, but between the engineer and the bank manager.
From a banking perspective, an Account
’s balance is defined as:
the sum of debit and credit entries, at a given point in time.
A given point in time implies that it should be possible to retrieve the Account
’s balance given any point in time. In the above code, the balance
is stored in a single variable:
balance
.This makes it impossible to accurately represent the bank manager’s understanding of an Account
’s balance — which is not a characteristic of a prose.
We can refactor this instead into:
Note that implementation for Credit
and Debit
are omitted for brevity — and are left as an exercise for you. The takeaway here is that this version follows more closely the definition of a balance:
The initial version is already ✅ consistent. But through this refactoring process, we complete the other criteria for a prose more closely:
- The definition of an
Account
’s balance is implemented as the difference between the sum ofCredit
s andDebit
s, at a given point in time. (✅ unambiguous)
Closing
All of the examples we used — Account
, AccountNumber
, Money
,Debit
, Credit
, Balance
— are all what’s known as Domain Primitives. They are concepts taken directly from the domain (in this case, the bank) and is written explicitly in code. Through this, we can ensure that our code is:
* ✅ Consistent — means one thing, regardless of change in time
* ✅ Unambiguous — means one thing, to all readers
Some of the domain primitives such as Money
are generic. For such cases, libraries are often already available on your language like dinero.js and go-money. For your specific application however, make sure to talk to your domain experts to get a hold of the accurate definition of a term or concept. Strive to follow closely their vocabulary (e.g., number
vs AccountNumber
).
Keep coding in prose!