Uma correção recente de bug exigia que eu revisasse o código escrito por outros membros da equipe, onde encontrei isso (é C #):
return (decimal)CostIn > 0 && CostOut > 0 ? (((decimal)CostOut - (decimal)CostIn) / (decimal)CostOut) * 100 : 0;
Agora, permitindo que haja uma boa razão para todos esses elencos, isso ainda parece muito difícil de seguir. Houve um pequeno erro no cálculo e tive que desembaraçar para corrigir o problema.
Conheço o estilo de codificação dessa pessoa a partir da revisão de código, e sua abordagem é que menor é quase sempre melhor. E, claro, há valor lá: todos nós vimos cadeias desnecessariamente complexas de lógica condicional que podem ser arrumadas com alguns operadores bem posicionados. Mas ele é claramente mais hábil do que eu em seguir cadeias de operadores amontoados em uma única declaração.
É claro que isso é uma questão de estilo. Mas alguma coisa foi escrita ou pesquisada para reconhecer o ponto em que a busca pela brevidade do código deixa de ser útil e se torna uma barreira à compreensão?
O motivo dos lançamentos é o Entity Framework. O banco de dados precisa armazená-los como tipos anuláveis. Decimal? não é equivalente a decimal em c # e precisa ser convertido.
fonte
CostOut
seja igual aDouble.Epsilon
e, portanto, seja maior que zero. Mas(decimal)CostOut
, nesse caso, é zero, e temos uma divisão por erro zero. O primeiro passo deve ser o de corrigir o código , o que acho que não é. Corrija, faça casos de teste e, em seguida, torne-o elegante . Código elegante e código breve têm muito em comum, mas às vezes a brevidade não é a alma da elegância.Respostas:
Para responder sua pergunta sobre pesquisa existente
Sim, tem havido trabalho nesta área.
Para entender essas coisas, você precisa encontrar uma maneira de calcular uma métrica para que as comparações possam ser feitas de forma quantitativa (em vez de apenas realizar a comparação com base na inteligência e na intuição, como as outras respostas). Uma métrica em potencial examinada é
Complexidade ciclomática Lines Linhas de código- fonte ( SLOC )
No seu exemplo de código, essa proporção é muito alta, porque tudo foi compactado em uma linha.
Ligação
Aqui estão algumas referências, se você estiver interessado:
McCabe, T. e A. Watson (1994), Complexidade de Software (CrossTalk: The Journal of Defense Software Engineering).
Watson, AH e McCabe, TJ (1996). Teste Estruturado: Uma Metodologia de Teste Utilizando a Métrica da Complexidade Ciclomática (Publicação Especial NIST 500-235). Recuperado em 14 de maio de 2011, no site da McCabe Software: http://www.mccabe.com/pdf/mccabe-nist235r.pdf
Rosenberg, L., Hammer, T., Shaw, J. (1998). Métricas e confiabilidade de software (Procedimentos do Simpósio Internacional IEEE sobre Engenharia de Confiabilidade de Software). Recuperado em 14 de maio de 2011, no site da Penn State University: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.104.4041&rep=rep1&type=pdf
Minha opinião e solução
Pessoalmente, nunca avaliei a brevidade, apenas a legibilidade. Às vezes, a brevidade ajuda na legibilidade, às vezes não. O mais importante é que você esteja escrevendo o Código Realmente Óbvio (ROC) em vez do Código Somente Gravação (WOC).
Apenas por diversão, aqui está como eu escreveria e pedia aos membros da minha equipe que escrevessem:
Observe também que a introdução das variáveis de trabalho tem o feliz efeito colateral de acionar a aritmética de ponto fixo em vez da aritmética inteira, de modo que a necessidade de todos esses lançamentos
decimal
é eliminada.fonte
if ((costIn < 0) || (costOut < 0)) throw new Exception("costs must not be negative");
A brevidade é boa quando reduz a confusão em torno das coisas que importam, mas quando se torna concisa , compacta muitos dados relevantes com muita densidade para ser facilmente seguida, então os dados relevantes se tornam desorganizados e você tem um problema.
Nesse caso em particular, os elencos
decimal
estão sendo repetidos repetidamente; provavelmente seria melhor reescrevê-lo como algo como:De repente, a linha que contém a lógica é muito mais curta e se encaixa em uma linha horizontal, para que você possa ver tudo sem precisar rolar, e o significado é muito mais aparente.
fonte
((decOut - decIn ) / decOut) * 100
para outra variável.CostOut > 0
), para que você tivesse que expandir o condicional para umaif
declaração. Não que haja algo errado com isso, mas adiciona mais verbosidade do que apenas a introdução de um local.Embora eu não possa citar nenhuma pesquisa específica sobre o assunto, sugiro que todas as transmissões no seu código violem o princípio Não se repita. O que o seu código parece estar tentando fazer é converter o
costIn
ecostOut
digitarDecimal
, em seguida, executar algumas checagens sobre os resultados de tais conversões, e os que executam operações adicionais sobre esses valores convertidos se os cheques passar. De fato, seu código executa uma das verificações de sanidade em um valor não convertido, aumentando a possibilidade de costOut manter um valor maior que zero, mas menor que a metade do tamanho do menor não nulo queDecimal
possa representar. O código seria muito mais claro se ele definisse variáveis do tipoDecimal
para armazenar os valores convertidos e depois atuasse sobre eles.Parece curioso que você estaria mais interessado na proporção das
Decimal
representações decostIn
e docostOut
que na proporção dos valores reais decostIn
ecostOut
, a menos que o código também use as representações decimais para algum outro propósito. Se o código fizer mais uso dessas representações, isso seria um argumento adicional para a criação de variáveis para manter essas representações, em vez de ter uma sequência contínua de projeções em todo o código.fonte
Decimal
elenco será arredondado depende da magnitude do valor em questão, acho difícil imaginar regras de negócios que exijam a maneira como os elencos realmente se comportarão.Decimal
tipo não. O valor 1.0d / 3.0 terá mais dígitos à direita do decimal que podem ser mantidos ao usar números maiores. Portanto, adicionar e subtrair o mesmo número maior causará perda de precisão. Os tipos de ponto fixo podem perder a precisão com multiplicação ou divisão fracionária, mas não com adição, subtração, multiplicação ou divisão com o restante (por exemplo, 1,00 / 7 é 0,14 restante, 0,2; 1,00 div 0,15 é 6, restante 0,10).Eu olho para esse código e pergunto "como um custo pode ser 0 (ou menos)?". Que caso especial isso indica? Código deve ser
Estou adivinhando os nomes aqui: altere
BothCostsAreValidProducts
eNO_PROFIT
conforme apropriado.fonte
if (CostIn <= 0 || CostOut <= 0)
está completamente bem.A brevidade deixa de ser uma virtude quando esquecemos que é um meio para um fim, e não uma virtude em si mesma. Gostamos da brevidade porque ela se correlaciona com a simplicidade, e gostamos da simplicidade porque o código mais simples é mais fácil de entender, mais fácil de modificar e conter menos bugs. No final, queremos que o código atinja esse objetivo:
Cumprir os requisitos de negócios com a menor quantidade de trabalho
Evite bugs
Permita-nos fazer alterações no futuro que continuem cumprindo 1 e 2
Esses são os objetivos. Qualquer princípio ou método de projeto (seja KISS, YAGNI, TDD, SOLID, provas, sistemas de tipos, metaprogramação dinâmica etc.) é apenas virtuoso na medida em que nos ajuda a alcançar esses objetivos.
A linha em questão parece ter perdido o objetivo final. Embora seja curto, não é simples. Na verdade, ele contém redundância desnecessária, repetindo a mesma operação de conversão várias vezes. A repetição de código aumenta a complexidade e a probabilidade de erros. A mistura da transmissão com o cálculo real também dificulta o código.
A linha tem três preocupações: Proteções (revestimento especial 0), fundição e cálculo de tipos. Cada preocupação é bastante simples quando considerada isoladamente, mas, como tudo foi misturado na mesma expressão, torna-se difícil de seguir.
Não está claro por que
CostOut
não é lançado na primeira vez em que é usadoCostIn
. Pode haver uma boa razão, mas a intenção não é clara (pelo menos não sem contexto), o que significa que um mantenedor deve ter cuidado em alterar esse código, pois pode haver algumas suposições ocultas. E isso é um anátema para manutenção.Como
CostIn
é convertido antes de comparar com 0, presumo que seja um valor de ponto flutuante. (Se fosse um int, não haveria razão para lançar). Mas seCostOut
for um ponto flutuante, o código poderá ocultar um bug obscuro de divisão por zero, pois um valor de ponto flutuante pode ser pequeno, mas diferente de zero, mas zero quando convertido para um decimal (pelo menos eu acredito que isso seria possível).Portanto, o problema não é a brevidade ou a falta dele, o problema é a lógica repetida e a confusão de preocupações que levam a códigos difíceis de manter.
A introdução de variáveis para manter os valores convertidos provavelmente aumentaria o tamanho do código contado em número de tokes, mas diminuirá a complexidade, separará as preocupações e melhorará a clareza, o que nos aproxima do objetivo do código que é mais fácil de entender e manter.
fonte
A brevidade não é uma virtude. Legibilidade é a virtude.
A brevidade pode ser uma ferramenta para alcançar a virtude ou, como no seu exemplo, pode ser uma ferramenta para alcançar algo exatamente oposto. De uma forma ou de outra, ele carrega quase nenhum valor próprio. A regra de que o código deve ser "o mais curto possível" também pode ser substituída por "o mais obsceno possível" - todos são igualmente sem sentido e potencialmente prejudiciais se não servirem a um propósito maior.
Além disso, o código que você postou nem segue a regra da brevidade. Se as constantes fossem declaradas com o sufixo M, a maioria dos
(decimal)
lançamentos horríveis poderia ser evitada, pois o compilador promoveria o restanteint
paradecimal
. Acredito que a pessoa que você está descrevendo está apenas usando a brevidade como desculpa. Provavelmente não deliberadamente, mas ainda assim.fonte
Nos meus anos de experiência, acredito que a brevidade final é a do tempo - o tempo domina todo o resto. Isso inclui tempo de desempenho - quanto tempo um programa leva para fazer um trabalho - e tempo de manutenção - quanto tempo leva para adicionar recursos ou corrigir bugs. (Como você equilibra esses dois depende da frequência com que o código em questão está sendo executado versus aprimorado - lembre-se de que a otimização prematura ainda é a raiz de todo mal .) A brevidade do código é para melhorar a brevidade de ambos; código mais curto geralmente roda mais rápido e geralmente é mais fácil de entender e, portanto, manter. Se isso não acontecer, é um negativo líquido.
No caso mostrado aqui, acho que a brevidade do texto foi mal interpretada como brevidade da contagem de linhas, em detrimento da legibilidade, o que pode aumentar o tempo de manutenção. (Também pode levar mais tempo para executar, dependendo de como a conversão é feita, mas a menos que a linha acima seja executada milhões de vezes, provavelmente não é uma preocupação.) Nesse caso, as conversões decimais repetitivas diminuem a legibilidade, pois é mais difícil veja qual é o cálculo mais importante. Eu teria escrito da seguinte maneira:
(Editar: este é o mesmo código da outra resposta, então pronto).
Sou fã do operador ternário
? :
, então deixo isso lá.fonte
? :
- acho que o exemplo acima é suficientemente compacto, esp. comparado a um if-then-else.:
.if-else
parece inglês: não pode perder o que isso significa.Como quase todas as respostas acima, a legibilidade deve sempre ser seu objetivo principal. Eu também acho que a formatação pode ser uma maneira mais eficaz de conseguir isso ao criar variáveis e novas linhas.
Concordo plenamente com o argumento da complexidade ciclomática na maioria dos casos, no entanto, sua função parece ser pequena e simples o suficiente para lidar melhor com um bom caso de teste. Por curiosidade, por que a necessidade de converter para decimal?
fonte
decimal
, certo?double
! =decimal
, há uma grande diferença.Para mim, parece que um grande problema de legibilidade aqui reside na completa falta de formatação.
Eu escreveria assim:
Dependendo do tipo
CostIn
e do tipo deCostOut
ponto flutuante ou integral, algumas das transmissões também podem ser desnecessárias. Ao contrário defloat
edouble
, valores integrais são implicitamente promovidos paradecimal
.fonte
O código pode ser escrito às pressas, mas o código acima deve ser escrito no meu mundo com nomes de variáveis muito melhores.
E se eu li o código corretamente, ele está tentando fazer um cálculo de margem bruta.
fonte
Estou assumindo que CostIn * CostOut são inteiros
É assim que eu escreveria
M (Money) é decimal
fonte
O código é escrito para ser entendido pelas pessoas; a brevidade, neste caso, não compra muito e aumenta a carga sobre o mantenedor. Por essa brevidade, você absolutamente deve expandir isso, tornando o código mais auto-documentado (melhores nomes de variáveis) ou adicionando mais comentários explicando por que funciona dessa maneira.
Quando você escreve um código para resolver um problema hoje, esse código pode ser um problema amanhã quando os requisitos forem alterados. A manutenção sempre deve ser levada em consideração e é essencial melhorar a compreensibilidade do código.
fonte
A brevidade não é mais uma virtude quando
fonte
Se isso passasse nos testes de unidade de validação, seria bom se uma nova especificação fosse adicionada, um novo teste ou uma implementação aprimorada fosse necessária e fosse necessário "desembaraçar" a dispersão do código, ou seja, quando o problema surgiria.
Obviamente, um erro no código mostra que há outro problema com a Q / A que foi uma supervisão, portanto o fato de haver um erro que não foi detectado é motivo de preocupação.
Ao lidar com requisitos não funcionais, como "legibilidade" do código, ele precisa ser definido pelo gerente de desenvolvimento e gerenciado pelo desenvolvedor líder e respeitado pelos desenvolvedores para garantir implementações adequadas.
Tente garantir uma implementação padronizada de código (padrões e convenções) para garantir a "legibilidade" e a facilidade de "manutenção". Mas se esses atributos de qualidade não forem definidos e aplicados, você terá um código como o exemplo acima.
Se você não gosta de ver esse tipo de código, tente concordar com a equipe sobre os padrões e convenções de implementação e anote-o e faça análises aleatórias de código ou verificações pontuais para confirmar se a convenção está sendo respeitada.
fonte