
Preconditions are a technique you can employ to express the requirements that a given module has so it can execute its job correctly. By using preconditions, you arrive at code that’s clearer, better documented, and thus easier to understand and maintain.
As the title says, in this article, we offer you an overview of the preconditions, along with examples. You’ll learn what preconditions are, why and how you’d use them, how they relate to other techniques, and the role they play in programming.
What is a precondition?
You can think of a precondition as a requirement or demand that a given software module—usually a function—makes of its callers
Definition
You can think of a precondition as a requirement or demand that a given software module—usually a function—makes of its callers. If the caller doesn’t match those demands, then the function refuses to do any more work.
If the module progresses through the preconditions-checking phase, the caller can know for sure that the preconditions were met and the module has everything it needs to perform its work properly.
What is the difference between a precondition and a postcondition?
Since preconditions exist, it sounds reasonable that postconditions should exist as well. And yes, they do indeed. So, what’s the relationship between these two concepts?
Preconditions and postconditions are the opposite of each other. As you’ve just seen, a precondition is a requirement or demand that a function makes of its callers. On the other hand, a postcondition is a sort of guarantee or statement that a function makes to its callers.
Here are examples of both, in the form of English sentences:
- Precondition: “I need x, y, and z to be true; otherwise, I can’t do my job.”
- Postcondition: “I’ve just completed my job, and I assure you that a, b, and c are true.”
A routine […] should perform a useful task. It is necessary to express this task precisely … You may specify the task performed by a routine by two assertions associated with the routine: a precondition and a postcondition. The precondition states the properties that must hold whenever the routine is called; the postcondition states the properties that the routine guarantees when it returns.
– Bertrand Meyer, in Object-Oriented Software Construction
The role of preconditions in programming
We’ve just explained what preconditions are and how they relate to their sibling concept, postconditions. Now it’s time to go a bit deeper and explain how preconditions help ensure correctness in practice and what problem they are meant to solve.
What is the problem?
Within an application, you usually have many methods that talk to each other all the time. Many things can go wrong regarding this communication, since these functions accept arguments and return their own results. Whose job is it to certify the correctness of all these interactions?
Design by contract is a proposed solution for this problem, and this concept has everything to do with preconditions.
How do preconditions relate to contract-based programming?
Bertrand Meyer, in his book Object-Oriented Software Construction, created the concept of design by contract, also often called contract-based programming, among other names. The gist of the concept is that software modules can express the obligations and expectations of each one via contracts, so they can play together nicely and efficiently.
As Mark Seemmann puts it, the pillars of contract-based programming are preconditions, postconditions, and invariants:
Contracts, according to Meyer, describe three properties of objects:
- Preconditions: What client code must fulfil in order to successfully interact with the object.
- Invariants: Statements about the object that are always true.
- Postconditions: Statements that are guaranteed to be true after a successful interaction between client code and object.
– Mark Seemann, “Stubs and mocks break encapsulation”
What is an example of a precondition?
Imagine you have a function that determines and returns how many days a given month has in a given year. You call the function, passing the desired year and month as integers. A precondition for this function could be that the month argument has to be between 1 and 12, inclusive.
Another common and useful example of postconditions is verifying whether arguments are null in situations where null isn’t an acceptable value.
The Eiffel programming language, created by Bertrand Meyer, has full native support for contract-based programming, which includes preconditions, postconditions, and invariants
How are preconditions specified?
As you’ve seen so far, it’s not hard to understand the theory of what a precondition is and how it can be useful. But how are preconditions implemented in practice?
Different languages deal with preconditions differently. The Eiffel programming language, created by Bertrand Meyer, has full native support for contract-based programming, which includes preconditions, postconditions, and invariants.
For other languages, programmers have to resort to other resources, such as:
- Specialized libraries. For many mainstream programming languages, you can obtain support for contract-based programming by installing third-party libraries. Examples include PyContracts for Python, Code Contracts (now legacy), PostSharp for C#/.NET, and C4J for Java.
- Guard clauses. Another common way of implementing preconditions is by making use of guard clauses. That is, right at the start of the function, you use if statements to check for the conditions and then throw exceptions if they aren’t met.
- Assertions. Many programming languages support the use of assertions, which can be used to check for preconditions, usually just in development time, though.
Examples of preconditions
Let’s see some examples of preconditions in a few different languages. Starting with C#, let’s examine an example of a function that implements the string calculator kata by Roy Osherov and has a precondition against null strings:
public class StringCalculator
{
public static int Add(string? numbers)
{
if (numbers is null)
throw new ArgumentNullException(nameof(numbers), "Input cannot be null");
if (numbers is [])
return 0;
var delimiters = new[] { ',', '\n' };
var numberList = numbers.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
List<int> negatives = [];
int sum = 0;
foreach (var number in numberList)
{
int num = int.Parse(number);
if (num < 0)
{
negatives.Add(num);
}
else if (num <= 1000)
{
sum += num;
}
}
if (negatives.Count > 0)
{
throw new ArgumentException("Negative numbers are not allowed: " + string.Join(", ", negatives));
}
return sum;
}
}
The example above uses a guard clause to check the precondition. By using PostSharp’s built-in NotNull contract, we can make the code slightly simpler:
public static int Add([NotNull] string? numbers)
{
if (numbers is [])
return 0;
var delimiters = new[] { ',', '\n' };
var numberList = numbers.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
List<int> negatives = [];
int sum = 0;
foreach (var number in numberList)
{
int num = int.Parse(number);
if (num < 0)
{
negatives.Add(num);
}
else if (num <= 1000)
{
sum += num;
}
}
if (negatives.Count > 0)
{
throw new ArgumentException("Negative numbers are not allowed: " + string.Join(", ", negatives));
}
return sum;
}
Here’s a simple Python example that retrieves an item from a list and uses assertions to express several preconditions:
def get_list_item(items, index):
"""Get item from list with preconditions."""
# Preconditions
assert isinstance(items, list), "First argument must be a list"
assert isinstance(index, int), "Index must be an integer"
length = len(items)
assert 0 <= index < length, f"Index {index} out of range [0, {length-1}]" if length > 0 else "List is empty"
return items[index]
if __name__ == '__main__':
numbers = [1, 2, 3]
index = 2
result = get_list_item(numbers, index)
print(f'Item in position {index} in list {numbers} is {result}')
As you can see, since Python is a dynamically typed language, we have to use guard clauses—in this example, using the language’s assert facilities—to check for type adherence.
How can you test preconditions?
It makes perfect sense to test preconditions, since they are a crucial mechanism when it comes to ensuring program correctness. The good news is that testing preconditions usually isn’t a big challenge, and you might already be doing it.
What you’d usually do is just write normal unit tests that exercise the preconditions and include them in your normal test suite. In those tests, you exercise the functions, passing in arguments values that violate the preconditions, and then assert that the expected contract violation was indeed triggered (for instance, that an expected exception was thrown).
For instance, here’s a simple unit test for the C# example:
[Fact]
public void Add_WhenInputIsNull_ThrowsArgumentNullException()
{
// Arrange
string? input = null;
// Act & Assert
var exception = Assert.Throws<ArgumentNullException>(() =>
StringCalculator.Add(input));
Assert.Equal("numbers", exception.ParamName);
Assert.Contains("Input cannot be null", exception.Message);
}
Conclusion
A fast, intuitive, and secure application isn’t of much use if it doesn’t do what you expect, which means correctness should be your primary concern when developing software. A great technique you can use when writing code is utilizing preconditions.
As you’ve learned, preconditions are what allow you to express the demands that a given function makes when it comes to the data and conditions it needs in order to perform its job. Different languages and frameworks offer different ways to go about implementing them in your code, but at the end of the day, the only thing that matters is that you’re able to express the contracts that make your software modules work efficiently with one another, in a well-documented manner.
This post was written by Carlos Schults. Carlos is a skilled software engineer and an accomplished technical writer for various clients. His passion is to get to the bottom (the original source) of things and captivate readers with approachable and informative technical content.