É errado usar um parâmetro booleano para determinar valores?

39

De acordo com Está errado usar um parâmetro booleano para determinar o comportamento? , eu sei a importância de evitar usar parâmetros booleanos para determinar um comportamento, por exemplo:

versão original

public void setState(boolean flag){
    if(flag){
        a();
    }else{
        b();
    }
    c();
}

nova versão:

public void setStateTrue(){
    a();
    c();
}

public void setStateFalse(){
    b();
    c();
}

Mas e quanto ao caso em que o parâmetro booleano é usado para determinar valores em vez de comportamentos? por exemplo:

public void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

Estou tentando eliminar o sinalizador isHintOn e criar duas funções separadas:

public void setHintOn(){
    this.layer1.visible=true;
    this.layer2.visible=false;
    this.layer3.visible=true;
}

public void setHintOff(){
    this.layer1.visible=false;
    this.layer2.visible=true;
    this.layer3.visible=false;
}

mas a versão modificada parece menos sustentável porque:

  1. tem mais códigos do que a versão original

  2. não é possível mostrar claramente que a visibilidade da camada 2 é oposta à opção de dica

  3. quando uma nova camada (por exemplo: layer4) é adicionada, preciso adicionar

    this.layer4.visible=false;
    

    e

    this.layer4.visible=true;  
    

    em setHintOn () e setHintOff () separadamente

Então, minha pergunta é, se o parâmetro booleano é usado para determinar apenas os valores, mas não os comportamentos (por exemplo: no if-else nesse parâmetro), ainda é recomendado eliminar esse parâmetro booleano?

    
por mmmaaa 10.01.2018 / 10:07
fonte

13 respostas

95

O design da API deve se concentrar no que é mais útil para um cliente da API, do lado de chamada .

Por exemplo, se essa nova API exigir que o chamador grave regularmente um código como este

if(flag)
    foo.setStateTrue();
else
    foo.setStateFalse();

então deve ser óbvio que evitar o parâmetro é pior do que ter uma API que permita ao chamador escrever

 foo.setState(flag);

A versão anterior apenas produz um problema que então tem que ser resolvido no lado da chamada (e provavelmente mais de uma vez). Isso não aumenta a legibilidade nem a capacidade de manutenção.

O lado da implementação , no entanto, não deve ditar como a API pública se parece. Se uma função como setHint com um parâmetro precisar de menos código na implementação, mas uma API em termos de setHintOn / setHintOff parecer mais fácil de usar para um cliente, poderá implementá-lo desta forma:

private void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

public void setHintOn(){
   setHint(true);
}

public void setHintOff(){
   setHint(false);
}

Assim, embora a API pública não tenha um parâmetro booleano, não há lógica duplicada aqui, portanto, apenas um lugar para alterar quando um novo requisito (como no exemplo da pergunta) chegar.

Isso também funciona ao contrário: se o método setState acima precisar alternar entre dois códigos diferentes, esses códigos poderão ser refatorados para dois métodos privados diferentes. Então, IMHO não faz sentido procurar um critério para decidir entre "um parâmetro / um método" e "zero parâmetros / dois métodos", olhando para os internos. No entanto, veja como você gostaria de ver a API no papel de um consumidor dela.

Em caso de dúvida, tente usar o "test driven development" (TDD), que forçará você a pensar sobre a API pública e como usá-la.

    
por 10.01.2018 / 12:09
fonte
41

Martin Fowler cita Kent Beck em recomendação de métodos separados em setOn() setOff() , mas também diz que isso não deve ser considerado inviolável:

If you pulling[sic] data from a boolean source, such as a UI control or data source, I'd rather have setSwitch(aValue) than

if (aValue)
  setOn();
else
  setOff();

This is an example that an API should be written to make it easier for the caller, so if we know where the caller is coming from we should design the API with that information in mind. This also argues that we may sometimes provide both styles if we get callers in both ways.

Outra recomendação é usar um valor enumerado ou tipo de sinalizadores para dê true e false melhor, nomes específicos de contexto. No seu exemplo, showHint e hideHint poderiam ser melhores.

    
por 10.01.2018 / 10:56
fonte
3

