Codificação: concisão / eficiência versus legibilidade

4

Eu sou relativamente novo em c # e estou tentando aprender as melhores práticas. Fui confrontado com muitas situações na última semana em que precisei escolher entre um código mais longo + mais simples ou um código mais curto que combina várias ações em uma única instrução. Quais são os padrões que os codificadores veteranos usam ao tentar escrever um código claro e conciso? Aqui está um exemplo de código que estou escrevendo com duas opções. Qual é preferível?

A)

    if (ncPointType.StartsWith("A"))//analog points
    {
        string[] precisionString = Regex.Split(unitsParam.Last(), ", ");
        precision = int.Parse(precisionString[1]);
    }
    else
    {
        precision = null;
    }

B)

    if (ncPointType.StartsWith("A"))//analog points
        precision = int.Parse(Regex.Split(unitsParam.Last(), ", ")[1]);
    else
        precision = null;

C)

    precision = ncPointType.StartsWith("A") == true ? 
            int.Parse(Regex.Split(unitsParam.Last(), ", ")[1]) : 
            null;
    
por MatthewHagemann 15.05.2014 / 23:42
fonte

5 respostas

16

É como escrever em inglês. Concise raramente é uma coisa ruim, contanto que o leitor possa ver todas as informações necessárias para entender a intenção.

A razão pela qual a opção C não é perfeitamente legível não é porque é muito concisa, é porque não é concisa o suficiente. Olhe de novo:

precision = ncPointType.StartsWith("A") == true ? 
            int.Parse(Regex.Split(unitsParam.Last(), ", ")[1]) : 
            null;

E se isso fosse dito?

precision = ncPointType.StartsWith("A") ? SecondToken(unitsParam.Last()) : null

Isso parece muito legível para mim. É muito claro que, se ncPointType iniciar com um A, definiremos a precisão para o segundo token no último parâmetro de unitsParam. Caso contrário, vamos defini-lo como nulo.

Os detalhes da tokenização são irrelevantes para a pessoa que está lendo esta linha de código. Mas se eles querem saber, eles podem olhar para o método, que dirá.

public int SecondToken(string param)
{
    return int.Parse(GetTokens(param)[1]);
}

Faz sentido. Muito claro. E então ...

public string[] GetTokens(string param)
{
    return Regex.Split(param, ", ");
}

Agora você vai dizer que isso é muito mais palavras do que qualquer uma das suas opções. E isso é. Mas pense em cada método como parte de um glossário de termos. Você está criando uma linguagem como você vai.

Não fazer isso é como tentar escrever uma história sobre seus animais de estimação, depois de remover as palavras cachorro e gato do dicionário.

Existem muitas palavras na definição de cão; ainda mais no dicionário como um todo. Mas não ter essa definição para o cão significa que você tem que dizer "mamífero domesticado carnívoro" em vez disso e sua história se torna menos legível.

Dito isto, como aludi no início, há um ponto em que você perde informação. Por exemplo:

precision = ncPointType.IsOk() ? GetIt() : Dont();

Este é um passo longe demais. Para bater a última peça de utilidade de uma analogia: É como escrever uma história sobre seus animais de estimação, referindo-se a eles sempre como "isso".

    
por 16.05.2014 / 00:50
fonte
2

De Código limpo :

Each line represents an expression or a clause, and each group of lines represents a complete thought. Those thoughts should be separated from each other with blank lines.

Eu também acho que operadores ternários podem ser ilegíveis se as linhas forem muito longas; então eu iria com B .

No entanto, também do Código Limpo, eu dividiria o condicional em seu próprio método.

It only takes a few seconds of thought to explain most of your intent in code. In many cases it's simply a matter of creating a function that says the same thing as the comment you want to write.

Considere, o que é mais legível, isto:

if (ncPointType.StartsWith("A"))//analog points
    precision = int.Parse(Regex.Split(unitsParam.Last(), ", ")[1]);
