Os booleanos como argumentos do método são inaceitáveis? [fechadas]


123

Um colega meu afirma que os booleanos como argumentos de método não são aceitáveis . Eles devem ser substituídos por enumerações. No começo, não vi nenhum benefício, mas ele me deu um exemplo.

O que é mais fácil de entender?

file.writeData( data, true );

Ou

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

Agora entendi! ;-)
Esse é definitivamente um exemplo em que uma enumeração como segundo parâmetro torna o código muito mais legível.

Então, qual sua opinião sobre esse assunto?


7
Esta foi uma leitura muito interessante, implementarei esse método com mais frequência.
Sara Chipps

hrm, eu já fiz isso antes, mas nunca percebi o quão bom é um padrão de design. então o enum entra em arquivo?
Shawn

Enums certamente fazem mais sentido com um ponto de vista semântico. Em outra nota, seria interessante ver o que alguns programadores criaram para lidar com a lógica difusa.
James P.

2
basta perguntar para o cara de limão em Adventure Time se isso é inaceitável
ajax333221

Respostas:


131

Booleanos representam opções de "sim / não". Se você deseja representar um "sim / não", use um booleano, que deve ser auto-explicativo.

Mas se é uma escolha entre duas opções, nenhuma das quais é claramente sim ou não, então um enum pode às vezes ser mais legível.


3
Além disso, o nome do método deve ser claro sobre o que o argumento yes ou no faz, ou seja, anular turnLightOn (bool) definindo clealy true ou yes acenderá a luz.
simon

10
Embora neste caso eu prefira turnLightOn () e turnLightOff (), dependendo da situação.
skaffman

14
"turnLightOn (false)" significa "não acender a luz"? Confusiong.
Jay Bazuzi

17
Que tal setLightOn(bool).
Finbarr 14/05

10
Comentário tardio, mas @ Jay Bazuzi: Se o seu método se chama turnLightOn e você passa em falso, é melhor não chamar o método, passando em falso diz que não acenda a luz. Se a luz já estiver acesa, isso não significa desligá-lo, significa não ligá-lo ... Se você tiver um método 'turnLight', uma enumeração com 'On' e 'Off' faz sentido, turnLight ( On), acenda a luz (Off). Concordo com skaffman tho, eu prefiro dois métodos explícitos diferentes, turnLightOn () e turnLightOff (). (BTW: Esse material é explicado no livro "Clean Code" do tio Bobs))
Phill


32

Use o que melhor modela seu problema. No exemplo que você dá, o enum é uma escolha melhor. No entanto, haveria outros momentos em que um booleano é melhor. O que faz mais sentido para você:

lock.setIsLocked(True);

ou

enum LockState { Locked, Unlocked };
lock.setLockState(Locked);

Nesse caso, posso escolher a opção booleana, pois acho que é bastante clara e inequívoca, e tenho certeza de que meu bloqueio não terá mais de dois estados. Ainda assim, a segunda opção é válida, mas desnecessariamente complicada, IMHO.


2
no seu exemplo, eu prefiro ter dois métodos. lock.lock () lock.release () e lock.IsSet, mas tudo depende do que faz mais sentido para o código que está consumindo.
Robert Paulson

3
Esse é um comentário justo, mas acho que também ilustra o ponto mais amplo de que existem muitas maneiras de modelar um determinado problema. Você deve usar o melhor modelo para as suas circunstâncias e também as melhores ferramentas que o ambiente de programação fornece para se ajustar ao seu modelo.
Jeremy Bourque

Eu concordo totalmente :), eu estava apenas comentando sobre o pseudocódigo em particular, oferecendo mais uma tomada. Eu concordo com sua resposta.
Robert Paulson

14

Para mim, nem usar booleano nem enumeração é uma boa abordagem. Robert C. Martin captura isso muito claramente em sua dica de código limpo nº 12: Eliminar argumentos booleanos :

