Is it correct to program returning exceptions to the expected event?

Asked

Viewed 97 times

10

When we are developing a software we can go through situations where something happens that we are not expecting and usually when it happens in many programming languages we can treat it within exceptions the famous blocks of code try/catch.

Let’s say we have a class Conta.

class ContaCorrente extends Conta {

    public void deposita(double valor) {
        super.saldo += (valor);
    }

    public void saca(double valor) {
        if (super.saldo < valor) {
            throw new SaldoInsuficienteException(
                    "O saldo não é suficiente para saque, Saldo atual é: " + super.saldo + " Você tentou sacar " + valor);
        }
        super.saldo += (valor - 0.10);
    }

}

And in that case I made an exception unchecked java:

public class SaldoInsuficienteException extends RuntimeException{
    
    public SaldoInsuficienteException(String msg) {
        super(msg);
    }

}

To test I created a simple main:

public static void main(String[] args) {
        Conta conta = new ContaCorrente();
        conta.deposita(200);
        
        try {
            conta.saca(300);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

What to return? It is to the taste of the customer or has a better shape?

2 answers

9


Controversy

This generates a bit of controversy because there are those who defend one thing and others who think the opposite. No one has proved what is right, so everyone only has opinions. Some are more grounded than others. Depending on who reads and the prior knowledge of each, who reads will believe one thing or another. I myself have spoken of this in several questions on the subject:

Basically the question has already been answered.

The summary of all this is that there is a school that says that exception is the solution to everything that wasn’t quite what you wanted, or in other words "cure cancer exception" and you should do exactly what you did. In fact in Java there is a tendency to do this, the whole API adopts this.

Java has modernized

But more recently Java has stopped being so dogmatic, repeating the mantra that language is object-oriented, and for the good of all has evolved into a more functional style. I think it’s fantastic that a lot of the community has adopted this style. And he preaches less the use of exceptions. For example they created the Optional to use an error code instead of throwing an exception when the error is expected.

So modern Java code would not do what was done and adopt an error code that could be better than a checked exception. You inform the inadequacy of what one tried to do so without making a mess in the code, without a huge exception weight.

Java started to adopt the school that preaches that exceptions should not be used for flow control, that they should only be released when something happened that shouldn’t happen, ie programming errors, and environment failures.

Your code is the old way of doing something because it is expected that there is no balance in some situations, it is not a mistake that the programmer committed and it is not something that the environment caused, it is an expected business rule.

Your case

Congratulations for having created a specific exception to this, in general people do not create. But there is still another mistake that almost everyone makes. It basically uses the same structure of Exception and sends a message that may not be suitable for use in a given situation.

The right thing to do when creating a new exception is to try to do something that only she has. The exception already indicates what the problem was and in each context you capture it may be that the message is different. Perhaps the most appropriate is just to aggregate the balance (not as a message, have a field for it) in the exception for the consumer to use it in a personalized message. But I’m not saying it’s wrong, it depends on the requirement, it may be that neither the balance should put, for security and privacy. Perhaps the value of the loot is in case it is included.

See more in Create more specific or more generic custom exceptions?.

But there’s another problem, when it comes to capturing the exception, it took the most generic possible, and that doesn’t make sense. I will not go into detail because I have answered in: There is some drawback in always catching Exception and not something more specific?. If you get a memory error you’re doing something you can’t imagine.

I don’t like checked exceptions, in the way Java has implemented, in complex systems a lot of wrong things can happen, but if you are going to follow this school of abuse of exceptions, then your exception should be checked since you cannot ignore the indication of failure (at least this is my interpretation for the problem).

And this gives a clue that exception is a difficult mechanism to use. Almost everyone who posts something here is making mistakes with it and in general make the same mistake every time.

Some say your code is perfect (rare), some say you were only wrong to capture Exception instead of SaldoInsuficienteException, and also those who will say that you were also wrong to make the exception not checked, and finally those who will say that you should not have used exception.

At my school, which is followed by some of the best programmers in the world, having no balance is not a failure, it is an expected situation that happens and is part of the business rule, and today Java has the right way to treat this.

Workaround

I don’t usually use Java and mainly the new features I don’t have domain, but it would be more or less this:

import java.util.*;

class Conta {
    public double saldo;
}

class ContaCorrente extends Conta {
    public void deposita(double valor) {
        super.saldo += (valor);
    }
    public Optional<Double> saca(double valor) {
        if (super.saldo < valor) return Optional.empty();
        super.saldo += (valor - 0.10);
        return Optional.of(valor);
    }
}

public class Main {
    public static void main(String[] args) {
        ContaCorrente conta = new ContaCorrente();
        conta.deposita(200);
        var sacado = conta.saca(300);
        if (sacado.isEmpty()) System.out.println("O saldo nao eh suficiente para saque");
        else System.out.println("A operação foi concluída e foi sacado " + sacado.get());
    }
}

Behold working in the ideone. And in the repl it.. Also put on the Github for future reference.

You can do it even more functionally, I think it’s good not to change the way you learned too much. Do not take as the perfect code, this implementation can be improved.

It may be that the requirement indicates that you can withdraw the available balance instead of denying the entire drawing. This works with this mechanism, except it has like, but it gets weird because it will have given exception, but it worked and would have to carry the value that worked out within the exception. Even if you don’t need to do this in this case, in another you may need it. Using the right mechanism is consistent.

But I have my doubts whether you should do so. Maybe it’s just a matter of returning true or false if the operation didn’t work out, and that Java always had, would look something like this:

import java.util.*;

class Conta {
    public double saldo;
}

class ContaCorrente extends Conta {
    public void deposita(double valor) {
        super.saldo += (valor);
    }
    public boolean saca(double valor) {
        if (super.saldo < valor) return false;
        super.saldo += (valor - 0.10);
        return true;
    }
}

public class Main {
    public static void main(String[] args) {
        ContaCorrente conta = new ContaCorrente();
        conta.deposita(200);
        if (!conta.saca(300)) System.out.println("O saldo nao eh suficiente para saque");
    }
}

Behold working in the ideone. And in the repl it.. Also put on the Github for future reference.

Attention that there is still another error in the code. And there are other conceptual errors in it.

  • Very good! very good explanation. thanks.

5

Exceptions serve to signal that something has failed.

Exceptions checked (checked), that is to say, Exception and their subclasses that are not RuntimeException, serve to signal things that must be properly treated so as to forget to treat them must be considered a programming error.

No verified exceptions already (unchecked), that is to say, RuntimeException and its subclasses, serve to signal things that by themselves are programming errors that manifested themselves at runtime, so that trying to treat them should be innocuous.

Here is an important difference. A RuntimeException signals a programming error that has occurred, while the others Exceptions signal situations that are not programming errors, but would become programming errors if not handled.

There are still the Errors, which signal serious, unexpected, unpredictable and probably intractable problems. They are rarely treated because rarely is there anything that can be done to treat them.

You then have a method to do a certain thing, which is to withdraw money from an account.

  • Can this method fail? Yes!
  • How can it fail? If there is not enough balance in the account.
  • Does this case have to be handled? Absolutely!
  • What if it is untreated? Failing to treat this case is a programming error!

Hence, this is the case of creating a verified exception (checked).

That is, what you have done is more or less on the right track, but there are still some mistakes. The first is that you have to do this:

public class SaldoInsuficienteException extends Exception {

    public SaldoInsuficienteException(String msg) {
        super(msg);
    }

}

That is, inherit from Exception, not of RuntimeException. The exception is checked, nay unchecked.

Then capture Exception generally speaking is bad programming practice. Good practice is to capture and treat specific exceptions, not generic exceptions:

public static void main(String[] args) {
    Conta conta = new ContaCorrente();
    conta.deposita(200);
        
    try {
        conta.saca(300);
    } catch (SaldoInsuficienteException e) {
        System.out.println(e.getMessage());
    }
}

In addition, since the observed exceptions must be treated, therefore they must be declared in the clause throws:

class ContaCorrente extends Conta {

    public void deposita(double valor) {
        super.saldo += (valor);
    }

    public void saca(double valor) throws SaldoInfuficienteException {
        if (super.saldo < valor) {
            throw new SaldoInsuficienteException(
                    "O saldo não é suficiente para saque, Saldo atual é: "
                    + super.saldo + " Você tentou sacar " + valor);
        }
        super.saldo += (valor - 0.10);
    }

}

Many people have a certain fear and are frustrated in using verified exceptions, and often refer to them as a failed experiment. However, what happens is that many of them are and have been used incorrectly since the early days of Java, even within the JDK, there are several situations where exceptions that should be verified were represented by RuntimeExceptionand various situations that should not be verified that were represented by Exceptions forcing the use of clauses catch harmless and irritating to situations that never occur. Another problem is that clauses throws do not get along very well with over-writing of methods or with Lambdas. Also, bad programming practice of using throws Exception or catch (Exception ex) also causes the advantages of having verified exceptions to be lost. However, knowing how to use the concept of verified and unverified exceptions correctly and with discipline, the value of having verified exceptions arises in not letting exceptional situations, however important and plausible, are forgotten to be treated.

  • Nice explanation! thank you very much.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.