De acordo com É errado usar um parâmetro booleano para determinar o comportamento? , Eu sei a importância de evitar o uso de 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 o 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 2 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:
tem mais códigos que a versão original
não pode mostrar claramente que a visibilidade da camada2 é oposta à opção de dica
quando uma nova camada (por exemplo: layer4) é adicionada, preciso adicionar
this.layer4.visible=false;
e
this.layer4.visible=true;
em setHintOn () e setHintOff () separadamente
Portanto, minha pergunta é: se o parâmetro booleano for usado apenas para determinar valores, mas não comportamentos (por exemplo: nenhum if-else nesse parâmetro), ainda é recomendável eliminar esse parâmetro booleano?
setHint(boolean isHintOn)
como um privado método, e adicionar públicasetHintOn
esetHintOff
métodos que respectivamente chamamsetHint(true)
esetHint(false)
.setHint(true|false)
. Potahto de batata. Pelo menos use algo comosetHint
eunsetHint
.is
no início.isValid
etc. Então, por que mudar isso por duas palavras? Além disso, "mais natural" está nos olhos de quem vê. Se você quer pronunciar-lo como uma frase Inglês, em seguida, para mim seria mais natural ter "se a dica é sobre" com um "a" dobrado.Respostas:
O design da API deve se concentrar no que é mais utilizável para um cliente da API, do lado da chamada .
Por exemplo, se essa nova API exigir que o chamador escreva regularmente códigos como este
deve ser óbvio que evitar o parâmetro é pior do que ter uma API que permita ao chamador escrever
A versão anterior apenas produz um problema que deve ser resolvido no lado de 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 a aparência da API pública. Se uma função como
setHint
um parâmetro precisa de menos código na implementação, mas uma API em termossetHintOn
/setHintOff
parece mais fácil de usar para um cliente, é possível implementá-la desta maneira:Portanto, embora a API pública não tenha 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
setState
método acima precisar alternar entre dois trechos de código diferentes, esses trechos de código poderão ser refatorados para dois métodos particulares diferentes. Portanto, no 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. Veja, no entanto, a maneira como você gostaria de ver a API no papel de consumidor dela.Em caso de dúvida, tente usar o "TDD), que forçará você a pensar na API pública e em como usá-la.
fonte
SetLoanFacility(bool enabled)
, porque, tendo concedido um empréstimo, pode não ser fácil retirá-lo novamente, e as duas opções podem envolver uma lógica completamente diferente - e você deseja mover para separar Create / Remova métodos.Toggle()
não é a função correta a ser fornecida. Esse é o ponto inteiro; se o chamador se preocupa apenas com "alterá-lo" e não "como ele termina", entãoToggle()
é a opção que evita verificações e tomadas de decisão adicionais. Eu não chamaria isso de caso COMUM, nem recomendaria disponibilizá-lo sem um bom motivo, mas se o usuário precisar de uma alternância, eu as alternaria.Martin Fowler cita Kent Beck ao recomendar
setOn()
setOff()
métodos separados , mas também diz que isso não deve ser considerado inviolável:Outra recomendação é usar um valor enumerado ou bandeiras digite para dar
true
efalse
melhor, nomes específicos ao contexto. No seu exemplo,showHint
ehideHint
poderia ser melhor.fonte
-[NSView setNeedsDisplay:]
método pelo qual você passaYES
se uma visão deve ser redesenhada eNO
se não deveria. Você quase nunca precisa dizer para não fazê-lo, então o UIKit simplesmente não possui-[UIView setNeedsDisplay]
parâmetro. Não possui o-setDoesNotNeedDisplay
método correspondente .bool isPremium
flag no exemplo dele, mas usaria um enum (BookingType bookingType
) para parametrizar o mesmo método, a menos que a lógica para cada reserva fosse bem diferente. A "lógica emaranhada" a que Fowler se refere é frequentemente desejável se alguém quiser ver qual é a diferença entre os dois modos. E se eles forem radicalmente diferentes, eu exporia o método parametrizado externamente e implementaria os métodos separados internamente.Eu acho que você está misturando duas coisas no seu post, a API e a implementação. Nos dois casos, não acho que exista uma regra forte que você possa usar o tempo todo, mas considere essas duas coisas de forma independente (o máximo possível).
Vamos começar com a API, ambos:
e:
são alternativas válidas, dependendo do que seu objeto deve oferecer e como seus clientes vão usar a API. Como o Doc apontou, se os usuários já tiverem uma variável booleana (de um controle de interface do usuário, de uma ação do usuário, de uma API externa, etc.), a primeira opção fará mais sentido; caso contrário, você estará forçando uma instrução if extra 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 como isto:
enquanto a segunda opção fornece:
qual IMO é muito mais legível, então eu vou com a segunda opção neste caso. Obviamente, nada impede você de oferecer as duas opções (ou mais, você pode usar uma enumeração, como disse Graham, 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 o usarão, não com base em como você o implementará.
Então você deve escolher como implementar sua API pública. Digamos que escolhemos os métodos
setHintOn
esetHintOff
como nossa API pública e eles compartilham essa lógica comum, como no seu exemplo. Você poderia abstrair facilmente essa lógica por meio de um método privado (código copiado do Doc):Por outro lado, digamos que escolhemos
setHint(boolean isHintOn)
uma API, mas vamos reverter o seu exemplo, por qualquer motivo que tenha definido a dica como Ativada, é completamente diferente de desativada. Nesse caso, podemos implementá-lo da seguinte maneira:Ou até:
O ponto é que, nos dois casos, escolhemos nossa API pública e depois adaptamos nossa implementação para se ajustar à 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ê vinculou 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 uma regra rígida (embora, nesse caso específico, geralmente seja a coisa certa a fazer) é quebrá-lo em várias funções).
fonte
begin
eend
ou sinônimos, apenas para esclarecer explicitamente o que eles fazem e implicar que um o começo tem que ter um final e vice-versa.using
blocos em C #,with
gerenciadores de contexto de instruções em Python, passando o corpo de como lambda ou objecto pode ser chamado (por exemplo, a sintaxe de bloco rubi), etcPrimeiras coisas primeiro: o código não é automaticamente menos sustentável, apenas porque é um pouco mais longo. Clareza é o que importa.
Agora, se você realmente está apenas lidando com dados, o que você tem é um configurador para uma propriedade booleana. Nesse caso, você pode simplesmente armazenar esse valor diretamente e derivar as visibilidades da camada, ou seja,
(Tomei a liberdade de dar nomes reais às camadas - se você não tiver isso no código original, eu começaria com isso)
Isso ainda deixa você com a questão de ter um
setHintVisibility(bool)
método. Pessoalmente, eu recomendaria substituí-lo por um métodoshowHint()
ehideHint()
- ambos serão realmente simples e você não precisará alterá-los quando adicionar camadas. Não é um corte claro certo / errado, no entanto.Agora, se a chamada da função realmente alterar a visibilidade dessas camadas, você realmente terá um comportamento. Nesse caso, eu recomendaria definitivamente métodos separados.
fonte
showHint
vssetHintVisibility
). Mencionei a nova camada apenas porque o OP estava preocupado com isso. Além disso, nós só precisa adicionar um novo método:isLayer4Visible
.showHint
ehideHint
apenas defina oisHintVisible
atributo como true / false e isso não muda.Parâmetros booleanos são bons no segundo exemplo. Como você já descobriu, os parâmetros booleanos não são, por si só, problemáticos. É mudar o comportamento com base em um sinalizador, o que é problemático.
O primeiro exemplo é problemático, porque a nomeação indica um método setter, mas as implementações parecem ser algo diferente. Então você tem o antipadrão de mudança de comportamento e um método enganosamente nomeado. Mas se o método é realmente um levantador regular (sem mudança de comportamento), não há problema com
setState(boolean)
. Ter dois métodossetStateTrue()
esetStateFalse()
complicar desnecessariamente as coisas sem nenhum benefício.fonte
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:
E então seu código de chamada ficaria assim:
E seu código de implementação ficaria assim:
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 mapeia claramente as intenções nomeadas e fortemente tipadas para os conjuntos de estados correspondentes. Eu acho que é melhor por toda parte.
fonte
Quando eu tenho dúvidas sobre essas coisas. Eu gosto de imaginar como seria o rastreamento de pilha.
Por muitos anos, trabalhei em um projeto PHP que usava a mesma função que um setter e um getter . Se você passou nulo, ele retornaria o valor, caso contrário, defina-o. Foi horrível trabalhar com .
Aqui está um exemplo de rastreamento de pilha:
Você não tinha idéia do estado interno e isso dificultava a depuração. Existem vários outros efeitos colaterais negativos, mas tente ver se há valor em um nome detalhado da função e clareza quando as funções executam uma única ação.
Agora imagine trabalhar com o rastreamento de pilha para uma função que tenha um comportamento A ou B com base em um valor booleano.
Isso é confuso se você me perguntar. As mesmas
setVisible
linhas produzem dois caminhos de rastreamento diferentes.Então, de volta à sua pergunta. Tente imaginar como será o rastreamento da pilha, como ele se comunica com uma pessoa e o que está acontecendo e pergunte a si mesmo se você está ajudando uma futura pessoa a depurar o código.
Aqui estão algumas dicas:
Às vezes, o código parece excessivo sob um microscópio, mas quando você retorna à visão geral, uma abordagem minimalista o faz desaparecer. Se você precisar se destacar para poder mantê-lo. A adição de muitas funções pequenas pode parecer excessivamente detalhada, mas melhora a capacidade de manutenção quando usada amplamente em um contexto maior.
fonte
setVisible
rastreamento de pilha é que a chamadasetVisible(true)
parece resultar em uma chamada parasetVisible(false)
(ou vice-versa, dependendo de como você conseguiu que esse rastreamento ocorresse).Em quase todos os casos em que você está passando um
boolean
parâmetro para um método como um sinalizador para alterar o comportamento de algo, considere uma explicação mais explícita e segura de fazer isso.Se você não fizer nada além de usar um
Enum
que represente o estado, melhorou a compreensão do seu código.Este exemplo usa a
Node
classe deJavaFX
:é sempre melhor do que, como encontrado em muitos
JavaFX
objetos:mas penso
.setVisible()
e.setHidden()
sou a melhor solução para a situação em que a bandeira é umaboolean
, pois é a mais explícita e menos detalhada.no caso de algo com várias seleções, é ainda mais importante fazê-lo dessa maneira.
EnumSet
existe apenas por esse motivo.fonte
Ao decidir entre uma abordagem para alguma interface que passa um parâmetro (booleano) versus métodos sobrecarregados sem esse parâmetro, observe os clientes consumidores.
Se todos os usos passassem valores constantes (por exemplo, verdadeiro, falso), isso indica as sobrecargas.
Se todos os usos passassem um valor variável, isso argumentaria com o método com abordagem de parâmetros.
Se nenhum desses extremos se aplicar, isso significa que há uma mistura de usos do cliente; portanto, você deve optar por dar suporte a ambos os formulários ou criar uma categoria de clientes para se adaptar ao outro (o que para eles é um estilo menos natural).
fonte
Há duas considerações a serem projetadas:
Eles não devem ser confundidos.
É perfeitamente bom:
Como tal, qualquer argumento que tente equilibrar os custos / benefícios de um design de API pelos custos / benefícios de um design de implementação é duvidoso e deve ser examinado cuidadosamente.
No lado da API
Como programador, geralmente irei favorecer APIs programáveis. O código é muito mais claro quando posso encaminhar um valor do que quando preciso de um
if
/switch
instrução no valor para decidir qual função chamar.O último pode ser necessário se cada função esperar argumentos diferentes.
No seu caso, portanto, um único método
setState(type value)
parece melhor.No entanto , não há nada pior do que sem nome
true
,false
,2
, etc ... esses valores mágicas não têm significado por conta própria. Evite a obsessão primitiva e adote uma digitação forte.Assim, a partir de um POV API, eu 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 mantê-lo junto. Se o fluxo de controle for complicado, é melhor separá-lo em vários métodos, cada um lidando com um sub-caso ou uma etapa do pipeline.
Por fim, considere agrupar .
No seu exemplo (com espaço em branco adicionado para facilitar a leitura):
Porque é
layer2
contrariando a tendência? É um recurso ou é um bug?Seria possível ter 2 listas
[layer1, layer3]
e[layer2]
, com uma explicação explícita nome indicando o motivo pelo qual eles estão agrupados e, em seguida, iterar sobre essas listas.Por exemplo:
O código fala por si, é claro por que existem dois grupos e seu comportamento é diferente.
fonte
Separadamente do
setOn() + setOff()
vsset(flag)
pergunta , 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 torna mais difícil obter o booleano da maneira errada, por exemplo:
vs
Com o enum, será muito mais fácil estender quando alguém decide que deseja uma opção 'se necessário':
vs
fonte
Eu sugeriria reavaliar esse conhecimento.
Antes de tudo, não vejo a conclusão que você está propondo na pergunta SE que você vinculou. Eles estão falando principalmente sobre o encaminhamento de um parâmetro em várias etapas de chamadas de método, onde é avaliado muito longe na 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 em usar parâmetros booleanos; e obviamente qualquer parâmetro determinará um comportamento, ou por que você o teria em primeiro lugar?
fonte
Definindo a pergunta
Sua pergunta do título é "É errado [...]?" - mas o que você quer dizer com "errado"?
De acordo com um compilador C # ou Java, não está errado. Estou certo de que você está ciente disso e não é isso que você está perguntando. Receio que, além disso, só tenhamos opiniões diferentes dos
n
programadoresn+1
. Esta resposta apresenta o que o livro Clean Code tem a dizer sobre isso.Responda
O Código Limpo apresenta argumentos fortes contra argumentos de função em geral:
Um "leitor" aqui pode ser o consumidor da API. 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 duas funções separadamente ou por uma função duas vezes , mantendo
true
e lembrando uma vezfalse
.Em resumo, use o mínimo de argumentos possível .
O caso específico de um argumento flag é posteriormente abordado diretamente:
Para responder suas perguntas diretamente:
De acordo com o Clean Code , é recomendável eliminar esse parâmetro.
Informação adicional:
Seu exemplo é bastante simples, mas mesmo lá você pode ver a simplicidade propagando-se para o seu código: As funções no-parameter fazem apenas atribuições simples, enquanto a outra função precisa fazer aritmética booleana para atingir o mesmo objetivo. É aritmética booleana trivial neste exemplo simplificado, mas pode ser bastante complexo em uma situação real.
Eu já vi muitos argumentos aqui de que você deve torná-lo dependente do usuário da API, porque fazer isso em muitos lugares seria estúpido:
Eu não concordo que algo não-ideal está acontecendo aqui. Talvez seja óbvio demais para ver, mas sua primeira frase está mencionando "a importância de evitar [sic] usar parâmetros booleanos para determinar um comportamento" e essa é a base para toda a pergunta. Não vejo uma razão para tornar mais fácil o que é ruim para o usuário da API.
Não sei se você faz testes - nesse caso, considere também o seguinte:
fonte