Exceptions, exceptions, exceptions....
like Steve Ballmer once said: developers,
developers, developers...
So, if you are working with C# you probably heard a lot about exceptions.
First of all, I would like to say that
working with exceptions is hard. To handle them you first need to
understand why they are invented at first place. If you Google about
this question you’ll find out that this goes way back before the first modern
languages were invented (you can look at this thread at stackoverflow about historical facts).
So, why are exceptions invented?
In short, they are used to signal that program is in an
unexpected state.
What does it mean?
Every programmer knows that sooner or later he will be facing with
unexpected problem on which he didn't count. But detecting these situations
could be very hard and expensive - error is encountered on every 10th
iteration, user has lost data until he finally noticed that something
went wrong, etc.
But, let’s write some small example:
bool ExecuteSql(string sql)
{
if (!OpenConnection() &&
!ParseQuery(sql) &&
!Execute(sql))
{
return false;
}
CloseConnection();
}
void DoTheJob()
{
ExecuteSql("INSERT INTO USERS VALUES ('John')");
ExecuteSql("UPDATE INTO PERMISSIONS VALUES ('John',
'Read')");
ExecuteSql("UPDATE INTO PERMISSIONS VALUES ('John', 'Write')");
ExecuteSql("UPDATE INTO PERMISSIONS_DETAILS VALUES ('John',
'Write', 'CUSTOMERS')");
}
In this little example we have two methods: ExecuteSql – used to execute sql on remote
server; DoTheJob – used to execute some
business logic.
In DoTheJob method we are inserting data into three
different tables. These tables are in the following relationship:
We have three tables and each has its own PK but PERMISSONS
has also FK constraint declared on USERS PK and PERMISSONS_DETAILS has FK
constraint on PERMISSONS PK. Our business logic says that in USERS table we add
users, in PERMISSIONS table we add permissions fro that user and it could be
read/write permission. In our third table PERMISSIONS_DETAILS we add more
detailed info about permissions (e.g. which table could be read/write by some
user).
Let’s start examine DoTheJob method. In this method we are
inserting data into all tables but we must insert it in specific order (remember
we have declared FK constraints). First, we insert into the USERS table, then
into the PERMISSONS and then into the PERMISSIONS_DETAILS. Every dissent
programmer will notice one problem here – we assumed that inserting user ‘John’
is valid operation that could never fail, which is clearly wrong and it would
lead to domino effect if this first insert fails. So, let’s first define that
in our pseudo code, pseudo C# language we don’t have exceptions. We’ll pretend
that we don’t know anything about them and thus we will need to figure out how
to handle errors.
If we look at ExecuteSql method we will see that it returns
Boolean – we can leverage this to handle if anything goes wrong with execution to
abort insertions. So we can rewrite DoTheJob as following:
void DoTheJob()
{
if (ExecuteSql("INSERT
INTO USERS VALUES ('John')"))
{
ExecuteSql("UPDATE INTO PERMISSIONS VALUES ('John',
'Read')");
if (ExecuteSql("UPDATE
INTO PERMISSIONS VALUES ('John', 'Write')"))
{
ExecuteSql("UPDATE INTO PERMISSIONS_DETAILS VALUES ('John',
'Write', 'CUSTOMERS')");
}
}
}
Now this code looks robust, but we can notice that it is
also much harder to read it than before. So, imagine that we have many of these
calls to ExecuteSql method, in major cases we expect that it is going to
execute without any errors, but in some cases it will fail. Now, we would like
to have some kind of mechanism that will provide us exactly that – we can call
any method without checking its result (because we rarely except it to fail)
but if something goes wrong this mechanism will inform us – in other words,
exceptions.
Exceptions are…. exceptions, what else. It is something
which we don’t expect normally, but if this unexpected situation occurs we must
know about it and we must somehow handle it.
But, wait; we still
don’t know how to implement these special, unexpected situations. So, we try
for ourselves to implement something and we write something like this:
delegate void ErrorHandler();
void ExecuteSql(string sql, ErrorHandler handler)
{
if (!OpenConnection() ||
!ParseQuery(sql)
||
!Execute(expression))
{
handler();
return ;
}
CloseConnection();
}
void DoTheJob()
{
ErrorHandler handler = delegate()
{
MessageBox.Show("Error
occurred user not inserted.");
};
ExecuteSql("INSERT INTO USERS VALUES ('John')",
handler);
ExecuteSql("UPDATE INTO PERMISSIONS VALUES ('John', 'Read')",
handler);
ExecuteSql("UPDATE INTO PERMISSIONS VALUES ('John',
'Write')", handler);
ExecuteSql("UPDATE INTO PERMISSIONS_DETAILS VALUES ('John', 'Write',
'CUSTOMERS')", handler);
}
Now we have defined a new delegate which we pass to
ExecuteSql method and it will be called if anything goes wrong. If we try this
mechanism we will notice something strange, although we don’t need to check for
return value and we’ll be still informed if something goes wrong, we are still executing
next insert statement!
So, to overcome this problem we need to deal with
continuation, we must say to compiler “Hey! Stop executing these set of
instructions and go to the next one”. But wait, we don’t have any set defined,
which set of instructions do we want to skip?
OK then, we can rewrite it again:
void DoTheJob()
{
ErrorHandler handler = delegate()
{
MessageBox.Show("Error
occurred user not inserted.");
};
{
ExecuteSql("INSERT INTO USERS VALUES ('John')",
handler);
ExecuteSql("UPDATE INTO PERMISSIONS VALUES ('John',
'Read')", handler);
ExecuteSql("UPDATE INTO PERMISSIONS VALUES ('John',
'Write')", handler);
ExecuteSql("UPDATE INTO PERMISSIONS_DETAILS VALUES ('John',
'Write', 'CUSTOMERS')", null);
}
}
Here, we have added additional curly braces to mark the set
of instructions that we want to be skipped, so if anything goes wrong with any
of the ExecuteSql calls, rest of the calls must be skipped. We must exit from
method, because nothing more is left to execute. Note: in case that we had some
instructions under the block - that would be our next instruction to execute
not returning from method.
So, now we have defined which set of instructions should be
skipped if anything goes wrong, but to implement this mechanism we need to
deal, as I said previously, with continuation, or we can incorporate this exactly
into the compiler because we can control more easily continuation and we can
construct various branching solutions. And that’s exactly where exceptions are
incorporate – in C# we define which block should be called when something goes
wrong and also which block should be terminated if something goes wrong (if you
want to read more on .NET, please visit this link http://blogs.msdn.com/b/cbrumme/archive/2003/10/01/51524.aspx
but even before you read this you should read about SEH mechanism http://www.microsoft.com/msj/0197/exception/exception.aspx).
Now we know why exceptions are invented – we have mechanism
that will always inform us if anything goes wrong.
But after some time we found out that dealing with exception
is hard and that we always have to take care of two things:
- Catching existing exceptions
- Throwing your own exceptions
Let start with throwing exceptions – say we have our API and
we want to throw exception because of some error we got from our internal
business logic. The most important question to ask yourself is – is it a really
rare situation that will rarely happen? If it is not, you should return some
error value instead. Remember, exceptions must be used to signal that something
is wrong, in major cases that shouldn't be handled at all – we either have
wrong architecture design or we have external dependency on which we can’t
affect (e.g. out of memory exception).
Now, let’s see how to catch exceptions.
The most important question to ask yourself is – do I know
what to do with this exception, do I really expect to see this exception?
I've seen many cases when people are catching exceptions
just because some method declared that it throws some exceptions.
The best example of this is this one:
try
{
string s1
= null;
string s2
= string.Empty;
File.Copy(null, string.Empty);
}
catch (ArgumentException) { }
catch (ArgumentNullException) { }
catch (PathTooLongException) { }
This is WRONG! You should check for arguments, you don’t
need to catch any of these exceptions. But OK, this is fairly simple and
obvious example.
Let’s see another one more difficult to recognize:
ComboBox _combobox = new
ComboBox();
void f()
{
_combobox.SelectedIndexChanged
+= new EventHandler(_combobox_SelectedIndexChanged);
_combobox.Items.Add(new MyClass());
_combobox.Items.Add(new MyClass());
}
void
_combobox_SelectedIndexChanged(object
sender, EventArgs e)
{
try
{
((MyClass)_combobox.SelectedItem).Name = "Custom text";
}
catch
{
}
}
You don’t need catch block for casting. Yes, SelectedItem is
of type “object” and it could be anything if you just examine property per-se,
but in this context, it is of type MyClass, and it will always be, this is
compile time resolution not run-time!
Someone could say – well, this is not safe; you should use
generic classes instead. I will agree, but in some cases you don’t have generic
classes as in this example and in these cases you should always analyze your
context, don’t swallow your exceptions.
But what about next one:
void MethodA()
{
MethodB("apple");
}
void MethodB(string
s)
{
try
{
MethodC(s);
}
catch (MyException)
{
// It's ok, we expect this exception
// will try to call once again this method but with better parameters
MethodC("12345");
}
}
void MethodC(string
s)
{
if (s.Length < 5)
{
throw new MyException();
}
if (s.StartsWith("*"))
{
throw new MyOtherException();
}
// Do Stuff
}
We have three methods: MethodA, MethodB and MethodC. We call
from MethodA -> MethodB and from MethodB -> MethodC. When calling MethodB
we don’t expect any exception but when calling MethodC we can see that it has two
exceptions that it can throw: MyException and MyOtherException (please ignore for
now how did we concluded that this method can throw two exceptions – in C# we
can do that from xml comments and in more painful way with .NET reflector or
any similar tool, unfortunately we don’t have checked exceptions in C# L). But as from MethodB’s
point of view, it is only interested in MyException. We don’t expect for some reason
that MyOtherException is ever thrown. So, we test our example and it all works
OK, but suddenly when we pass to MethodA string like “*pple” it fails with
exception:
public void MethodA()
{
try
{
MethodB("*pple");
}
catch
{
// Ignore exceptions
}
}
What should we do – catch general exception or try to figure
out what is going on?
It depends – in major cases we must find out what is going
on, but in some cases we are “allowed” to catch exceptions.
When we mustn't catch general exception:
- When this is our code – we have the source code for all the methods that are involved in hierarchical calling tree
- In case when this isn't our code but we can use preconditions to avoid exceptions (like example with ArgumentException)
When we can catch general exception:
- When it is 3rd party API and we don’t want our code to crash
- When we have "mission critical" code to execute and we must provide robust design, in this case it is not important whether it is 3rd API or our internal code
- When it is top-most exception for thread – we can use some custom logic to inform user about problem that has occurred and we can try to recover somehow or we can just terminate the application or just that thread
This is the major problem with exceptions – it depends on
context what should be done. In major cases we can say that catching exceptions
should be avoided as much as it is possible, catching general exception even
more but as we can see it really depends on the context, so you must analyze
for yourself every time you need to throw or to catch the exception.

No comments:
Post a Comment