Returning an object. Any Smell code here?

Asked

Viewed 106 times

1

I thought of the following public method:

public RespostaDeArme armar();

The class RespostaDeArme would be so:

public class RespostaDeArme {

    private final ResultadoDeArme resultado;
    private final Set<Integer> zonasAbertas;

    public RespostaDeArme(ResultadoDeArme resultado) {
        this(resultado, new HashSet<>());
    }

    public RespostaDeArme(ResultadoDeArme resultado, Set<Integer> zonasAbertas) {
        this.resultado = resultado;
        this.zonasAbertas = new HashSet<>(zonasAbertas);
    }

    public ResultadoDeArme getResultado() {
        return resultado;
    }

    public Set<Integer> getZonasAbertas() {
        return zonasAbertas;
    }
}

public enum ResultadoDeArme {
    SUCESSO(1),
    ARME_RECUSADO_PARTICAO_JA_ARMADA(2),
    ARME_RECUSADO_EXISTEM_ZONAS_ABERTAS(3),
    ERRO(4);

    private final int numero;

    private ResultadoDeArme(int numero) {
        this.numero = numero;
    }

    public int getNumero() {
        return numero;
    }
}

It only makes sense to call the method getZonasAbertas() when the result is ARME_RECUSADO_EXISTEM_ZONAS_ABERTAS.

Question: does this code have any code Smell? The only one I thought was an eventual need to refactor the class containing the method armar() with Hide Delegate not to need the intermediary RespostaDeArme to get the open zones (which I don’t know if would be the case here).

Does anyone see a better way to implement this behavior? If necessary I give more context. I don’t like the idea of having two methods in the same class, one armar() and a getZonasAbertas(), because the two would have an unnecessary temporal link (the programmer would need to know that he should first call the armar(), then the other).

Example of use:

public void executarArme(int idDaCentral) {
    RespostaDeArme resposta = conexao.armar();
    escritorDaFila.escrever(gerarJsonDeRespostaDeArme(idDaCentral, resposta));
}

private JSONObject gerarJsonDeRespostaDeArme(int idDaCentral, RespostaDeArme resposta) {

    JSONObject json = new JSONObject();

    json.put("tipoDeMensagem", Mensagens.RESPOSTA_DE_ARME.getNumero());
    json.put("idDaCentral", idDaCentral);
    json.put("resultado", resposta.getResultado().getNumero());

    if (resposta.getResultado() == ResultadoDeArme.ARME_RECUSADO_EXISTEM_ZONAS_ABERTAS) {
        json.put("zonasAbertas", new JSONArray(resposta.getZonasAbertas()));
    }

    return json;
}
  • What are these open zones? How are these numbers used by those who call the method getZonasAbertas()?

  • It’s a central alarm. A partition has n zones represented by numbers, which must all be closed at the time of the arme for it to be successful. Failing to arm due to open zones, I send this information to a process that records the result of the arme in the bank along with which zones were open, sends a notification to the user, etc. The user has the option to close the zones (manually, this zone is for example a magnetic sensor coupled to a window) before trying a new arm.

  • You can give an example of a typical code in which the method armar() and the getZonasAbertas() would be used? I tried to create another answer that wasn’t based on exceptions, but I came to the conclusion that in order to succeed, I would need to know better in what context this is used.

  • @Victorstafusa I can try, but I don’t have a code on hand, what I have today is done a little differently and I’m trying to make it more "correct". But basically what I’m going to do with this list of open zones is to include it in a JSON along with a value representing the result of the arme and send this JSON to a message queue, then the other process turns around. Today I convert this zone list into a comma-separated number string and send it to JSON.

  • Where is the method armar()? In what situation ResultadoDeArme is ARME_RECUSADO_PARTICAO_JA_ARMADA?

  • @ramaral The method armar() today is in a class ConexaoComCentralDeAlarme but I don’t know if this is where I should be (it’s one of the things I’m racking my brain about to get better, but this is outside the scope of the current question). The ARME_RECUSADO_PARTICAO_JA_ARMADA is a conflict resulting from a user (or multiple) trying to arm via application and via keyboard that is attached to the alarm center, for example.

  • 1

    Ah, in your Enum ResultadoDeArme, the method getNumero() could be implemented like this: public int getNumero() { return ordinal() + 1; } and with that you don’t need the field numero nor the builder.

Show 2 more comments

2 answers

1


Now that you’ve edited the question, it can be solved with a simple Optional, hiding the constructor and exposing static methods of Factory:

public final class RespostaDeArme {

    private final ResultadoDeArme resultado;
    private final Optional<Set<Integer>> zonasAbertas;

    public static RespostaDeArme sucesso() {
        return new RespostaDeArme(ResultadoDeArme.SUCESSO, Optional.empty());
    }

