« Worthy of Praise
» Announcing ListLibrary.net
Code: command/query separation, email, ListLibrary.net, referential transparency, threading
Objects contain both state (data) and methods, and methods should be classifiable into commands that change state and queries that introspect state. The principle of Command/Query Separation (CQS) expresses a design principle I’ve intuitively used as a rule of thumb. With the conscious consideration that comes from hearing it, I knew how to improve some of my own code.
I violated CQS in part of the design of ListLibrary.net (mentioned here). It was both beneficial and harmful (especially in debugging).
Emails are threaded together to show the flow of responses. In this example Chris, Mike, and Jon all responded directly to Tiago and there was a series of responses to Mike. It’s difficult to put these threads together well because email clients often don’t (accurately) include the metadata to specify the message an email is responding to. You sometimes just have to guess what goes where, and it’s further complicated by messages that are lost or (in date-organized archives) split off into other directories.
I started with the threading heuristic by JWZ. It’s a well-described, often-implemented bit of code. I implemented in two steps: first, as messages are added, their references are examined to do some basic relatinships between them (JWZ’s step 1). The rest of the steps require more computation and are more likely to be invalidated as messages are added, so I do them lazily: the threading is calculated (and cached) when it’s requested… violating CQS. The query to list the threads in a message also acts as a command to generate them.
This is both a performance win (yes, I tested) and leads to better-threaded messages, using all available messages are in reduces the amount of ambiguous situations it has to guess at, which can persist even after adding other messages that would have resolved it. But it’s also a great way to make my life difficult in testing: when I insert debugging queries they can trigger commands that change the way messages are threaded. It creates heisenbugs, bugs that appear, disappear, or change when they are being debugged.
I was really frustrated by heisenbugs for a while: I need to violate CQS for performance, but I’m creating all this extra work for myself in the most complex piece of the codebase. The solution is to create referentially transparent query commands specifically for debugging. They’ll introspect on the internal data structures and present them in their raw forms without triggering threading (making them useless for non-debugging work, but that’s OK), and their tests can confirm this.
I find the CQS principle to be a really useful tool. Its application leads to better-designed and easier-test code, and its violation can indicate bad code (a Code Smell).