Acho que você está misturando duas coisas em sua postagem, na API e na implementação. Em ambos os casos, não acho que exista uma regra strong que você possa usar o tempo todo, mas você deve considerar essas duas coisas de maneira independente (tanto quanto possível).

Vamos começar com a API, ambos:

public void setHint(boolean isHintOn)

e:

public void setHintOn()
public void setHintOff()

são alternativas válidas dependendo do que seu objeto deve oferecer e de como seus clientes usarão a API. Como o Doc apontou, se seus usuários já tiverem uma variável Booleana (de um controle de UI, uma ação do usuário, uma API externa, etc) a primeira opção faz mais sentido, senão você está forçando uma instrução extra if no código do cliente . No entanto, se, por exemplo, você estiver alterando a dica para true ao iniciar um processo e para false no final, a primeira opção fornecerá algo assim:

setHint(true)
// Do your process
…
setHint(false)

enquanto a segunda opção lhe dá isto:

setHintOn()
// Do your process
…
setHintOff()

qual IMO é muito mais legível, então irei com a segunda opção neste caso. Obviamente, nada impede que você ofereça ambas as opções (ou mais, você poderia usar um enum como Graham disse, se isso faz mais sentido, por exemplo).

O ponto é que você deve escolher sua API com base no que o objeto deve fazer e como os clientes vão usá-la, não com base em como você irá implementá-la.

Depois, você deve escolher como implementa sua API pública. Digamos que escolhemos os métodos setHintOn e setHintOff como nossa API pública e eles compartilham essa lógica comum, como no seu exemplo. Você pode facilmente abstrair essa lógica através de um método privado (código copiado do Doc):

private void setHint(boolean isHintOn){
    this.layer1.visible=isHintOn;
    this.layer2.visible=!isHintOn;
    this.layer3.visible=isHintOn;
}

public void setHintOn(){
   setHint(true);
}

public void setHintOff(){
   setHint(false);
}

Por outro lado, digamos que escolhemos setHint(boolean isHintOn) a nossa API, mas vamos inverter o seu exemplo, devido à razão pela qual configurar a dica Em é completamente diferente de configurá-la como Desativada. Neste caso, podemos implementá-lo da seguinte forma:

public void setHint(boolean isHintOn){
    if(isHintOn){
        // Set it On
    } else {
        // Set it Off
    }    
}

Ou até mesmo:

public void setHint(boolean isHintOn){    
    if(isHintOn){
        setHintOn()
    } else {
        setHintOff()
   }    
}

private void setHintOn(){
   // Set it On
}

private void setHintOff(){
   // Set it Off 
}

A questão é que, em ambos os casos, escolhemos nossa API pública e depois adaptamos nossa implementação para se adequar à API escolhida (e às restrições que temos), e não o contrário.

A propósito, acho que o mesmo se aplica à postagem que você relacionou sobre o uso de um parâmetro booleano para determinar o comportamento, ou seja, você deve decidir com base em seu caso de uso específico e não em alguma regra difícil (embora nesse caso específico geralmente o correto é quebrá-lo em várias funções).

    
por 10.01.2018 / 14:15
fonte
2

Primeiras coisas primeiro: o código não é automaticamente menos sustentável, só porque é um pouco mais longo. Clareza é o que importa.

Agora, se você está realmente lidando com dados, o que você tem é um setter para uma propriedade booleana. Nesse caso, você pode querer apenas armazenar esse valor diretamente e derivar as visibilidades da camada, ou seja,

bool isBackgroundVisible() {
    return isHintVisible;
}    

bool isContentVisible() {
    return !isHintVisible;
}

(Eu tomei a liberdade de dar nomes reais às camadas - se você não tem isso em seu código original, eu começaria com isso)

Isso ainda deixa você com a questão de ter um método setHintVisibility(bool) . Pessoalmente, eu recomendaria substituí-lo por um método showHint() e hideHint() - ambos serão muito simples e você não precisará alterá-los quando adicionar camadas. Não é claro, certo / errado, no entanto.