Argumentos booleanos declaram em voz alta que a função faz mais de uma coisa. Eles são confusos e devem ser eliminados.

Se um método faz mais de uma coisa, você deve escrever dois métodos diferentes, por exemplo, no seu caso: file.append(data)efile.overwrite(data) .

Usar uma enumeração não torna as coisas mais claras. Não muda nada, ainda é um argumento de bandeira.


7
Isso não significa que uma função que aceita uma string ASCII de comprimento N faz 128 ^ N coisas?
detly 26/03/10

@ delty É um comentário sério? Se sim, você codifica um if em todos os valores possíveis de uma String com frequência? Existe alguma comparação possível com o caso de um argumento booleano?
Pascal Thivent

Acredito que sejam aceitáveis ​​quando você estiver definindo um valor booleano dentro de um objeto. Um exemplo perfeito seria setVisible(boolean visible) { mVisible = visible; }. Qual seria uma alternativa que você sugeriria?
Brad

2
Mostrar @ Brad () {mVisible = true} hide () {mVisible = false}
Oswaldo Acauan

@Oswaldo, embora ainda esteja correto, não acho que faça total sentido ter dois métodos diferentes para atribuir um valor booleano a valores diferentes. Você não tem um setIntToOne (), setIntToTwo () setIntToThree (), certo? É um pouco mais ambíguo quando você pode ter apenas dois valores possíveis, mas por uma questão de limpeza, use um booleano nesse caso.
Brad

13

Eu acho que você quase respondeu a si mesmo, acho que o objetivo final é tornar o código mais legível e, neste caso, o enum fez isso, a OMI sempre é melhor olhar para o objetivo final em vez de regras gerais, talvez pense mais nisso como diretriz, ou seja, as enumerações geralmente são mais legíveis em código do que bools, ints etc., mas sempre haverá exceções à regra.


13

Lembra da pergunta que Adlai Stevenson fez ao embaixador Zorin na ONU durante a crise dos mísseis cubanos ?

"Você está no tribunal da opinião mundial no momento e pode responder sim ou não . Negou que [os mísseis] existam e quero saber se o entendi corretamente ... Estou preparado para esperar pela minha resposta até o inferno congelar, se essa é sua decisão. "

Se o sinalizador que você possui no seu método é de tal natureza que você pode atribuí-lo a uma decisão binária , e essa decisão nunca se transformará em uma decisão de três ou de duas vias, escolha booleano. Indicações: sua bandeira é chamada isXXX .

Não o torne booleano no caso de algo que seja uma opção de modo . Sempre há mais um modo que você pensava ao escrever o método em primeiro lugar.

O dilema de mais um modo assombrou o Unix, onde os possíveis modos de permissão que um arquivo ou diretório pode ter hoje resultam em estranhos significados duplos de modos, dependendo do tipo de arquivo, propriedade etc.


13

Há duas razões pelas quais me deparei com isso ser uma coisa ruim:

  1. Porque algumas pessoas escreverão métodos como:

    ProcessBatch(true, false, false, true, false, false, true);
    

    Obviamente, isso é ruim porque é muito fácil misturar parâmetros e você não faz ideia do que está especificando. Apenas um bool não é tão ruim assim.

  2. Como controlar o fluxo do programa por um simples ramo sim / não pode significar que você tem duas funções completamente diferentes que são agrupadas em uma de uma maneira estranha. Por exemplo:

    public void Write(bool toOptical);
    

    Realmente, isso deve ser dois métodos

    public void WriteOptical();
    public void WriteMagnetic();
    

    porque o código nestes pode ser totalmente diferente; eles podem ter que fazer todo tipo de manipulação e validação de erro diferentes, ou talvez até precisar formatar os dados de saída de maneira diferente. Você não pode dizer isso apenas usando Write()ou mesmo Write(Enum.Optical)(embora, é claro, você possa ter um desses métodos, basta chamar métodos internos WriteOptical / Mag, se desejar).

