How to handle errors with grace: failing silently is not an option

Well, it is what it is.

An interesting side note, at least to me, is that the default implementation for a new C# method is to throw a NotImplementedException, whereas the default for a new python method is “pass”.

I’m not sure if this is a C# convention or just how my Resharper was configured, but the result is basically setting up python to fail silently.

I wonder how many developers have spent a long and sad debugging session trying to figure what was going on, only to find out they had forgotten to implement a placeholder method.

But wait, you could easily create a cluttered mess of error checking and exception throwing which is quite similar to the previous error checking sections!public MyDataObject UpdateSomething(MyDataObject toUpdate){ if (_dbConnection == null) { throw new DbConnectionError(); } try { var newVersion = _dbConnection.

Update(toUpdate); if (newVersion == null) { return null; } MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { throw new DbConnectionError(); } catch (MyDataObjectUnhappyException dou) { throw new MalformedDataException(); } catch (Exception ex) { throw new UnknownErrorException(); }}So, of course, throwing exceptions will not protect you from unreadable and unmanageable code.

You need to apply exception throwing as a well thought out strategy.

If your scope is too big, your application might end up in an inconsistent state.

If your scope is too small, you’ll end up with a cluttered mess.

My approach to this problem is as follows:Consistency rulezzz.

You must make sure that your application is always in a consistent state.

Ugly code makes me sad, but not as much as actual problems which affect the users of whatever it is your code is actually doing.

If that means you have to wrap every couple of lines with a try/catch block — hide them inside another function.

def my_function(): try: do_this() do_that() except: something_bad_happened() finally: cleanup_resource()Consolidate errors.

It’s fine if you care about different kinds of errors which need to be handled differently, but do your users a favor and hide that internally.

Externally, throw a single type of exception just to let your users know something went wrong.

They shouldn’t really care about the details, that’s your responsibility.

public MyDataObject UpdateSomething(MyDataObject toUpdate){ try { var newVersion = _dbConnection.

Update(toUpdate); MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { HandleDbConnectionClosed(); throw new UpdateMyDataObjectException(); } catch (MyDataObjectUnhappyException dou) { RollbackVersion(); throw new UpdateMyDataObjectException(); } catch (Exception ex) { throw new UpdateMyDataObjectException(); }}Catch early, catch often.

Catch your exceptions as close to the source at the lowest level possible.

Maintain consistency and hide the details (as explained above), then try to avoid handling errors until the very top level of your application.

Hopefully there aren’t too many levels along the way.

If you can pull this off, you’ll be able to clearly separate the normal flow of your application logic from the error handling flow, allowing your code to be clear and concise without mixing concerns.

def my_api(): try: item = get_something_from_the_db() new_version = do_something_to_item(item) return new_version except Exception as ex: handle_high_level_exception(ex)Thanks for reading this far, I hope it was helpful!.Also, I’m only starting to form my opinions on this subject, so I’d be really happy to hear what your strategy is for handling errors.

The comments section is open!.

. More details

Leave a Reply