else
    precision = null;

ou isto:

if (isAnalog(ncPointType))
    precision = int.Parse(Regex.Split(unitsParam.Last(), ", ")[1]);
else
    precision = null;
    
por 15.05.2014 / 23:53
fonte
1

Mais código. Mais linhas de código não se traduzem em mais instruções executadas.

Em todos os seus exemplos, eu esperaria que o compilador produzisse código idêntico (com exceção de algumas variações nos mapeamentos de números de linha para uso em diagnósticos). Eu também esperaria que qualquer referência nas três variações produzisse resultados idênticos.

Compiladores são softwares muito inteligentes escritos por um grande número de pessoas muito inteligentes ao longo de vários anos.

Seu programa será mantido por uma pessoa (provavelmente você mesmo) que pode não ser tão inteligente e pode ter apenas alguns minutos para ler seu código e resolver um problema.

Portanto, a sua prioridade deve ser o otário pobre que leu o seu código, não o compilador optimizador super inteligente que transformará qualquer lixo antigo num código de máquina razoavelmente eficiente.

A chave aqui é a legibilidade e compreensão. O que quer que torne sua intenção mais clara é o melhor código. Você pode fazer isso dividindo linhas densas de código ou, inversamente, você pode fazer isso reduzindo várias linhas de código a uma única declaração concisa - é tudo uma questão de julgamento.

    
por 16.05.2014 / 03:24
fonte
0

Não estando ciente do resto do seu código, eu claramente prefiro a opção A.

A) Caso você queira depurar, você terá acesso fácil a precisionString

B) Não tão bom, o valor de retorno da análise de expressão regular não será facilmente acessado na depuração. Além disso, a ausência do {} é perigosa: adicionar uma instrução (por exemplo: uma linha de depuração) quebrará a lógica

C) Na verdade é muito detalhado, também seria acessível:

precision = ncPointType.StartsWith("A") /* Analog unit */ ? 
        int.Parse(Regex.Split(unitsParam.Last(), ", ")[1]) : 
        null;

(sem o acesso fácil no valor de retorno da divisão regexp)

    
por 18.05.2014 / 19:29
fonte
0

Se você puder ter legibilidade E concisão E eficiência, isso seria ideal. No entanto, concordo que às vezes ter muita concisão pode tornar o código ilegível. Por exemplo, muitas vezes, quebro meu código em várias linhas e atribuo a cada parte intermediária de meus cálculos um nome, em vez de mesclar cálculos complexos em uma linha de código.

Pessoalmente, eu prefiro as instruções if-else sobre a notação em C. É mais fácil fazer pontos de interrupção se você precisar testar um ponto específico. Se você codificar em poucas linhas, é mais difícil de testar. Além disso, como a instrução if-else é bem recuada, um leitor pode ver a estrutura do código sem ler nada.

Suponho que minha regra pessoal seja a seguinte: "Se eu explicasse meu bloco de código a alguém do inglês mais simples possível, o código refletiria minhas palavras?" Se eu achar que teria que quebrar o código e explicar cada pedaço dele, então o próprio código precisa ser quebrado.

E como James Anderson mencionou, o mais provável é que não faça muita diferença em termos de desempenho. Normalmente, se eu sou forçado a escolher, eu escolheria a legibilidade sobre a concisão.

Eu recomendaria considerar a regra 90/10. 90% de sua lentidão geralmente está concentrada em 10% do seu código. Algumas pessoas preferem eficiência ao ponto em que o código é ilegível. Então, o próximo desenvolvedor tem que gastar tanto tempo decifrando o código que ele não tem tempo para otimizar os 10% do código que está realmente causando problemas de eficiência. Torne mais fácil para a próxima pessoa vasculhar seu código, para que ele tenha mais tempo para otimizar o que realmente precisa ser otimizado.

    
por 28.10.2016 / 22:31
fonte

Tags