Is it considered to be a good practice to convert all types of exceptions (exceptions from internal logic of application + exceptions from application's external dependencies - for example: File System) to application specific exceptions?
For example,
I am developing a Job Scheduling Software in Python (using layered architecture). One of the layer in this architecture is Persistence layer. This layer is responsible for storing/retrieving state of the Job to/from the persistence store (File System).
I have defined two application specific exception classes "PersistenceReadError" and "PersistenceWriteError" for exceptions raised from persistence layer APIs (read_jobs, write_jobs etc).
I am not sure if this is considered to be a good practice i.e. is it right to even catch exceptions like FileNotFoundError, FilePermissionsError etc. and wrap them in PersistenceRead/PersistenceWrite exceptions? Also how far should I go with creating exception classes vs using limited exception classes (to group similar exceptions together) with error code/messages to distinguish subtypes of exceptions.
1 Answer 1
It depends on what your overall approach and intent are.
I tend to keep the standard exceptions separate from the application-specific exceptions.
The question is, what do you do with them afterwards.
- Reraise as is: no need to wrap them in app-specific exceptions.
- Use in reporting or logging: wrapping is recommended.
- Add more info, such as error codes and messages: definitely wrap them.
- Group: if multiple exceptions are considered part of the same action/task.
ie:FileNotFoundError
,FilePermissionsError
could raisePersistenceIOError
Example:
try:
try:
doSomething()
except SystemError:
do1()
raise MainEx
except ValueError:
do2()
raise MainEx
except IOError as e:
# Add more info
raise PersistenceIOError(code=123, msg='File Bad', other=e)
except TypeError:
# Raise as is
raise
except Exception as e:
raise UnknownEx
except MainEx:
doMain()
Update:
Here's an idea of how I handle same exception with multiple error codes:
try:
if a:
raise PersistenceError(code=1, msg='1 Bad')
elif b:
raise PersistenceError(code=2, msg='2 Bad')
else:
raise PersistenceError(code=3, msg='3 Bad')
except PersistenceError as e:
if e.code == 1:
do1()
elif e.code == 2:
do2()
elif e.code == 3:
do3()
else:
raise
-
I would add that if you are going to build logic around these errors, wrapping is likely a good idea.JimmyJames– JimmyJames02/13/2019 20:24:15Commented Feb 13, 2019 at 20:24
-
1I'd also suggest focusing on the semantics of the wrappers and the functions which will be raising them. For example, if your persistence layer is a black box that "magically" persists data without the caller having to know where it lives, then allowing a
FileNotFoundError
to propagate out of the layer is probably a Bad Idea, because your caller will not know anything about the offending filesystem path. That should be transformed into a wrapper. But OTOH if you're "just" persisting to a path the caller gave you, then you should allow such errors to propagate as-is.Kevin– Kevin02/13/2019 23:33:39Commented Feb 13, 2019 at 23:33 -
@kevin, this is indeed a very good point. Persistence is a black box.user317612– user31761202/14/2019 02:39:59Commented Feb 14, 2019 at 2:39
-
My main difficulty with wrapping - how far should I go with creating exception classes vs using limited exception classes with error code to distinguish subtypes of exceptions.user317612– user31761202/14/2019 02:44:39Commented Feb 14, 2019 at 2:44
-
@slybloty Thanks for the details. I am not sure how to handle grouped exceptions in the callee side of the function. For example, should I use errorcodes or use error message or use both to identify which error was thrown and whether I can handle it or not?user317612– user31761202/14/2019 02:58:45Commented Feb 14, 2019 at 2:58
I have defined two application specific exception classes "
to what end?