Buy JitterTed's TDD Card Game!

Domain vs. Technical

Domain-Centric Code & Tests Are More Understandable

ddd

Originally published on . Last updated on .


I was recently reminded (thanks to Jon Reid @qcoding) of a tweet I wrote back in July 2020:

At the time, I had just started teaching my new online class[1], where this came up in a somewhat unexpected way. In the class, we’re working on fixing some Code Smells in the code of a console-based Blackjack card game. As part of the refactoring process, we extract code that compares the value of two Hands—the player’s and the dealer’s—to see if one of them Beats the other. We do the same for another method that compares the hands to see if they have the same value[2], called a Push.

When extracting these methods, the temptation is to name these methods with TECHNICAL terminology, i.e., using compareTo() for the comparison method, and equals() for the one checking for hands having the same value. However, there are two problems with these names:

  1. compareTo() has a specific usage in Java: for sorting, or with data structures that need ordering (such as TreeMap). Since Hand instances have no need to be ordered, using compareTo() would be misleading to readers of the code. Similarly, equals() has a specific technical meaning, but it would also be incorrect to say that two different Hand instances are equal if they happen to have the same total value of their cards.

  2. If we’re defining public methods on a Hand class (a class concerned with an aspect of the Blackjack domain) that forms its API (what I call its “surface area”), those methods need to “speak” in the language of Blackjack. compareTo() and equals() are TECHNICAL names, so instead we prefer to use DOMAIN terms in the names: beats() and pushes().

Benefits

The benefit of using DOMAIN terms is that it reinforces that the class implements domain behavior. It also makes the code easier to understand, even for non-coders. For me, the biggest benefit is that it helps keep the public API of the class at the appropriate level of abstraction. So, my tests are testing DOMAIN behavior, not TECHNICAL behavior. This allows the code to be more easily refactored, as domain terms are often at a higher level of abstraction than technical terms, and less prone to arbitrary change.

Example

Two test code listings, the first one asserting hand.contains(aceCard), the second asserting that hand.hasAce()
Technical vs. Domain Methods

Here, the first example uses a TECHNICAL method, contains(), which works, but asking (querying) the Hand whether it hasAce() is much clearer. If you do have a DOMAIN need to find out if a Hand has an arbitrary Card, you might want to look deeper at the domain meaning of such a query.

Domain Knowledge

All this assumes that, as a developer, you are familiar with the domain you’re working in. If it’s a Blackjack game, you should know the terms “busted”, “pushes”, “hit”, “surrender”, etc. If it’s an accounting system, you’ll need to know the meaning of “credit”, “debit”, “journal”, “ledger”, etc. While you can write code without much knowledge of the domain, the more you deeply understand it, and the better you can work with those who define what the system needs to do.

What Do You Think?

What domains do you work in? Does your code reflect the domain, or are methods more technical? How does that affect the testability of the code or the “brittleness” of the tests? Let me know on Twitter or join my Discord to discuss this and other topics.


  1. Now called Refactoring to Testable Code. Subscribe to my newsletter, so you don’t miss future classes. ↩︎

  2. In Blackjack, each hand has a numeric value based on the cards it has. For details, see the rules from my Blackjack game code that I use in my course. ↩︎


Join the TED.DEV Newsletter

Subscribe to hear about new courses and events, along with articles on Refactoring, TDD, and Hexagonal Architecture.

    We respect your privacy. Unsubscribe at any time.