Eu acho que só depende. Eu não faria muito acordo a não ser o número 1.


Muito bons pontos! Dois parâmetros booleanos em um método parecem realmente terríveis (a menos que você tenha sorte de ter nomeado parâmetros, é claro).
Yarik 30/10/08

Essa resposta pode se beneficiar de alguma reformatação! ;-)
Yarik 30/10/08

7

As enums são melhores, mas eu não chamaria os parâmetros booleanos de "inaceitáveis". Às vezes, é mais fácil colocar um pouco de booleano e seguir em frente (pense em métodos particulares etc.)


apenas torne o método muito descritivo, para que fique claro o que significa true ou yes.
simon

6

Os booleanos podem estar OK em idiomas que tenham parâmetros nomeados, como Python e Objective-C, pois o nome pode explicar o que o parâmetro faz:

file.writeData(data, overwrite=true)

ou:

[file writeData:data overwrite:YES]

1
IMHO, writeData () é um mau exemplo do uso de um parâmetro booleano, independentemente de os parâmetros nomeados serem ou não suportados. Não importa como você nomeie o parâmetro, o significado do valor False não é óbvio!
Yarik 30/10/08

4

Eu não concordaria que é uma boa regra . Obviamente, o Enum cria um código explícito ou detalhado melhor em algumas instâncias, mas, como regra, parece muito exagerado.

Primeiro, deixe-me dar o seu exemplo: a responsabilidade (e a capacidade) dos programadores de escrever um bom código não é realmente comprometida por ter um parâmetro booleano. No seu exemplo, o programador poderia ter escrito o mesmo código detalhado escrevendo:

dim append as boolean = true
file.writeData( data, append );

ou eu prefiro mais geral

dim shouldAppend as boolean = true
file.writeData( data, shouldAppend );

Segundo: O exemplo de Enum que você deu é apenas "melhor" porque você está passando um CONST. Provavelmente, na maioria das aplicações, pelo menos alguns dos parâmetros de tempo que são passados ​​para as funções são VARIABLES. Nesse caso, meu segundo exemplo (fornecer variáveis ​​com bons nomes) é muito melhor e o Enum teria dado poucos benefícios.


1
Embora eu concorde que os parâmetros booleanos são aceitáveis ​​em muitos casos, no caso deste exemplo writeData (), o parâmetro booleano como shouldAppend é muito inapropriado. A razão é simples: não é imediatamente óbvio qual é o significado de Falso.
Yarik 30/10/08

4

As enums têm um benefício definido, mas você não deve substituir todos os seus booleanos por enums. Existem muitos lugares onde verdadeiro / falso é realmente a melhor maneira de representar o que está acontecendo.

No entanto, usá-los como argumentos de método é um pouco suspeito, simplesmente porque você não pode ver sem se aprofundar nas coisas que eles deveriam fazer, pois permite que você veja qual é o verdadeiro / falso realmente significa

