Exception handling and encapsulation

B

benben

Hello C++ experts,

This is rather a design question, I must say. My question is, how do you
write exception safe code while ensuring encapsulation?

In C++, if a function by itself does not know how to handle an exception
raised from a lower level system, we usually implement the function in
such a way that it is exception safe, yet allow the exception to be
propagated to a higher level system.

However, since each system level defines a certain abstraction, allowing
exceptions to propagate through multiple system levels may breach
encapsulation, because it exposes low level implementation details. The
exception hence can be less useful than it should be, as the higher
level system will have little knowledge how such low level exceptions
are to be handled accurately.

Furthermore, should the higher level system choose to handle exceptions
from multiple layers down, it risks to be turned invalid as soon as
changes are made to the lower level systems. This is very similar to
accessing a class member that is ought to be private.

To give an example, consider the following program, which consists of
three distinct system levels: at the lowest, a database class; up one
level, an enrolment form; and at the highest level, the main function
that uses the enrolment forms.



#include <iostream>
#include <sstream>

// Incomplete program, will not link.



///////////////////////////////////////////////////
// Low level system

class connection_error{};
class IO_error{};
class repetition_error{};

class database
{
public:
void connect(std::string location) throw (connection_error);
void create_record(std::string rc) throw (IO_error,
repetition_error);
// ...
};




///////////////////////////////////////////////////
// Mid level system

class submission_error{};
class invalid_name{};
class invalid_age{};

class enrolment_form
{
std::string name;
unsigned int age;

void check() const
{
if (name.size() == 0)
throw invalid_name();

if (age > 80 || age < 18)
throw invalid_age();
}

public:

enrolment_form(std::string _name,
unsigned int _age);

// submission routine version 1
// throw everything has it
void submit1() const
{
check(); // may throw invalid_name
// or invalid age

database db;
db.connect("enrolment"); // may throw connection_error

std::eek:stringstream oss;
oss << name << ":" << age;

db.create_record(oss.str()); // may throw IO_error
}


// submission routine version 2
// translate lower level exceptions
void submit2() const
try
{
submit1();
}
catch (connection_error)
{
throw submission_error();
}
catch (IO_error)
{
throw submission_error();
}
};



///////////////////////////////////////////////////
// High level

int main()
{
enrolment_form form1("Ben", 22);
form1.submit1();

enrolment_form form2("Bob", 22);
form2.submit2();
}



Here is the dilemma:

1) If main() is to handle the exceptions from submit1() member function
call, it will have to deal with exceptions from both enrolment_form
(mid-level) and database (low-level.) Since the use of a database is to
be encapsulated from main(), the writer of main() obviously knows little
how to handle such exceptions.

2) If the mid-level enrolment_form class is to catch every lowe-level
exceptions and translate it to one of its own (but higher level in
abstraction) exception types, such as submission_error, encapsulation is
enforced but:

a. The writer of main() will be equally clueless on how exceptions, such
as submission_error shall be handled, since the nature of the exception
is abstracted away (that is, the type information of the original
exception, at least)

b. The implementation of mid-level system, enrolment_form, is
complicated as it is responsible to translate all lower-level
exceptions. For the very least, functions are littered with try-catch
blocks, which is ugly IMO.

It can be done though, through snowballing fashion, in which the lower
level exception information is rolled into the translated exception
every time the exception propagates up one abstraction level. The down
side of this solution is it is complicated. It requires exceptions to
have more complex data structure which itself may raise a few terminal
exceptions. Exception types with different conventions can also make
this strategy very difficult, if not at all impossible.


Most of you must have been involved in some large C++ projects dealing
with such problems. I would like to hear what you think, how you deal
with it in your designs, what tools you use to levitate such problem, or
just generally what other options I still have. I would love to find out
a graceful solution to problems of this kind!

Thanks!

Ben
 
P

Paul Brettschneider

benben said:
Hello C++ experts,

