December 12, 2013

Assertions are not bad

Or: I assert that assertions keep the code cleaner

Let's start with a simple example:

public class MyController : ApiController{
  
  public MyController(Repository repository){
    this.repository = repository;
  }

  [HttpPost]
  public MyEntity Create(MyEntity myEntity){

    Debug.Assert(myEntity != null, "Entity must not be null");

    this.repository.Add(myEntity);
    this.repository.Commit();

    return myEntity;
  }

  /* ... */
}
public class MyEntityRepository : Repository<MyEntity>{

  private Dictionary myEntities = new Dictionary<MyEntity, State>();

  public MyEntityRepository(connectionString) : base(connectionString){

  }

  public void Add(MyEntity myEntity) {
    Debug.Assert(myEntity, "Entity must not be null");
    myEntities.Add(myEntity, State.Added);
  }

  public void Delete(MyEntity myEntity) {
    Debug.Assert(myEntity, "Entity must not be null");
    myEntities.Add(myEntity, State.Deleted);
  }

  public void Commit(){

    /*
    Code responsible for persisting.  
    */
  }
}

Most part of the code is self-explaining, but it is necessary to explain the application context for these snippets. MyController is considered to be a web service which is triggered by HTTP-Requests (here expressed by C# WebAPI-like implementation). MyEntityRepository is part of a persistence layer. It just demonstrates the intention for persisting objects. Apparently, both classes make use of the same kind of assertions, but the assertion in MyController is terribly wrong.

Why the assertion in MyController is terribly wrong?

First of all, I do not complain the 'duplicated' assertions. I assume that the user of MyEntityRepository has no code insight, and does not know what's going on inside as the repository class is designed to be reusable. There are other reasons, why I complain the really bad use of assert. Most obviously, it kills the application (a web service!) when the argument is null. Ok, this assertion won't trigger in production code, but that is not the point. The intention of its use is wrong. Most probably someone passes invalid arguments to a web service, which will be serialized to null. The assertion is used as exception handling. The only good thing is, that it won't be triggered in the release version. So, this assertion is crap.

Why the assertions in MyEntityRepository are admissable?

One might tend to allow null for Add and Delete. In the method Commit then all null-entries will be skipped. The code seems more robust then. But, there are some drawbacks when doing so. As an old C++ coder I would say: Waste of memory. Why shall I add something to a list, what I'll never use? Just because a dumb coder used my methods in the wrong way? But memory doesn't matter nowadays (*sigh*). Talking about clean code, we can see that the complexity of Commit grows. Additionally, we delegate the responsibility to another part of our code. Commit tries to deal with erronous additions and/or deletions. Another point is, that the assertion statements in the methods Add and Delete explain the code in a comprehensive way. It is like a functional comment that guarantees a correct logic. This guarantee is intended for the user of the code, not the user of the application.

Assertions are for programmers, not for users

Priorly, I said the intention of use is wrong, but it is more than that. It is about the adressees. Assertions do not adress user, they adress programmer. Assertions help the programmer to avoid logical errors. They apply only for the development cycle, when the programmer deals with debug builds. They are a guidance for the programmer that use the code.
When somebody uses the Repository class, he will be informed that null is not allowed. He knows that he made a severe programming mistake and will change his code according to our assertion. This is the true intent of an assertion.

Assertions are not Exception Handling

Exception handling gives control about undesired but possible occurances during runtime. It is about behavioural failure at runtime, while assertions are about programming failures. It is important to emphasize that exception handling allows to define an appropriate behaviour in case of runtime problems. Assertions do not give any control about behaviour at all. Assertions simply exit the application in a rude way. Exception handling remains in debug and release builds. Assertions apply for debug builds only (at least in C, C++, C# - Java treats assertions slightly different). Usually, Exception handling bubbles from the code abysses up to the surface. In any case they should be handled to keep the application running, or at least exits in a controlled way. Assertions do not bubble, and cannot be handled. Therefore, they are not testable (see paragraph below). As one can see, exception handling and assertions are different but complementary strategies to create better and more robust programs. It is important to know, when to use assertions. The following image depicts it in a general form.

Assertions are not testable

While exceptions are specific events that can and must be handled, assertions cannot be handled and tested. For many coders this is a no-go, and so the do not use assertions. But those who complain the non-testability do not clearly understand assertions. Tests are done against functionality and behaviour. If a test ends in an assertion, then it is simply a programming error. Maybe the test is written wrong, or the assertion in the tested code is wrong. Assertions are per definitionem not intended to be testable.

When to use Assertions?

As the image shows, assertions are for developers, and also is exception handling. How do you know when to use one or another?
First of all, use them wisely. Extensive and unnecessary use of both, assertions and exception handling, clutters code. Usually, there are much less assertions in a code, than exceptions. The following list should help to determine when to use assertions.
  • A situation that shall never ever occur should be protected by an assertion
  • Use assertions only in code parts, which are intended for programmers
  • Think of an assertion as a functional comment, and/or guidance for other developers
  • Assertions trigger only in debug builds.

No comments:

Post a Comment