Agora, se chamar a função realmente alterar a visibilidade dessas camadas, você realmente terá um comportamento. Nesse caso, eu recomendaria definitivamente métodos separados.

    
por 10.01.2018 / 11:06
fonte
1

Os parâmetros booleanos estão bem no segundo exemplo. Como você já descobriu, os parâmetros booleanos não são em si problemáticos. Está mudando o comportamento com base em um sinalizador, o que é problemático.

O primeiro exemplo é problemático, porque a nomenclatura indica um método setter, mas as implementações parecem ser algo diferente. Então você tem o antipadrão de comutação de comportamento, e um método chamado incorretamente. Mas se o método realmente é um setter regular (sem mudança de comportamento), então não há problema com setState(boolean) . Com dois métodos, setStateTrue() e setStateFalse() estão complicando desnecessariamente as coisas sem nenhum benefício.

    
por 10.01.2018 / 13:37
fonte
1

Outra maneira de resolver esse problema é introduzir um objeto para representar cada dica e fazer com que o objeto seja responsável por determinar os valores booleanos associados a essa dica. Dessa forma, você pode adicionar novas permutações em vez de simplesmente ter dois estados booleanos.

Por exemplo, em Java, você pode fazer:

public enum HintState {
    SHOW_HINT(true, false, true),
    HIDE_HINT(false, true, false);

    private HintState(boolean layer1Visible, boolean layer2Visible, boolean layer3Visible) {
         // constructor body and accessors omitted for clarity
    }
}

E, em seguida, seu código de chamada ficaria assim:

setHint(HintState.SHOW_HINT);

E o seu código de implementação ficaria assim:

public void setHint(HintState hint) {
    this.layer1Visible = hint.isLayer1Visible();
    this.layer2Visible = hint.isLayer2Visible();
    this.layer3Visible = hint.isLayer3Visible();
}

Isso mantém o código de implementação e o código do chamador concisos, em troca da definição de um novo tipo de dados que claramente mapeia strongmente tipado, chamado intenções para os conjuntos de estados correspondentes. Eu acho que é melhor ao redor.

    
por 10.01.2018 / 15:09
fonte
1

Definindo a questão

A sua pergunta de título é "É errado [...]?" - mas o que você quer dizer com "errado"?.

De acordo com um compilador C # ou Java, não está errado. Tenho certeza de que você está ciente disso e não é o que você está pedindo. Eu tenho medo, além disso, nós só temos n programadores n+1 opiniões diferentes. Esta resposta apresenta o que o livro Código Limpo tem a dizer sobre isso.

Resposta

Código Limpo faz um argumento strong contra os argumentos da função em geral:

Arguments are hard. They take a lot of conceptual power. [...] our readers would have had to interpret it each time they saw it.

Um "leitor" aqui pode ser o consumidor da API. Ele também pode ser o próximo codificador, que ainda não sabe o que esse código faz - que pode ser você em um mês. Eles passarão por 2 funções separadamente ou por 1 função duas vezes , mantendo em mente true e uma vez false .
Resumindo, use o mínimo possível de argumentos .

O caso específico de um argumento de sinalização é posteriormente tratado diretamente:

Flag arguments are ugly. Passing a boolean into a function is a truly terrible practice. It immediately complicates the signature of the method, loudly proclaiming that this function does more than one thing. It does one thing if the flag is true and another if the flag is false!

Para responder às suas perguntas diretamente:
De acordo com o Código Limpo , é recomendável eliminar esse parâmetro.

Informações adicionais:

O seu exemplo é bastante simples, mas mesmo assim você pode ver a simplicidade se propagando para o seu código: As funções no-parameter fazem apenas atribuições simples, enquanto a outra função tem que fazer aritmética booleana para alcançar o mesmo objetivo. É uma aritmética booleana trivial neste exemplo simplificado, mas pode ser bastante complexa em uma situação real.

Eu tenho visto muitos argumentos aqui que você deve fazer isso depender do usuário da API, porque ter que fazer isso em muitos lugares seria estúpido:

if (isAfterSunset) light.TurnOn();
else light.TurnOff();

