2

In this specific scenarios, are asserts more appropriate then exceptions?

It is my understanding that assert should be used when program is FUBAR to a degree where it can not recover and will exit.

I also was told to always throw exceptions for clarity and error message handling.

Is there a fine line between when to use each? Is there an example where assert must be used in place of exception unconditionally?

   public void subscribe(DataConsumer c) throws IllegalArgumentException {
        if (c == null) {
            // Almost certainly FUBAR
            throw new IllegalArgumentException("Can't subscribe null as a DataConsumer. Object not initialized");
        }

        if (dataConsumerList == null) {
            // Definetely FUBAR
            throw new IllegalArgumentException("Nothing to subscribe to. DataConsumerList is null");
        }

        dataConsumerList.add(c);
    }
James Raitsev
  • 92,517
  • 154
  • 335
  • 470

4 Answers4

4

Personally I'm not keen on using assertions for this sort of thing, simply because they can be turned off. Some places use assertions when running tests, but then disable them for production for the sake of speed. To me this is like taking your driving test as normal, but then removing your seatbelt when you get on the motorway.

An assertion is only going to throw an exception anyway, of course. If you absolutely want to take the JVM down right now, you'd need to use something like Runtime.halt.

So I'm a fan of exceptions here, although I'd typically use a NullPointerException when given a null argument, and if dataConsumerList is part of your state then I would personally use IllegalStateException to differentiate that situation. (It's a shame that Java doesn't have the same ArgmentNullException that .NET has, given what a common check it is.)

Guava has the useful Preconditions class which lets you write this more concisely:

public void subscribe(DataConsumer c) throws IllegalArgumentException {
    Preconditions.checkNotNull(c,
        "Can't subscribe null as a DataConsumer. Object not initialized");
    Preconditions.checkState(dataConsumerList != null,
        "Nothing to subscribe to. DataConsumerList is null");
    dataConsumerList.add(c);
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I don't agree with your simile, as (like you said) an assertion will only throw an exception, and a null dereferencing will throw a not dissimilar exception. The only main difference is that the exception an assertion throws carries specific information. – biziclop Feb 01 '11 at 14:33
  • @biziclop: No, the difference is that if you're using an exception directly the check will always be performed - whereas with assertions, you can turn them off at which point you may end up continuing blindly with invalid data for a while... possibly corrupting state. Suppose `c` is null but `dataConsumerList` isnt' in this case - turning off assertions could lead to a problem much later on, by which time you may have saved the invalid data somewhere. – Jon Skeet Feb 01 '11 at 14:35
  • @biziclop, an assertion will throw an `Error`. – Péter Török Feb 01 '11 at 14:38
  • If I go into a situation, that can not or should not (usually the use-case for assertions), I prefer an Error over an Exception. Conveniently I usually throw an AssertionError in such cases (the same the assert-statement would throw). – Mnementh Feb 01 '11 at 14:53
  • @Jon Skeet Fair enough, although I expect any sane system to check its data before persisting and throw a proper business exception if anything's amiss. At worst, the database should complain about it. If data consistency hinges on a single setter method's check, you're up the creek anyway. – biziclop Feb 01 '11 at 14:55
  • @biziclop: But that's just spreading the same question across the system - I doubt that anyone's suggesting using assertions for *one* method but nowhere else. – Jon Skeet Feb 01 '11 at 14:58
  • @Jon Skeet No, because what I'm saying is that it should either be checked exceptions or assertions, but not runtime exceptions. Anyway, I didn't want to start a holy war, more of a discussion on the subject, but I guess comments to an already answered question isn't the best forum for that. – biziclop Feb 01 '11 at 15:34
  • @biziclop: Yes, I think we'll have to agree to disagree on this front. – Jon Skeet Feb 01 '11 at 15:36
1

General rule (copied from here)

assertions should protect from (not always obvious) mistakes of the developer, e.g. using a pointer despite its being NULL.

exceptions are a way to handle errors that may legitimately occur at runtime, e.g. the failure of trying to connect to some server (which may not respond for various reasons).

And there is a better way of writing the above code using google-guava Preconditions.checkNotNull() class.

public void subscribe(DataConsumer c) throws IllegalArgumentException 
{
checkNotNull(c, "Can't subscribe null as a DataConsumer. Object not initialized");
checkNotNull(dataConsumerList , "Nothing to subscribe to. DataConsumerList is null");

dataConsumerList.add(c);
}
Aravind Yarram
  • 78,777
  • 46
  • 231
  • 327
0

The Java technote Programming With Assertions contain this explicit line in with regards to usage:

Do not use assertions for argument checking in public methods.

That should be a pretty definitive answer to your question.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
0

If you could put this in English terms, use assert for "gotta" (Got to, Must) and exceptions for "otta" (Ought to, should).

Use the assert for show-stopping, critical conditions that must be true for the execution to continue. Examples might be that a division happens correctly (think of the Intel chip floating point bug) or that your database connection is not null after you have correctly opened it. If these have occurred, then program execution should not continue.

Use the throw for foreseeable errors that your method may handle. The throw is a part of a contract that declares to you and other programmers that certain types of errors may be encountered (and that it's not your responsibility).

In your example, my guess is that a null consumer or an empty list should never happen under normal circumstances. If my guess is correct, then you would want to use an assert here, declaring that subscribe() will handle it.

If my guess is wrong and a null consumer happens, say 1 out of 50 times, then the throw would be better and you would be declaring that subscribe() forms a contract with a calling method, whereby the calling method handles the error.

rajah9
  • 11,645
  • 5
  • 44
  • 57