Deal with exception that guaranteed will not occur

Asked

Viewed 246 times

6

Here is an example of a method to illustrate the context of my doubt:

public void comprar(int numeroLoja, int numeracaoProduto, String nomeMarca) throws LojaNaoExisteException, ProdutoNaoExisteException, MarcaNaoExisteException {
    Loja loja = null;
    Produto produto = null;
    Marca marca = null;

    try {
        loja = obterLoja(numeroLoja);
    } catch (LojaNaoExisteException e) {
        throw e;
    }

    try {
        produto = obterProduto(loja, numeracaoProduto);
    } catch (ProdutoNaoExisteException e) {
        throw e;
    }

    try {
        marca = obterMarca(loja, produto, nomeMarca);
    } catch (MarcaNaoExisteException e) {
        throw e;
    }
}

Having this scenario and knowing that the method obterProduto can launch the Exception LojaNulaException and that the method obterMarca may launch the exceptions LojaNulaException and ProdutoNuloException, Moreover my buying method can only cast the stated exceptions with throws (LojaNaoExisteException, ProdutoNaoExisteException, MarcaNaoExisteException).

With the above conditions it is possible to ensure that both obterProduto how much obterMarca will never release exceptions for null objects, but still I have a build error if I do not treat these exceptions in any way. How can I solve this problem without having to make exceptions for null objects in my method comprar?

  • 3

    You can’t quite understand what you want. Perhaps the problem is already in the so-called methods. Perhaps these exceptions should not even exist. You say you have a build error, but you don’t say which one. Put a throw e within the catch is the last thing you should do. This way of "treating" exceptions is the worst possible. The only guarantee this gives is that problems will occur. Put more information and I try to give an answer.

3 answers

5


TL;DR

The shape correct to deal with exceptions that should not occur is not to use them in the first place.

Deducting the scenario

You must have the code something like this:

Produto obterProduto(Loja loja, int numeracaoProduto) {
    if (loja == null) throw new LojaNulaException();
    ...
}

This is a common practice and I have worked in companies that used exception to treat all "exceptional" cases of methods (there was even an excel spreadsheet to generate exceptions automatically, size was the number of classes).

Analyzing the Exceptions

There are at least two reasons why exceptions should not be used:

1. This case is not really exceptional.

Treating nulls is not something that is part of the planned system flows. You would not create a diagram or flowchart to treat each parameter that can be null.

By this I do not mean that the parameters should not be checked, on the contrary. Every good developer checks well the incoming entry.

However, parameter checking is something known as preconditions. Preconditions are requirements that need to be met for a routine to actually perform.

As I will show below, there are other mechanisms to treat these preconditions.

2. This generates an unnecessary "dumbness"

All the unnecessary bureaucracy makes development worse. People don’t like it, you spend more time on things that don’t make sense and you don’t solve any problems.

Specifically in this case, do you really want to create specific exceptions to all the possible problems that your program might have? Why not create an exception for each field that is not populated.

I know many argue that this helps the developer to know that he can’t pass a null but this could simply be documented in the method.

"Ah, but if you don’t follow the documentation?". Well, that’s what tests are for. If a developer can put into production a code that has never been executed, passing an invalid argument, you have a serious problem in the development process.

And at the end of the day, even in difficult cases where you don’t get an obvious mistake like this, it doesn’t make much of a difference if the program stops with a LojaNula or NullPointer. The user will not be happy. And with the stack you can easily find the problem in both cases.

Alternatives to treat preconditions or cases that "should not occur"

More general exceptions

There is a reason why the exceptions have a message. They can be reused, but it seems that many architects forget this.

If a parameter has an invalid value, you can do this:

if (tamanho < 0)
    throw new IllegalArgumentException("Tamanho deve ser positivo!");

If you want to avoid null parameters, a simple alternative is to launch:

if (parametro == null)
    throw new NullPointerException("Parâmetro XYZ não pode ser nulo!");

If by chance you have any special treatment for your null parameters (which I doubt), you can do something like this:

throw new ParametroNulo("XYZ");

And in the exception constructor you only receive the parameter name and generate a more friendly message.

I’m always amazed to see how people forget that they can extend Exception and add attributes and arguments to the constructor.

Assertions

Another way to treat unexpected behavior is through assertions.

The point here is to draw a line between known errors and the unexpected.

  • An expected problem can be easily dealt with with with an exception. You know that that is a situation that has a legitimate probability of occurring. Still check if there is no longer an exception made for this.
  • An unexpected problem does not need an exception, you can simply make an assertion like: this should never occur, but if by chance it does occur, throw an error.

Java has good assertion support and they basically work as unverified exceptions.

Google Guava Preconditions

Guava is a library known to reinforce good practices. One of them is to use assertions to treat preconditions.

Example:

checkArgument(tamanho > 0, "Tamanho deve ser positivo: %s", count);
checkNotNull(loja, "Loja deve ser informada");

The above examples should use import static to import class methods Preconditions and will issue exceptions in case the check fails.

This makes your code easier to read and requires less typing, decreasing the tangle of ifs if there are many cases

Considerations

Every project should draw the line between exceptional treatment and preconditions.

Hundreds and thousands of exceptions do not make the development better, so I suggest forgetting the old and known bad practice of creating standard exceptions for each entity of your CRUD (sometimes I think people do this just to feel in control or to get the impression that they are having more productivity).

