Como observado nos comentários de @ benjamin-gruenbaum, isso é chamado de armadilha booleana:
Digamos que eu tenho uma função como esta
UpdateRow(var item, bool externalCall);
e no meu controlador, esse valor para externalCall
sempre será VERDADEIRO. Qual é a melhor maneira de chamar essa função? Eu costumo escrever
UpdateRow(item, true);
Mas pergunto a mim mesmo, devo declarar um booleano, apenas para indicar o que esse valor "verdadeiro" significa? Você pode saber disso olhando a declaração da função, mas é obviamente mais rápido e mais claro se você apenas visse algo como
bool externalCall = true;
UpdateRow(item, externalCall);
PD: Não tenho certeza se essa pergunta realmente se encaixa aqui, se não, onde eu poderia obter mais informações sobre isso?
PD2: não codifiquei nenhum idioma porque pensei que era um problema muito genérico. Enfim, eu trabalho com c # e a resposta aceita funciona para c #
data CallType = ExternalCall | InternalCall
em haskell, por exemplo.Respostas:
Nem sempre existe uma solução perfeita, mas você tem muitas alternativas para escolher:
Use argumentos nomeados , se disponíveis no seu idioma. Isso funciona muito bem e não possui desvantagens particulares. Em algumas linguagens, qualquer argumento pode ser passado como um argumento nomeado, por exemplo
updateRow(item, externalCall: true)
( C # ) ouupdate_row(item, external_call=True)
(Python).Sua sugestão de usar uma variável separada é uma maneira de simular argumentos nomeados, mas não possui os benefícios de segurança associados (não há garantia de que você tenha usado o nome correto da variável para esse argumento).
Use funções distintas para sua interface pública, com nomes melhores. Essa é outra maneira de simular parâmetros nomeados, colocando os valores do parâmetro no nome.
Isso é muito legível, mas gera muitos clichês para você, que está escrevendo essas funções. Também não pode lidar bem com a explosão combinatória quando há vários argumentos booleanos. Uma desvantagem significativa é que os clientes não podem definir esse valor dinamicamente, mas devem usar if / else para chamar a função correta.
Use um enum . O problema com os booleanos é que eles são chamados de "verdadeiro" e "falso". Portanto, introduza um tipo com nomes melhores (por exemplo
enum CallType { INTERNAL, EXTERNAL }
). Como um benefício adicional, isso aumenta a segurança de tipo do seu programa (se o seu idioma implementar enumerações como tipos distintos). A desvantagem das enumerações é que elas adicionam um tipo à sua API publicamente visível. Para funções puramente internas, isso não importa e as enumerações não têm desvantagens significativas.Em idiomas sem enumerações, às vezes são usadas seqüências curtas . Isso funciona e pode até ser melhor que os booleanos crus, mas é muito suscetível a erros de digitação. A função deve afirmar imediatamente que o argumento corresponde a um conjunto de valores possíveis.
Nenhuma dessas soluções tem um impacto proibitivo no desempenho. Parâmetros nomeados e enums podem ser resolvidos completamente em tempo de compilação (para um idioma compilado). O uso de strings pode envolver uma comparação de strings, mas o custo disso é insignificante para literais de strings pequenos e para a maioria dos tipos de aplicativos.
fonte
A solução correta é fazer o que você sugere, mas empacotá-lo em uma mini-fachada:
A legibilidade supera a micro-otimização. Você pode pagar a chamada de função extra, certamente melhor do que o esforço do desenvolvedor de ter que procurar a semântica do sinalizador booleano pelo menos uma vez.
fonte
true
faz.) Valeria a pena, mesmo que custam tanto tempo de CPU quanto economiza tempo de desenvolvedor, pois o tempo de CPU é bem mais barato.UpdateRow(item, true /*external call*/);
que seria mais limpo, em idiomas que permitem essa sintaxe de comentário. Inchaço do código com uma função extra apenas para evitar escrever um comentário não parece valer a pena para um caso simples. Talvez se houvesse muitos outros argumentos para essa função e / ou algum código circundante desordenado e complicado, ele começaria a atrair mais. Mas acho que, se você estiver depurando, acabará verificando a função wrapper para ver o que ela faz e precisará lembrar quais funções são wrappers finas em torno de uma API de biblioteca e quais realmente têm alguma lógica.Por que você tem uma função como essa?
Sob quais circunstâncias você o chamaria com o argumento externalCall definido para valores diferentes?
Se um é de, digamos, um aplicativo cliente externo e o outro está dentro do mesmo programa (ou seja, módulos de código diferentes), então eu argumentaria que você deveria ter dois métodos diferentes , um para cada caso, possivelmente até definidos em distintos Interfaces.
Se, no entanto, você estivesse fazendo a chamada com base em dados retirados de uma fonte que não é do programa (por exemplo, um arquivo de configuração ou leitura do banco de dados), o método de passagem booleana faria mais sentido.
fonte
Embora eu concorde que seja ideal usar um recurso de idioma para reforçar a legibilidade e a segurança de valor, você também pode optar por uma abordagem prática : comentários na hora da chamada. Gostar:
ou:
ou (com razão, conforme sugerido por Frax):
fonte
UpdateRow(item, /* externalCall */ true )
. O comentário de sentenças completas é muito mais difícil de analisar, na verdade é principalmente ruído (especialmente a segunda variante, que também é muito pouco acoplada ao argumento).Você pode 'nomear' seus bools. Abaixo está um exemplo para uma linguagem OO (onde pode ser expressa na classe
UpdateRow()
), no entanto, o próprio conceito pode ser aplicado em qualquer idioma:e no site da chamada:
Acredito que o item 1 seja melhor, mas não força o usuário a usar novas variáveis ao usar a função. Se a segurança do tipo é importante para você, você pode criar um
enum
tipo eUpdateRow()
aceitar isso em vez debool
:UpdateRow(var item, ExternalCallAvailability ec);
Você pode modificar o nome da função de alguma forma, para refletir melhor o significado do
bool
parâmetro. Não tenho muita certeza, mas talvez:UpdateRowWithExternalCall(var item, bool externalCall)
fonte
externalCall=false
, seu nome não faz absolutamente nenhum sentido. O resto da sua resposta é boa.UpdateRowWithUsuallyExternalButPossiblyInternalCall
.Outra opção que ainda não li aqui é: Use um IDE moderno.
Por exemplo, o IntelliJ IDEA imprime o nome da variável no método que você está chamando se estiver passando um literal como
true
ornull
ouusername + “@company.com
. Isso é feito em uma fonte pequena, para não ocupar muito espaço na tela e parecer muito diferente do código real.Ainda não estou dizendo que é uma boa ideia jogar booleanos em todos os lugares. O argumento que diz que você lê código muito mais frequentemente do que escreve é muito forte, mas, nesse caso em particular, depende muito da tecnologia que você (e seus colegas!) Usa para dar suporte ao seu trabalho. Com um IDE, é muito menos problemático do que com o vim, por exemplo.
Exemplo modificado de parte da minha configuração de teste:
fonte
2 dias e ninguém mencionou polimorfismo?
Quando sou um cliente que deseja atualizar uma linha com um item, não quero pensar no nome do banco de dados, na URL do microsserviço, no protocolo usado para se comunicar ou se uma chamada externa é ou não precisará ser usado para que isso aconteça. Pare de me impor esses detalhes. Basta pegar meu item e atualizar uma linha em algum lugar já.
Faça isso e isso se torna parte do problema de construção. Isso pode ser resolvido de várias maneiras. Aqui está um:
Se você está olhando para isso e pensando "Mas minha linguagem nomeou argumentos, não preciso disso". Tudo bem, ótimo, vá usá-los. Apenas mantenha essa bobagem fora do cliente que está chamando, que nem deveria saber com o que está falando.
Note-se que isso é mais do que um problema semântico. Também é um problema de design. Passe um valor booleano e você será forçado a usar a ramificação para lidar com isso. Não é muito orientado a objetos. Tem dois ou mais booleanos para passar? Deseja que seu idioma tenha vários envios? Veja o que as coleções podem fazer quando você as aninha. A estrutura de dados correta pode facilitar sua vida.
fonte
Se você pode modificar a
updateRow
função, talvez você possa refatorá-la em duas funções. Dado que a função aceita um parâmetro booleano, suspeito que seja assim:Isso pode ser um pouco de cheiro de código. A função pode ter um comportamento dramaticamente diferente, dependendo de como a
externalCall
variável está configurada; nesse caso, ela tem duas responsabilidades diferentes. A refatoração em duas funções que possuem apenas uma responsabilidade pode melhorar a legibilidade:Agora, em qualquer lugar que você chamar essas funções, poderá saber rapidamente se o serviço externo está sendo chamado.
Isso nem sempre é o caso, é claro. É situacional e uma questão de gosto. Vale a pena considerar a refatoração de uma função que leva um parâmetro booleano para duas funções, mas nem sempre é a melhor opção.
fonte
Se o UpdateRow estiver em uma base de código controlada, eu consideraria o padrão de estratégia:
Onde Request representa algum tipo de DAO (um retorno de chamada em caso trivial).
Caso verdadeiro:
Caso falso:
Normalmente, a implementação do ponto de extremidade seria configurada em um nível mais alto de abstração e eu a usaria aqui. Como um efeito colateral gratuito e útil, isso me dá a opção de implementar outra maneira de acessar dados remotos, sem nenhuma alteração no código:
Em geral, essa inversão de controle garante menor acoplamento entre o armazenamento e o modelo de dados.
fonte