Coding in Prose

Writing consistent and unambiguous code

Reijhanniel Jearl Campos
Xendit Engineering
5 min readJan 20, 2021

--

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:

Implicit types.

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 and 2451234 ? 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:

Account number as an explicit type.

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 Accounts led to the new 10-digit length.

Let’s turn this into prose:

Account number as an explicit type.

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 as AccountNumber (✅ 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:

Withdrawing in prose.

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 and string are now hidden, and is instead explicitly called Money (✅ unambiguous)
  • A future maintainer no longer needs to care on low-level details such as allowing $ vs USD, 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:

Flattened 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:

Explicit definition of a “balance”.

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:

Retrieve balance at any point in time.

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 of Credits and Debits, 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!

--

--