Meu estilo de codificação para chamadas de função aninhadas é o seguinte:
var result_h1 = H1(b1);
var result_h2 = H2(b2);
var result_g1 = G1(result_h1, result_h2);
var result_g2 = G2(c1);
var a = F(result_g1, result_g2);
Recentemente, mudei para um departamento em que o seguinte estilo de codificação é muito utilizado:
var a = F(G1(H1(b1), H2(b2)), G2(c1));
O resultado da minha maneira de codificar é que, no caso de uma função com falha, o Visual Studio pode abrir o dump correspondente e indicar a linha onde o problema ocorre (estou especialmente preocupado com violações de acesso).
Receio que, no caso de uma falha devido ao mesmo problema programado na primeira maneira, não seja capaz de saber qual função causou a falha.
Por outro lado, quanto mais processamento você colocar em uma linha, mais lógica obterá em uma página, o que aprimora a legibilidade.
Meu medo está correto ou estou faltando alguma coisa e, em geral, a preferida em um ambiente comercial? Legibilidade ou manutenção?
Não sei se é relevante, mas estamos trabalhando em C ++ (STL) / C #.
fonte
HX
eGX
pode mudar na linha única, como a ordem de avaliação dos argumentos da função não é especificada. Se, por algum motivo, você depender da ordem dos efeitos colaterais (consciente ou inconscientemente) nas invocações, essa "refatoração de estilo" poderá acabar afetando mais do que apenas legibilidade / manutenção.result_g1
que você realmente usa ou esse valor realmente representa algo com um nome sensível; por exemplopercentageIncreasePerSecond
. Isso seria realmente o meu teste para decidir entre os doisRespostas:
Se você se sentiu obrigado a expandir um forro como
Eu não te culpo. Isso não é apenas difícil de ler, é difícil de depurar.
Por quê?
Se você expandi-lo com resultados intermediários, obtém
e ainda é difícil de ler. Por quê? Ele resolve dois dos problemas e introduz um quarto:
É densoAlguns depuradores destacam tudo de uma só vezSe você expandi-lo com nomes que adicionam significado novo, bom e semântico, melhor ainda! Um bom nome me ajuda a entender.
Agora, pelo menos, isso conta uma história. Isso corrige os problemas e é claramente melhor do que qualquer outra coisa oferecida aqui, mas exige que você crie os nomes.
Se você faz isso com nomes sem sentido como
result_this
eresult_that
porque simplesmente não consegue pensar em bons nomes, eu realmente prefiro que você nos poupe da confusão de nomes sem sentido e a expanda usando um bom e velho espaço em branco:É tão legível, se não mais, do que aquele com os nomes de resultados sem sentido (não que esses nomes de função sejam tão bons).
É densoAlguns depuradores destacam tudo de uma só vezEstá cheio de nomes não descritivosQuando você não consegue pensar em bons nomes, é tão bom quanto possível.
Por alguma razão, os depuradores adoram novas linhas, então você deve achar que isso não é difícil para depurar:
Se isso não for suficiente, imagine que
G2()
foi chamado em mais de um lugar e aconteceu:Eu acho bom que, uma vez que cada
G2()
chamada esteja em sua própria linha, esse estilo o leve diretamente à chamada ofensiva em geral.Portanto, não use os problemas 1 e 2 como uma desculpa para nos manter com o problema 4. Use bons nomes quando puder pensar neles. Evite nomes sem sentido quando não puder.
As Raças da Luminosidade no comentário da Orbit apontam corretamente que essas funções são artificiais e têm nomes próprios mortos. Então, aqui está um exemplo de aplicação desse estilo a algum código natural:
Eu odeio olhar para esse fluxo de ruído, mesmo quando a quebra de linha não é necessária. Veja como fica sob esse estilo:
Como você pode ver, eu descobri que esse estilo funciona bem com o código funcional que está sendo movido para o espaço orientado a objetos. Se você pode criar bons nomes para fazer isso em estilo intermediário, terá mais poder para você. Até então eu estou usando isso. Mas, em qualquer caso, por favor, encontre uma maneira de evitar nomes de resultados sem sentido. Eles fazem meus olhos doerem.
fonte
Eu discordo totalmente disso. Basta olhar para seus dois exemplos de código como incorretos:
é ouvido para ler. "Legibilidade" não significa densidade da informação; significa "fácil de ler, entender e manter".
Às vezes, o código é simples e faz sentido usar uma única linha. Outras vezes, isso dificulta a leitura, sem nenhum benefício óbvio além de colocar mais em uma linha.
No entanto, eu também diria que "fácil diagnosticar falhas" significa que o código é fácil de manter. Código que não falha é muito mais fácil de manter. "Fácil de manter" é alcançado principalmente por meio do código fácil de ler e entender, com um bom conjunto de testes automatizados.
Portanto, se você estiver transformando uma única expressão em uma linha múltipla com muitas variáveis apenas porque seu código geralmente falha e você precisa de melhores informações de depuração, pare de fazer isso e torne o código mais robusto. Você deve preferir escrever código que não precise depurar sobre código fácil de depurar.
fonte
F(G1(H1(b1), H2(b2)), G2(c1))
seja difícil de ler, isso não tem nada a ver com ficar muito denso. (Não tenho certeza se você quis dizer isso, mas pode ser interpretado dessa maneira.) Aninhar três ou quatro funções em uma única linha pode ser perfeitamente legível, principalmente se algumas das funções forem simples operadores de infix. São os nomes não-descritivos que são o problema aqui, mas esse problema é ainda pior na versão de várias linhas, onde mais nomes não-descritivos são introduzidos. Adicionar apenas clichês quase nunca ajuda na legibilidade.G1
necessários 3 parâmetros ou apenas 2 eG2
é outro parâmetroF
. Eu tenho que apertar os olhos e contar os parênteses.F (G1 (H1 b1) (H2 b2)) (G2 c1)
.)result_h1
não pode ser reutilizado se não existir, e o encanamento entre as 4 variáveis é óbvio.Seu primeiro exemplo, o formulário de atribuição única, é ilegível porque os nomes escolhidos são totalmente sem sentido. Isso pode ser um artefato de tentar não divulgar informações internas de sua parte; o código verdadeiro pode ser bom nesse aspecto, não podemos dizer. De qualquer forma, é muito complicado devido à densidade de informações extremamente baixa, que geralmente não se presta a um entendimento fácil.
Seu segundo exemplo é condensado em um grau absurdo. Se as funções tiverem nomes úteis, isso pode ser bom e bem legível, porque não há muito , mas, como está, é confuso na outra direção.
Depois de introduzir nomes significativos, você pode verificar se uma das formas parece natural ou se existe um meio de ouro para se escolher.
Agora que você possui um código legível, a maioria dos bugs será óbvia, e os outros terão mais dificuldade em se esconder de você.
fonte
Como sempre, quando se trata de legibilidade, a falha está nos extremos . Você pode seguir qualquer bom conselho de programação, transformá-lo em regra religiosa e usá-lo para produzir código totalmente ilegível. (Se você não acredita em mim, confira esses dois vencedores da IOCCC , borsanyi e goren, e veja como eles usam funções para tornar o código totalmente ilegível. Dica: Borsanyi usa exatamente uma função, goren muito, muito mais ...)
No seu caso, os dois extremos são 1) usando apenas expressões de expressão única e 2) juntando tudo em instruções grandes, concisas e complexas. Qualquer abordagem levada ao extremo torna seu código ilegível.
Sua tarefa, como programador, é encontrar um equilíbrio . Para cada afirmação que você escreve, é sua tarefa responder à pergunta: "Essa afirmação é fácil de entender e serve para tornar minha função legível?"
O ponto é que não há uma única complexidade mensurável de declaração que possa decidir o que é bom ser incluído em uma única declaração. Tomemos, por exemplo, a linha:
Essa é uma afirmação bastante complexa, mas qualquer programador que se preze deve poder entender imediatamente o que isso faz. É um padrão bastante conhecido. Como tal, é muito mais legível que o equivalente
que divide o padrão conhecido em um número aparentemente sem sentido de etapas simples. No entanto, a declaração da sua pergunta
me parece muito complicado, mesmo que seja uma operação a menos que o cálculo da distância . Claro, isso é uma consequência direta de mim não saber nada sobre
F()
,G1()
,G2()
,H1()
, ouH2()
. Eu poderia decidir diferente se soubesse mais sobre eles. Mas esse é precisamente o problema: a complexidade aconselhável de uma declaração depende fortemente do contexto e das operações envolvidas. E você, como programador, é quem deve dar uma olhada neste contexto e decidir o que incluir em uma única declaração. Se você se preocupa com a legibilidade, não pode transferir essa responsabilidade para alguma regra estática.fonte
@ Dominique, acho que na análise da sua pergunta, você está cometendo o erro de que "legibilidade" e "manutenção" são duas coisas separadas.
É possível ter um código que possa ser mantido, mas ilegível? Por outro lado, se o código é extremamente legível, por que ele se tornaria insustentável por ser legível? Nunca ouvi falar de nenhum programador que jogasse esses fatores um contra o outro, tendo que escolher um ou outro!
Em termos de decisão de usar variáveis intermediárias para chamadas de funções aninhadas, no caso de 3 variáveis fornecidas, chamadas para 5 funções separadas e algumas chamadas aninhadas em 3 profundas, eu tenderia a usar pelo menos algumas variáveis intermediárias para detalhar isso, como você fez.
Mas certamente não chego ao ponto de dizer que as chamadas de função nunca devem ser aninhadas. É uma questão de julgamento nas circunstâncias.
Eu diria que os seguintes pontos têm influência no julgamento:
Se as funções chamadas representam operações matemáticas padrão, elas são mais capazes de serem aninhadas do que as funções que representam alguma lógica de domínio obscura cujos resultados são imprevisíveis e não podem necessariamente ser avaliados mentalmente pelo leitor.
Uma função com um único parâmetro é mais capaz de participar de um ninho (como uma função interna ou externa) do que uma função com vários parâmetros. A mistura de funções de diferentes áreas em diferentes níveis de aninhamento é propensa a deixar o código parecido com a orelha de um porco.
Um ninho de funções que os programadores estão acostumados a ver expressos de uma maneira particular - talvez porque represente uma técnica ou equação matemática padrão, que tenha uma implementação padrão - pode ser mais difícil de ler e verificar se está dividido em variáveis intermediárias.
Um pequeno ninho de chamadas de função que executa uma funcionalidade simples e que já é clara de ler, e depois é decomposto excessivamente e atomizado, é capaz de ser mais difícil de ler do que aquele que não foi decomposto.
fonte
Ambos são subótimos. Considere os comentários.
Ou funções específicas em vez de gerais:
Ao decidir quais resultados serão explicados, lembre-se do custo (cópia x referência, valor l vs valor r), legibilidade e risco, individualmente para cada declaração.
Por exemplo, não há valor agregado ao mover conversões simples de unidade / tipo para suas próprias linhas, porque são fáceis de ler e extremamente improváveis de falhar:
Com relação à sua preocupação em analisar despejos de memória, a validação de entrada geralmente é muito mais importante - é provável que ocorra uma falha real nessas funções, e não na linha que as chama, e mesmo se não for, você normalmente não precisa saber exatamente onde as coisas explodiram. É muito mais importante saber onde as coisas começaram a desmoronar do que saber onde elas finalmente explodiram, que é o que a validação de entrada captura.
fonte
A legibilidade é a maior parte da manutenção. Duvida de mim? Escolha um projeto grande em uma linguagem que você não conhece (provavelmente a linguagem de programação e a linguagem dos programadores) e veja como você a refatoraria ...
Eu colocaria a legibilidade como algo entre 80 e 90 de manutenção. Os outros 10% a 20% são como favoráveis à refatoração.
Dito isto, você efetivamente passa duas variáveis para sua função final (F). Essas 2 variáveis são criadas usando outras 3 variáveis. Seria melhor passar b1, b2 e c1 para F, se F já existir, crie D que faça a composição para F e retorne o resultado. Nesse ponto, é apenas uma questão de dar a D um bom nome, e não importa qual estilo você usa.
Em um não relacionado, você diz que mais lógica na página ajuda na legibilidade. Isso está incorreto, a métrica não é a página, é o método, e a lógica MENOS que um método contém, mais legível é.
Legível significa que o programador pode manter a lógica (entrada, saída e algoritmo) em sua cabeça. Quanto mais ele faz, MENOS um programador pode entender. Leia sobre complexidade ciclomática.
fonte
Independentemente de você estar em C # ou C ++, desde que esteja em uma compilação de depuração, uma solução possível é agrupar as funções
Você pode escrever uma expressão on-line e ainda ser apontado para onde o problema está simplesmente observando o rastreamento da pilha.
Obviamente, se você chamar a mesma função várias vezes na mesma linha, não poderá saber qual função, mas ainda poderá identificá-la:
Esta não é uma bala de prata, mas não é tão ruim assim.
Sem mencionar que o agrupamento de funções do grupo pode ser ainda mais benéfico para a legibilidade do código:
fonte
Na minha opinião, o código de auto-documentação é melhor para manutenção e legibilidade, independentemente do idioma.
A afirmação dada acima é densa, mas "auto-documentada":
Quando dividido em estágios (mais fácil para testar, certamente) perde todo o contexto, conforme declarado acima:
E, obviamente, o uso de nomes de variáveis e funções que afirmam claramente seu objetivo é inestimável.
Mesmo blocos "se" podem ser bons ou ruins na auto-documentação. Isso é ruim porque você não pode forçar facilmente as duas primeiras condições a testar a terceira ... todas não têm relação:
Este faz mais sentido "coletivo" e é mais fácil criar condições de teste:
E essa afirmação é apenas uma sequência aleatória de caracteres, vista de uma perspectiva de auto-documentação:
Observando a afirmação acima, a manutenção ainda é um grande desafio se as funções H1 e H2 alteram as mesmas "variáveis de estado do sistema" em vez de serem unificadas em uma única função "H", porque alguém eventualmente altera o H1 sem nem pensar que há uma Função H2 para olhar e pode quebrar H2.
Acredito que um bom design de código é muito desafiador porque não há regras rígidas que possam ser sistematicamente detectadas e aplicadas.
fonte