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.
return
palavra - chave adicionada . Esses sete personagens adicionam bastante clareza aos meus olhos.
[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
... porque false.blank?
retorna verdadeiro e o operador ternário poupa alguns personagens ... ¯ \ _ (ツ) _ / ¯
return
palavra - chave deve adicionar ?! Não fornece nenhuma informação . É pura desordem.