Programming tip: Stay away from throwing exceptions.

21    11 Mar 2016 22:57 by u/roznak

I will be massively down voted for this tip but it is going to improve your code quality.

Newbie developers tend to be overexcited when they discover exception handling. It is the lazy way to avoid returning a function result.

The thing is, that throwing an exception should be the extreme rare exception indicating an unrecoverable bug and not be done for a recoverable error like a simple situation like a file not found or a printer not ready.

The reason is this:

  • Throwing an exception for recoverable error like a printer not online, will jump to the location where the the first catch will catch it. But that is runtime depended. And a moving target to debug. It ads code complexity because you have to catch that exception everywhere
  • Throwing an exception can cause instability problems in your code. You tend to end up with partial initialized objects and therefor in an unstable condition. Recovering code becomes messy fast.
  • Not all errors are critical. Sometimes it is enough to recreate a missing file and retry. The code that detected the error most likely can also repair it. It is not the job of some complete different part of the code to repair.
  • Triggering 100's of exceptions means that you can't use the debugger to find a critical bug in your software. It is annoying as hell when it jumps to debug mode every single time.
  • Avoiding throwing exception and rely on function results is very helpful to test you code during development simulating errors you introduce by overriding the return variable.

Short version: Don't use exceptions for normal errors but for extreme rare unrecoverable errors.

34 comments

17

Anyone who has actually done serious programming can tell you that this is absolutely terrible advice. This post reeks of a fundamental misunderstanding of how exceptions are used in actual real-life application.

-1

Troll name hahaha.... Nice try.

-1

Yeah, because someone with a troll username could never possibly be correct about anything! /s

0

Using excessive exception causes more problems than it solves. It only works on software that is small scale.

Of course if you never created big applications then would not know the difference. You would be convinced that the misery you get is normal and failing projects is part of normality.

0

You're an idiot if you truly think this, not to mention shifting the goalposts with "excessive exception [use]". I've worked on a number of large projects, both as a regular developer and as an architect and it's not that hard to make judicious, proper use of exceptions. You strike me as a noob developer who has been burnt once by badly misusing exceptions and now think they're not but the devil's tools.

Git gud or stop coding, you scrub.

1

I've worked on a number of large projects, both as a regular developer and as an architect and it's not that hard to make judicious, proper use of exceptions.

It is interesting that you left out if your projects were successful and actually useful.

-1

Considering one of them was a platform that kept thousands of people paid, I'd say at least that one was. And another is too new to tell yet, or at least needs better marketing.

0

So one success and one might be a success. So you worked on 2 projects so far?

The one platform that kept thousands of people paid, how many regression bugs were reported? How many fixes were not done because you are too scared to touch the code? How many change requests were bounced back because they were not in scope of the analysis from 2 years ago?

0

There were never any regressions caused by exception handling in all the time I worked for that company, and bug fixes were rarely an issue and never anything that anyone in our team was afraid of. And neither there nor at my current employer do we push back CRs; we're professionals, not wusses.

9

I wouldn't go that far. But exceptions are for exceptional events, like a full disk or other events you can't prepare for; not for handling user inputs or edge cases. Throwing an exception should be the last measure, the code should deal with handable problems without exceptions.

2

Throwing an exception should be the last measure,

That was my intention to say.

It also depends on the situation:

  • If your project depends on a critical WCF service that is not there then throw an exception.
  • If your projects depends on a critical WCF service on a G4 network that is shaky as hell, then don't throw an exception but return a normal error code so you can create code to retry. If 10 retries fail then throw the exception.
4

Wasn't one of the primary reasons to use exceptions to get away from error codes?

Rather than having to memorize that error code 10 was G4Network down you're supposed to throw G4NetworkException? Seems more logical to me to throw an exception than return some random error code, espically if you wanted your function to return a String or something else. Now you have a String going into your method that gets mutated so that your function can return an error code.

1

Wasn't one of the primary reasons to use exceptions to get away from error codes?

Actually no, it was an addition not a replacement. For simple components it works well but once you scale your projects it becomes a nightmare to debug. These projects becomes near impossible to refactor.

