Which is better/more efficient: check for bad valu

2019-01-23 04:48发布

问题:

Which is more efficient in Java: to check for bad values to prevent exceptions or let the exceptions happen and catch them?

Here are two blocks of sample code to illustrate this difference:

void doSomething(type value1) {
    ResultType result = genericError;

     if (value1 == badvalue || value1 == badvalue2 || ...) {
          result = specificError;
     } else {
          DoSomeActionThatFailsIfValue1IsBad(value1);
          // ...
          result = success;
     }
     callback(result);
}

versus

void doSomething(type value1) {
     ResultType result = genericError;
     try {
          DoSomeActionThatFailsIfValue1IsBad(value1);
          // ...
          result = success;
     } catch (ExceptionType e) {
          result = specificError;
     } finally {
          callback(result);
     }
}

On the one hand, you're always doing a comparison. On the other hand, I honestly don't know what the internals of the system do to generate an exception, throw it, and then trigger the catch clause. It has the sound of being less efficient, but if it doesn't add overhead in the non-error case, then it's more efficient, on average. Which is it? Does it add similar checking anyway? Is that checking there in the implicit code added for exception handling, even with the additional layer of explicit checking? Perhaps it always depends on the type of exception? What am I not considering?

Let's also assume that all "bad values" are known -- that's an obvious issue. If you don't know all the bad values -- or the list is too long and not regular -- then exception handling may be the only way, anyway.

So, what are the pros and cons of each, and why?

Side questions to consider:

  • How does your answer change if the value is "bad" (would throw an exception) most of the time?
  • How much of this would depend on the specifics of the VM in use?
  • If this same question was asked for language-X, would the answer be different? (Which, more generally, is asking if it can be assumed checking values is always more efficient than relying on exception handling simply because it adds more overhead by current compilers/interpreters.)
  • (New) The act of throwing an exception is slow. Does entering a try block have overhead, even if an exception is not thrown?

Similarities on SO:

  • This is similar to the code sample in this answer, but states they are similar only in concept, not compiled reality.
  • The premise is similar to this question but, in my case, the requester of the task (e.g. "Something") isn't the caller of the method (e.g. "doSomething") (thus no returns).
  • And this one is very similar, but I didn't find an answer to my question.

  • And similar to far too many other questions to list, except:

    I'm not asking about theoretical best practice. I'm asking more about runtime performance and efficiency (which should mean, for specific cases, there are non-opinion answers), especially on resource limited platforms. For instance, if the only bad value was simply a null object, would it be better/more efficient to check for that or just attempt to use it and catch the exception?

回答1:

"How does your answer change if the value is "bad" (would throw an exception) most of the time?" I think that's the key right there. Exceptions are expensive as compared to comparisons, so you really want to use exceptions for exceptional conditions.

Similarly, your question about how this answer might change depending on the language/environment ties into that: The expense of exceptions is different in different environments. .Net 1.1 and 2.0 are incredibly slow the first time an exception is thrown, for instance.



回答2:

Purely from an efficiency standpoint, and given your code examples, I think it depends on how often you expect to see bad values. If bad values are not too uncommon, it's faster to do the comparison because exceptions are expensive. If bad values are very rare, however, it may be faster to use the exception.

The bottom line, though, is that if you're looking for performance, profile your code. This block of code may not even be a concern. If it is, then try it both ways and see which is faster. Again, it depends on how often you expect to see bad values.



回答3:

I could find surprisingly little current information about the cost of throwing Exceptions. Pretty obviously there must be some, you are creating an object, and probably getting stack trace information.

In the specific example you talk about:

if (value1 == badvalue || value1 == badvalue2 || ...) {
      result = specificError;
 } else {
      DoSomeActionThatFailsIfValue1IsBad(value1);
      // ...
      result = success;
 }

The problem for me here is that you are in danger if (probably incompletely) replicating logic in the caller that should be owned by the method you are calling.

Hence I would not perform those checks. Your code is not performing an experiment, it does "know" the data it's supposed to be sending down I suppose? Hence the likelyhood of the Exception being thrown should be low. Hence keep it simple, let the callee do the checks.



回答4:

In my opinion you should have try/catch blocks around anything that could potentially throw exceptions, if only to have a safe running system. You have finer control of error responses if you check for possible data errors fist. So I suggest doing both.



回答5:

Well, exceptions are more expensive, yes but for me, its about weighting the cost of efficiency vs bad design. unless your use case demands it, always stick to the best design.

