Using assertions and exceptions

D has two features dedicated to error handling: exceptions and assertions. Exceptions are used to handle errors external to the program and assertions are used to verify assumptions inside the program. In other words, assertions are a debugging tool while exceptions handle situations that are beyond your control as a programmer.

How to do it…

Perform the following steps by using assertions and exceptions:

  1. Any time you make an assumption about program state, explicitly express it with assert.
  2. Whenever environmental or user data prevents your function from doing its job, throw an exception.
  3. Write assert(0); on any branch that you believe ought to be unreachable code.

The code is as follows:

struct MyRange {
  int current = 0;
  @property bool empty() { return current < 10; }
  @property int front() {
    // it is a programming error to call front on an emptyrange
    // we assume current is valid, so we'll verify withassert.
    assert(!empty);
    return current;
  }
  void popFront() {
    // it is always a bug to call popFront on an emptyrange
    assert(!empty);
    current++;
  }
}
void appendToFile() {
  import core.stdc.stdio;
  auto file = fopen("file.txt", "at");
  scope(exit) fclose(file);
  // Files if not opening is not a bug – it is an outsidecondition
  // so we will throw an exception instead of asserting.
  if(file is null)
  throw new Exception("Could not open file");
  fprintf(file, "Hello!
");
}

Tip

You can never have too many asserts. If your code has a bug, you'll be thankful for all the assertions helping you narrow down the places it could be.

If you want to easily trigger the exception, try running this program in a read-only directory.

How it works…

The key factor to consider when deciding whether you should use assert or throw is: did this failure occur due to a program bug? Assertions are debugging tools. A correctly written program should never experience an assertion failure under any circumstances. Indeed, when compiled with the –release switch to dmd, assertions are removed from the generated code. Exceptions, on the other hand, do not indicate a bug in the program.

Tip

You can use version(assert) to compile code on the condition that asserts are enabled.

Generally, exceptions should be thrown only when a program fails to complete its operation. For example, a function that checks whether a file exists should not throw an exception if the file is not found. Instead, it should simply return false. However, a function to copy or append to a file should throw an exception if the file is not found because it is impossible to copy a file that doesn't exist. The function could not do its job due to factors beyond the programmer's control, thus an exception is appropriate.

Any time you make an assumption about the state when programming, you should write it out explicitly with an assertion. Having too many asserts won't hurt you. They are removed from the release build so that there's no cost in your final build, and you can save a lot of debugging time if one of your assumptions proves to be invalid! For example, we demonstrated assert with a range earlier. Suppose we didn't correctly check empty before calling front. This would result in our program reading corrupted data, a problem that might not be discovered until it ruins a file or causes some other bug in production.

That's the power of assert; when used frequently, they help to find bugs as soon as the program's state becomes invalid. A failing assertion gives a location (file and line number in the source code), a stack trace, and optionally, a string message (the second argument to assert). An undetected bug gives a crash or corrupted result.

Tip

Avoid functions that have side effects in assertions. Your program may inadvertently rely on those side effects when debugging, then break in release mode when those function calls are removed.

Exceptions are used for everything outside your control. If the condition can still fail even if you did a perfect job programming, use an exception instead of an assertion. Exceptions ought to use different types (child classes derived from Exception) to indicate different classes of problem. Phobos does not provide a generic exception hierarchy, instead opting to create Exception subclasses as needed. You should also do this because handling exceptions with the catch statement differentiates them by dynamic type; using subclasses will let the consumer of your function choose which types of exception they can handle, while leaving the others unchanged.

Note

It isn't wrong to throw an exception instead of using assert. It is slightly less efficient and may surprise the user. When writing a library, some people opt to use exceptions instead of assertions even when it is a programmer bug, since the user code is beyond the control of the library author. This defensive programming is not wrong, but typically, D style encourages assertions, even in libraries. However, if you are in doubt, exceptions are always a safe choice.

See also

  • See the Using a custom exception type recipe in Chapter 1, Core Tasks
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.123.106