Skip to content

Coding guide error

Péter Kardos edited this page Jul 28, 2017 · 5 revisions

Error handling guide

The engine uses exceptions for error handling. The main reasons behind this choice are that:

  • exceptions cannot be overlooked
  • exceptions result in much smaller error handling code if properly used along with RAII
  • exceptions travel all up the stack automatically, unlike "if spaghetti" and repetition for error codes.

Error codes are completely prohibited from the engine, even on interface levels. Boolean return values are accepted when overlooking errors is of no signifant consequence, and the nature of the error is a simplistic yes or no, and the error is relatively frequent or expected.

Assertions are heavily used by the engine to help development.

To throw, or not to throw

When to use exceptions and when to use asserts? Both exceptions and assert serve the same purpose: validate input and give a signal if it's wrong. The difference is that asserts make a local decision that the program must be terminated, whereas exceptions let the caller decide. In many cases, lower level code cannot know with what intention it was called, thus it cannot decide to terminate. Use asserts if it is certain that the local or caller context contains a programming bug, use exceptions otherwise.

An assertion can and must be eliminated by fixing the parts of the software that trigger it. Assertions are useful to detect internal state corruption or invalid input that is produced by a bugous caller. An example of state corruption is a null pointer to the previous or next node in a doubly linked list's non-end element. Another one is a binary search tree node having a greater left child. Examples for invalid input are out-of-bounds indices, nullptr input when clearly stated it's invalid, or negative value for elapsed time. Even though these might not mean programmer errors (though likely), it is evident in the context that the caller can and should ensure correct input values. Negative elapsed time is definitely a bug.

Exceptions, on the other hand, are used when something goes wrong on an even lower level, or when we don't know for sure the caller has enough knowledge to verify inputs. Lower level errors include network problems, (graphics) driver issues, missing files or hardware components and the like. In these cases, throw an exception to let the caller decide. If the problem is really fatal, the exception will just slip through and terminate just like an assertion. Back to the out-of-bounds indices, why don't we throw an exception? Because the caller most certainly has the knowledge that negative indices are not cool, thus, its the caller's job to fix it. Another case is when we have two rendering nodes: one creates a texture, the other renders a depth prepass into that buffer. The depth prepass node gets the target texture from the texture creator node, not the caller (who requests the rendering), and it can't blame receiving read-only textures on the caller. Someone else made a mistake, so throw an exception and let the bosses deal with it. The callers might just ask the engine user to change rearrange pipeline nodes, without terminating the application.

To handle, or not to handle

The lack of error handling is not tolerated. Errors must be dealt with as described above.

Clone this wiki locally