Propriedades (especialmente com inicializadores de objetos C # 3) ou argumentos de palavras-chave (à la ruby ​​ou python) são uma maneira muito melhor de ir para onde você usaria um argumento booleano.

Exemplo de C #:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

Exemplo de Ruby

validates_presence_of :name, :allow_nil => true

Exemplo de Python

connect_to_database( persistent=true )

A única coisa em que consigo pensar onde um argumento de método booleano é a coisa certa a fazer é em java, onde você não tem argumentos de propriedades ou de palavras-chave. Esta é uma das razões pelas quais eu odeio java :-(


4

Embora seja verdade que, em muitos casos, as enums sejam mais legíveis e extensíveis que os booleanos, uma regra absoluta de que "os booleanos não são aceitáveis" é insensata. É inflexível e contraproducente - não deixa espaço para julgamento humano. Eles são um tipo básico incorporado na maioria dos idiomas porque são úteis - considere aplicá-lo a outros tipos internos: dizer, por exemplo, "nunca use um int como parâmetro" seria uma loucura.

Essa regra é apenas uma questão de estilo, não de potencial para erros ou desempenho em tempo de execução. Uma regra melhor seria "preferir enums a booleanos por razões de legibilidade".

Veja a estrutura .Net. Os booleanos são usados ​​como parâmetros em vários métodos. A API .Net não é perfeita, mas não acho que o uso de booleano como parâmetros seja um grande problema. A dica de ferramenta sempre fornece o nome do parâmetro e você também pode criar esse tipo de orientação - preencha seus comentários XML sobre os parâmetros do método, eles aparecerão na dica de ferramenta.

Devo acrescentar também que existe um caso em que você deve refatorar claramente os booleanos para uma enumeração - quando você tem dois ou mais booleanos em sua classe ou nos parâmetros de método e nem todos os estados são válidos (por exemplo, não é válido tê-los ambos são verdadeiros).

Por exemplo, se sua classe tiver propriedades como

public bool IsFoo
public bool IsBar

E é um erro ter os dois verdadeiros ao mesmo tempo, o que você realmente tem são três estados válidos, melhor expressos como algo como:

enum FooBarType { IsFoo, IsBar, IsNeither };

4

Algumas regras que seu colega pode aderir melhor são:

  • Não seja dogmático com seu design.
  • Escolha o que melhor se adapta aos usuários do seu código.
  • Não tente bater pinos em forma de estrela em todos os buracos só porque você gosta da forma este mês!

3

Um booleano só seria aceitável se você não pretender estender a funcionalidade da estrutura. O Enum é preferível porque você pode estender o enum e não interromper implementações anteriores da chamada de função.

A outra vantagem do Enum é que é mais fácil de ler.


2

Se o método fizer uma pergunta como:

KeepWritingData (DataAvailable());

Onde

bool DataAvailable()
{
    return true; //data is ALWAYS available!
}

void KeepWritingData (bool keepGoing)
{
   if (keepGoing)
   {
       ...
   }
}

argumentos de método booleano parecem fazer absolutamente perfeito sentido.


Algum dia você precisará adicionar "continue escrevendo se tiver espaço livre" e, de qualquer maneira, passará de bool para enum.
Ilya Ryzhenkov 25/09/08

E então você vai ter alteração de quebra, ou têm sobrecarga obsoleto, ou pode ser algo como KeppWritingDataEx :)
Ilya Ryzhenkov

1
@Ilya ou talvez você não! Criar uma situação possível em que não exista atualmente não nega a solução.
Jesse C. Slicer

1
Jesse está certo. Planejar uma mudança como essa é bobagem. Faça o que faz sentido. Nesse caso, o booleano é intuitivo e claro. c2.com/xp/DoTheSimplestThingThatCouldPossiblyWork.html
Derek Park

@Derek, neste caso, boolean é nem mesmo necessário, porque DataAvailable sempre retorna true :)
Ilya Ryzhenkov

2

Depende do método. Se o método faz algo que obviamente é uma coisa verdadeira / falsa, então tudo bem, por exemplo, abaixo [embora não esteja dizendo que esse é o melhor design para esse método, é apenas um exemplo de onde o uso é óbvio].

CommentService.SetApprovalStatus(commentId, false);

No entanto, na maioria dos casos, como o exemplo que você mencionou, é melhor usar uma enumeração. Existem muitos exemplos no próprio .NET Framework em que essa convenção não é seguida, mas é porque eles introduziram essa diretriz de design bastante tarde no ciclo.


2

Isso torna as coisas um pouco mais explícitas, mas começa a ampliar massivamente a complexidade de suas interfaces - em uma simples escolha booleana, como acrescentar / substituir, parece um exagero. Se você precisar adicionar uma opção adicional (que não consigo pensar neste caso), sempre poderá executar um refatoramento (dependendo do idioma)


1
Que tal anexar como uma terceira opção possível? ;-))
Yarik 30/10/08

