Le contexte
Je me suis récemment intéressé à produire un meilleur code formaté. Et mieux je veux dire "suivre des règles approuvées par suffisamment de gens pour le considérer comme une bonne pratique" (car il n'y aura jamais une "meilleure" façon unique de coder, bien sûr).
Ces jours-ci, je code principalement en Ruby, j'ai donc commencé à utiliser un linter (Rubocop) pour me fournir des informations sur la "qualité" de mon code (cette "qualité" étant définie par le projet de style communautaire ruby-guide-style ).
Notez que j'utiliserai la «qualité» comme dans la «qualité de la mise en forme», pas tant sur l'efficacité du code, même si dans certains cas, l'efficacité du code est en fait affectée par la façon dont le code est écrit.
Quoi qu'il en soit, en faisant tout cela, j'ai réalisé (ou du moins, je me suis souvenu) quelques choses:
- Certains langages (notamment Python, Ruby et autres) permettent de créer de grandes lignes de code
- Suivre certaines directives pour votre code peut le rendre beaucoup plus court et pourtant très clair
- Pourtant, suivre ces directives trop strictement peut rendre le code moins clair / facile à lire
- Le code peut respecter presque parfaitement certaines directives et être toujours de mauvaise qualité
- La lisibilité du code est principalement subjective (comme dans "ce que je trouve clair peut être complètement obscur pour un collègue développeur")
Ce ne sont que des observations, pas des règles absolues bien sûr. Vous remarquerez également que la lisibilité du code et les directives suivantes peuvent sembler sans rapport à ce stade, mais ici, les directives sont un moyen de réduire le nombre de façons de réécrire un morceau de code.
Maintenant, quelques exemples, pour rendre tout cela plus clair.
Exemples
Prenons un cas d'utilisation simple: nous avons une application avec un User
modèle " ". Un utilisateur a une adresse facultative firstname
et surname
obligatoire email
.
Je veux écrire une méthode " name
" qui retournera alors le nom ( firstname + surname
) de l'utilisateur si au moins son firstname
ou surname
est présent, ou sa email
valeur de repli sinon.
Je veux aussi que cette méthode prenne un " use_email
" comme paramètre (booléen), permettant d'utiliser l'email de l'utilisateur comme valeur de secours. Ce use_email
paramètre " " devrait par défaut (s'il n'est pas passé) comme " true
".
La façon la plus simple d'écrire cela, en Ruby, serait:
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
Ce code est le moyen le plus simple et le plus facile à comprendre de le faire. Même pour quelqu'un qui ne "parle" pas Ruby.
Essayons maintenant de raccourcir cela:
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
C'est plus court, toujours facile à comprendre, sinon plus facile (la clause de garde est plus naturelle à lire qu'un bloc conditionnel). La clause Guard la rend également plus conforme aux directives que j'utilise, donc gagnant-gagnant ici. Nous réduisons également le niveau de retrait.
Maintenant, utilisons un peu de magie Ruby pour le raccourcir encore:
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
Encore plus court et en suivant parfaitement les directives ... mais beaucoup moins clair car le manque de déclaration de retour le rend un peu déroutant pour ceux qui ne connaissent pas cette pratique.
C'est ici que nous pouvons commencer à poser la question: cela en vaut-il vraiment la peine? Faut-il dire "non, rendez-le lisible et ajoutez return
" "(sachant que cela ne respectera pas les directives). Ou devrions-nous dire "ça va, c'est la façon Ruby, apprends la putain de langue!"?
Si nous prenons l'option B, alors pourquoi ne pas la raccourcir encore plus:
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
Le voici, le one-liner! Bien sûr, il est plus court ... ici nous profitons du fait que Ruby retournera une valeur ou l'autre en fonction de celle qui est définie (puisque l'email sera défini dans les mêmes conditions qu'auparavant).
On peut aussi l'écrire:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
C'est court, pas si difficile à lire (je veux dire, nous avons tous vu à quoi peut ressembler un laid), bon Ruby, il est conforme à la directive que j'utilise ... Mais quand même, par rapport à la première façon d'écrire ça, c'est beaucoup moins facile à lire et à comprendre. Nous pouvons également affirmer que cette ligne est trop longue (plus de 80 caractères).
Question
Quelques exemples de code peuvent montrer que le choix entre un code "pleine taille" et bon nombre de ses versions réduites (jusqu'au fameux one-liner) peut être difficile car, comme nous pouvons le voir, les one-liners ne sont pas si effrayants mais encore, rien ne battra le code "full-size" en termes de lisibilité ...
Voici donc la vraie question: où s'arrêter? Quand est court, assez court? Comment savoir quand le code devient "trop court" et moins lisible (en gardant à l'esprit qu'il est assez subjectif)? Et encore plus: comment toujours coder en conséquence et éviter de mélanger des lignes simples avec des morceaux de code "pleine taille" quand j'en ai envie?
TL; DR
La principale question ici est la suivante: quand il s'agit de choisir entre un "morceau de code long mais clair, lisible et compréhensible" et un "puissant, plus court mais plus difficile à lire / comprendre", sachant que ces deux sont le top et le bas d'une échelle et pas les deux seules options: comment définir où est la frontière entre "assez clair" et "pas aussi clair qu'il devrait être"?
La question principale n'est pas le classique "One-liners vs lisibilité: lequel est le meilleur?" mais "Comment trouver l'équilibre entre ces deux?"
Modifier 1
Les commentaires dans les exemples de code sont censés être "ignorés", ils sont là pour clarifier ce qui se passe, mais ne doivent pas être pris en compte lors de l'évaluation de la lisibilité du code.
la source
return
mot - clé ajouté . Ces sept personnages ajoutent un peu de clarté à mes yeux.[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip
... carfalse.blank?
retourne vrai et l'opérateur ternaire vous enregistre quelques caractères ... ¯ \ _ (ツ) _ / ¯return
mot clé est-il censé ajouter?! Il ne fournit aucune information . C'est du fouillis pur.Réponses:
Quel que soit le code que vous écrivez, la lisibilité est la meilleure. Court est le deuxième meilleur. Et lisible signifie généralement assez court pour que vous puissiez comprendre le code, les identificateurs bien nommés et adhérer aux idiomes communs de la langue dans laquelle le code est écrit.
Si cela était indépendant de la langue, je pense que ce serait certainement basé sur l'opinion, mais dans les limites de la langue Ruby, je pense que nous pouvons y répondre.
Tout d'abord, une fonctionnalité et une façon idiomatique d'écrire Ruby consiste à omettre le
return
mot - clé lors du retour d'une valeur, à moins de revenir tôt à partir d'une méthode.Une autre fonctionnalité et idiome combinés utilise des
if
instructions de fin pour augmenter la lisibilité du code. L'une des idées directrices de Ruby est d'écrire du code qui se lit comme un langage naturel. Pour cela, nous allons au Poignant Guide to Ruby de _why, chapitre 3 .Compte tenu de cela, l'exemple de code # 3 est le plus idiomatique pour Ruby:
Maintenant, quand nous lisons le code, il dit:
Ce qui est sacrément proche du code Ruby actuel.
Il ne s'agit que de 2 lignes de code réel, il est donc assez concis et adhère aux idiomes du langage.
la source
use_email
avant les autres conditions car c'est une variable plutôt qu'un appel de fonction. Mais là encore, l'interpolation de chaîne submerge la différence de toute façon.do send an email if A, B, C but no D
suivantes, suivre votre prémisse serait naturel de taper 2 blocs if / else , ce qui serait probablement plus facile à coderif not D, send an email
. Soyez prudent au moment de lire le langage naturel et transformez-le en code car cela peut vous faire écrire une nouvelle version de "l'histoire sans fin" . Avec des classes, des méthodes et des variables. Ce n'est pas grave après tout.if !D
est meilleur, c'est bien aussi london qu'unD
nom significatif. Et si l'!
opérateur se perd parmi les autres codes, alors avoir un identifiant appeléNotD
serait approprié.Je ne pense pas que vous obtiendrez une meilleure réponse que "utilisez votre meilleur jugement". En bref, vous devez viser la clarté plutôt que la brièveté . Souvent, le code le plus court est également le plus clair, mais si vous vous concentrez uniquement sur la réalisation de la brièveté, la clarté peut en souffrir. C'est clairement le cas dans les deux derniers exemples, ce qui nécessite plus d'efforts pour comprendre que les trois exemples précédents.
Une considération importante est l'audience du code. La lisibilité dépend bien sûr totalement de la personne qui lit. Les personnes que vous attendez de lire le code (à côté de vous) connaissent-elles réellement les idiomes du langage Ruby? Eh bien, cette question n'est pas quelque chose que les gens au hasard sur Internet peuvent répondre, c'est seulement votre propre décision.
la source
Une partie du problème ici est «ce qu'est la lisibilité». Pour moi, je regarde votre premier exemple de code:
Et j'ai du mal à lire car il est plein de commentaires "bruyants" qui ne font que répéter le code. Retirez-les:
et c'est maintenant beaucoup plus lisible. En le lisant ensuite, je pense "hmm, je me demande si Ruby supporte l'opérateur ternaire? En C #, je peux l'écrire comme:
Est-ce que quelque chose comme ça est possible en rubis? En parcourant votre message, je vois qu'il y a:
Toutes les bonnes choses. Mais ce n'est pas lisible pour moi; tout simplement parce que je dois faire défiler pour voir toute la ligne. Corrigeons donc cela:
Maintenant je suis content. Je ne suis pas complètement sûr du fonctionnement de la syntaxe, mais je peux comprendre ce que fait le code.
Mais c'est juste moi. D'autres personnes ont des idées très différentes sur ce qui fait un morceau de code agréable à lire. Vous devez donc connaître votre public lors de l'écriture de code. Si vous êtes un débutant absolu, vous voudrez rester simple et éventuellement l'écrire comme votre premier exemple. Si vous travaillez parmi un ensemble de développeurs professionnels avec de nombreuses années d'expérience ruby, écrivez du code qui tire parti du langage et restez court. Si c'est quelque part entre les deux, visez quelque part entre les deux.
Une chose que je dirais cependant: méfiez-vous du "code intelligent", comme dans votre dernier exemple. Demandez-vous,
[firstname, surname].all?(&:blank?)
ajoute-t-il autre chose que de vous faire sentir intelligent parce que cela montre vos compétences, même si c'est maintenant un peu plus difficile à lire? Je dirais que cet exemple est probablement complet dans cette catégorie. Si vous compariez cinq valeurs, je le verrais comme un bon code. Encore une fois, il n'y a pas de ligne absolue ici, gardez juste à l'esprit d'être trop intelligent.Donc, en résumé: la lisibilité nécessite que vous connaissiez votre public et cibliez votre code en conséquence et que vous écriviez un code succinct mais clair; n'écrivez jamais de code "intelligent". Soyez bref, mais pas trop court.
la source
return "#{firstname} #{surname}".strip
C'est probablement une question où il est difficile de ne pas donner de réponse basée sur une opinion, mais voici mes deux cents.
Si vous trouvez que raccourcir le code n'a pas d'impact sur la lisibilité, ni même l'améliore, allez-y. Si le code devient moins lisible, vous devez vous demander s'il existe une assez bonne raison de le laisser ainsi. Le faire simplement parce qu'il est plus court, ou cool, ou simplement parce que vous le pouvez, sont des exemples de mauvaises raisons. Vous devez également vous demander si le raccourcissement du code le rendrait moins compréhensible pour les autres personnes avec lesquelles vous travaillez.
Quelle serait donc une bonne raison? C'est vraiment un jugement, mais un exemple pourrait être quelque chose comme une optimisation des performances (après des tests de performances, bien sûr, pas à l'avance). Quelque chose qui vous procure un avantage que vous êtes prêt à payer avec une lisibilité réduite. Dans ce cas, vous pouvez atténuer l'inconvénient en fournissant un commentaire utile (qui explique ce que fait le code et pourquoi il a dû être rendu un peu cryptique). Encore mieux, vous pouvez extraire ce code dans une fonction distincte avec un nom significatif, de sorte que ce soit juste une ligne sur le site d'appel qui explique ce qui se passe (via le nom de la fonction) sans entrer dans les détails (cependant, les gens ont des différences opinions à ce sujet, c'est donc un autre jugement que vous devez faire).
la source
La réponse est un peu subjective, mais vous devez vous demander avec toute l'honnêteté que vous pouvez rassembler, si vous pouviez comprendre ce code lorsque vous y reviendriez dans un mois ou deux.
Chaque changement devrait améliorer la capacité de la personne moyenne à comprendre le code. Pour rendre le code compréhensible, il est utile d'utiliser les directives suivantes:
Cela dit, il y a des moments où le code étendu aide à mieux comprendre ce qui se passe. Un exemple avec cela vient de C # et LINQ. LINQ est un excellent outil et peut améliorer la lisibilité dans certaines situations, mais j'ai également rencontré un certain nombre de situations où il était beaucoup plus déroutant. J'ai eu des commentaires lors de l'examen par les pairs qui ont suggéré de transformer l'expression en une boucle avec des instructions if appropriées afin que d'autres puissent mieux la maintenir. Quand j'ai obéi, ils avaient raison. Techniquement, LINQ est plus idiomatique pour C #, mais il y a des cas où il dégrade la compréhensibilité et une solution plus verbeuse l'améliore.
Je dis tout ça pour dire ceci:
N'oubliez pas que vous ou quelqu'un comme vous devrez conserver ce code plus tard. La prochaine fois que vous rencontrerez, cela pourrait prendre des mois. Rendez-vous service et ne poursuivez pas la réduction du nombre de lignes au détriment de la compréhension de votre code.
la source
La lisibilité est une propriété que vous souhaitez avoir, ce n'est pas le cas de la présence de plusieurs lignes . Donc, plutôt que "one-liners vs readability", la question devrait être:
Quand les monolignes augmentent-ils la lisibilité et quand la nuisent-ils?
Je pense que les monolignes sont bonnes pour la lisibilité lorsqu'elles remplissent ces deux conditions:
Par exemple, disons que ce
name
n'était pas un bon ... nom pour votre méthode. Que combiner le prénom et le nom de famille, ou utiliser l'e-mail à la place du nom, n'était pas naturel. Donc, au lieu dename
la meilleure chose que vous pourriez trouver, cela s'est avéré long et lourd:Un nom aussi long indique que c'est très spécifique - s'il était plus général, vous auriez pu trouver un nom plus général. Donc, l'envelopper dans une méthode n'aide ni à la lisibilité (c'est trop long) ni à DRYness (trop spécifique pour être utilisé ailleurs), il est donc préférable de simplement laisser le code là-dedans.
Pourtant - pourquoi en faire une doublure? Ils sont généralement moins lisibles que le code multiligne. C'est là que nous devons vérifier ma deuxième condition - le flux du code environnant. Et si vous avez quelque chose comme ça:
Le code multiligne lui-même est lisible - mais lorsque vous essayez de lire le code environnant (en imprimant les différents champs), cette construction multiligne interrompt le flux. C'est plus facile à lire:
Votre flux n'est pas interrompu et vous pouvez vous concentrer sur l'expression spécifique si vous en avez besoin.
Est-ce votre cas? Définitivement pas!
La première condition est moins pertinente - vous l'avez déjà jugée suffisamment générale pour mériter une méthode, et vous avez trouvé un nom pour cette méthode qui est beaucoup plus lisible que son implémentation. De toute évidence, vous ne l'extrayeriez plus dans une fonction.
Quant à la deuxième condition - interrompt-elle le flux du code environnant? Non! Le code qui l'entoure est une déclaration de méthode, que choisir le
name
est son seul but. La logique de choisir le nom n'interrompt pas le flux du code environnant - c'est le but même du code environnant!Conclusion - ne faites pas de l'ensemble du corps de fonction une ligne
Les lignes simples sont bonnes lorsque vous voulez faire quelque chose d'un peu complexe sans interrompre le flux. Une déclaration de fonction interrompt déjà le flux (de sorte qu'elle ne sera pas interrompue lorsque vous appelez cette fonction), ce qui fait que le corps de la fonction dans son intégralité n'aide pas la lisibilité.
Remarque
Je fais référence aux fonctions et méthodes "complètes" - pas aux fonctions en ligne ou aux expressions lambda qui font généralement partie du code environnant et doivent s'adapter à son flux.
la source