Software Contracts
Specifying the obligations and expectations between client and provider
Introduction
Contracts have existed for millennia, shaping agreements across civilizations. The image above is a Sumerian contract circa 2600 BCE specifying the terms of selling a field and house.
Real world contracts, such as the one above, specify the legally binding Obligations and Expectations for a specified time between Client and Provider.
Software Contracts
Software Contracts allow developers of a software component to communicate component behaviors effectively to other developers who will invoke their software components. Good communication among developers through their code is core to Software Engineering.
Software Contracts specify the Obligations and Expectations of behaviors of software components independent of the implementation.
Contract by Design
Bertrand Meyer created Contract by Design (DbC) as a concept for the Eiffel Programming Language.
Contract by Design includes:
- Preconditions: Valid Input
- Postconditions: Expected Output
- Side Effects: State Changes
Obligations
Clients bear some responsibility in the relationship. To fulfill a contract, the Client is obligated to provide accurate and valid information to the Service Provider. The contract should define what qualifies as accurate and valid so that Clients know their obligations.
If the Client fails to provide valid input (e.g., an invalid phone number), the Service Provider is not required to fulfill the request. Instead, the request can be rejected—possibly by throwing an exception.
Accepting inaccurate or invalid information and attempting to honor the contract produces Garbage In, Garbage Out results.
Client Obligations align with Preconditions in the DbC above.
Expectations
If accurate and valid information has been provided, then the Client has the expectation that the contract will be fulfilled/honored. That is, the software component will do what has been documented in the contract. For example, if the Client has provided a valid phone number, then the Client expects either the corresponding result or a clear indication that no data exists.
The contract does not define the means that will fulfill that expectation.
Client Expectations align with output values returned, postconditions and side effects in the DbC above.
Real World Contract Example
Taxes are complicated in the United States. Many Americans seek assistance from tax professionals. All tax professionals support the same basic contract:
- Client Obligation: Provide tax documents and payment
- Client Expectation: Receive completed tax forms, ready for signature and filing
Tax professionals come in many forms with different degrees of sophistication, such as: Tax Attorneys, Certified Public Accountants, Tax Preparation Companies (H&R Block), Tax Preparation Software (Intuit), or a trusted friend or family member. Each might employ different means to complete the forms, such as: Proprietary Software, deferring to a commercial Tax Software Package or filling the forms out by hand. Regardless of their means, they honor the contract.
Software Contract Scope
Real world contracts vary in scope, such as the contracts for:
- Mergers of multi-billion-dollar corporations
- Buying or selling a house
- Terms of Service for an online service
- A personal I.O.U.
The scope of software contracts can be broad or narrow. Software contracts connect Client and Provider across various spans, such as:
- APIs, for example: RESTful
- Interfaces, for example: Java packages
- Function/Method signatures including public, protected and private
Regardless of scope, the principles of a good contract specify the contract’s obligations and expectations in a meaningful way.
What vs How
A well-crafted software contract defines what a component does, not how it does it. This distinction is critical: the contract outlines the expected behavior—inputs, outputs, and side effects—while deliberately hiding implementation details. By decoupling the interface from the implementation, contracts enable flexibility, testability, and the possibility of multiple solutions that fulfill the same promises. Whether sorting data or retrieving a user record, the contract ensures consistency of outcome, allowing the underlying logic to evolve without breaking client expectations.
This separation is the essence of the Strategy Design Pattern.
Client Point of View
Cognitive scientist Donald Norman is one of the pioneers of User Centered Design. He developed a theory that focuses upon the user’s experience. He applied this to everyday objects in his book, The Design of Everyday Things, but the concepts apply to software contract too. Doors are one of his primary examples, which have been coined Norman Doors.
Opening a door should be obvious, but that’s not always the case:
When a device as simple as a door has to come with an instruction manual—even a one-word manual—then it is a failure, poorly designed. — Donald Norman
Norman described scenarios where the door designer hid all visible clues to operate doors for aesthetic purposes. Users would stand in front of these doors bewildered by how to open them.
I can identify with this to some degree. My last office location, Bellworks, which interestingly enough is the filming location for the exteriors for Lumon Industries in the TV show Severance, had Norman doors. All doors were glass with a meter long vertical bar attached on one side. All doors looked alike; some were push, some were pull and some were slide. I eventually memorized which door did what through trial and error. But sometimes I’d absent mindedly forget. I’d push, then pull, and finally slide.
Well-designed things scream how they are intended to be used. Contracts should be designed from the client’s point of view. Ideally their chosen names will reflect how the client interacts with the contract and not necessarily what the provider provides.
If contracts contain a good naming design, then their use within the client code should feel natural. That is, it should feel like the contract was designed specifically for the client’s domain. The class, methods and parameters of a contract should be self-explanatory.
Ideally your user should not need to read the documentation or comments to use your contract. This won’t always be the case, but hopefully your contract names are expressive enough that someone reading code that accesses your contract can understand it in how it’s being used. Contract documentation and comments won’t appear in client code that references the contract. Ideally, anyone reading the client code should not have to pause and look up contract documentation or comments when encountering contract elements in how it’s being referenced.
Without care, a contract tends to reflect what was implemented rather than what the client wants or needs. Contracts like these feel like an afterthought, and that’s one reason why so many of them are difficult to use. Worse yet they may leak implementation details, known as a Leaky Abstraction.
Here are some elements of a good contract:
- It is designed from the user’s point of view to support their intent.
- Its elements form a cohesive whole. Add something, and it’s out of place. Remove something, and the remaining elements fall apart.
- It observes the Principle of Least Astonishment, a principle stating that software should behave in a way users intuitively expect. That is, no surprises or astonishment when its actual behavior is understood.
- It prefers Value Objects over Primitives:
Keep contracts lean. Less is more. You can always add more later without breaking existing users. But once you remove something or change its behavior, it will affect existing users. Sometimes we may want to deprecate parts of contracts but do this cautiously. Once a contract is in general use, you can’t easily remove elements or change behaviors. Hyrum’s Law predicts that with enough users, all observable behaviors of your system will become a dependency for somebody regardless of the developer’s intent.
Contracts may need versions to support updates or changes. This is common with RESTful APIs.
Behavior-Driven/Test-Driven Development
How do we know that our contracts meet our users’ needs? Using BDD and TDD techniques, we act as the first users of our contracts.
We want our contracts to make sense to our users. BDD and TDD techniques allow us to specify contract expectations and obligations in a confirmable executable form. If it’s difficult for you to write tests interacting with your contract, then it will be more difficult for your users. Adjust the contract so that it is easier for your tests and for your users.
Traditionally BDD and TDD confirm behavior within code, but this case is a bit different. There is no code associated with a contract. A contract only specifies Obligations and Expectations. It doesn’t implement them. This also allows you to define the contract before implementation details may influence it.
Since contracts do not depend upon their implementations, contract specification tests will tend to be higher tests, possibly Acceptance Tests (AT) (Blog TBD). While ATs still follow the principles of BDD and TDD, they may feel more like complete user scenarios. This can be useful to gain a better understanding of how the user may interact with the contract rather than being concerned about the implementation of the contract.
Contracts are Fixed/Stable Elements in most designs. In UML class diagrams other design elements depend upon the contracts rather than the contracts depending upon those elements. This is visually represented by the dependency arrowheads modeling the relationship between elements consistently pointing inward toward contract elements and rarely away from them. The only time contracts depend upon other elements is when they depend upon other contracts. For example, Java Interfaces can only extend other Interfaces.
NOTE: Technically interfaces depend upon their parameters, which can be other classes, but these tend to be core Business Elements that are implemented as Value Objects.
What code is being executed in contract tests that confirm expectations and obligations? This is going to sound counter intuitive, but there is no code that’s being executed. There is no implementation for the contract. The code being executed will be Test Doubles.
This is not a case of testing test doubles. This is a case of emulating the contract’s expectation and obligation behaviors via test doubles so that the tests not only specify contract behaviors but demonstrate them in an executable form.
These contract tests may serve an additional function. They can be the basis for future tests against the implementation. Rather than providing test double references for the contracts, provide an actual implementation. Some of the set up in the Given portion of the test and confirmation in the Then portion of the test may need to be updated for the implementation, but the overall scenario should be the same.
Personal Note: I have not written executable tests against my contracts as I advocated above. However, before I learned TDD and BDD I wrote non-executable operational scenarios against my contracts to visualize how they would be used. They became the basis of my tests once I started to implement the code.
Summary
Software development is, at its best, a discipline of communication—between people, between components, and across time. By thinking in terms of contracts, we make that communication explicit. We name what we expect and what we offer in return.
Software contracts define the boundaries between components, clarify behavior, and reduce ambiguity. Contracts lead to more intentional, testable, and maintainable code. Every function becomes a promise; every call, a handshake. Embracing contracts invites more resilient systems.
References
- Donald Norman Quotations
- The End of Software Versions blog post by Pieter Hintjens