Dividir o cálculo do valor de retorno e a declaração de retorno em métodos de uma linha?


26

Eu tive uma discussão com um colega de trabalho sobre a quebra de uma returndeclaração e a declaração que calcula o valor de retorno em duas linhas.

Por exemplo

private string GetFormattedValue()
{
    var formattedString = format != null ? string.Format(format, value) : value.ToString();
    return formattedString;
}

ao invés de

private string GetFormattedValue()
{
    return format != null ? string.Format(format, value) : value.ToString();
}

Em termos de código, não vejo realmente um valor na primeira variante. Para mim, o último é mais claro, principalmente para métodos tão curtos. Seu argumento foi que a variante anterior é mais fácil de depurar - o que é um mérito muito pequeno, já que o VisualStudio nos permite uma inspeção muito detalhada das instruções, quando a execução é interrompida devido a um ponto de interrupção.

Minha pergunta é, se ainda é um ponto válido escrever código menos claro, apenas para facilitar a depuração de um vislumbre? Existem outros argumentos para a variante com o cálculo e a returninstrução de divisão ?


18
Se não estiver trabalhando no VS, mas presumiria que você não pode definir um ponto de interrupção condicional em uma expressão complicada (ou seria complicado inserir), portanto, provavelmente colocaria atribuir e retornar em instruções separadas, apenas por conveniência. Provavelmente, o compilador apresentaria o mesmo código para ambos.
tofro

11
Possivelmente, isso depende da linguagem, especialmente em linguagens em que variáveis ​​têm comportamento de objeto (possivelmente complexo) em vez de comportamento de ponteiro. A declaração de @Paul K provavelmente é verdadeira para linguagens com comportamento de ponteiro, linguagens em que os objetos têm comportamento de valor simples e linguagens com compiladores maduros e de alta qualidade.
MSalters

4
"já que o VisualStudio nos permite uma inspeção muito detalhada das instruções, quando a execução é interrompida devido a um ponto de interrupção" - é isso. Então, como você obtém o valor de retorno se a função retornou uma estrutura com mais de um membro? (e o suporte para esse recurso é irregular, na melhor das hipóteses, há muitas combinações nas quais você não obtém o valor de retorno).
Voo

2
Como a "inspeção muito detalhada das instruções" no depurador que alguém está usando HOJE faz com que seja uma opção ruim escrever o código para facilitar a depuração em QUALQUER depurador?
Ian

16
Irrite-o ainda mais, reduzindo todo o corpo da função paraprivate string GetFormattedValue() => string.Format(format ?? "{0}", value);
Graham

Respostas:


46

A introdução de variáveis ​​explicativas é uma refatoração bem conhecida que às vezes pode ajudar a tornar expressões complicadas melhor legíveis. No entanto, no caso mostrado,

  • a variável adicional não "explica" nada que não esteja claro no nome do método circundante
  • a declaração fica ainda mais longa, então (um pouco) menos legível

Além disso, versões mais recentes do depurador do Visual Studio podem mostrar o valor de retorno de uma função na maioria dos casos, sem introduzir uma variável supérflua (mas cuidado, existem algumas ressalvas, dê uma olhada nesta postagem mais antiga do SO e nas respostas diferentes ).

Portanto, neste caso específico, eu concordo com você, no entanto, há outros casos em que uma variável explicativa pode realmente melhorar a qualidade do código.


Também concordo que definitivamente existem casos em que é útil, sem dúvida.
Paul Kertscher

2
Eu costumo usar resultcomo o nome da variável. Não é muito mais longo e mais fácil de depurar
edc65

26
@ edc65: um nome genérico como result frequentemente adiciona ruído ao código e raramente aumenta a legibilidade, que é exatamente o ponto da minha resposta. Isso pode ser justificado no contexto em que ajuda na depuração, mas eu evitaria ao usar um depurador que não precisa de uma variável separada.
Doc Brown

6
@JonHanna ferramenta longa maneira de acordo comigo. O nome resulttransmite as informações de que esse é o valor resultante da função, para que você possa vê-lo antes que a função retorne.
Edc65

11
@ edc65, mas isso faz com que pareça útil. Então agora, quando estou lendo seu código, não percebo imediatamente que não. Portanto, seu código se tornou menos legível.
Jon Hanna

38

Dados os fatos que:

a) Não há impacto no código final, pois o compilador otimiza a variável.

b) Separá-lo melhora a capacidade de depuração.

Pessoalmente, cheguei à conclusão de que é uma boa prática separá-los 99% do tempo.

Não há desvantagens materiais em fazê-lo dessa maneira. O argumento de que ele incha o código é um nome impróprio, porque o código inchado é um problema trivial em comparação com o código ilegível ou difícil de depurar. Além disso, esse método não pode, por si só, criar código confuso, isso depende inteiramente do desenvolvedor.


9
Esta é a resposta correta para mim. Isso facilita a definição de um ponto de interrupção e a visualização do valor durante a depuração, além de não ter nenhuma desvantagem de que estou ciente.
Matthew James Briggs

