Exceptions vs Assertion for library API?
Any advice in choosing between throwing an exception vs asserting for invalid uses of a library API? I'm building a small "transactional" library I'd like to contribute to boost where certain API calls are constrained to only be valid if the transaction is still "in-progress". Such calls will never succeed, and are easy for the implementation to diagnose. I'd like to have the library fail an assertion, which is far clearer to the user that the code logic is incorrect, but it bothers me that assertions are much more difficult to write unit tests for. (Is there even a concept of a test that passes if if fails to compile or fails with an assertion in the boost testing framework?) I suppose I could mangle the interface to ensure that the user has nothing to invoke the API calls in question unless the transaction is in-progress, but that generally requires a custom "lock" interface for my RAII implementation that makes the API more complex to grasp. -- Jon Biggar jon@biggar.org
On 10.11.2017 20:53, Jonathan Biggar via Boost wrote:
Any advice in choosing between throwing an exception vs asserting for invalid uses of a library API?
Interesting question ! I'd strongly argue for assertions, against exceptions, in that case.
I'm building a small "transactional" library I'd like to contribute to boost where certain API calls are constrained to only be valid if the transaction is still "in-progress". Such calls will never succeed, and are easy for the implementation to diagnose.
Now that is different: now the question is about behaviour, i.e. do you want the library to report the error, or do you want to assume that the input is valid ? Assertions are useful if you allow different build variants, where a "debug" variant can be used to validate application code against your library, while a "release" variant simply assumes that the code uses your library correctly. If you don't want to make that distinction, i.e. always want to have a check, that would be an argument for exceptions.
I'd like to have the library fail an assertion, which is far clearer to the user that the code logic is incorrect, but it bothers me that assertions are much more difficult to write unit tests for. (Is there even a concept of a test that passes if if fails to compile or fails with an assertion in the boost testing framework?)
Yes, if you want to test the assertion of preconditions and invariants, you can certainly do that. Make sure that an invalid argument to a function call has the expected result, i.e. either an exception or an abort. Of course, you wouldn't be able to test that in-process (as the process would abort), but with a robust testing framework you can catch aborts as valid process outcomes. Of course, testing for raised exceptions is even easier, as they don't require you to run testsĀ in sub-processes.
I suppose I could mangle the interface to ensure that the user has nothing to invoke the API calls in question unless the transaction is in-progress, but that generally requires a custom "lock" interface for my RAII implementation that makes the API more complex to grasp.
Regards, Stefan -- ...ich hab' noch einen Koffer in Berlin...
AMDG On 11/10/2017 06:53 PM, Jonathan Biggar via Boost wrote:
Any advice in choosing between throwing an exception vs asserting for invalid uses of a library API?
I'm building a small "transactional" library I'd like to contribute to boost where certain API calls are constrained to only be valid if the transaction is still "in-progress". Such calls will never succeed, and are easy for the implementation to diagnose. I'd like to have the library fail an assertion, which is far clearer to the user that the code logic is incorrect, but it bothers me that assertions are much more difficult to write unit tests for. (Is there even a concept of a test that passes if if fails to compile or fails with an assertion in the boost testing framework?)
You should not define the API as asserting. Instead, you should say that if the preconditions are not met, the behavior is undefined. Then you can implement it using an assertion for normal use, with the ability to switch to an exception for testing.
I suppose I could mangle the interface to ensure that the user has nothing to invoke the API calls in question unless the transaction is in-progress, but that generally requires a custom "lock" interface for my RAII implementation that makes the API more complex to grasp.
It's hard to say, without actually seeing the API in question, but when possible, it's usually better to design APIs that cannot be misused rather than relying on the user to make calls in the correct sequence. In Christ, Steven Watanabe
Jonathan Biggar wrote:
I'd like to have the library fail an assertion, which is far clearer to the user that the code logic is incorrect, but it bothers me that assertions are much more difficult to write unit tests for. (Is there even a concept of a test that passes if if fails to compile or fails with an assertion in the boost testing framework?)
Easiest is probably to just use a run-fail test. A failed assertion exits
the process with a nonzero code.
Or, you can define BOOST_ENABLE_ASSERT_HANDLER and do whatever you like in
your assertion handler, including throwing an exception that you can then
test with BOOST_TEST_THROWS, but this requires your functions to not be
noexcept. If they are, you either need to conditionally remove the noexcept
from them when some "test mode" macro is defined, or do something like
#include
Hi, Am 11.11.2017 02:53, schrieb Jonathan Biggar via Boost:
I'm building a small "transactional" library I'd like to contribute to boost where certain API calls are constrained to only be valid if the transaction is still "in-progress".
I think there is some room of improvement on the API, whenever an issue like that arises. For a transactional library I'd try and create an interface, where the lifetime of the transaction object is in line with the transaction being "in progress". The destructor would simply roll back, if the transaction has not been moved from, while a commit() function that takes an rvalue reference to a transaction commits it. The compiler would warn, if the transaction object is used after it has been moved from. Something like this: ---- // begin transaction auto t = transaction{transactional_memory}; // do something auto bar = t.find_foo("bar"); // might throw bar.extend("blabla"); // will commit if extend() did not throw, rollback otherwise. // Note, that t is moved into the parameter of commit here. commit(t); // undefined behaviour, compiler will warn. auto lala = t.find_foo("lala"); // will do move assignment. t is usable again, // but now refers to the other transaction. t = get_another_transaction_somehow(); // now this is safe, but will of course use the new transaction. auto lala = t.find_foo("lala"); ---- But maybe I just misunderstood, what you are trying to do. Christof
participants (5)
-
Christof Donat
-
Jonathan Biggar
-
Peter Dimov
-
Stefan Seefeld
-
Steven Watanabe