2

Enums certamente podem tornar o código mais legível. Ainda há algumas coisas a serem observadas (pelo menos em .net)

Como o armazenamento subjacente de uma enum é um int, o valor padrão será zero, portanto, verifique se 0 é um padrão sensato. (Por exemplo, estruturas têm todos os campos definidos como zero quando criados, portanto, não há como especificar um padrão diferente de 0. Se você não tiver um valor 0, não poderá nem testar a enum sem converter para int, o que seria estilo ruim.)

Se sua enumeração é privada ao seu código (nunca exposta publicamente), você pode parar de ler aqui.

Se suas enumerações forem publicadas de alguma forma no código externo e / ou salvas fora do programa, considere numerá-las explicitamente. O compilador os numera automaticamente de 0, mas se você reorganizar suas enumerações sem fornecer valores, poderá acabar com defeitos.

Eu posso escrever legalmente

WriteMode illegalButWorks = (WriteMode)1000000;
file.Write( data, illegalButWorks );

Para combater isso, qualquer código que consuma uma enumeração que você não pode ter certeza (por exemplo, API pública) precisa verificar se a enumeração é válida. Você faz isso via

if (!Enum.IsDefined(typeof(WriteMode), userValue))
    throw new ArgumentException("userValue");

A única ressalva Enum.IsDefinedé que ele usa reflexão e é mais lento. Ele também sofre de um problema de versão. Se você precisar verificar o valor da enumeração com frequência, seria melhor o seguinte:

public static bool CheckWriteModeEnumValue(WriteMode writeMode)
{
  switch( writeMode )
  {
    case WriteMode.Append:
    case WriteMode.OverWrite:
      break;
    default:
      Debug.Assert(false, "The WriteMode '" + writeMode + "' is not valid.");
      return false;
  }
  return true;
}

A questão do controle de versão é que o código antigo pode saber apenas como lidar com as 2 enumerações que você possui. Se você adicionar um terceiro valor, Enum.IsDefined será verdadeiro, mas o código antigo não pode necessariamente lidar com isso. Ops.

Há ainda mais diversão que você pode fazer com [Flags]enumerações, e o código de validação para isso é um pouco diferente.

Também observarei que, para portabilidade, você deve usar call ToString()no enum e usá-lo Enum.Parse()ao lê-los novamente. Ambos ToString()e Enum.Parse()podem lidar com[Flags] enum é assim, então não há nenhuma razão para não usá-los. Veja bem, é mais uma armadilha, porque agora você não pode nem mudar o nome da enum sem possivelmente quebrar o código.

Então, às vezes você precisa ponderar tudo isso acima quando se perguntar: Posso fugir com apenas um bool?


1

IMHO parece que um enum seria a escolha óbvia para qualquer situação em que mais de duas opções sejam possíveis. Mas definitivamente existem situações em que um booleano é tudo que você precisa. Nesse caso, eu diria que usar um enum em que um bool funcionaria seria um exemplo de uso de 7 palavras quando 4 funcionarão.


0

Os booleanos fazem sentido quando você tem uma alternância óbvia que pode ser apenas uma de duas coisas (ou seja, o estado de uma lâmpada, ligada ou desligada). Fora isso, é bom escrevê-lo de tal maneira que seja óbvio o que você está passando - por exemplo, gravações em disco - sem buffer, com buffer de linha ou síncrono - deve ser transmitido como tal. Mesmo que você não queira permitir gravações síncronas agora (e, portanto, esteja limitado a duas opções), vale a pena considerar torná-las mais detalhadas para saber o que elas fazem à primeira vista.

Dito isto, você também pode usar Falso e Verdadeiro (booleano 0 e 1) e, se precisar de mais valores posteriormente, expanda a função para oferecer suporte a valores definidos pelo usuário (por exemplo, 2 e 3) e seus antigos valores 0/1 será portado muito bem, então seu código não deve ser quebrado.