Para o ponto b, no Visual Studio Code, basta colocar o ponto de interrupção no retorno e adicionar a expressão: GetFormattedValue () e isso mostrará o resultado quando o ponto de interrupção for atingido, para que a linha extra não seja necessária. Mas é mais fácil ver os locais com uma linha extra, pois não será necessário adicionar nenhuma expressão adicional no depurador. Então, é realmente uma questão de preferência pessoal.
Jon Raynor

3
@JonRaynor para o valor de retorno, coloque o ponto de interrupção no colchete de fechamento da função. Ele captura o valor retornado, mesmo em funções com múltiplos retornos.
precisa

16

Freqüentemente, a introdução de uma variável apenas para nomear algum resultado é muito útil quando torna o código mais auto-documentado. Nesse caso, isso não é um fator, porque o nome da variável é muito semelhante ao nome do método.

Observe que os métodos de uma linha não têm nenhum valor inerente. Se uma alteração introduz mais linhas, mas torna o código mais claro, é uma boa alteração.

Mas, em geral, essas decisões são altamente dependentes de suas preferências pessoais. Por exemplo, acho as duas soluções confusas porque o operador condicional está sendo usado desnecessariamente. Eu teria preferido uma declaração se. Mas em sua equipe você pode ter concordado em diferentes convenções. Então faça o que suas convenções sugerem. Se as convenções forem silenciosas em um caso como esse, observe que essa é uma mudança extremamente pequena que não importa a longo prazo. Se esse padrão ocorrer repetidamente, talvez inicie uma discussão sobre como você, como equipe, deseja lidar com esses casos. Mas isso é dividir os cabelos entre "bom código" e "talvez um pouco melhor".


11
"Acho as duas soluções confusas porque o operador condicional está sendo usado desnecessariamente." - Não é um exemplo do mundo real, eu apenas tive que inventar algo rapidamente. É certo que este pode não ser o melhor exemplo.
Paul Kertscher

4
+1 por dizer essencialmente que essa é uma diferença "sob o radar" de que (outras coisas são iguais) não vale a pena ser incomodada.
TripeHound

3
@ Mindwin, quando eu uso o operador ternário, geralmente o divido em várias linhas, para que fique claro qual é o caso verdadeiro e o falso.
Arturo Torres Sánchez

2
@ ArturoTorresSánchez Eu também faço isso, mas em vez de a ?e a :eu uso if() {e } else {- - - - \\ :)
Mindwin

3
@Mindwin, mas não posso fazer isso quando estou no meio de uma expressão (como um inicializador de objetos) #
Arturo Torres Sánchez

2

Em resposta às suas perguntas:

Minha pergunta é: se ainda é um ponto válido escrever código menos claro, apenas para facilitar a depuração de um vislumbre?

Sim. De fato, parte da sua declaração anterior parece-me um pouco míope (veja em negrito abaixo) " O argumento dele foi que a variante anterior é mais fácil de depurar - o que é um mérito muito pequeno , já que o VisualStudio permite uma inspeção muito detalhada das instruções, quando a execução é interrompida devido a um ponto de interrupção " .

Facilitar a depuração (quase) nunca é de " pequeno mérito " porque, segundo algumas estimativas, 50% do tempo de um programador é gasto na depuração ( Software de Depuração Reversível ).

Existem outros argumentos para a variante com o cálculo de divisão e a declaração de retorno?

Sim. Alguns desenvolvedores argumentam que o cálculo dividido é mais fácil de ler. Isso, é claro, ajuda na depuração, mas também ajuda quando alguém está tentando decifrar quaisquer regras de negócios que seu código possa executar ou aplicar.

NOTA: As regras de negócios podem ser melhor atendidas em um banco de dados, pois podem ser alteradas com frequência. No entanto, a codificação clara nessa área ainda é fundamental. ( Como criar um mecanismo de regras de negócios )


1

Eu iria além:

private string GetFormattedValue()
{
    if (format != null) {
        formattedString = string.Format(format, value);
    } else {
        formattedString = value.ToString()
    }
    return formattedString;
}

Por quê?

Usar operadores ternários para uma lógica mais complexa seria ilegível; portanto, você usaria um estilo como o acima para instruções mais complexas. Sempre usando esse estilo, seu código é consistente e mais fácil para um ser humano analisar. Além disso, introduzindo esse tipo de consistência (e usando lints de código e outros testes), você pode evitar goto failerros de tipo.

Outra vantagem é que seu relatório de cobertura de código informará se você esqueceu de incluir um teste para formatnão ser nulo. Este não seria o caso para o operador ternário.


Minha alternativa preferida - se você estiver no grupo "obtenha um retorno o mais rápido possível" e não contra vários retornos de um método:

private string GetFormattedValue()
{
    if (format != null) {
        return string.Format(format, value);
    }

    return value.ToString();
}

Portanto, você pode ver o último retorno para ver qual é o padrão.

É importante ser consistente - e fazer com que todos os seus métodos sigam uma ou outra convenção.


