Exception handling
In this post i will share how error handling is done and what options we have.Error handling is complex topic :-)
I will add some context from wikipedia on what is exception handling before going down the rabbit hole of exception handling
Exception handling is the process of responding to the occurrence, during computation, of exceptions – anomalous or exceptional conditions requiring special processing – often disrupting the normal flow of program execution. It is provided by specialized programming language constructs, computer hardware mechanisms like interrupts or
operating system IPC facilities like signals.
In general, an exception breaks the normal flow of execution and executes a pre-registered exception handler. The details of how this is done depends on whether it is a hardware or software exception and how the software exception is implemented. Some exceptions, especially hardware ones, may be handled so gracefully that execution can resume where it was interrupted.
Source – https://en.wikipedia.org/wiki/Exception_handling
Few things that is highlighted are ” disrupting the normal flow of program execution” , “pre-registered exception handler” , hardware or software exception.
So it explain what error handling is , so i will not spend time on that.
One interesting thing mention is 2 types are exception exists( hardware & software), hardware friends have handled very gracefully and it on software engineer to do part.
Software one are the hard and too many programming languages makes it even harder.
I want you refer to simple-testing-can-prevent-most post on which i try to explain the side effect of wrong error handing and it pure as the result of exception handling pattern.
C way
I am sure if you seen this and have thought that “is this the right way ?”.
Code snippet of C error handling
int main () { FILE * fp; fp = fopen ("somejunkfile.txt", "rb"); if (fp == NULL) { printf("errno: %d76485//"////, errno); printf("Error opening the file: %s76485//"////, strerror//(errno)); exit(EXIT_FAILURE) } else { fclose (fp); } return 0; }
This approach has several issue
– You have to check for error after calling every function that can fail.
– No safety from compiler on forcing/indicating that error can be thrown at this point
– Error handling is completely optional
Java Way
Then came java and came with mindset let me fix all the error handling and they invented checked/unchecked exception.
Look at code snippet
public void close() { try { this.writer.close(); } catch (Exception e) { throw new RuntimeException(e); } }
This approach has every more issues
– Code is full with verbosity of error handling code
– Compiler forces you to handle checked exception in wrong way(i.e just log it or ignore it)
– Nothing meaningful is done apart from log something and wrap it in RuntimeExcetion to get passed compiler.
– Wrapping makes things worse because you start loosing context on what caused error
Functional Programming Way
This world has to do better than “imperative” world and what they did ? Invented Monads.
def main(args: Array[String]): Unit = { val res = divide(10, 0) res match { case Left(value) => println(s"Result is ${value}") case Right(e) => println(s"Calculation failed reason ${e}") } val result = trydivide(10, 0) result match { case Success(r) => println(s"Result ${r}") case Failure(error) => println(s"Failed to calculate reason : ${error.getMessage}") } }
Things to consider when using this approach
– You have to learn fancy technical jargon of Category theory or monads
– Now gives 2 value and you have to write little less verbose code to handle both the path
– Performance issues due to extra wrapping of value and when you have millions of them then it hits you very hard
– No compile time safety , caller have an option to get around this by directly getting the value
– and i think this was attempt to fix Optional/MayBe value, in which you don’t know why value is not available.
– Stacktrace is gone and in some case it is useful especially building system that is calling third party libs
Go Lang Way
GoLang wanted to do better than C/Java and took some inspiration from Elm language and came up with delegation or Killer(i.e Panic) approach
listener, listenError := net.ListenTCP("tcp", addr) if listenError != nil { return nil, fmt.Errorf("Listen: %s", listenError) }
This is interesting approach but
– With err return in very function call , caller has to add error handling code
– Trace is lost, so you have to very careful in adding all the context to message so that recovery is possible.
– Panic is not good for library or framework because you can’t kill the process , it has to be client responsibility to decided on what to do.
JavaScript/Python way
I will leave this for now.
No clear winner in which option is best and each language is doing some trade-off. We don’t exceptions like java ans also like Go Lang, it is 2 end of the pendulum.
What could be good is having caller option to decided on what approach to use it could be Java Style or Go Lang.
Better way to separating control flow and error because in some case default value on error could be good option or just cleanup/recovery or send to upper layer to better handing.
So code in catch block tell lot about what client want and that should decided what error handling pattern you should use.
I think it is more about education on what is right in context and use the pattern.
Happy to know about what you think about error handling and how it should done.
Published on .NET Code Geeks with permission by Ashkrit Sharma, partner at our NCG program. See the original article here: Exception handling
Opinions expressed by .NET Code Geeks contributors are their own. |
I do not understand why JCG published it. It is probably a good article for students, but not for Java Code Geeks (Java developers)…
Greetings! Good article, but a bit rough. Needs more grammar checking, code indentation, and development of the reasons why things are bad or good.
Enjoyed the read, thanks!