Hello,
My name is Pierre Talbot, I participated to GSoC 2011 and began the
implementation of the Boost.Check library (for checking the validity of
any number having a check digit — credit card number, ISBN, …). It was
two years ago and the design is still evolving due to many variation
points in the code. One of the function looks like :
template
boost::optional<typename check_algo::checkdigit_type>
compute_checkdigit(const range &x);
Using boost::optional tell us that: "A check digit should be computed if
'x' is a valid sequence". Since 'x' has many reasons to be incorrect,
many errors could be raised. Based on a policy template class, it
launches exception or returns with an empty optional . Then I though a
lot about a better way to do it, allowing the user to get an exception
or an error code. But it was quite complex for a so little part of my
library… I decided that the policy "throw exception or nothing" should
be enough.
Yesterday, I watched the video of Mr. Alexandrescu on Boost.Expected and
I think it would be a very useful library. It could mainly be useful in
system programming (where many error codes can arise from a single
call), but in any code where many exception errors can be thrown (like
in Boost.Check).
As you suspected, I'm interested in coding Boost.Expected during the
summer as a GSoC student.
Firstly, it could be very useful to list the resources on the subject
(aside the talk), I have several articles that I will talk about later.
Secondly, and hoping you'll debate, I would like to ask your opinion
about several ideas and facts:
1) In the Boost project description, we can read: "adding a class
expected-or-error_code". Obviously, the main design decision made by
Alexandrescu is to consider that error code are exception. Do we need an
abstraction of exception/error code ? Why do you think a
"expected-or-error_code" class is interesting ?
2) Consider this code (from Alexandrescu slides) :
// Caller
string s = readline();
auto x = parseInt(s).get(); // throw on error
auto y = parseInt(s); // won’t throw
if (!y.valid()) {
// handle locally
if (y.hasExceptionstd::invalid_argument()) { // ------------------ <
The flagged line.
// no digits
...
}
y.get(); // just "re"throw
}
The flagged line has some tastes of "return to the past" flavor. Back to
the C procedural language, the basic error code handling system was a
lot criticized because:
* Readability of the code decrease ;
* Error handling occurs in the middle of the execution flow ;
* <Add your favourite reason here>.
Several links on the subjects (if you have others, I'm interested)
http://www.joelonsoftware.com/articles/Wrong.html
http://www.joelonsoftware.com/items/2003/10/13.html
http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx
http://nedbatchelder.com/text/exceptions-vs-status.html
Basically, only the last one is clearly for exception. The main argument
against the procedural approach is the readability. I would say that the
Expected approach just differ by allowing to rethrow exception. But if
you want to handle it, you must code multiple if-than-else statements.
So I considered a complementary approach working with Expected to handle
multiple error cases:
string visa_number = readline();
expected<char> expected_checkdigit = compute_checkdigit<visa>(visa_number);
if(expected_checkdigit.valid(visa_error_resolver))
{
visa_number += expected_checkdigit.get();
std::cout << visa_number << std::endl;
}
With this code, there is only a if statement, and no more multiple error
cases handles. But what is this error_resolver ?
It may be declared as :
// Somewhere in visa.hpp. A type list.
typedef error_list visa_errors;
// Somewhere in the user code.
error_resolver visa_error_resolver; // in
this case, expected_type is a char.
// Initialize error handler on specific exception/errors.
visa_error_resolver.on(size_error_handler)
.on(unknown_character_exception)
...
Now we are agree that visa_error_resolver can be reused everywhere we
want to resolve an error on a visa number.
What are the handlers ? There are [Function|Functor|Lambda] (pick up
your favourite) with this form :
expected<ExpectedType> size_error_handler(const size_error_exception&)
expected<ExpectedType> unknown_character_exception(const
unknown_character_exception&)
Now you can understand for what the type list "error_list" stands for,
we can store these handlers into the error_resolver and call them
without any virtual cost.
Why the return type of error handler is expected<ExpectedType> ?
Consider this size_error_handler code :
expected<ReturnType> size_error_handler(const size_error_exception& e)
{
std::cout << "The number you gave has a bad size." << std::endl;
std::cout << "Enter it again : " << std::endl;
return read_visa_checkdigit();
}
read_visa_checkdigit can call recursively valid() until it's valid.
Though there are some ways to make this treatment iterative.
A basic treatment could be to print an warning message and just returns
(in this case, valid returns false):
expected<ReturnType> size_error_handler(const size_error_exception& e)
{
std::cout << "Warning: the VISA field is incorrect." << std::endl;
return expected<ReturnType>::fromException(e);
}
Results:
* The error code handling is delegated to specific functions ;
* The readability is still excellent ;
* You can easily re-use your error handler function ;
* If you don't like it, you can still throw exception on failure with
"get()".
I can code a "proof of concept" if you think this is a good idea.
Do not hesitate to comment it, point out programming pitfalls, request
further clarification, or anything you judge useful.
Thank you for reading it !
Pierre Talbot