Contexto
Recentemente, fiquei interessado em produzir um código melhor formatado. E por melhor, quero dizer "seguir regras endossadas por pessoas suficientes para considerá-la uma boa prática" (já que nunca haverá uma "melhor" maneira única de codificar, é claro).
Hoje em dia, eu principalmente codigo em Ruby, então comecei a usar um linter (Rubocop) para fornecer algumas informações sobre a "qualidade" do meu código (essa "qualidade" sendo definida pelo projeto orientado à comunidade do ruby-style-guide ).
Observe que usarei "qualidade" e "qualidade da formatação", não tanto sobre a eficiência do código, mesmo que em alguns casos, a eficiência do código seja afetada pela maneira como o código é escrito.
Enfim, fazendo tudo isso, percebi (ou pelo menos lembrei) algumas coisas:
- Algumas linguagens (principalmente Python, Ruby e outras) permitem criar ótimas linhas de código
- Seguir algumas diretrizes para seu código pode torná-lo significativamente mais curto e ainda assim muito claro
- No entanto, seguir essas diretrizes muito estritamente pode tornar o código menos claro / fácil de ler
- O código pode respeitar algumas diretrizes quase perfeitamente e ainda ser de baixa qualidade
- A legibilidade do código é principalmente subjetiva (como em "o que eu acho claro pode ser completamente obscuro para um colega desenvolvedor")
Essas são apenas observações, não regras absolutas, é claro. Você também observará que a legibilidade do código e as diretrizes a seguir podem parecer não relacionadas neste momento, mas aqui as diretrizes são uma maneira de diminuir o número de maneiras de reescrever um pedaço de código.
Agora, alguns exemplos, para deixar tudo isso mais claro.
Exemplos
Vamos dar um caso de uso simples: temos uma aplicação com um " User
" modelo. Um usuário tem um endereço opcional firstname
e surname
obrigatório email
.
Quero escrever um método " name
" que retorne o nome ( firstname + surname
) do usuário, se pelo menos dele firstname
ou surname
estiver presente, ou se email
for um valor de fallback.
Eu também quero que este método use_email
use " " como parâmetro (booleano), permitindo usar o email do usuário como o valor de fallback. Este use_email
parâmetro " " deve ser padronizado (se não for passado) como " true
".
A maneira mais simples de escrever isso, em Ruby, seria:
def name(use_email = true)
# If firstname and surname are both blank (empty string or undefined)
# and we can use the email...
if (firstname.blank? && surname.blank?) && use_email
# ... then, return the email
return email
else
# ... else, concatenate the firstname and surname...
name = "#{firstname} #{surname}"
# ... and return the result striped from leading and trailing spaces
return name.strip
end
end
Esse código é a maneira mais simples e fácil de entender. Mesmo para alguém que não "fala" Ruby.
Agora vamos tentar fazer isso mais curto:
def name(use_email = true)
# 'if' condition is used as a guard clause instead of a conditional block
return email if (firstname.blank? && surname.blank?) && use_email
# Use of 'return' makes 'else' useless anyway
name = "#{firstname} #{surname}"
return name.strip
end
Isso é mais curto, ainda fácil de entender, se não mais fácil (a cláusula de guarda é mais natural de se ler do que um bloco condicional). A cláusula de guarda também a torna mais compatível com as diretrizes que eu estou usando, portanto, ganha-ganha aqui. Também reduzimos o nível de recuo.
Agora vamos usar um pouco de magia Ruby para torná-lo ainda mais curto:
def name(use_email = true)
return email if (firstname.blank? && surname.blank?) && use_email
# Ruby can return the last called value, making 'return' useless
# and we can apply strip directly to our string, no need to store it
"#{firstname} #{surname}".strip
end
Ainda mais curto e seguindo perfeitamente as diretrizes ... mas muito menos claro, pois a declaração de falta de retorno torna um pouco confuso para quem não está familiarizado com essa prática.
É aqui que podemos começar a fazer a pergunta: vale a pena? Deveríamos dizer "não, torne legível e adicione ' return
'" (sabendo que isso não respeitará as diretrizes). Ou deveríamos dizer "Está tudo bem, é o jeito Ruby, aprenda a língua!"?
Se escolhermos a opção B, por que não torná-la ainda mais curta:
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
Aqui está, o one-liner! É claro que é mais curto ... aqui aproveitamos o fato de que Ruby retornará um valor ou outro dependendo de qual deles estiver definido (já que o email será definido nas mesmas condições de antes).
Também podemos escrever:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
É curto, não é tão difícil de ler (quero dizer, todos nós vimos como pode ser uma frase feia), bom Ruby, está em conformidade com as diretrizes que eu uso ... Mas ainda assim, comparado à primeira maneira de escrever é muito menos fácil de ler e entender. Também podemos argumentar que essa linha é muito longa (mais de 80 caracteres).
Questão
Alguns exemplos de código podem mostrar que escolher entre um código "em tamanho real" e muitas de suas versões reduzidas (até o famoso one-liner) pode ser difícil, pois, como podemos ver, os one-liners podem não ser tão assustadores, mas ainda assim, nada superará o código "em tamanho real" em termos de legibilidade ...
Então aqui está a verdadeira questão: onde parar? Quando é curto, curto o suficiente? Como saber quando o código se torna "muito curto" e menos legível (tendo em mente que é bastante subjetivo)? E ainda mais: como sempre codificar de acordo e evitar misturar one-liners com blocos de código "em tamanho normal" quando me apetece?
TL; DR
A principal questão aqui é: quando se trata de escolher entre um "longo, mas claro, legível e compreensível pedaço de código" e um "poderoso, mais curto e ainda mais difícil de ler / entender uma linha", sabendo que esses dois são os principais e os parte inferior de uma escala e não as duas únicas opções: como definir onde está a fronteira entre "suficientemente claro" e "não tão claro quanto deveria ser"?
A principal questão não é a clássica "one-liners vs. legibilidade: qual é o melhor?" mas "Como encontrar o equilíbrio entre os dois?"
Editar 1
Os comentários nos exemplos de código devem ser "ignorados", eles estão aqui para esclarecer o que está acontecendo, mas não devem ser levados em consideração ao avaliar a legibilidade do código.
fonte
return
palavra - chave adicionada . Esses sete personagens adicionam bastante clareza aos meus olhos.[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
... porquefalse.blank?
retorna verdadeiro e o operador ternário poupa alguns personagens ... ¯ \ _ (ツ) _ / ¯return
palavra - chave deve adicionar ?! Não fornece nenhuma informação . É pura desordem.Respostas:
Independentemente do código que você escreve, o melhor é legível. Curto é o segundo melhor. E legível geralmente significa curto o suficiente para que você possa entender o código, identificadores bem nomeados e aderir às expressões comuns do idioma em que o código está escrito.
Se isso fosse independente da linguagem, acho que isso definitivamente seria baseado em opiniões, mas dentro dos limites da linguagem Ruby, acho que podemos responder.
Primeiro, um recurso e uma maneira idiomática de escrever Ruby é omitir a
return
palavra - chave ao retornar um valor, a menos que retorne cedo de um método.Outro recurso e idioma combinados é o uso de
if
instruções finais para aumentar a legibilidade do código. Uma das idéias principais do Ruby é escrever um código que seja lido como linguagem natural. Para isso, vamos ao Por que o Guia Pungente de Ruby, Capítulo 3 .Diante disso, o exemplo de código nº 3 é mais idiomático para Ruby:
Agora, quando lemos o código, ele diz:
O que é bem parecido com o código Ruby real.
São apenas duas linhas de código real, portanto, são bastante concisas e aderem aos idiomas da linguagem.
fonte
use_email
antes das outras condições, já que é uma variável e não uma chamada de função. Mas, novamente, a interpolação de cordas inunda a diferença de qualquer maneira.do send an email if A, B, C but no D
, seguir sua premissa seria natural digitar 2 blocos if / else , quando provavelmente seria mais fácil codificarif not D, send an email
. Tenha cuidado no momento de ler a linguagem natural e transforme-a em código, pois isso pode fazer você escrever uma nova versão da "História sem fim" . Com classes, métodos e variáveis. Afinal, não é grande coisa.if !D
for melhor, tudo bem, lond, poisD
tem um nome significativo. E se o!
operador se perder entre o outro código, ter um identificador chamadoNotD
seria apropriado.Eu não acho que você terá uma resposta melhor do que "use seu bom senso". Em suma, você deve buscar a clareza e não a falta . Muitas vezes, o código mais curto também é o mais claro, mas se você se concentrar apenas em obter clareza de falta, pode sofrer. Esse é claramente o caso dos dois últimos exemplos, o que requer mais esforço para entender do que os três exemplos anteriores.
Uma consideração importante é a audiência do código. A legibilidade é obviamente totalmente dependente da pessoa que está lendo. As pessoas que você espera ler o código (fora de si) realmente conhecem os idiomas da linguagem Ruby? Bem, essa pergunta não é algo que pessoas aleatórias na internet possam responder, essa é apenas sua própria decisão.
fonte
Parte do problema aqui é "o que é legibilidade". Para mim, eu olho para o seu primeiro exemplo de código:
E acho difícil ler, pois está cheio de comentários "barulhentos" que apenas repetem o código. Retire-os:
e agora é muito mais legível. Ao lê-lo, penso "hmm, gostaria de saber se Ruby suporta o operador ternário? Em C #, posso escrever como:
É possível algo assim em rubi? Analisando sua postagem, vejo que existe:
Tudo de bom. Mas isso não é legível para mim; simplesmente porque eu tenho que rolar para ver a linha inteira. Então, vamos consertar isso:
Agora estou feliz. Não tenho muita certeza de como a sintaxe funciona, mas posso entender o que o código faz.
Mas sou só eu. Outras pessoas têm idéias muito diferentes sobre o que torna um bom código de leitura. Então, você precisa conhecer seu público ao escrever código. Se você é um iniciante em ensino absoluto, convém mantê-lo simples e possivelmente escrever como seu primeiro exemplo. Se você trabalha com um conjunto de desenvolvedores profissionais com muitos anos de experiência em ruby, escreva um código que tire proveito do idioma e mantenha-o curto. Se estiver em algum lugar no meio, mire em algum lugar no meio.
Uma coisa que eu diria: cuidado com o "código inteligente", como no seu último exemplo. Pergunte a si mesmo:
[firstname, surname].all?(&:blank?)
acrescenta algo além de fazer você se sentir inteligente, porque mostra suas habilidades, mesmo que agora seja um pouco mais difícil de ler? Eu diria que este exemplo provavelmente se encaixa nessa categoria. Se você estivesse comparando cinco valores, eu o veria como um bom código. Então, novamente, não há uma linha absoluta aqui, apenas lembre-se de ser inteligente demais.Então, em resumo: a legibilidade requer que você conheça seu público-alvo e direcione seu código adequadamente e escreva um código sucinto, mas claro; nunca escreva código "inteligente". Mantenha-o curto, mas não muito curto.
fonte
return "#{firstname} #{surname}".strip
Provavelmente, essa é uma pergunta em que é difícil não dar uma resposta com base em opiniões, mas aqui estão meus dois centavos.
Se você achar que tornar o código mais curto não afeta a legibilidade ou até a melhora, tente. Se o código ficar menos legível, será necessário considerar se existe um motivo razoavelmente bom para deixá-lo assim. Fazer isso apenas porque é mais curto, legal, ou apenas porque você pode, são exemplos de más razões. Você também precisa considerar se a redução do código tornaria menos compreensível para outras pessoas com quem você trabalha.
Então, qual seria uma boa razão? É uma decisão de julgamento, na verdade, mas um exemplo pode ser algo como uma otimização de desempenho (após testes de desempenho, é claro, sem antecedência). Algo que lhe proporciona algum benefício que você está disposto a pagar com menor legibilidade. Nesse caso, você pode atenuar a desvantagem fornecendo um comentário útil (que explica o que o código faz e por que ele precisava ser um pouco enigmático). Melhor ainda, você pode extrair esse código em uma função separada com um nome significativo, para que seja apenas uma linha no site de chamada que explique o que está acontecendo (por meio do nome da função) sem entrar em detalhes (no entanto, as pessoas têm diferenças) opiniões sobre isso, então essa é outra decisão que você deve fazer).
fonte
A resposta é um pouco subjetiva, mas você deve se perguntar com toda a honestidade que conseguir, se conseguir entender esse código quando voltar a usá-lo em um mês ou dois.
Cada mudança deve melhorar a capacidade da pessoa média de entender o código. Para tornar o código compreensível, é útil usar as seguintes diretrizes:
Dito isto, há momentos em que o código expandido ajuda a entender o que está acontecendo melhor. Um exemplo disso vem do C # e do LINQ. O LINQ é uma ótima ferramenta e pode melhorar a legibilidade em algumas situações, mas também encontrei várias situações em que era muito mais confuso. Eu tive alguns comentários na revisão por pares que sugeriram transformar a expressão em um loop com instruções if apropriadas para que outras pessoas pudessem mantê-la melhor. Quando eu cumpri, eles estavam certos. Tecnicamente, o LINQ é mais idiomático para C #, mas há casos em que diminui a compreensibilidade e uma solução mais detalhada a aprimora.
Eu digo tudo isso para dizer o seguinte:
Lembre-se de que você ou alguém como você precisará manter esse código posteriormente. A próxima vez que você se deparar com isso poderá demorar meses. Faça um favor a si mesmo e não procure reduzir a contagem de linhas com o custo de entender seu código.
fonte
A legibilidade é uma propriedade que você deseja ter, ter muitos liners não. Então, em vez de "one-liners vs readability", a pergunta deve ser:
Quando as one-liners aumentam a legibilidade e quando elas prejudicam?
Eu acredito que one-liners são bons para facilitar a leitura quando cumprem essas duas condições:
Por exemplo, digamos que
name
não era um bom nome para o seu método. Combinar o primeiro nome e o sobrenome, ou usar o email em vez do nome, não era algo natural a se fazer. Então, ao invés dename
melhor coisa que você pode encontrar, acabou sendo complicado e longo:Esse nome longo indica que isso é muito específico - se fosse mais geral, você poderia ter encontrado um nome mais geral. Portanto, agrupá-lo em um método não ajuda na legibilidade (é muito longa) nem no DRYness (muito específico para ser usado em qualquer outro lugar); portanto, é melhor deixar o código lá.
Ainda - por que torná-lo um one-liner? Eles geralmente são menos legíveis que o código de múltiplas linhas. É aqui que devemos verificar minha segunda condição - o fluxo do código circundante. E se você tiver algo parecido com isto:
O próprio código multilinha é legível - mas quando você tenta ler o código circundante (imprimindo os vários campos), essa construção multilinha está interrompendo o fluxo. É mais fácil de ler:
Seu fluxo não é interrompido e você pode se concentrar na expressão específica, se precisar.
Esse é o seu caso? Definitivamente não!
A primeira condição é menos relevante - você já considerou geral o suficiente para merecer um método e criou um nome para esse método que é muito mais legível do que sua implementação. Obviamente, você não a extrairia para uma função novamente.
Quanto à segunda condição - interrompe o fluxo do código circundante? Não! O código circundante é uma declaração de método, que escolher
name
é seu único objetivo. A lógica de escolher o nome não está interrompendo o fluxo do código circundante - é o próprio objetivo do código circundante!Conclusão - não faça de todo o corpo da função uma linha
As one-liners são boas quando você quer fazer algo um pouco complexo sem interromper o fluxo. Uma declaração de função já está interrompendo o fluxo (para que não seja interrompida quando você chama essa função), portanto, tornar todo o corpo da função uma linha única não ajuda na legibilidade.
Nota
Estou me referindo a funções e métodos "completos" - não a funções embutidas ou expressões lambda que geralmente fazem parte do código circundante e precisam se encaixar no fluxo.
fonte