I'm not an expert by any means, but I hope you don't mind if I throw in my
2c.
This is rather a design question, I must say. My question is, how do you
write exception safe code while ensuring encapsulation?

RAII and KISS.
If your application is designed from ground up with this two principles in
mind, I think you will have no big problem with exception handling.
In C++, if a function by itself does not know how to handle an exception
raised from a lower level system, we usually implement the function in
such a way that it is exception safe, yet allow the exception to be
propagated to a higher level system.

I don't quite understand: what do you mean with "know how to handle an
exception"? An exception is just that: an exceptional event which prevents
processing as usual. So it depends on what kind of application you're
designing: A GUI application would typically alert the user, a server
application would write an error message to the client and write a line to
the log-file and finally a CLI application would print an error to the
console and stop execution.

So basically my answer would be to catch the exception at the highest
possible level.
[...]
Furthermore, should the higher level system choose to handle exceptions
from multiple layers down, it risks to be turned invalid as soon as
changes are made to the lower level systems. This is very similar to
accessing a class member that is ought to be private.

Why would it? The exception should know how to produce a sensible error
message, therefore any change in the lower level would just affect the
exception itself, but not the higher level. All you need is a sane
interface for exceptions. Or am I missing something?
 
B

benben

[snip]
I don't quite understand: what do you mean with "know how to handle an
exception"? An exception is just that: an exceptional event which prevents
processing as usual. So it depends on what kind of application you're
designing: A GUI application would typically alert the user, a server
application would write an error message to the client and write a line to
the log-file and finally a CLI application would print an error to the
console and stop execution.

Thanks for you comment. But let me say a few words...

Alerting the user or logging the error is fine. However, in some
situations the software should do more than just that, in order to
recover from the exceptional event. For example, depending on the
situation, to handle a network connection error a program can resend a
packet or re-establish a new connection or what have you.

What I wanted to say in the original message, is that the exception that
can be thrown from a subsystem is part of the system's interface and
will too participate in the encapsulation process. Letting an exception
from much lower level to propagate to a high level function breaches the
encapsulation.

For example, you are going to an ATM machine and try to check your bank
account. Somewhere in the ATM software system a low level logging
function finds that the log file is full. It throws an exception and the
it is not caught until it hits the highest level--you as the exception
handler.

You are notified, "operation halt: log file full". You as a user must be
much puzzled, because the error message is too low level in abstraction,
and does not give you any useful information about what goes wrong.
Ideally you should only be exposed to an abstraction of the immediate
lower system where exceptions such as "wrong pin" or "card expired" are
raised occasionally.

What do you do? Perhaps try again? Or maybe you should use another ATM
machine, or just go to the bank counter and file a complaint. But then
your decision (to handle the exception) is not based on the nature of
the exception. You may have some idea what "log file full" means, but
others don't.

So basically my answer would be to catch the exception at the highest
possible level.

In my opinion, though, an exception shall be handled at the lowest
possible level. This minimizes the system complexity as this piece of
implementation detail is abstracted away from the higher level systems.

It is only when exception handling locally is impossible or unnecessary
the exception is to propagate upwards. This is exactly where my problem
comes in.

Even if the exception is to propagate upwards, it does not mean it
should always go all the way to the highest level (e.g. main()). It
depends on the problem nature, IMO.
[...]
Furthermore, should the higher level system choose to handle exceptions
from multiple layers down, it risks to be turned invalid as soon as
changes are made to the lower level systems. This is very similar to
accessing a class member that is ought to be private.

Why would it? The exception should know how to produce a sensible error
message, therefore any change in the lower level would just affect the
exception itself, but not the higher level. All you need is a sane
interface for exceptions. Or am I missing something?

It gets complicated when the exception means more than just producing a
sensible error message.

Regards,
Ben
 
A

Alf P. Steinbach

* benben:
Hello C++ experts,

This is rather a design question, I must say. My question is, how do you
write exception safe code while ensuring encapsulation?

In C++, if a function by itself does not know how to handle an exception
raised from a lower level system, we usually implement the function in
such a way that it is exception safe, yet allow the exception to be
propagated to a higher level system.