Also an error code return in a normal method does not mean that it must be a string or integer. It could be a complete class with tons of information about the error.

int error = MyFunction()
Log.WriteLine("Code: "+error.Code)

could be

ErrorCode errorResult = MyFunction()
Log.WriteLine("Description:"+errorResult.Description+ ", Code: "+errorResult.Code)
Log.WriteLine("Details:"+errorResult.ToString())

Extend it

do {
     ErrorCode errorResult = MyFunction()
     Log.WriteLine("Description:"+errorResult.Description+ ", Code: "+errorResult.Code)
     Log.WriteLine("Details:"+errorResult.ToString())
     if (!errorResult.IsSuccess) {
         RecoverSomething()
    }
while (errorResult.IsSuccess)

And now comes the interesting parts

do {
     ErrorCode errorResult = MyFunction()
     errorResult.Code = 100; // add this to test your code how it behaves when it has a simulated fail
     Log.WriteLine("Description:"+errorResult.Description+ ", Code: "+errorResult.Code)
     Log.WriteLine("Details:"+errorResult.ToString())
     if (!errorResult.IsSuccess) {
         RecoverSomething()
    }
while (errorResult.IsSuccess)

Remove the test error code by adding a comment in front of it

do {
     ErrorCode errorResult = MyFunction()
      // errorResult.Code = 100; // add this to test your code how it behaves when it has a simulated fail
     Log.WriteLine("Description:"+errorResult.Description+ ", Code: "+errorResult.Code)
     Log.WriteLine("Details:"+errorResult.ToString())
     if (!errorResult.IsSuccess) {
         RecoverSomething()
    }
while (errorResult.IsSuccess)

As you have noticed I can now building "readable" recovery loops that are easier to follow without obscuring the real functionality.

6

This is fairly poor and naive advice as should be guessed from the absolutionist tone.

Throwing an exception for recoverable error like a printer not online, will jump to the location where the the first catch will catch it. But that is runtime depended. And a moving target to debug. It ads code complexity because you have to catch that exception everywhere

I'm not sure what you mean here. Where the first catch catches an exception is entirely detectable through static analysis. I can mentally walk through any code I've touched in the past 5+ years and tell you exactly where a thrown exception will be caught. I do this kind of analysis on a near daily basis with a fair bit of ease. I'm not sure what kind of tooling or language you're using where this is the case.

Throwing an exception can cause instability problems in your code. You tend to end up with partial initialized objects and therefor in an unstable condition. Recovering code becomes messy fast.

This only happens if you don't know how to catch and handle exceptions properly. If at the end of exception handling, your code isn't prepared to continue execution in a correct state, then you're doing it wrong. In my experience, recovery code is rarely complex unless you're trying to do too much at once. Generally, the whole affair at that point should be considered a code smell worthy of deeper investigation.

Not all errors are critical. Sometimes it is enough to recreate a missing file and retry. The code that detected the error most likely can also repair it. It is not the job of some complete different part of the code to repair.

Yes. Strawman argument here. If exceptions are your only tool, then every problem looks like a nail. But they aren't. Don't use exceptions for normal control flow. This is a d'uh, and not a reason to not use exceptions ever.

Triggering 100's of exceptions means that you can't use the debugger to find a critical bug in your software. It is annoying as hell when it jumps to debug mode every single time.

Not sure what kind of programming environment you live in, but thanks to the fact that I can fully inspect a caught exception, I can know exactly where the error is without even debugging (hint: logging + stack traces). I'm also not sure what you mean by triggering 100s of exceptions. Generally, one exception is thrown at one location and caught and handled at another (perhaps rarely rethrown for the sake of adding information pertinent to a higher level of abstraction). But it's not like your whole application is going to throw all of its exceptions at once or constantly be in a state of throwing exceptions. This point alone wreaks of exception inexperience.

Avoiding throwing exception and rely on function results is very helpful to test you code during development simulating errors you introduce by overriding the return variable.

Let me try this: "Throwing exceptons is very helpful to test your code during development simulating errors you introduce by manually throwing an exception." See? This door swings both ways and doesn't at all negate using exceptions.

1

This is fairly poor and naive advice as should be guessed from the absolutionist tone.

They all say that I am wrong, but in the end I am the one that is saving them when their projects fail. :-)

Not sure what kind of programming environment you live in, but thanks to the fact that I can fully inspect a caught exception, I can know exactly where the error is without even debugging (hint: logging + stack traces).

Only if you designed the code yourself and know every part of that code. But you will fail when you get back to that code 2 years later and have to fix issues.

The 2 epic sentences I always hear are :

  • I don't understand my own code anymore that I created last year.
  • No, we won't change that because it works and we don't want to break stuff. It is too hard to fix.

When you avoid using exceptions then you hear this:

  • I have not seen this code for a year but yes we can change it with almost no risk.
0

Only if you designed the code yourself and know every part of that code. But you will fail when you get back to that code 2 years later and have to fix issues.

Not true at all. I've come along on a number of brownfield projects and could always immediately understand even the bad exception handling.

I've never avoided exceptions. The projects I've worked on have never avoided exceptions. And very little of my time in even an entire work year is spent on perplexing exception handling. I think you've had some bad experiences and are severely blowing things out of proportion. Maybe you came into a project that had severely poorly designed exception handling? Is it the language you're using? Does your language have bad tool support that prevents you from quickly understanding call hierarchies?

The 2 epic sentences I always hear are : I don't understand my own code anymore that I created last year. No, we won't change that because it works and we don't want to break stuff. It is too hard to fix.

Taking these and putting them on exception handling is really distorting the issues. In the first case, that's true of many developers and the code they write regardless of whether they use exceptions or return codes. The second case is another red herring. In a work environment, when anything is said to be "too hard to fix", what that means is it's too costly to fix. In other words, if it costs $15k in developer time, it better produce $15k in profits as either a feature or reducing further maintenance. And again, this does not result from using exceptions or return error codes. I've seen plenty of code that is a nightmare to work on because it's had new implicit requirements thrust upon it for over a decade and has been retrofitted with duct tape and hope to get by. It can be littered with bad programming practices (floating point equality comparison, unsynchronized multi-threaded variable access, etc.), and yet the thing still works. So you don't dare go in and change it just to make it "right" as that may break the way it's working now. "Fixing" one of these things may require you addressing a mountain of a problem. It's a case where you grab that first string, and now you have to unravel the whole sweater. And none of this is made more or less complex by return values or thrown exceptions. Both can be done equally well or equally poor as far as understanding and maintenance is concerned. I implore you to investigate outside of your current context. The whole world is not doom and gloom when it comes to exceptions.

3

Like you said, exceptions are basically a GoTo statement. And as such they typically ruin any kind of optimization / predictive instruction loading. I definitely avoid using them when I can. For the most part I leave them for developer errors. Things that are meant to be fixed by developers/in development. Like ArgumentOutOfBounds. If it's supposed to end up on the UI then it shouldn't be an exception.

1

You got it :-)

0

Most C++ compilers simply make a function call for exceptions (MSVC is something like CxxRaiseException) and thus optimization is not sacrificed (since the linker just sees it as a function call).

C on the other hand is in fact ruined by setjmp exceptions.

2

Does your process generate output and terminate as the functional mechanism? Then you don't really need exceptions, you can exit with an error. Exiting with an error, and that error is an uncaught exception, should be considered rude.

Does your process provide a service, or open interactive windows for user interaction as the functional mechanism? Then you should use exceptions, and catch those exceptions.

2

Good advice, in most languages. I think something people tend not to understand about exceptions is how they tear open the logic of the application that throws them. It's often compared to a 'goto' statement, but since 'goto' is omitted in most modern languages and is pretty much useless, it doesn't get the point across.

tl;dr: Throwing exceptions allows execution to leave a stack frame without popping or pushing.

Code is just lots and lots of functions. Object-oriented languages will fool you, because they have all this nice syntax, but they're actually just making big tables of functions that take 'self' as a hidden argument, where 'self' is a struct that contains all your state (instance variables, etc).

So, when you call a function, the processor has to push all its state (usually just register contents) onto the stack, pass the function arguments (in registers or on the stack) and then advance the base pointer and hand control over to the new function. After the jump, the new function does whatever it's going to do, including maybe repeating this process for yet another function, and then each one "unwinds" the stack when it's done to hand control back to the caller.

Most implementations of exceptions jump out of the stack to wherever the handler is, which means, they have to figure out how not to explode the stack. That's pretty expensive. In good implementations, the compiler will optimize try/catch pairs pretty well, but in many implementations, even pretty basic throws cost a lot.

http://blogs.msdn.com/b/ricom/archive/2003/12/19/44697.aspx http://stackoverflow.com/questions/567579/how-expensive-are-exceptions http://stackoverflow.com/questions/891217/how-expensive-are-exceptions-in-c

1

You are absolutely correct.

I recall that when Delphi 1 came out, or maybe even back in Borland Pascal 7????

I disassembled the code to see what it was doing. I recall that the "try" pushed the address of the "catch" onto the stack. When an exception happened then a jmp to that location was executed and it got into that "catch" code. The "Try" itself has almost no penalty, but the catch itself was a huge penalty in lost cpu cycles.

But in my experience it is not the CPU lost cycles that is the biggest issue, but these if your code gets big:

  • Strange exceptions coming from out of the blue ending up in code that has nothing to do with the original exception.
  • Your throw jumps to an unpredictable location that depends on runtime execution.
  • Dumping meaningless stack traces for a user to see can makes them run screaming away.
  • The stack traces cause by exceptions most of the time does not even give valid support for the IT support so they have to annoy the developer.
  • Stack traces caused by exceptions most of the time are useless without trace loggings.

The idea of WINAPI GetLastError() returning just an integer is too cryptic.

The idea of an exception class that you inherit from is better, but the throw is what makes it bad. It is better to return the exception class as a function result instead of throwing it.

1

That's fair. And then there's the intellectual load: it's much harder to determine if a function is throwing than if it's returning a value that may be empty.

1

One more tip.

If you are confronted with code that does use exceptions a lot realize that that catch statement can be way more simplified.

You can, create a method like this (I need to go to bed, so a draft version to get the idea)

private void ProcessException(Exception e){
    if (e is Exception1) {
         var ex1= e as Exception1;
         log.WriteLine(ex1.Message, ex1.SomeProperty1)
   } if (e is Exception2) {
         var ex2= e as Exception2;
         log.WriteLine(ex2.Message, ex2.SomeProperty2)
   } else  {
      log.WriteLine(e.Message)
}

Now you can use it like this:

public methiod1(){
  try {
   // do something
  } catch (exception e){
    ProcessException(e);
  }
}
public methiod2(){
  try {
   // do something
  } catch (exception e){
    ProcessException(e);
  }
}
public methiod3(){
  try {
   // do something
  } catch (exception e){
    ProcessException(e);
  }
}

It reduces the catch part because it is repetitive and exposes the real functional code from obscurity.

And you can even migrate the ProcessException() to a different class that extends intelligence to report the exception more easily

1

Except throwing/catching exceptions is a convention in python (ask forgiveness, not permission).
Just take a look at Django, there are a few different types of exceptions that allow you to catch that exception, and handle it accordingly. (eg model DoesNotExist, MultipleObjectsReturned)

0

my instructor a year ago said as much. She didn't like using exceptions and preferred to use data validation, maybe even reg expressions. I forgot now. Though for some things it's required to throw exceptions.

2

The rule should be that only throw an exception when your code have absolutely no clue how to deal with it. It would basically mean that in your code you have an "exceptional" situation and press the panic button.

But in production environments you will see tons of abuse by many developers because they are lazy and take the shortcut out. Small scale projects, it can work but as it scales in size in become one giant misery.

-1

I mostly agree with the short version. There are times in normal code flow where an exception is a good idea- a lot of it depends on what exceptions are in that specific language. If the goal is to make the code look a bit cleaner, but you are hammering an exception when you should have used a branch, that is usually poor practice.

Phrasing the tip as absolute isn't very good, though. You really need to see what exceptions will do for you and what they will, err, do TO you, and base your decisions on that.