11
O primeiro exemplo parece ser uma prática ruim porque value.ToString()é chamado desnecessariamente quando o formato é não nulo. No caso geral, isso pode incluir cálculos não triviais e pode levar mais tempo que a versão, incluindo uma sequência de formato. Considere, por exemplo, um valueque armazena o PI com um milhão de casas decimais e uma sequência de formato que solicita apenas os primeiros dígitos.
Steve

11
por que não O private string GetFormattedValue() => string.Format(format ?? "{0}", value); mesmo afeta e use testes de unidade para garantir a correção em vez de confiar no depurador.
Berin Loritsch

11
Embora eu concorde que um ternário possa ser menos claro, o terminador nulo pode tornar as coisas mais claras. Pelo menos neste caso.
Berin Loritsch

11
Caro diário, hoje eu li que escrever código claro e conciso usando paradigmas, expressões idiomáticas e operadores conhecidos (existentes há cerca de 40 anos) é: citação, citação dupla sendo citação dupla inteligente, sem aspas - mas, ao invés disso, escrevendo código excessivamente detalhado violando DRY e não usando operadores acima mencionados, expressões idiomáticas e paradigmas, enquanto em vez tentando evitar qualquer coisa que poderia possivelmente parece enigmática apenas para um período de cinco anos com nenhum fundo anterior em programação - é clareza vez. Inferno, eu devo ter ficado velho, meu querido diário ... Eu deveria ter aprendido Go quando tive a chance.
precisa

11
"Usar operadores ternários para uma lógica mais complexa seria ilegível". Embora esse seja o caso (e vi pessoas com lógica complicada), isso não é verdade para o código do OP e também não é específico para os operadores ternários. A única coisa que posso dizer com total confiança é que a fila é muito longa. gist.github.com/milleniumbug/cf9b62cac32a07899378feef6c06c776 é como eu o reformataria.
precisa

1

Eu não acho que essa técnica possa ser justificada pela necessidade de depurar. Eu já encontrei essa abordagem mil vezes e, de tempos em tempos, continuo fazendo isso, mas sempre lembro o que Martin Fowler disse sobre a depuração :

As pessoas também subestimam o tempo que gastam na depuração. Eles subestimam quanto tempo eles podem gastar perseguindo um longo bug. Com os testes, eu sei imediatamente quando adicionei um bug. Isso permite que eu conserte o bug imediatamente, antes que ele rasteje e se esconda. Existem poucas coisas mais frustrantes ou perda de tempo do que depuração. Não seria muito mais rápido se simplesmente não tivéssemos criado os bugs?


Martin Fowler é um homem inteligente e eu gostei de ler suas opiniões (e suas). Embora eu acredite firmemente que testes são necessários e que mais tempo deve ser gasto nesse esforço, o fato de sermos todos seres humanos falíveis sugere que nenhuma quantidade de testes erradicará todos os bugs. Portanto, a depuração sempre fará parte do processo de desenvolvimento e suporte do programa.
tale852150

1

Eu acho que algumas pessoas estão se pendurando em questões tangenciais à questão, como o operador ternário. Sim, muitas pessoas odeiam isso, então talvez seja bom falar sobre isso de qualquer maneira.

Com relação ao foco da sua pergunta, movendo a declaração retornada para ser referenciada por uma variável ...

Esta pergunta faz duas suposições que eu não concordo:

  1. Que a segunda variante é mais clara ou fácil de ler (digo que o oposto é verdadeiro) e

  2. que todo mundo usa o Visual Studio. Eu usei o Visual Studio várias vezes e posso usá-lo muito bem, mas geralmente estou usando outra coisa. Um ambiente de desenvolvimento que força um IDE específico é do qual eu seria cético.

Dividir algo com uma variável nomeada raramente dificulta a leitura, mas quase sempre faz o oposto. A maneira específica pela qual alguém faz isso pode causar problemas, como se um supervisor de auto-documentação o fizesse var thisVariableIsTheFormattedResultAndWillBeTheReturnValue = ...obviamente ruim, mas esse é um problema separado.var formattedText = ...está bem.

Neste específico caso , e possivelmente em muitos casos, já que estamos falando de 1-liners, a variável não diria muito a você que o nome da função ainda não o informou. Portanto, a variável não adiciona tanto. O argumento de depuração ainda pode se manter, mas, novamente, neste caso específico, não vejo nada que possa ser o seu foco durante a depuração e sempre pode ser facilmente alterado posteriormente se, de alguma forma, alguém precisar desse formato para depuração ou qualquer outra coisa.

Em geral, e você solicitou a regra geral (seu exemplo foi apenas isso, um exemplo de forma generalizada), todos os argumentos feitos a favor da variante 1 (2-liner) estão corretos. Essas são boas diretrizes para ter. Mas as diretrizes precisam ser flexíveis. Por exemplo, o projeto no qual estou trabalhando agora tem no máximo 80 caracteres por linha, então divido muitas linhas, mas geralmente encontro as linhas 81 a 85 caracteres que seriam difíceis de dividir ou reduzir a legibilidade e deixo-as o limite.

Como é improvável agregar valor, eu não faria duas linhas para o exemplo específico dado. Eu faria a variante 2 (o liner 1) porque os pontos não são fortes o suficiente para fazer o contrário neste caso.

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.