However, since each system level defines a certain abstraction, allowing
exceptions to propagate through multiple system levels may breach
encapsulation, because it exposes low level implementation details. The
exception hence can be less useful than it should be, as the higher
level system will have little knowledge how such low level exceptions
are to be handled accurately.

Furthermore, should the higher level system choose to handle exceptions
from multiple layers down, it risks to be turned invalid as soon as
changes are made to the lower level systems. This is very similar to
accessing a class member that is ought to be private.

An exception just says "I failed", possibly augmented with "because".

When you receive an exception you have just two options: achieve the
goal anyway, or in turn fail and report that via an exception.

In some cases the "because" is critical to select the proper way to
achieve the goal in spite of the exception, e.g. possible retry versus
trying some other way to do the same versus informing the user (or
whatever that's allowed as success by the function's contract). In that
case the exception generally needs to be handled by the immediate
caller, or at least not very far up the call chain, where there is
knowledge of possible "because" reasons. Otherwise it doesn't really
matter where in the call chain the exception is finally caught.

What you write about exposing low level implementation details is only
relevant for logging and reporting exceptions.

For logging you want as much detail as practically possible, extracted
from the exception object and the immediate call context for where the
exception originated, plus possibly the entire call chain, versus for an
interactive user you usually want a highest level description possible,
extracted from the exception object or possibly just the exception
occurrence, plus the highest level call context, the highest level goal
to be achieved. These are contradictory requirements. Happily logging
and reporting to the user do not need to be done by the same code... ;-)

To give an example, consider the following program, which consists of
three distinct system levels: at the lowest, a database class; up one
level, an enrolment form; and at the highest level, the main function
that uses the enrolment forms.



#include <iostream>
#include <sstream>

// Incomplete program, will not link.



///////////////////////////////////////////////////
// Low level system

class connection_error{};
class IO_error{};
class repetition_error{};

Use standard exception classes, i.e. derive from std::exception, or even
better (in general) from std::runtime_error.

class database
{
public:
void connect(std::string location) throw (connection_error);

And don't use exception specifications (except possibly empty ones).

void create_record(std::string rc) throw (IO_error,
repetition_error);
// ...
};
Here is the dilemma:

1) If main() is to handle the exceptions from submit1() member function
call, it will have to deal with exceptions from both enrolment_form
(mid-level) and database (low-level.) Since the use of a database is to
be encapsulated from main(), the writer of main() obviously knows little
how to handle such exceptions.

Just do

catch( std::exception const& ) { ... }


Cheers, & hth.,

- Alf
 
J

Jim Langston

benben said:
Hello C++ experts,

This is rather a design question, I must say. My question is, how do you
write exception safe code while ensuring encapsulation?

In C++, if a function by itself does not know how to handle an exception
raised from a lower level system, we usually implement the function in
such a way that it is exception safe, yet allow the exception to be
propagated to a higher level system.

However, since each system level defines a certain abstraction, allowing
exceptions to propagate through multiple system levels may breach
encapsulation, because it exposes low level implementation details. The
exception hence can be less useful than it should be, as the higher level
system will have little knowledge how such low level exceptions are to be
handled accurately.

Furthermore, should the higher level system choose to handle exceptions
from multiple layers down, it risks to be turned invalid as soon as
changes are made to the lower level systems. This is very similar to
accessing a class member that is ought to be private.

To give an example, consider the following program, which consists of
three distinct system levels: at the lowest, a database class; up one
level, an enrolment form; and at the highest level, the main function that
uses the enrolment forms.

In general, you want to handle the exception at the lowest level that can do
something about it, either fix it (attempt to relog into the database, try
to reopen the file, etc...). A lot of times you will have to handle the
exception in a number of places. The function that actually caused the
exception may need to catch it and if it can't fix it itself, perhaps set
some state before propogating it upward (in a database where the exception
was connection closed, if it can't reestablish the connection, but the
connection status in a bad state somehow) and then the higher level would
catch the exception and do what it can about it (the caller sees that the
connection was closed, it can try to re-establish connection, if it can't
send a UI message that the connection was closed to the user), and so on up
until the error is fixed, or the program is terminated.
#include <iostream>
#include <sstream>

