Musings and Scribbles on Software Development
Not a day passes without us hearing of some software woe that has happened somewhere in the world. Bugs, bugs, bugs users scream…
I mean, Wikipedia even keeps a list of the most “awesome” bugs in the world.
As software developers/engineers/craftsman/gardeners in order to sleep peacefully at night we must make sure that no bugs are in our code.
But that’s just thinking of ourselves. The real motivation for wanting reliable and robust code is that:
In face of these two very critical quality demands on software, Design by Contract emerges as the industry’s most comprehensive method for ensuring reliability, by which we mean:
Coined by Bertrand Meyer Design by Contract (DbC), is an approach for designing software. It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with pre-conditions, post-conditions and invariants.
These specifications are referred to as “contracts”, in accordance with a conceptual metaphor with the conditions and obligations of business contracts.
Software contracts are like business contracts, in that they are characterized by relations of client-supplier and obligations-benefits. Also, just like business contracts, software contracts can be broken, by either party, by not meeting with their obligations.
The important bit here is that, since in software the contract must run within its specifications, breaking a contract indicates the presence of a defect or bug.
In software programming the smallest unit of functionality is a routine and so, if we want to ensure reliability, we must tackle the problem at this level. What DbC then says is that we must define correctness for any routine by clearly and explicitly indicating its:
And more than just defining the pre-conditions and post-conditions, we must embed them into code, in such way that whenever the code runs, its self validating, taking a pro-active position towards avoiding bugs rather than applying defensive programming.
(Routines is old school we do classes and objects these days…)
The Object-Orientation paradigm, commonly used to design and program software, gave us objects, a new unit of software after functions/routines. And so we must also ensure that objects state is always valid. And in DbC we’re meant to do it by explicitly and clearly defining it’s invariants.
If either a pre-condition, post-condition or invariant is violated an Exception must be raised immediately, to stop the routine from not doing it’s job properly or the object from getting into invalid state.
Whenever we send messages to an object we’re essentially doing two things:
1 - asking it to provide us with some insight about it’s state 2 - asking it to mutate it’s state
With that in mind, Meyer divides functions into two categories:
Given this separation Meyer goes forward and provides us with the Command-Query Separation CQS) principle:
any routine should be a query or a command but not a mixture of the two
The reason for CQS is that it is impossible for us to reason about the correctness of the state of an object, by using queries that change the object. Because we use the queries attributes and functions to construct our pre-conditions and post-conditions. If one of the functions were to change the object then the result would no longer be valid.
The question about implementation is programming-tools bound, it mostly depends upon the language one is using. Some programming languages like Eiffel (designed by Meyer) and D provide language keywords and constructs for applying it. But most “mainstream” programming languages do not.
The fact that these languages don’t provide tools for it though doesn’t mean we’re by any means restricted from applying DbC. We just have to be a little bit more creative. In fact all we need are boolean conditions and exceptions.
All we have to do is strategically place (based on verification type, i.e pre-condition, post..) conditional checks in the code, that raise exceptions when not met.
The whole gist of DbC is summarized in the code snippet bellow that exhibits an example of an hypothetical Bank Account object, which as per DbC guidelines must protect it self from getting into an invalid state.
class Account(object): def __init__(self, name: Name, currency: Currency): self._name = name self._currency = currency # Checks Invariants (conditions that must always be true) for this object. self.protect_class_invariants() @property def balance(self) -> Decimal: """Query that fetches account balance.""" return self._compute_balance() def deposit(self, amount: Decimal) -> None: """Command that records a new deposit transaction on the account.""" # Checks Invariants (conditions that must always be true) for this object. self.protect_class_invariants() # Check Pre-Conditions (conditions that must be true for routine to work correctly) assert amount > 0, 'Deposit amount must always be greater than zero' # Keep a copy of the original object to verify post-conditions old = copy.deepcopy(self) # record a new transaction (mutate object) # Check Post-Condition (conditions that must be true after routine execution) assert self.balance == old.balance + amount # Checks Invariants (conditions that must always be true) for this object. self.protect_class_invariants() def protect_class_invariants(self): """Checks Invariants (conditions that must always be true) for this object. Raises: AssertionError: If any class invariant is not held. """ assert self.balance > 0, 'Account balance must always be greater than zero' def _compute_balance(self) -> Decimal: """Computes account balance based on transaction history""" pass
DbC is a very simple and proven technique which every developer should learn and try to apply to software being developed in order to guarantee robustness and reliability
No framework/library/base class is necessary in order to use DbC. In fact I advise against that, as any of those components, although saving key strokes, can introduce a level of indirection to the code, which would otherwise be clearer with, just the basic language features being used.
(It is arguable but) DbC in grand part takes care of unit-testing, as it is constantly ensuring that it is doing things properly. Further more, this is what unit testing tries to fix, and it does indeed do a good job, but it is not as powerful because there’s a limit to how much it can “spy” into the System Under Test to assert if it is indeed working properly.
From the idea of the CQS principle, Gregory Young and Udi Dahan derived CQRS, which is essentially applying CQS at an architectural level.