One fundamental fact of quantum mechanics challenges our basic perception of the world: Observing something alters its behavior.
This is crazy! In real life, you wouldn’t expect the act of watching television to alter the original content recorded in the studio.
For the same reason, you wouldn’t expect a file browser application to make changes to the files it displays every time it is opened. This behavior would be useless — you couldn’t afford to open the file browser without worrying about unwanted changes.
In software engineering, the same principle, of dividing data observation from modification, applies. Take a database, for example. There’s a clear separation between the query, insertion and deletion methods. You wouldn’t write data-changing code in the implementation of the query() method.
This segregation makes it easy to use the database API without having to reason about what could happen when each of these functionalities is used.
If this segregation makes sense for the database, why not enforce it in every functionality we write in our code-base? After all, each piece of code eventually comes down to querying data or executing commands which affect data.
It might be tempting to pack some code in a function like this:
You can have an idea of what it does, but let’s take a moment and think about how can we describe its behavior in a single sentence.
In fact, there is no way to describe the behavior in one sentence because you must split the function behavior to two different scenarios:
- Function called if alarm already scheduled.
- Function called when alarm is not scheduled.
This means that a reader of code using this function has to invest additional effort to understand what it does. They would probably have to also read the function implementation, just to be on the safe side.
When we declare a function, we hope to reuse it more than once. Functions like this have no chance to be used anywhere other than in very high-level business logic procedures.
Applying Command Query Separation
There are now two separate functionalities. High-level procedure will now have to contain three lines instead of one, but it will be much more readable.
Another positive effect of this separation is that there are plenty of good examples of how each one of these two functions (or different APIs) could be reused. For example:
- Other business logic could make use of knowledge if alarm scheduled or not.
- They could show the current scheduling state in the UI to the user.
This architectural approach goes hand in hand with the functional programming paradigm, which encourages you to isolate and avoid state-dependent code and write descriptive functions with clear inputs and outputs.
Separating “commands” functionality, which mutates something in the system, from functionalities responsible for querying data, makes functional code inevitable.
It can be easy to approach software engineering as a craft or an art.
However, I believe our goal should be to set rules and principles to ensure a deterministic way to produce robust, reusable and scalable code.
From my experience, following rules like CQS (Command Query Separation) improves these factors dramatically.