// Incomplete program, will not link.



///////////////////////////////////////////////////
// Low level system

class connection_error{};
class IO_error{};
class repetition_error{};

class database
{
public:
void connect(std::string location) throw (connection_error);
void create_record(std::string rc) throw (IO_error,
repetition_error);
// ...
};




///////////////////////////////////////////////////
// Mid level system

class submission_error{};
class invalid_name{};
class invalid_age{};

class enrolment_form
{
std::string name;
unsigned int age;

void check() const
{
if (name.size() == 0)
throw invalid_name();

if (age > 80 || age < 18)
throw invalid_age();
}

public:

enrolment_form(std::string _name,
unsigned int _age);

// submission routine version 1
// throw everything has it
void submit1() const
{
check(); // may throw invalid_name
// or invalid age

database db;
db.connect("enrolment"); // may throw connection_error

std::eek:stringstream oss;
oss << name << ":" << age;

db.create_record(oss.str()); // may throw IO_error
}


// submission routine version 2
// translate lower level exceptions
void submit2() const
try
{
submit1();
}
catch (connection_error)
{
throw submission_error();
}
catch (IO_error)
{
throw submission_error();
}
};



///////////////////////////////////////////////////
// High level

int main()
{
enrolment_form form1("Ben", 22);
form1.submit1();

enrolment_form form2("Bob", 22);
form2.submit2();
}



Here is the dilemma:

1) If main() is to handle the exceptions from submit1() member function
call, it will have to deal with exceptions from both enrolment_form
(mid-level) and database (low-level.) Since the use of a database is to be
encapsulated from main(), the writer of main() obviously knows little how
to handle such exceptions.

1. Where did submit() get the information of "enrolment"? If it is actually
hard coded and the connection can not be established, then there is nothing
that the program can do about the connection error at all. The user will
have to find out why it is not establishing connection. In this case it has
to propogate up to the UI with a message that there is a connection error
and the user will have to fix it (ooop, disconnected the network cable).

If submit() got the information of "enrolment" from somewhere else, perhaps
it's a variable, where did it come from? Was it passed into the constructor?
If so, then whatever constructed the class needs to handle the error, and
with this simple 2 class system it had to of been main, so again, main has
to handle the exception.

Same with the user name and age.

Again, the lowest level that can handle the exception and do something about
it should handle it. If it can't it needs to propogate it up into the user
interface and state there is a problem and give the user a chance to fix it
(type in the correct name and age or check the connection or type in the
correct database to connect to, etc...).

With data errors (which these both are) the user is the only one that can
fix it, and so it has to propogate up to the user interface, main or
whatever handles the UI.

With other types of errors (out of disk space, out of memory) then a lower
level system can sometimes handle it (try a smaller file size, delete a temp
file it created, try another disk, try allocating more memory, etc...) and
so it doesn't have to propogate up. But if it does again it goes to the
user interface "out of disk space" "out of memory" etc...
 
G

Guest

[snip]
I don't quite understand: what do you mean with "know how to handle an
exception"? An exception is just that: an exceptional event which prevents
processing as usual. So it depends on what kind of application you're
designing: A GUI application would typically alert the user, a server
application would write an error message to the client and write a line to
the log-file and finally a CLI application would print an error to the
console and stop execution.

Thanks for you comment. But let me say a few words...

Alerting the user or logging the error is fine. However, in some
situations the software should do more than just that, in order to
recover from the exceptional event. For example, depending on the
situation, to handle a network connection error a program can resend a
packet or re-establish a new connection or what have you.

What I wanted to say in the original message, is that the exception that
can be thrown from a subsystem is part of the system's interface and
will too participate in the encapsulation process. Letting an exception
from much lower level to propagate to a high level function breaches the
encapsulation.