the question really is, when do you throw an exception? in exceptional situations.

if your arguments are not in the range that you're looking for, i'd suggest returning an error code or a boolean.

for instance, a method,

public int IsAuthenticated(String username, String password)
{

     if(!Validated(username,password)
     {
          // just an error
          // log it
          return -2;   
     }

     // contacting the Database here
     if cannot connect to db
     {
          // woww this is HUUGE
          throw new DBException('cannot connect'); // or something like that
     }

     // validate against db here
     if validated, return 0;

    // etc etc

}

thats my 2 cents



回答6:

My personal opinion is that exceptions indicate that something is broken - this might well be an API called with illegal arguments or division by zero or file not found etc. This means that exceptions could be thrown by checking values.

For the reader of your code - again my personal opinion - it is much easier to follow the flow if you can be certain that it is not put aside by all kinds of strange throws (which is essentially gotos in disguise if used as part of the program flow). You simply have less to think about.

This is in my opinion a good thing. "Smart" code is hard to wrap your head around.

On a side note - JVM's get much much smarter - coding for efficiency usually doesn't pay off.



回答7:

Normally, one would assume that try-catch is more expensive because it looks heavier in the code, but that entirely depends on the JIT. My guess is that it's impossible to tell without having a real case and some performance measurements. The comparisons could be more expensive, especially when you have many values, for example, or because you have to call equals() since == won't work in many cases.

As for which one you should chose (as in "code style"), my answer is: Make sure that the user gets a useful error message when it fails. Anything else is a matter of taste and I can't give you rules for that.



回答8:

To be safe, assume exceptions are expensive. They often are, and if they aren't it will at least push you towards using exceptions wisely. (Entering a try block is usually trivially cheap, since implementors do their best to make it so, even at the cost of making exceptions more expensive. After all, if exceptions are used properly, the code will enter the try block many times more often than it will throw.)

More importantly, exceptions are a style issue. Exceptions for exceptional conditions make code simpler because there's less error-checking code, so the actual functionality is clearer and more compact.

However, if exceptions might be thrown in more normal circumstances, there's invisible flows of control that the reader has to keep in mind, comparable to Intercal's COME FROM...UNLESS... statement. (Intercal was one of the very early joke languages.) This is very confusing, and can easily lead to misreading and misunderstanding the code.

My advice, which applies to every language and environment I know about:

Don't worry about efficiency here. There are strong reasons besides efficiency for using exceptions in a way that will prove efficient.

Use try blocks freely.

Use exceptions for exceptional conditions. If an exception is likely, test for it and handle it in another way.



回答9:

a question like this is like asking,

"is it more efficient to write an interface or a base class with all abstract functions"

does it matter which is more efficient? only one of them is the right way for a given situation



回答10:

Note that if your code doesn't throw exceptions then it doesn't always imply that the input is within bounds. Relying on throwing exceptions by the standard Java (API + JVM), such as NullPointerException or ArrayIndexOutOfBoundsExceptions is a very unhealthy way to validate input. Garbage-in sometimes generates garbage-but-no-exception-out.

And yes, exceptions are quite expensive. They should not be thrown during a normal processing flow.



回答11:

Optimizationally, I think you're going to find it's probably a wash. They'll both perform alright, I don't think exception throwing is ever going to be your bottleneck. You should probably be more concerned with what Java is designed to do (and what other Java programmers will expect) and that is thrown exceptions. Java is very much designed around throwing/catching exceptions and you can bet the designers made that process as efficient as possible.

I think it's mostly a philosophy and language culture sort of thing. In Java, the general accepted practice is that the method signature is a contract between your method and the code calling it. So if you receive an improper value, you generally throw an unchecked exception and let it be dealt with at a higher level:

public void setAge(int age)
{
    if(age < 0)
    {
        throw new IllegalArgumentException("Array can't be negative");
    }

    this.age = age;
}

In this case, the caller broke their end of the contract, so you spit their input back at them with an exception. The "throws" clause is for use when you can't fulfill your end of the contract for some reason.

public void readFile(String filename) throws IOException
{
   File myfile = new File(filename);
   FileInputStream fis = new FileInputStream(myfile);

   //do stuff

   fis.read();

   //do more stuff
}

In this case, as the method writer, you've broken your end of the contract because the user gave you valid input, but you couldn't complete their request due to an IOException.

Hope that kinda puts you on the right track. Good luck!