In fact, in all cases where you have virtually generated code, maybe even a code generator, it’s best to think and rethink ten times if it wouldn’t be better to create a unified code that can be reused.

And if you’ve fallen into a trap like this and are now having trouble maintaining code, get together with the team and create a mini-code cleaning project. In a project like the one mentioned in the question, I would spend a day or two removing all the specific exceptions and replacing them with more generic ones and perhaps assertions. If you have many cases, a replacement with Regex can do much of the job, if not all.

  • 1

    Great answer. It is difficult even for those who are not used to understand where to draw the line between what is an if and what is an exception, or what is an unverified exception and what is verified. Especially for those who only learned C in college, with their return codes and errnos.

1

I’m not sure I understand exactly the problem, but I believe you can treat each of these null exceptions separately, and fire new exceptions within the type provided in the method statement comprar.

The code goes something like this:

public void comprar(int numeroLoja, int numeracaoProduto, String nomeMarca) throws LojaNaoExisteException, ProdutoNaoExisteException, MarcaNaoExisteException {

    Loja loja = null;
    Produto produto = null;
    Marca marca = null;

    try {

        loja = obterLoja(numeroLoja);
        produto = obterProduto(loja, numeracaoProduto);
        marca = obterMarca(loja, produto, nomeMarca);

    } catch (LojaNulaException e) {
        // Trata LojaNulaException
        throw new LojaNaoExisteException();

    } catch (ProdutoNuloException e) {
        // Trata ProdutoNuloException
        throw new ProdutoNaoExisteException();

    } catch (MarcaNaoExisteException e) {
        // Trata MarcaNaoExisteException
        throw e;

    }
}

This chaining can be extended as needed:

    try {

        loja = obterLoja(numeroLoja);
        produto = obterProduto(loja, numeracaoProduto);
        marca = obterMarca(loja, produto, nomeMarca);

    } catch (LojaNulaException e) {
        // Trata LojaNulaException
        throw new LojaNaoExisteException();

    } catch (ProdutoNuloException e) {
        // Trata ProdutoNuloException
        throw new ProdutoNaoExisteException();

    } catch (LojaNaoExisteException e) {
        // Trata LojaNaoExisteException

    } catch (ProdutoNaoExisteException e) {
        // Trata ProdutoNaoExisteException 

    } catch (MarcaNaoExisteException e) {
        // Trata MarcaNaoExisteException
    ...
    ... 
    ...
    ... 
    } catch (Exception e) {
        // Trata outra possível Exception que possa acontecer

    } finally {
        // Opcional: executa um código de finalização 
    }

Another possibility that should solve the problem, is to chain the treatment blocks internally.
An example of what the code would look like:

    loja = obterLoja(numeroLoja);
    try {
        // Trata LojaNulaException
        produto = obterProduto(loja, numeracaoProduto);

        try {
            // Trata ProdutoNuloException
            // Se a marca não existir, já vai disparar MarcaNaoExisteException
            marca = obterMarca(loja, produto, nomeMarca);

        } catch (ProdutoNuloException e) {
            // dispara ProdutoNaoExisteException durante a busca da marca
            throw new ProdutoNaoExisteException();

        }

    } catch (LojaNulaException e) {
        // dispara LojaNaoExisteException durante a busca da marca ou produto
        throw new LojaNaoExisteException();
    }

-1

Usually when I don’t know exactly what might happen, I treat it this way:

try {
    //codigo
} catch (Exception e) {
    //Tratamento
}

The reason for such a treatment, I think it is more for safety, of course leaving deforms much more generic, you will be treating all occurrences only in one way.

I always use it for safety, in case something goes unnoticed, at least I’m sure that somehow I’ll treat in the end.

  • This is terrible. You are capturing all the possible mistakes and doing the same thing with all of them. The only situation I can think of that would fit this is at the highest level of code (in main) to throw errors in a special file or display in a dialog before letting the application die. Anywhere else this can swallow serious programming errors: application will continue running as if nothing had happened and you will only know that there is something wrong when you no longer have a way to track where the error came from. Don’t do this!

  • More so you may be logging in the error and capturing them for correction. I believe it is a better way than letting the application fall to users. If he is treating the exceptions he knows that can occur, and in the end treat the rest, what harm is there? Treat what you know and in the end log what goes unnoticed... I didn’t say to simply put an "Exception and" alone, that would be a big mistake.

  • As I said, the only sensible thing to do in this case would be to log the exception (perhaps by sending it to an analysis server, for example). However, dropping the application to users is the most sensible thing to do. Don’t take my word for truth-see what Chrome does, for example (dead tab) or Firefox (asks for a report and restarts). Letting the application run after getting an unknown error is very dangerous for your(ua) user(a). An example:

  • Imagine that you are creating a text editor (or images, videos, etc.): if you swallow an unknown error after sending to a log, what guarantees that this error has nothing to do with, say, saving the file? The (a) user(a) may think that your Ctrl-S is working because your program says it is, but it is only saying this because of a bug caused by an invalid state that should not occur, but occurred because of the unknown exception. He(a) can lose working hours because of it. Now imagine a financial application? Convenience isn’t worth the risk.

Browser other questions tagged

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