Yes, an exception should generally be handled at the lowest level
possible. Some exceptions can not be handled in the current subsystem
and then it might be a good idea to catch it in the subsystem and then
throw a new more generic error to the caller of the subsystem.
For example, you are going to an ATM machine and try to check your bank
account. Somewhere in the ATM software system a low level logging
function finds that the log file is full. It throws an exception and the
it is not caught until it hits the highest level--you as the exception
handler.

You are notified, "operation halt: log file full". You as a user must be
much puzzled, because the error message is too low level in abstraction,
and does not give you any useful information about what goes wrong.
Ideally you should only be exposed to an abstraction of the immediate
lower system where exceptions such as "wrong pin" or "card expired" are
raised occasionally.

What do you do? Perhaps try again? Or maybe you should use another ATM
machine, or just go to the bank counter and file a complaint. But then
your decision (to handle the exception) is not based on the nature of
the exception. You may have some idea what "log file full" means, but
others don't.

The user should just be informed that an unrecoverable error has
occurred, perhaps with the possibility to get more information.
In my opinion, though, an exception shall be handled at the lowest
possible level. This minimizes the system complexity as this piece of
implementation detail is abstracted away from the higher level systems.

Yes, an exception is an error that can not be handled at the current
level and should thus propagate up to a level where it can be handled.
At suitable places (such as subsystem boundaries) the exception might be
caught and a new more general exception can be thrown.
It is only when exception handling locally is impossible or unnecessary
the exception is to propagate upwards. This is exactly where my problem
comes in.

If it is unnecessary to handle the exception it should not have been
thrown to begin with.
Even if the exception is to propagate upwards, it does not mean it
should always go all the way to the highest level (e.g. main()). It
depends on the problem nature, IMO.

Only unrecoverable errors (or those that can be handled in main())
should ever reach main(), those that can not be handled should there be
caught and an error message be written and the application terminated
with as much clean-up as possible.
 
P

paolo.brandoli

Hello C++ experts,

This is rather a design question, I must say. My question is, how do you
write exception safe code while ensuring encapsulation?

In C++, if a function by itself does not know how to handle an exception
raised from a lower level system, we usually implement the function in
such a way that it is exception safe, yet allow the exception to be
propagated to a higher level system.

However, since each system level defines a certain abstraction, allowing
exceptions to propagate through multiple system levels may breach
encapsulation, because it exposes low level implementation details. The
exception hence can be less useful than it should be, as the higher
level system will have little knowledge how such low level exceptions
are to be handled accurately.

Furthermore, should the higher level system choose to handle exceptions
from multiple layers down, it risks to be turned invalid as soon as
changes are made to the lower level systems. This is very similar to
accessing a class member that is ought to be private.

To give an example, consider the following program, which consists of
three distinct system levels: at the lowest, a database class; up one
level, an enrolment form; and at the highest level, the main function
that uses the enrolment forms.

#include <iostream>
#include <sstream>

// Incomplete program, will not link.

///////////////////////////////////////////////////
// Low level system

class connection_error{};
class IO_error{};
class repetition_error{};

class database
{
public:
void connect(std::string location) throw (connection_error);
void create_record(std::string rc) throw (IO_error,
repetition_error);
// ...
};

///////////////////////////////////////////////////
// Mid level system

class submission_error{};
class invalid_name{};
class invalid_age{};

class enrolment_form
{
std::string name;
unsigned int age;

void check() const
{
if (name.size() == 0)
throw invalid_name();

if (age > 80 || age < 18)
throw invalid_age();
}

public:

enrolment_form(std::string _name,
unsigned int _age);

// submission routine version 1
// throw everything has it
void submit1() const
{
check(); // may throw invalid_name
// or invalid age

database db;
db.connect("enrolment"); // may throw connection_error

std::eek:stringstream oss;
oss << name << ":" << age;

db.create_record(oss.str()); // may throw IO_error
}

// submission routine version 2
// translate lower level exceptions
void submit2() const
try
{
submit1();
}
catch (connection_error)
{
throw submission_error();
}
catch (IO_error)
{
throw submission_error();
}
};