Eu faço concordo que algo não ótimo está acontecendo aqui. Talvez seja óbvio demais para ver, mas sua primeira frase é mencionar "a importância de evitar [sic] o uso de parâmetros booleanos para determinar um comportamento" e essa é a base para toda a questão. Eu não vejo uma razão para fazer isso que é ruim para fazer mais fácil para o usuário da API.

Eu não sei se você faz testes - nesse caso, considere isso também:

Arguments are even harder from a testing point of view. Imagine the difficulty of writing all the test cases to ensure that all the various combinations of arguments work properly. If there are no arguments, this is trivial.

    
por 11.01.2018 / 14:29
fonte
0

So my question is, if the boolean parameter is used to determine values only, but not behaviours (eg:no if-else on that parameter), is it still recommended to eliminate that boolean parameter?

Quando tenho dúvidas sobre essas coisas. Eu gosto de imaginar como seria o rastreamento da pilha.

Por muitos anos eu trabalhei em um projeto PHP que usava a mesma função de setter e getter . Se você passar nulo , retornará o valor, caso contrário, defina-o. Foi horrível trabalhar com .

Veja um exemplo de rastreamento de pilha:

function visible() : line 440
function parent() : line 398
function mode() : line 384
function run() : line 5

Você não tinha ideia do estado interno e tornou a depuração mais difícil. Há um monte de outros efeitos colaterais negativos, mas tente ver que existe valor em um nome da função verbose e clareza quando as funções executam um < em> única ação.

Agora imagine trabalhar com o rastreamento de pilha para uma função que tenha comportamento A ou B com base em um valor booleano.

function bar() : line 92
function setVisible() : line 120
function foo() : line 492
function setVisible() : line 120
function run() : line 5

Isso é confuso se você me perguntar. As mesmas linhas setVisible geram dois caminhos de rastreamento diferentes.

Então, voltemos à sua pergunta. Tente imaginar como será o rastreio da pilha, como ele comunica a uma pessoa o que está acontecendo e pergunte a si mesmo se você está ajudando uma pessoa futura a depurar o código.

Aqui estão algumas dicas:

  • um nome de função claro que implica intenção sem precisar conhecer os valores do argumento.
  • uma função executa uma única ação
  • o nome implica mutação ou comportamento imutável
  • resolva os desafios de depuração relacionados à capacidade de depuração da linguagem e das ferramentas.

Às vezes, o código parece excessivo sob um microscópio, mas quando você volta ao quadro geral, uma abordagem minimalista fará com que ele desapareça. Se você precisar que ele se destaque para que você possa mantê-lo. Adicionar muitas funções pequenas pode parecer excessivamente detalhado, mas melhora a capacidade de manutenção quando usado amplamente em um contexto maior.

    
por 10.01.2018 / 16:08
fonte
0

Em quase todos os casos em que você está passando um parâmetro boolean para um método como uma flag para alterar o comportamento de algo, você deve considerar um mais explícito e maneira segura de fazer isso.

Se você não fizer nada além de usar um Enum que representa o estado, você melhorou a compreensão do seu código.

Este exemplo usa a classe Node de JavaFX :

public enum Visiblity
{
    SHOW, HIDE

    public boolean toggleVisibility(@Nonnull final Node node) {
        node.setVisible(!node.isVisible());
    }
}

é sempre melhor do que o encontrado em muitos objetos JavaFX :

public void setVisiblity(final boolean flag);

mas acho que .setVisible() e .setHidden() são a melhor solução para a situação em que o sinalizador é um boolean , pois é o mais explícito e o menos detalhado.

No caso de algo com várias seleções, é ainda mais importante fazer isso dessa maneira. EnumSet existe apenas por esse motivo.

Ed Mann has a really good blog post on just this subject. I was about to paraphrase just what he says, so not to duplicate effort I will just post a link to his blog post as an addendum to this answer.

    
por 10.01.2018 / 16:27
fonte
0

Ao decidir entre uma abordagem para alguma interface que passa um parâmetro (booleano) versus métodos sobrecarregados sem o parâmetro mencionado, procure os clientes consumidores.

Se todas as utilizações passassem por valores constantes (por exemplo, verdadeiro, falso), isso seria o motivo das sobrecargas.