0

Às vezes, é mais simples modelar comportamentos diferentes com sobrecargas. Para continuar do seu exemplo seria:

file.appendData( data );  
file.overwriteData( data );

Essa abordagem é degradante se você tiver vários parâmetros, cada um permitindo um conjunto fixo de opções. Por exemplo, um método que abre um arquivo pode ter várias permutações do modo de arquivo (abrir / criar), acesso ao arquivo (leitura / gravação), modo de compartilhamento (nenhum / leitura / gravação). O número total de configurações é igual aos produtos cartesianos das opções individuais. Naturalmente, nesses casos, várias sobrecargas não são apropriadas.

Enums podem, em alguns casos, tornar o código mais legível, embora validar o valor exato de enum em alguns idiomas (C # por exemplo) possa ser difícil.

Geralmente, um parâmetro booleano é anexado à lista de parâmetros como uma nova sobrecarga. Um exemplo no .NET é:

Enum.Parse(str);  
Enum.Parse(str, true); // ignore case

A última sobrecarga ficou disponível em uma versão posterior da estrutura .NET que a primeira.

Se você sabe que sempre haverá duas opções, um booleano pode ser bom. As enumerações são extensíveis de maneira a não quebrar o código antigo, embora as bibliotecas antigas possam não suportar novos valores de enumeração, portanto o controle de versão não pode ser completamente desconsiderado.


EDITAR

Nas versões mais recentes do C #, é possível usar argumentos nomeados que, como IMO, podem tornar o código de chamada mais claro da mesma maneira que as enumerações. Usando o mesmo exemplo acima:

Enum.Parse(str, ignoreCase: true);

0

Onde eu concordo que as enums são um bom caminho a seguir, nos métodos em que você tem 2 opções (e apenas duas opções, você pode ter legibilidade sem enum.)

por exemplo

public void writeData(Stream data, boolean is_overwrite)

Ame os Enums, mas o booleano também é útil.


0

Esta é uma entrada tardia em um post antigo, e é tão longe na página que ninguém nunca o lerá, mas como ninguém já o disse ...

Um comentário embutido ajuda bastante a solucionar o boolproblema inesperado . O exemplo original é particularmente hediondo: imagine tentar nomear a variável na função declearation! Seria algo como

void writeData( DataObject data, bool use_append_mode );

Mas, por uma questão de exemplo, digamos que é a declaração. Então, para um argumento booleano inexplicável, coloquei o nome da variável em um comentário embutido. Comparar

file.writeData( data, true );

com

file.writeData( data, true /* use_append_mode */);

-1

Realmente depende da natureza exata do argumento. Se não for sim / não ou verdadeiro / falso, um enum torna-o mais legível. Mas com uma enumeração, você precisa verificar o argumento ou ter um comportamento padrão aceitável, pois valores indefinidos do tipo subjacente podem ser transmitidos.


-1

O uso de enumerações em vez de booleanos no seu exemplo ajuda a tornar a chamada do método mais legível. No entanto, este é um substituto para o meu item de desejo favorito em C #, denominado argumentos em chamadas de método. Esta sintaxe:

var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);

seria perfeitamente legível e, em seguida, você poderia fazer o que um programador deve fazer, que é escolher o tipo mais apropriado para cada parâmetro no método, independentemente da aparência no IDE.

O C # 3.0 permite argumentos nomeados em construtores. Não sei por que eles não podem fazer isso com métodos também.


Uma ideia interessante. Mas você seria capaz de reordenar parâmetros? Omitir parâmetros? Como o compilador saberia a qual sobrecarga você estava ligando se isso fosse opcional? Além disso, você precisaria nomear todos os parâmetros na lista?
de Drew Noakes

-1

Valores booleanos true/ falsesomente. Portanto, não está claro o que representa. Enumpode ter nome significativo, por exemplo OVERWRITE, APPEND, etc Então, enums são melhores.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.