///////////////////////////////////////////////////
// High level

int main()
{
enrolment_form form1("Ben", 22);
form1.submit1();

enrolment_form form2("Bob", 22);
form2.submit2();
}

Here is the dilemma:

1) If main() is to handle the exceptions from submit1() member function
call, it will have to deal with exceptions from both enrolment_form
(mid-level) and database (low-level.) Since the use of a database is to
be encapsulated from main(), the writer of main() obviously knows little
how to handle such exceptions.

2) If the mid-level enrolment_form class is to catch every lowe-level
exceptions and translate it to one of its own (but higher level in
abstraction) exception types, such as submission_error, encapsulation is
enforced but:

a. The writer of main() will be equally clueless on how exceptions, such
as submission_error shall be handled, since the nature of the exception
is abstracted away (that is, the type information of the original
exception, at least)

b. The implementation of mid-level system, enrolment_form, is
complicated as it is responsible to translate all lower-level
exceptions. For the very least, functions are littered with try-catch
blocks, which is ugly IMO.

It can be done though, through snowballing fashion, in which the lower
level exception information is rolled into the translated exception
every time the exception propagates up one abstraction level. The down
side of this solution is it is complicated. It requires exceptions to
have more complex data structure which itself may raise a few terminal
exceptions. Exception types with different conventions can also make
this strategy very difficult, if not at all impossible.

Most of you must have been involved in some large C++ projects dealing
with such problems. I would like to hear what you think, how you deal
with it in your designs, what tools you use to levitate such problem, or
just generally what other options I still have. I would love to find out
a graceful solution to problems of this kind!

Thanks!

Ben

My opinion is that any exception that is not handled should crash the
application (avoid catch(...) at all costs, until you rethrow what you
catch).
An exception is what its name suggests, and ignoring it may lead to
catastrophic results. If you derive your exceptions from
std::exception then a print out of the "what" member and the exception
typeid just before crashing is a good idea.
It is true that this would violate the "blackness" of the low level
function that thrown it, but it will also allow to locate and fix the
error faster.
Also I think if your software is mission critical, is better to have a
really visible boom than an undetected mess that acts nicely.

During its travel to the higher levels of abstraction, the exception
could be replaced with a different one, but there should be a way to
know which exception caused all the mess in the beginning.
Chained exceptions provide some clue, or check this for another
solution: http://www.ddj.com/cpp/191100567

Paolo Brandoli
 
J

Joe Greer

Hello C++ experts,

This is rather a design question, I must say. My question is, how do
you write exception safe code while ensuring encapsulation?

Wow, this is a nicely open-ended question. :) I think the easiest
answer is that exceptions are classes and as such can participate in an
inheritance heirarchy. Since this is C++, you can even use multiple
inheritance. So, what can this mean to encapsulation? Most simply, if
you derive your exceptions from one of the std:: heirarchy of exceptions
(std::exception, std::runtime_error, std::logic_error etc) then you
higher level classes can just catch those and won't care about the
actual exception. If you start to let your creative juices flow a bit,
then you can create your own type tags which can suggest a strategy for
handling the exception, or categories for the exception so that higher
level code doesn't care about the exact exception, but rather the
inherited type which can suggest a strategy for handling it. Remember
that catch clauses can not only catch the specific class being thrown,
but also any inherited classes. For example. if you have some classes
like 'reestablish_connection' or 'wait_and_retry' then you can inherit
from one or more of those and have your high level code catch those
instead of the specific exception. The high level code can then either
catch the specific exception and do some appropriate action or bail.

Another possibility is to define you own interface for the exception
such that higher level code can pull out the pieces of information they
need to take action, but still don't need to know the exact type of the
exception thrown. That is you can make the base class of your exception
understand GetErrorCode() or something to allow the handlers of the
exception the ability to deal with problems. If you inherit from
std::exception, you get the what() method that can return a string. The
string can either be a real message or just a small handle into another
lookup table. However you want to use it.

