Is it correct for a method to make the same exception for two different reasons?

Asked

Viewed 191 times

12

I’m practicing TDD simulating an alarm system. Alarm centers work connected to sensors that detect intrusion (opening a door or window, or movement within a room, for example). They have a fixed number of logical partitions (representing different locations to be protected) and slots for sensors. At the installation the sensors are associated to the respective partitions. Then the interface of my alarm center includes the following methods (Obs.: " arming" a partition means protecting it, i.e., the center will trigger the alarm if a partition sensor is triggered, and "disarm" means unprotected, i.e., ignore the drive of the partition sensors):

public interface InterfaceDeCentralDeAlarme {
    boolean armarParticao(int numero) throws IllegalArgumentException, IllegalStateException;
    boolean desarmarParticao(int numero) throws IllegalArgumentException, IllegalStateException;
    void associarSensorAParticao(int sensor, int particao) throws IllegalArgumentException, IllegalStateException;
    void desassociarSensorDeParticao(int sensor, int particao) throws IllegalArgumentException, IllegalStateException;
    ...
}

Here are some of the possible unit tests:

@Test(expected=IllegalStateException.class)
public void particaoSemSensoresAssociadosNaoPodeArmar() {
    InterfaceDeCentralDeAlarme central = criarCentralDeAlarme();
    central.armarParticao(1);
}

@Test(expected=IllegalStateException.class)
public void particaoJaArmadaNaoPodeArmar() {
    InterfaceDeCentralDeAlarme central = criarCentralDeAlarme();
    central.associarSensorAParticao(1, 1);
    central.fecharSensor(1);
    Assert.assertTrue(central.armarParticao(1));
    central.armarParticao(1);
}

The method armarParticao() delegates the arme to a class called Particao. So far, no big deal:

@Override
public boolean armarParticao(int numero) throws IllegalArgumentException, IllegalStateException {
    Particao particao = particoes.get(numero);
    if (particao == null) {
        throw new IllegalArgumentException();
    }

    return particao.armar();
}

In class Particao created a public method armar() that can cast IllegalStateException for two different reasons, that is, two different situations in which the object is in an improper state. One is when the partition is already armed; another is when the partition has no sensors associated with it (i.e., there is no way to detect intrusion):

public class Particao {

    private final Map<Integer, Sensor> sensores = new HashMap<>();
    private boolean armada = false;

    public boolean armar() throws IllegalStateException {
        if (armada) {
            throw new IllegalStateException("Partição já se encontra armada");
        }

        if (sensores.isEmpty()) {
            throw new IllegalStateException("Partição não possui sensor associado");
        }

        for (Sensor sensor : sensores.values()) {
            if (sensor.isAberto() && false == sensor.isInibido()) {
                return false;
            }
        }

        this.armada = true;
        return true;
    }

    ...
}

From the point of view of unit tests and TDD, is that bad? A colleague said yes, because if the method launches a IllegalStateException I’ll never be sure if it’s because of one reason or because of the other. The exception thrown may mask a different situation than the one I’m trying to test.

But I’m not sure that’s a problem. Maybe it is if I don’t have tests for every possible situation (including the two situations where IllegalStateException can be launched). But if I have, maybe this overlap doesn’t get to be a problem. In practice, when writing unit tests or doing TDD people have the habit of avoiding it (throw the same exception in a method in two different situations)?

Second question: if I have to use different exceptions, should I create custom exceptions (new classes) for each of the situations, or is this a more or less arbitrary decision? A(s) exception(s) created(s) shall be subclasses of IllegalStateException?

1 answer

9


Question modified after answer

Everything in engineering needs to be thought out for what you’re doing. For once the answer is a huge DEPENDS.

The first thing is to ask yourself if it will really make a difference to have different situations. Often you just need to know that the exception occurred, you don’t need to know the exact situation, so you can live with it. If that’s the case, you could, but not necessarily, rewrite it like this:

 if (condicao1 || condicao2) throw new IllegalStateException("Falhou devido à condição");

The test needs to be adequate

If you cannot make your life easier, one of the solutions is to take the test to identify the details of the exception, if this is important.

Example:

try {
    executarAcao();
} catch (Exception e) {
    assertThat(e).isInstanceOf(IllegalStateException.class)
                 .hasMessage("Falhou devido à condição 1");
}

I put in the Github for future reference.

Junit has the expectMessage() for this. I believe that most of the good frameworks have similar facilities.

You can also use annotation expected=IllegalStateException.class, message = "Falhou devido à condição 1".

It is normal for a method to bid more than one equal exception and need to be tested, so there are facilities to test. If the test needs to do this, do it and test it properly.

The method is wrong

Another solution is to release different, more customized exceptions.

There are those who say that this should always be done, that generic exceptions are not good. Others think exaggeration. It depends on the case. If you are going to change a small detail, it may be too much, but if it is unrelated, then it may be the case. You may be using a generic exception for "sloth". Remember that there are people who only throw Exception, then the problem is another. Launch IllegalStateException may fall into the same problem even though it is not so apparent. It may also be the right one for the case.

