Design by contract using assertions or exceptions?

2019-01-02 19:07发布

When programming by contract a function or method first checks whether its preconditions are fulfilled, before starting to work on its responsibilities, right? The two most prominent ways to do these checks are by assert and by exception.

  1. assert fails only in debug mode. To make sure it is crucial to (unit) test all separate contract preconditions to see whether they actually fail.
  2. exception fails in debug and release mode. This has the benefit that tested debug behavior is identical to release behavior, but it incurs a runtime performance penalty.

Which one do you think is preferable?

See releated question here

14条回答
像晚风撩人
2楼-- · 2019-01-02 19:55

The rule of thumb, to me, is that use assert expressions to find internal errors and exceptions for external errors. You can benefit much from the following discussion by Greg from here.

Assert expressions are used to find programming errors: either errors in the program's logic itself or in errors in its corresponding implementation. An assert condition verifies that the program remains in a defined state. A "defined state" is basically one that agrees with the program's assumptions. Note that a "defined state" for a program need not be an "ideal state" or even "a usual state", or even a "useful state" but more on that important point later.

To understand how assertions fit into a program, consider a routine in a C++ program that is about to dereference a pointer. Now should the routine test whether the pointer is NULL before the dereferencing, or should it assert that the pointer is not NULL and then go ahead and dereference it regardless?

I imagine that most developers would want to do both, add the assert, but also check the pointer for a NULL value, in order not to crash should the asserted condition fail. On the surface, performing both the test and the check may seem the wisest decision

Unlike its asserted conditions, a program's error handling (exceptions) refers not to errors in the program, but to inputs the program obtains from its environment. These are often "errors" on someone's part, such as a user attempting to login to an account without typing in a password. And even though the error may prevent a successful completion of program's task, there is no program failure. The program fails to login the user without a password due to an external error - an error on the user's part. If the circumstances were different, and the user typed in the correct password and the program failed to recognize it; then although the outcome would still be the same, the failure would now belong to the program.

The purpose of error handling (exceptions) is two fold. The first is to communicate to the user (or some other client) that an error in program's input has been detected and what it means. The second aim is to restore the application after the error is detected, to a well-defined state. Note that the program itself is not in error in this situation. Granted, the program may be in a non-ideal state, or even a state in which can do nothing useful, but there is no programming errorl. On the contrary, since the error recovery state is one anticipated by the program's design, it iss one that the program can handle.

PS: you may want to check out the similar question: Exception Vs Assertion.

查看更多
栀子花@的思念
3楼-- · 2019-01-02 19:57

Asserts are for catching something a developer has done wrong (not just yourself - another developer on your team also). If it's reasonable that a user mistake could create this condition, then it should be an exception.

Likewise think about the consequences. An assert typically shuts down the app. If there is any realistic expectation that the condition could be recovered from, you should probably use an exception.

On the other hand, if the problem can only be due to a programmer error then use an assert, because you want to know about it as soon as possible. An exception might be caught and handled, and you would never find out about it. And yes, you should disable asserts in the release code because there you want the app to recover if there is the slightest chance it might. Even if the state of your program is profoundly broken the user just might be able to save their work.

查看更多
弹指情弦暗扣
4楼-- · 2019-01-02 19:59

The principle I follow is this: If a situation can be realistically avoided by coding then use an assertion. Otherwise use an exception.

Assertions are for ensuring that the Contract is being adhered to. The contract must be fair, so that client must be in a position to ensure it complies. For example, you can state in a contract that a URL must be valid because the rules about what is and isn't a valid URL are known and consistent.

Exceptions are for situations that are outside the control of both the client and the server. An exception means that something has gone wrong, and there's nothing that could have been done to avoid it. For example, network connectivity is outside the applications control so there is nothing that can be done to avoid a network error.

I'd like to add that the Assertion / Exception distinction isn't really the best way to think about it. What you really want to be thinking about is the contract and how it can be enforced. In my URL example above that best thing to do is have a class that encapsulates a URL and is either Null or a valid URL. It is the conversion of a string into a URL that enforces the contract, and an exception is thrown if it is invalid. A method with a URL parameter is much clearer that a method with a String parameter and an assertion that specifies a URL.

查看更多
谁念西风独自凉
5楼-- · 2019-01-02 20:01

I outlined my view on the state of the matter here: How do you validate an object's internal state? . Generally, assert your claims and throw for violation by others. For disabling asserts in release builds, you can do:

  • Disable asserts for expensive checks (like checking whether a range is ordered)
  • Keep trivial checks enabled (like checking for a null pointer or a boolean value)

Of course, in release builds, failed assertions and uncaught exceptions should be handled another way than in debug builds (where it could just call std::abort). Write a log of the error somewhere (possibly into a file), tell the customer that an internal error occurred. The customer will be able to send you the log-file.

查看更多
妖精总统
6楼-- · 2019-01-02 20:02

Disabling assert in release builds is like saying "I will never have any issues whatsoever in a release build", which is often not the case. So assert shouldn't be disabled in a release build. But you don't want the release build crashing whenever errors occur either, do you?

So use exceptions and use them well. Use a good, solid exception hierarchy and ensure that you catch and you can put a hook on exception throwing in your debugger to catch it, and in release mode you can compensate for the error rather than a straight-up crash. It's the safer way to go.

查看更多
流年柔荑漫光年
7楼-- · 2019-01-02 20:02

The rule of thumb is that you should use assertions when you are trying to catch your own errors, and exceptions when trying to catch other people's errors. In other words, you should use exceptions to check the preconditions for the public API functions, and whenever you get any data that are external to your system. You should use asserts for the functions or data that are internal to your system.

查看更多
登录 后发表回答