The point of the above is to just get you thinking about possibilities.
The fact that exceptions are classes themselves gives you the ability to
make the full use of that power. With the full gamut of muliple
inheritance and interfaces, the choices are pretty endless.
In C++, if a function by itself does not know how to handle an
exception raised from a lower level system, we usually implement the
function in such a way that it is exception safe, yet allow the
exception to be propagated to a higher level system.

However, since each system level defines a certain abstraction,
allowing exceptions to propagate through multiple system levels may
breach encapsulation, because it exposes low level implementation
details. The exception hence can be less useful than it should be, as
the higher level system will have little knowledge how such low level
exceptions are to be handled accurately.

Furthermore, should the higher level system choose to handle
exceptions from multiple layers down, it risks to be turned invalid as
soon as changes are made to the lower level systems. This is very
similar to accessing a class member that is ought to be private.

To give an example, consider the following program, which consists of
three distinct system levels: at the lowest, a database class; up one
level, an enrolment form; and at the highest level, the main function
that uses the enrolment forms.



#include <iostream>
#include <sstream>

// Incomplete program, will not link.



///////////////////////////////////////////////////
// Low level system

class connection_error{};
class IO_error{};
class repetition_error{};

class database
{
public:
void connect(std::string location) throw (connection_error);
void create_record(std::string rc) throw (IO_error,
repetition_error);

These exeception specification generally don't do what people (except
for experts) expect. I think people expect this to be a declaration
telling the compile to flag a compilation error if the method can throw
something other than the spelled out exceptions. That isn't what
happens. What happens is that the compiler generates code to cause any
exception but the ones specified to invoke unexpected() which isn't
allowed to return, but can throw something. This is not generally the
behavior you want.

// ...

Here is the dilemma:

1) If main() is to handle the exceptions from submit1() member
function call, it will have to deal with exceptions from both
enrolment_form (mid-level) and database (low-level.) Since the use of
a database is to be encapsulated from main(), the writer of main()
obviously knows little how to handle such exceptions.

2) If the mid-level enrolment_form class is to catch every lowe-level
exceptions and translate it to one of its own (but higher level in
abstraction) exception types, such as submission_error, encapsulation
is enforced but:

a. The writer of main() will be equally clueless on how exceptions,
such as submission_error shall be handled, since the nature of the
exception is abstracted away (that is, the type information of the
original exception, at least)

b. The implementation of mid-level system, enrolment_form, is
complicated as it is responsible to translate all lower-level
exceptions. For the very least, functions are littered with try-catch
blocks, which is ugly IMO.

The idea would be to have all your exceptions derive from std::exception
or other class of your choosing and have the higher level guys catch
those exceptions instead of the specific ones. Define your own heirarchy
of exception classes that your high level code can handle. Then derive
your specific exceptions from one or more of those. You can then maintain
encapsulation and yet do some very interesting recovery strategies.


Hope that helps in some way.

joe
 
B

benben

Thanks everyone who has replied to my post! Sorry I wasn't responsive
over the last few week cuz I was in exams.

In summary, the responses to my OP fall into two catagories:

1) Exceptions are better handled locally. If they can't be handled
locally, much less can the rest of the higher level code deal with those
so the only thing they can and should do is to release a diagnostic
message (error, logging, etc) by using some sort of polymorphism in
exception classes.

2) Exceptions are to be propagated to the highest user-interface level
(that is if the program actually has a UI, of course) so the users are
left to deal with those.

Furthermore, the above two are not completely orthogonal.

My concern, however, is not much of a functional one but rather an
effort to keep a consistent level of abstraction across each layer of
code, if that makes any sense to you. The impression I have so far is,
my concern is less relevant than what I have pictured before.

Drop a word if you think I understood wrong.

Thanks,
Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,062
Latest member
OrderKetozenseACV

Latest Threads

Top