Click here to Skip to main content
14,241,004 members

Detecting Liskov Substitution Principle Violations with Code Contracts

Rate this:
3.90 (7 votes)
Please Sign up or sign in to vote.
3.90 (7 votes)
12 Mar 2016CPOL
Code Contracts provide tools to explicitly define assumptions about method parameters and by static code analysis help to find bugs before they will appear in the runtime. This article demonstrates usage of Code Contracts.

Introduction

Liskov Substitution Principle is one of five SOLID principles. It states that if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of that program[A1]. In other words - subclasses should be substitutable for their base classes[A2].

Violating LSP Will Surprise You

If software developers don’t comply with LSP, there’s a chance, bigger with each inheritance level, that produced objects will start to behave in an unexpected way. For instance, MakeASound method of a Salmon class will throw an exception even if salmons are animals and the abstract animal can make a sound.

Another example may be a Drink method executed with a Wine parameter on instance of a Child class. It’s expected that any Human can drink any DrinkableFluid, so if Child derives from Human and Wine derives from Alcohol which derives from DrinkableFluid which derives from Fluid – why – we ask ourselves - the Drink method throws the exception?

How to Avoid Surprises with Code Contracts

Years ago, the Microsoft Research team started the Code Contracts project. It allows to define contracts in the form of preconditions, postconditions, and object invariants. It provides both static and runtime contract checking. Properly defined contracts can save time and effort by introducing an automatic analysis of usually omitted assumptions about form of the input data.

The most obvious and academic example of the unconscious assumption is division by zero:

double Divide(double a, double b)
{
    return a / b;
}

A simple solution to this problem seems to be throwing an exception:

double Divide(double a, double b)
{
    if (b == 0)
    {
        throw new ArgumentException("Divider can't be 0.");
    }
    return a / b;  
}

Simple - yes, but not effective. The check will be executed in the runtime, so we’ll never be safe. We might catch the exception, present warning to the user and continue program execution, but in case of a complex system, it’s not really helpful.

With Code Contract, we can explicitly define our assumptions and if anywhere in our code they’ll be violated, we’ll be informed about it. Not in runtime only, but right after compiling process is finished.

double Divide(double a, double b)
{
    Contract.Requires(b != 0);
    return a / b;
}

If anywhere in our code we’ll try to execute Divide method with b equals zero, Code Contracts static code analyzer will warn us:

CodeContracts: requires is false: b != 0

Code Contracts and LSP

Code Contracts can help us to detect LSP violations too.

Let’s define a bunch of classes:

public class Human
{
    public int Age { get; set; }
    public int ConsumedCalories { get; set; }

    public Human(int age)
    {
        Contract.Requires(age > 0);
        Contract.Requires(age < 130);
        this.Age = age;
    }

    public virtual void Drink(DrinkableFluid fluid, int ml)
    {
        Contract.Requires(fluid != null);
        Contract.Requires(ml > 0);
        this.ConsumedCalories += Convert.ToInt32(ml * fluid.CaloriesPerMl);
    }
}

public class Child : Human
{
    public Child(int age)
        : base(age)
    {
        Contract.Requires(age > 0);
        Contract.Requires(age < 130);
        this.Age = age;
    }

    public override void Drink(DrinkableFluid fluid, int ml)
    {
        Contract.Requires(!fluid.GetType().IsAssignableFrom(typeof(Alcohol)));
        this.ConsumedCalories += Convert.ToInt32(ml * fluid.CaloriesPerMl);
    }
}

public abstract class DrinkableFluid
{
    public double CaloriesPerMl;
}

public class Sprite : DrinkableFluid
{
    public Sprite()
    {
        this.CaloriesPerMl = 0.27;
    }
}

public class Alcohol : DrinkableFluid
{
}

public class Wine : Alcohol
{
    public Wine()
    {
        this.CaloriesPerMl = 0.85;
    }
}

The problem with code above is poorly designed Human-Child inheritance. Both wine and sprite are drinkable fluids with no doubt. But wine can’t be consumed (sold, not consumed in fact, but let’s assume it can’t be consumed too in the ideal world) by children. If we have the classes Human and Child, we tend to think that Human is an adult. Children aren’t subtype of adults obviously because they can’t substitute the adults. They can’t work, can’t drink alcohol and do many other things. Human-Child inheritance hierarchy is a poor abstraction.

Without Code Contracts, we’d just add a condition in the Drink method of the Child class and throw some exception:

public override void Drink(DrinkableFluid fluid, int ml)
{
    if (fluid is Alcohol)
    {
        throw new ArgumentException("Children can't drink alcohol");
    }
    this.Calories += Convert.ToInt32(ml * fluid.CaloriesPerMl);
}

In the result, we’d be unaware of the problem until someone will execute the following code:

Human human= new Child(10);
DrinkableFluid fluid = new Wine();
human.Drink(fluid, 750);

But with Code Contracts, there’s no risk. Right after end of compilation, we’ll get a warning:

Conclusion

Code Contracts provide tools to explicitly define assumptions about method parameters and by static code analysis help to find bugs before they will appear in the runtime. The contracts can help you to avoid subtle issues like Liskov Substitution Principle violations.

Links

More information about Code Contracts can be found at:

Source code of the example:

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

I’m a full stack developer specialized in design and implementation of web portals using Microsoft technology stack (ASP.NET, ASP.NET MVC, C#, Entity Framework). I put emphasis on SOLID craftsmanship and strive to keep my code clean. Because I know how expensive technical debt can be. Because I understand my job is not to write code, but to solve problems. Because I want to help people to be more effective through the software.

Comments and Discussions

 
QuestionNice article, misleading example Pin
Paul Drury14-Mar-16 22:30
memberPaul Drury14-Mar-16 22:30 
AnswerRe: Nice article, misleading example Pin
Paul Drury15-Mar-16 1:21
memberPaul Drury15-Mar-16 1:21 
Questionit would be easier if ... Pin
Southmountain12-Mar-16 10:40
memberSouthmountain12-Mar-16 10:40 
AnswerRe: it would be easier if ... Pin
Nelek13-Mar-16 2:38
protectorNelek13-Mar-16 2:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 12 Mar 2016

Tagged as

Stats

12K views
7 bookmarked