O que é mais fácil de entender, uma grande declaração booleana (bastante complexa) ou a mesma declaração dividida em métodos predicados (muito código extra para ler)?
Opção 1, a grande expressão booleana:
private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
return propVal.PropertyId == context.Definition.Id
&& !repo.ParentId.HasValue || repo.ParentId == propVal.ParentId
&& ((propVal.SecondaryFilter.HasValue && context.SecondaryFilter.HasValue && propVal.SecondaryFilter.Value == context.SecondaryFilter) || (!context.SecondaryFilter.HasValue && !propVal.SecondaryFilter.HasValue));
}
Opção 2, As condições divididas em métodos predicados:
private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
return MatchesDefinitionId(context, propVal)
&& MatchesParentId(propVal)
&& (MatchedSecondaryFilter(context, propVal) || HasNoSecondaryFilter(context, propVal));
}
private static bool HasNoSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
{
return (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);
}
private static bool MatchedSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
{
return (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
}
private bool MatchesParentId(TValToMatch propVal)
{
return (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
}
private static bool MatchesDefinitionId(CurrentSearchContext context, TValToMatch propVal)
{
return propVal.PropertyId == context.Definition.Id;
}
Eu prefiro a segunda abordagem, porque vejo os nomes dos métodos como comentários, mas entendo que é problemático porque você precisa ler todos os métodos para entender o que o código faz, para abstrair a intenção do código.
c#
readability
willem
fonte
fonte
if
instrução em nenhum bloco de código. Sua pergunta é sobre expressões booleanas .Respostas:
A última abordagem. Não é apenas mais fácil de entender, mas é mais fácil escrever, testar, refatorar e estender também. Cada condição necessária pode ser dissociada e manuseada com segurança à sua maneira.
Não é problemático se os métodos forem nomeados corretamente. De fato, seria mais fácil entender como o nome do método descreveria a intenção da condição.
Para um espectador,
if MatchesDefinitionId()
é mais explicativo do queif (propVal.PropertyId == context.Definition.Id)
[Pessoalmente, a primeira abordagem me machuca os olhos.]
fonte
MatchesDefinitionId()
é limítrofe.Se esse for o único local em que essas funções de predicado seriam usadas, você também poderá usar
bool
variáveis locais :Estes também podem ser discriminados e reordenados para torná-los mais legíveis, por exemplo, com
e substituindo todas as instâncias de
propVal.SecondaryFilter.HasValue
. Uma coisa que se destaca imediatamente é quehasNoSecondaryFilter
usa AND lógico nasHasValue
propriedades negadas , enquantomatchesSecondaryFilter
usa um AND lógico em não negadoHasValue
- portanto, não é exatamente o oposto.fonte
Em geral, este último é preferido.
Isso torna o site de chamada mais reutilizável. Ele suporta DRY (o que significa que você tem menos lugares para alterar quando os critérios mudam e pode fazê-lo de maneira mais confiável). E muitas vezes esses subcritérios são coisas que serão reutilizadas independentemente em outros lugares, permitindo que você faça isso.
Ah, e isso facilita muito o teste de unidade, dando a você a confiança de que o fez corretamente.
fonte
repo
, que parece um campo / propriedade estático, ou seja, uma variável global. Os métodos estáticos devem ser determinísticos e não usar variáveis globais.Se estiver entre essas duas opções, a última será melhor. Essas não são as únicas opções, no entanto! Que tal dividir a função única em vários ifs? Teste maneiras de sair da função para evitar testes adicionais, emulando aproximadamente um "curto-circuito" em um teste de linha única.
Isso é mais fácil de ler (pode ser necessário verificar novamente a lógica do seu exemplo, mas o conceito é verdadeiro):
fonte
Eu gosto mais da opção 2, mas sugeriria uma mudança estrutural. Combine os dois cheques na última linha do condicional em uma única chamada.
A razão pela qual sugiro isso é que as duas verificações são uma única unidade funcional, e o aninhamento de parênteses em uma condição é suscetível a erros: do ponto de vista de escrever o código inicialmente e do ponto de vista da pessoa que o lê. Esse é especialmente o caso se os subelementos da expressão não seguirem o mesmo padrão.
Não tenho certeza se
MatchesSecondaryFilterIfPresent()
é o melhor nome para a combinação; mas nada melhor vem imediatamente à mente.fonte
Embora em C #, o código não seja muito orientado a objetos. Ele está usando métodos estáticos e o que parece ser campos estáticos (por exemplo
repo
). Geralmente, a estática dificulta a refatoração e o teste do código, dificultando a reutilização e a sua pergunta: o uso estático como esse é menos legível e sustentável do que a construção orientada a objetos.Você deve converter esse código em um formulário mais orientado a objetos. Ao fazê-lo, você descobrirá que existem lugares sensatos para colocar código que faz comparação de objetos, campos, etc. É provável que você possa pedir aos objetos que se comparem, o que reduziria sua declaração if grande a um solicitação simples de comparação (por exemplo
if ( a.compareTo (b) ) { }
, que pode incluir todas as comparações de campos).O C # possui um rico conjunto de interfaces e utilitários de sistema para fazer comparações entre objetos e seus campos. Para além do óbvio
.Equals
método, para começar, olhar paraIEqualityComparer
,IEquatable
e utilitários comoSystem.Collections.Generic.EqualityComparer.Default
.fonte
Definitivamente, este último é o preferido, já vi casos com o primeiro caminho e é quase sempre impossível ler. Cometi o erro de fazê-lo da primeira maneira e me pediram para alterá-lo para predicar métodos.
fonte
Eu diria que os dois são praticamente os mesmos, se você adicionar algum espaço em branco para facilitar a leitura e alguns comentários para ajudar o leitor nas partes mais obscuras.
Lembre-se: um bom comentário diz ao leitor o que você estava pensando quando escreveu o código.
Com mudanças como sugeri, eu provavelmente adotaria a abordagem anterior, pois ela é menos confusa e difusa. As chamadas de sub-rotina são como notas de rodapé: fornecem informações úteis, mas interrompem o fluxo da leitura. Se os predicados fossem mais complexos, eu os dividiria em métodos separados, para que os conceitos incorporados possam ser construídos em pedaços compreensíveis.
fonte
Bem, se houver partes que você queira reutilizar, separá-las em funções nomeadas apropriadas é obviamente a melhor idéia.
Mesmo que você nunca possa reutilizá-los, isso poderá permitir que você estruture melhor suas condições e dê a elas um rótulo descrevendo o que elas significam .
Agora, vejamos a sua primeira opção e admitimos que nem o seu recuo e quebra de linha foram tão úteis nem o condicional foi estruturado tão bem:
fonte
O primeiro é absolutamente horrível. Você está usando || por duas coisas na mesma linha; isso é um bug no seu código ou uma intenção de ofuscar seu código.
É pelo menos meio caminho decentemente formatado (se a formatação é complicada, é porque a condição if é complicada), e você tem pelo menos uma chance de descobrir se há algo sem sentido. Comparado com o seu lixo formatado se, qualquer outra coisa for melhor. Mas você parece capaz de fazer apenas extremos: uma bagunça completa de uma declaração if ou quatro métodos completamente inúteis.
Observe que (cond1 e& cond2) || (! cond1 && cond3) pode ser escrito como
o que reduziria a bagunça. Eu escreveria
fonte
Não gosto de nenhuma dessas soluções, ambas são difíceis de raciocinar e difíceis de ler. A abstração para métodos menores apenas por métodos menores nem sempre resolve o problema.
Idealmente, acho que você compararia metaprogrmaticamente propriedades, para não definir um novo método ou se ramificar toda vez que quiser comparar um novo conjunto de propriedades.
Eu não tenho certeza sobre c #, mas em javascript algo assim seria muito melhor e poderia pelo menos substituir MatchesDefinitionId e MatchesParentId
fonte
compareContextProp(propVal, "PropertyId", context.Definition.Id)
seria mais fácil ler uma combinação booleana de ~ 5 chamadas do que a combinação booleana do OP de ~ 5 comparações do formuláriopropVal.PropertyId == context.Definition.Id
. É significativamente mais longo e adiciona uma camada extra sem realmente esconder qualquer complexidade do site de chamada. (se isso realmente importa, eu não downvote)