Se todos os usos passassem por um valor variável, então, argumentaríamos sobre o método com a abordagem de parâmetros.

Se nenhum desses extremos se aplica, isso significa que há uma mistura de usos do cliente, então você tem que escolher se quer suportar ambas as formas, ou fazer uma categoria de clientes se adaptar à outra (o que para eles é mais antinatural ) estilo.

    
por 10.01.2018 / 17:20
fonte
0

Existem duas considerações a serem projetadas:

  • API: qual interface você apresenta ao usuário,
  • Implementação: clareza, manutenibilidade, etc ...

Eles não devem ser confundidos.

Não há problema em:

  • tem vários métodos no delegado da API para uma única implementação,
  • tem um único método no despacho da API para várias implementações, dependendo da condição.

Como tal, qualquer argumento que tente equilibrar os custos / benefícios de um projeto de API pelos custos / benefícios de um projeto de implementação é duvidoso e deve ser examinado com cuidado.

No lado da API

Como programador, geralmente agradeço a APIs programáveis. O código é muito mais claro quando posso encaminhar um valor do que quando preciso de uma instrução if / switch no valor para decidir qual função chamar.

O último pode ser necessário se cada função espera argumentos diferentes.

No seu caso, assim, um único método setState(type value) parece melhor.

No entanto , não há nada pior do que anônimo true , false , 2 , etc ... esses valores mágicos não têm significado sozinhos. Evite obsessão primitiva e aceite uma digitação strong.

Assim, a partir de um POV de API, quero: setState(State state) .

No lado da implementação

Eu recomendo fazer o que for mais fácil.

Se o método for simples, é melhor manter juntos. Se o fluxo de controle for complicado, é melhor separá-lo em vários métodos, cada um lidando com uma subpasta ou uma etapa do pipeline.

Por fim, considere agrupamento .

No seu exemplo (com espaço em branco adicional para legibilidade):

this.layer1.visible = isHintOn;
this.layer2.visible = ! isHintOn;
this.layer3.visible = isHintOn;

Por que layer2 está resistindo à tendência? É um recurso ou é um bug?

Poderia ser possível ter duas listas [layer1, layer3] e [layer2] , com um nome explícito indicando o motivo pelo qual elas estão agrupadas e, em seguida, iterar sobre essas listas.

Por exemplo:

for (auto layer : this.mainLayers) { // layer2
    layer.visible = ! isHintOn;
}
for (auto layer : this.hintLayers) { // layer1 and layer3
    layer.visible = isHintOn;
}

O código fala por si, está claro por que existem dois grupos e seu comportamento é diferente.

    
por 10.01.2018 / 17:42
fonte
0

Separadamente da questão setOn() + setOff() vs set(flag) , eu consideraria cuidadosamente se um tipo booleano é melhor aqui. Tem certeza de que nunca haverá uma terceira opção?

Pode valer a pena considerar um enum em vez de um booleano. Além de permitir a extensibilidade, isso também dificulta a obtenção do booleano errado, por exemplo:

setHint(false)

vs

setHint(Visibility::HIDE)

Com o enum, será muito mais fácil estender quando alguém decidir que deseja uma opção "se necessário":

enum class Visibility {
  SHOW,
  HIDE,
  IF_NEEDED // New
}

vs

setHint(false)
setHint(true)
setHintAutomaticMode(true) // New
    
por 11.01.2018 / 09:12
fonte
0

According to ..., I know the importance of avoid using boolean parameters to determine a behaviour

Eu sugiro reavaliar esse conhecimento.

Em primeiro lugar, não vejo a conclusão que você está propondo na questão da SE que você vinculou. Eles estão falando principalmente sobre o encaminhamento de um parâmetro ao longo de várias etapas de chamadas de método, onde é avaliado muito abaixo da cadeia.

No seu exemplo, você está avaliando o parâmetro diretamente no seu método. A esse respeito, não difere de nenhum outro tipo de parâmetro.

Em geral, não há absolutamente nada de errado com o uso de parâmetros booleanos; e obviamente qualquer parâmetro irá determinar um comportamento, ou por que você teria em primeiro lugar?

    
por 11.01.2018 / 13:51
fonte