Whether they should be subclasses or not depends on the case. This may be one that should, by the looks of it, but of course it is a very hypothetical example. I wouldn’t say it’s an arbitrary decision, it’s an individual decision.

Note that the problem there is of design method*. If so, solve this problem. The previous solution is to solve the design test, which should be preferred if your method is correct.

After editing the question you can see that the method has different problems. At least one of them is not an illegal state, it’s just a state that prevents the operation. Which points out that the exception is being used as business rule (I understand that it is common in Java to do this, but I would go for another way). The other may, but not necessarily, exist by a class failure, perhaps there should be the object with illegal state. If everything is right, the exceptions are completely different. The error is not to hinder the test, it is wrong specification. But I may have misinterpreted.

I find it absurd that people change their primary code to meet a test demand. You must allow the test, eventually to facilitate, but never at the cost of the primary responsibility of your code. There are those who disagree, but I find it a mistake to program for the test. Do something testable, ok, but the code must be done to meet the specification.

Completion

As far as I know people do not avoid throwing the same exception no, if the method needs, should do. And I see as something very common. I don’t know if you have any better solutions.

The question example shows how TDD is a complicated thing. It works well when: (a) the problem is widely known and has very good specification (rare); (b) an architectural genius did it.

Were you curious to ask the colleague what the solution is? Maybe he doesn’t know what he’s talking about, even if :). Remember that many people read about something being "good or bad practice" somewhere, repeat what they "saw", but do not understand the problem. Obviously I don’t know if that’s the case.

  • That recommendation "If it’s going to change a small detail, it might be too much, but if it’s something unrelated, it might be the case" It sounded a little vague, because of the very question, which did not show a concrete case. I will go into more detail the scenario to, if you think it is the case, leave the answer more targeted. But I am already quite satisfied with it.

  • 1

    It’s actually complicated what you did because despite having kept the subject, the question is quite another. Tomorrow I take a look.

  • It was not my intention to change the question, just to specify the situation. All right, tomorrow we’ll see what you do with it. And to answer your question, my colleague did not specify how to deal with the exceptions and left that to my charge, just recommended that I make two separate exceptions.

  • My question is rhetorical :)

  • In the end I think the question has not changed so much, I could keep most of the original answer. : ) I understood that an error code may be better for the already armed partition, while the other situation may not even need to exist, in fact it is not required of the class that these associations are dynamic; the class could be initialized with the sensors associated with the partitions already in the construction (I think that’s what you meant, no? ). This abundance of throws IllegalStateException on the interface can even be considered a code Smell.

  • That’s right. But it’s obvious that only you can confirm if this is possible. I think it’s code Smell, not everyone does. Each one has his own vision, right?... : ) Although code Smell don’t mean error, of course. But the most important thing is that if you want to keep the class like this, you can test it. If the question had like this from the beginning, I could have done something closer to the face, I didn’t want to touch much of what I had already written.

  • Stretching the question a little, and the IllegalArgumentException, would you keep? And as for the TDD being wrong, actually I didn’t even do the right TDD, I’m doing an experiment mixing post-design testing with a little bit of TDD and seeing how it affects my design. But it is really controversial to say that "if the TDD is the specification, then it is wrong", because I have the impression that this is exactly what the TDD proposes to be.

  • I don’t, but I’m a bit radical in the use of exceptions :) I would avoid one of them being cast guaranteed the proper state and the other I would find a way to notify otherwise that it is already armed, but I understand that this is not common in Java, the exception is preferred, People are even more used to it. I am against TDD in almost every situation. Your TDD is wrong :) It specifies something that should not be in the method (publicly). Of course I’m talking about the case of doing what I think you should, not releasing these exceptions. TDD specified them, right?

  • To IllegalArgumentException that I speak in the case is when I call armarParticao(int) passing an invalid partition number. And I’m not sure if it’s in the method signature because of the TDD, I don’t think so. The IllegalStateException Yes, but I might as well be doing it wrong. I actually think I should stop doing the TDD thing by half. But the discussion and the insights it generates are so productive. :)

  • But TDD should specify everything that is public, right? This is another reason I don’t like TDD, everyone thinks something about the subject :D

  • Yes, I should specify everything that is public. I am not saying that what I did was the correct TDD... I did not follow exactly what the TDD proposes. I don’t know if I’m ready to embrace TDD. I think it’s kind of religious, this whole "go three steps without question and your code will reach enlightenment" thing. It’s kind of magical that the design goes right and the productivity increases in the long term although it decreases in the short. But we try it right.

  • I had interpreted that in the case of the already armed partition I should use error code because it is a business rule. Reading more on the subject I saw that actually this is not a problem in itself, the error code is actually recommended because it is a predicted situation to happen. Business exceptions are acceptable as long as they are exception and not rule, and may include improve code expressiveness in streams with many steps.

Show 7 more comments

Browser other questions tagged

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