    public static RespostaDeArme armeFalhou() {
        return new RespostaDeArme(ResultadoDeArme.ERRO, Optional.empty());
    }

    public static RespostaDeArme particaoJaArmada() {
        return new RespostaDeArme(
                ResultadoDeArme.ARME_RECUSADO_PARTICAO_JA_ARMADA,
                Optional.empty());
    }

    public static RespostaDeArme existemZonasAbertas(Set<Integer> zonasAbertas) {
        return new RespostaDeArme(
                ResultadoDeArme.ARME_RECUSADO_EXISTEM_ZONAS_ABERTAS,
                Optional.of(Collections.unmodifiableSet<>(new LinkedHashSet<>(zonasAbertas))));
    }

    private RespostaDeArme(ResultadoDeArme resultado, Optional<Set<Integer>> zonasAbertas) {
        this.resultado = resultado;
        this.zonasAbertas = new HashSet<>(zonasAbertas);
    }

    public ResultadoDeArme getResultado() {
        return resultado;
    }

    public int getNumeroResultado() {
        return resultado.getNumero();
    }

    public Optional<Set<Integer>> getZonasAbertas() {
        return zonasAbertas;
    }
}
public enum ResultadoDeArme {
    SUCESSO,
    ARME_RECUSADO_PARTICAO_JA_ARMADA,
    ARME_RECUSADO_EXISTEM_ZONAS_ABERTAS,
    ERRO;

    public int getNumero() {
        return ordinal() + 1;
    }
}
public void executarArme(int idDaCentral) {
    RespostaDeArme resposta = conexao.armar();
    escritorDaFila.escrever(gerarJsonDeRespostaDeArme(idDaCentral, resposta));
}

private JSONObject gerarJsonDeRespostaDeArme(int idDaCentral, RespostaDeArme resposta) {

    JSONObject json = new JSONObject();

    json.put("tipoDeMensagem", Mensagens.RESPOSTA_DE_ARME.getNumero());
    json.put("idDaCentral", idDaCentral);
    json.put("resultado", resposta.getNumeroResultado());

    resposta.getZonasAbertas().ifPresent(zonas -> {
        json.put("zonasAbertas", new JSONArray(zonas));
    });

    return json;
}

0

It depends a lot on what is the context in which the method armar() will be used. However, the way it is, I believe so, that there is actually a Smell code, although it should not be a serious Smell code. From what I understand, a RespostaDeArme who has a ResultadoDeArme other than SUCESSO indicates that the method armar() failed. The correct mechanism to denote such failures is the handling of exceptions.

So your code can look like this:

public class ArmeException extends Exception {}

public class ParticaoJaArmadaException extends ArmeException {}

public class ExistemZonasAbertasException extends ArmeException {

    private final Set<Integer> zonasAbertas;

    public RespostaDeArme(Set<Integer> zonasAbertas) {
        this.zonasAbertas = new HashSet<>(zonasAbertas);
    }

    public Set<Integer> getZonasAbertas() {
        return zonasAbertas;
    }
}

public class ArmeFalhouException extends ArmeException {}

And your method armar() gets like this:

public void armar() throws
        ParticaoJaArmadaException,
        ExistemZonasAbertasException,
        ArmeFalhouException;
  • @bigown will fight you, it prefers to return error codes in such cases. :)

  • 1

    @Piovezan So tell him that can come hot I’m boiling. In my opinion, error codes are abominations that still persist from the age when exceptions did not exist.

  • 2

    The problem with exceptions is that these error situations are not exceptional, they are even relatively common. Think about the method InputStream.read() Java, which reads bytes and can return -1 (an error code) if the connection on the other side was closed cleanly instead of launching for example a ConnectionClosedException. Now a IOException is a true showstopper, capable of interrupting the normal flow of the program. I think making an exception to disrupting the flow for a predictable reason is to do flow control with exceptions.

  • @Piovezan To be honest, the correct way to treat this would be to use a language construct that does not exist in Java that would be the one that allows a call to a method to have several possible outputs, more or less like a block if or the operator ?: that have two possible exits each. There are ways to simulate this in Java, but they all go through some sort of scam, be they error codes, exceptions interrupting the flow, abuse in the use of ifs or switches, abuse in the use of Bins, creation of several complicated classes to solve something simple, abuse in Bins, among others.

  • Anyway, I’ll think about coming up with another answer that doesn’t depend on exceptions.

  • Out of curiosity, what is this construction of language called? And in which languages it appears?

  • @Piovezan Researching a little, I think it’s about Exclusive Choice, which is a generalization of behaviors such as if and of switch. I only saw it being applied in the real world in a few places such as in some Dsls of process modeling, flowcharts and BPMN. I believe this can be more or less simulated directly with a switch in a language that has weak typing, such as Javascript.

Show 2 more comments

Browser other questions tagged

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