web-dev-qa-db-fra.com

Mon patron me demande d'arrêter d'écrire de petites fonctions et de tout faire dans la même boucle

J'ai lu un livre intitulé Clean Code de Robert C. Martin. Dans ce livre, j'ai vu de nombreuses méthodes pour nettoyer le code comme écrire de petites fonctions, choisir les noms avec soin, etc. Il semble de loin le livre le plus intéressant sur le code propre que j'ai lu. Cependant, aujourd'hui, mon patron n'a pas aimé la façon dont j'ai écrit le code après avoir lu ce livre.

Ses arguments étaient

  • L'écriture de petites fonctions est pénible car elle vous oblige à vous déplacer dans chaque petite fonction pour voir ce que fait le code.
  • Mettez tout dans une grande boucle principale même si la boucle principale est de plus de 300 lignes, elle est plus rapide à lire.
  • N'écrivez de petites fonctions que si vous devez dupliquer du code.
  • N'écrivez pas de fonction avec le nom du commentaire, mettez votre ligne de code complexe (3-4 lignes) avec un commentaire ci-dessus; de même, vous pouvez modifier directement le code défaillant

C'est contre tout ce que j'ai lu. Comment écrivez-vous habituellement le code? Une grande boucle principale, pas de petites fonctions?

La langue que j'utilise est principalement Javascript. J'ai vraiment du mal à lire maintenant car j'ai supprimé toutes mes petites fonctions clairement nommées et mis le tout dans une grande boucle. Cependant, mon patron aime ça de cette façon.

Un exemple était:

// The way I would write it
if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

// The way he would write it
// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

Dans le livre que j'ai lu par exemple, les commentaires sont considérés comme un échec à écrire du code propre car ils sont obsolètes si vous écrivez de petites fonctions et conduisent souvent à des commentaires non mis à jour (vous modifiez votre code et non le commentaire). Cependant, ce que je fais, c'est supprimer le commentaire et écrire une fonction avec le nom du commentaire.

Eh bien, je voudrais des conseils, quelle est la meilleure façon/pratique d'écrire du code propre?

209

Prenons d'abord les exemples de code. Vous privilégiez:

if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

Et votre patron l'écrirait ainsi:

// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

À mon avis, les deux ont des problèmes. En lisant votre code, j'ai immédiatement pensé "vous pouvez remplacer ce if par une expression ternaire". Puis j'ai lu le code de votre patron et j'ai pensé "pourquoi a-t-il remplacé votre fonction par un commentaire?".

Je suggère que le code optimal se situe entre les deux:

phoneNumber = isApplicationInProduction(headers) ? headers.resourceId : DEV_PHONE_NUMBER;

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

Cela vous donne le meilleur des deux mondes: une expression de test simplifiée et le commentaire est remplacé par du code testable.

En ce qui concerne les vues de votre patron sur la conception de code:

L'écriture de petites fonctions est pénible car elle vous oblige à vous déplacer dans chaque petite fonction pour voir ce que fait le code.

Si la fonction est bien nommée, ce n'est pas le cas. isApplicationInProduction va de soi et il ne devrait pas être nécessaire d'examiner le code pour voir ce qu'il fait. En fait, l'inverse est vrai: l'examen du code révèle moins l'intention que le nom de la fonction (c'est pourquoi votre patron doit recourir aux commentaires).

Mettez tout dans une grande boucle principale même si la boucle principale est de plus de 300 lignes, il est plus rapide à lire

Il peut être plus rapide à parcourir, mais pour vraiment "lire" le code, vous devez être capable de l'exécuter efficacement dans votre tête. C'est facile avec de petites fonctions et c'est vraiment, vraiment difficile avec des méthodes qui sont longues de 100 lignes.

N'écrivez que de petites fonctions si vous devez dupliquer du code

Je ne suis pas d'accord. Comme le montre votre exemple de code, de petites fonctions bien nommées améliorent la lisibilité du code et doivent être utilisées chaque fois que, par exemple, vous n'êtes pas intéressé par le "comment", uniquement le "quoi" d'un élément de fonctionnalité.

N'écrivez pas de fonction avec le nom du commentaire, mettez votre ligne de code complexe (3-4 lignes) avec un commentaire ci-dessus. Comme cela, vous pouvez modifier directement le code défaillant

Je ne peux vraiment pas comprendre le raisonnement derrière celui-ci, en supposant qu'il soit vraiment sérieux. C'est le genre de chose que je m'attendrais à voir écrit en parodie par The Expert Beginner compte Twitter. Les commentaires ont un défaut fondamental: ils ne sont pas compilés/interprétés et ne peuvent donc pas être testés à l'unité. Le code est modifié et le commentaire est laissé seul et vous finissez par ne pas savoir ce qui est juste.

Il est difficile d'écrire du code auto-documenté et des documents supplémentaires (même sous forme de commentaires) sont parfois nécessaires. Mais le point de vue d '"oncle Bob" selon lequel les commentaires sont une erreur de codage est trop souvent vrai.

Demandez à votre patron de lire le livre Clean Code et essayez de résister à rendre votre code moins lisible juste pour le satisfaire. En fin de compte cependant, si vous ne pouvez pas le persuader de changer, vous devez soit faire la queue, soit trouver un nouveau patron qui peut mieux coder.

214
David Arno

Il y a d'autres problèmes

Aucun des deux codes n'est bon, car les deux sont fondamentalement gonfler le code avec un cas de test de débogage. Et si vous voulez tester plus de choses pour une raison quelconque?

phoneNumber = DEV_PHONE_NUMBER_WHICH_CAUSED_PROBLEMS_FOR_CUSTOMERS;

ou

phoneNumber = DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY;

Voulez-vous ajouter encore plus de branches?

Le problème important est que vous dupliquez essentiellement une partie de votre code et que vous ne testez donc pas réellement le vrai code. Vous écrivez du code de débogage pour tester le code de débogage, mais pas le code de production. C'est comme créer partiellement une base de code parallèle.

Vous vous disputez avec votre patron sur la façon d'écrire plus mal le mauvais code. Au lieu de cela, vous devez résoudre le problème inhérent au code lui-même.

injection de dépendance

Voici à quoi devrait ressembler votre code:

phoneNumber = headers.resourceId;

Il n'y a pas de branchement ici, car la logique ici n'a pas de branchement. Le programme doit extraire le numéro de téléphone de l'en-tête. Période.

Si vous voulez avoir DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY par conséquent, vous devez le mettre dans headers.resourceId. Une façon de le faire est d'injecter simplement un objet headers différent pour les cas de test (désolé si ce n'est pas le bon code, mes compétences JavaScript sont un peu rouillées):

function foo(headers){
    phoneNumber = headers.resourceId;
}

// Creating the test case
foo({resourceId: DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY});

En supposant que headers fait partie d'une réponse que vous recevez d'un serveur: Idéalement, vous disposez d'un serveur de test complet qui fournit headers de différents types à des fins de test. De cette façon, vous testez le code de production réel tel quel et non un code à moitié dupliqué qui peut ou non fonctionner comme le code de production.

222
null

Il n'y a pas de bonne ou de mauvaise réponse à cela. Cependant, je donnerai mon avis sur la base de 36 années d'expérience professionnelle dans la conception et le développement de systèmes logiciels ...

  1. Il n'y a pas de "code auto-documenté". Pourquoi? Parce que cette affirmation est complètement subjective.
  2. Les commentaires ne sont jamais des échecs. Ce est un échec est un code qui ne peut pas être compris du tout sans commentaires.
  3. 300 lignes de code ininterrompues dans un bloc de code est un cauchemar de maintenance et très sujet aux erreurs. Un tel bloc indique fortement une mauvaise conception et une mauvaise planification.

Parlant directement de l'exemple que vous avez fourni ... Placer isApplicationInProduction() dans sa propre routine est la chose intelligente (er) à faire. Aujourd'hui, ce test est simplement une vérification des "en-têtes" et peut être géré par un opérateur ternaire (?:). Demain, le test sera peut-être beaucoup plus complexe. En outre, "headers.resourceId" n'a pas de relation claire avec l'application "en état de production;" Je dirais qu'un test pour un tel statut doit être découplé des données sous-jacentes; un sous-programme fera cela et un ternaire ne le fera pas. De plus, un commentaire utile serait pourquoi resourceId est un test pour "en production".

Faites attention à ne pas aller trop loin avec "de petites fonctions clairement nommées". Une routine devrait encapsuler une idée plus que "juste du code". Je soutiens la suggestion amon de phoneNumber = getPhoneNumber(headers) et j'ajoute que getPhoneNumber() devrait faire le test "état de production" avec isApplicationInProduction()

59
LiamF

"Les entités ne doivent pas être multipliées au-delà de la nécessité."

- Le rasoir d'Occam

Le code doit être aussi simple que possible. Les bogues aiment se cacher entre la complexité, car ils sont difficiles à repérer là-bas. Alors, qu'est-ce qui rend le code simple?

Les petites unités (fichiers, fonctions, classes) sont une bonne idée . Les petites unités sont faciles à comprendre car il y a moins de choses que vous devez comprendre à la fois. Les humains normaux ne peuvent jongler qu'avec ~ 7 concepts à la fois. Mais la taille n'est pas seulement mesurée en lignes de code . Je peux écrire le moins de code possible en "jouant" avec le code (en choisissant des noms de variables courts, en prenant des raccourcis "intelligents", en écrasant autant de code que possible sur une seule ligne), mais le résultat final n'est pas simple. Essayer de comprendre un tel code ressemble plus à de l'ingénierie inverse qu'à de la lecture.

Une façon de raccourcir une fonction consiste à extraire diverses fonctions d'assistance. Cela peut être une bonne idée quand il extrait un morceau de complexité autonome . Isolé, ce morceau de complexité est beaucoup plus simple à gérer (et à tester!) Que lorsqu'il est intégré à un problème sans rapport.

Mais chaque appel de fonction a une surcharge cognitive : je n'ai pas seulement à comprendre le code dedans ma pièce actuelle du code, je dois aussi comprendre comment il interagit avec le code à l'extérieur - extérieur. Je pense qu'il est juste de dire que la fonction que vous avez extraite introduit plus de complexité dans la fonction qu'elle n'en extrait . C'est ce que votre patron voulait dire par " les petites fonctions [sont] une douleur car cela vous oblige à vous déplacer dans chaque petite fonction pour voir ce que fait le code."

Parfois, les longues fonctions ennuyeuses peuvent être incroyablement simples à comprendre, même lorsqu'elles font des centaines de lignes. Cela a tendance à se produire dans le code d'initialisation et de configuration, par exemple lors de la création d'une interface graphique à la main sans éditeur de glisser-déposer. Il n'y a aucun élément de complexité autonome que vous pourriez raisonnablement extraire. Mais si la mise en forme est lisible et qu'il y a des commentaires, ce n'est vraiment pas difficile de suivre ce qui se passe.

Il existe de nombreuses autres mesures de complexité: Le nombre de variables dans une étendue doit être aussi petit que possible. Cela ne signifie pas que nous devrions éviter variables. Cela signifie que nous devons restreindre chaque variable à la plus petite portée possible là où elle est nécessaire. Les variables deviennent également plus simples si nous ne modifions jamais la valeur qu'elles contiennent.

Une métrique très importante est la complexité cyclomatique (la complexité de McCabe). Il mesure le nombre de chemins indépendants à travers un morceau de code. Ce nombre augmente exponentiellement avec chaque conditionnel. Chaque boucle conditionnelle ou double double le nombre de chemins. Il existe des preuves suggérant qu'un score de plus de 10 points est trop complexe. Cela signifie qu'une fonction très longue qui peut avoir un score de 5 est peut-être meilleure qu'une fonction très courte et dense avec un score de 25. Nous pouvons réduire la complexité en extrayant le flux de contrôle dans des fonctions distinctes.

Votre conditionnel est un exemple d'un morceau de complexité qui pourrait être extrait entièrement:

function bigFatFunction(...) {
  ...
  phoneNumber = getPhoneNumber(headers);
  ...
}

...

function getPhoneNumber(headers) {
  return headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;
}

C'est encore très sur le point d'être utile. Je ne sais pas si cela diminue considérablement la complexité car ce conditionnel n'est pas très conditionnel . En production, il faudra toujours suivre le même chemin.


La complexité ne peut jamais disparaître. Il peut seulement être mélangé. Beaucoup de petites choses sont-elles plus simples que quelques grandes choses? Cela dépend beaucoup des circonstances. Habituellement, il y a une combinaison qui semble juste. Trouver ce compromis entre différents facteurs de complexité demande de l'intuition et de l'expérience, et un peu de chance.

Savoir écrire de très petites fonctions et des fonctions très simples est une compétence utile, car vous ne pouvez pas faire de choix sans connaître les alternatives. Suivre aveuglément les règles ou les meilleures pratiques sans réfléchir à la façon dont elles s'appliquent à la situation actuelle conduit au mieux à des résultats moyens, au pire une programmation cargo-culte .

C'est là que je suis en désaccord avec votre patron. Ses arguments ne sont pas invalides, mais le livre Clean Code n'est pas faux non plus. Il est probablement préférable de suivre les directives de votre patron, mais le fait même que vous réfléchissiez à ces problèmes, essayant de trouver une meilleure solution, est très prometteur. Au fur et à mesure que vous acquérez de l'expérience, vous trouverez plus facile de trouver un bon facteur d'affacturage pour votre code.

(Remarque: cette réponse est basée en partie sur les réflexions du Code raisonnable blog sur Le tableau blanc par Jimmy Hoffa , qui fournit une vue d'ensemble de ce qui rend le code simple.)

47
amon

Le style de programmation de Robert Martin est polarisant. Vous trouverez de nombreux programmeurs, même expérimentés, qui trouvent de nombreuses excuses pour lesquelles "diviser autant" est trop, et pourquoi garder des fonctions un peu plus grandes est "la meilleure façon". Cependant, la plupart de ces "arguments" sont souvent l'expression d'une réticence à changer les vieilles habitudes et à apprendre quelque chose de nouveau.

Ne les écoutez pas!

Chaque fois que vous pouvez enregistrer un commentaire en refactorisant un morceau de code dans une fonction distincte avec un nom expressif, faites-le - cela améliorera très probablement votre code. Vous n'êtes pas allé aussi loin que Bob Martin le fait dans son livre de code propre, mais la grande majorité du code que j'ai vu dans le passé qui a causé des problèmes de maintenance contenait des fonctions trop grandes, pas trop petites. Essayez donc d'écrire des fonctions plus petites avec des noms auto-descriptifs.

Les outils de refactorisation automatique facilitent l'extraction des méthodes. Et s'il vous plaît, ne prenez pas au sérieux les gens qui recommandent d'écrire des fonctions avec> 300 lignes - ces personnes ne sont certainement pas qualifiées pour vous dire comment coder.

27
Doc Brown

Dans votre cas: vous voulez un numéro de téléphone. Soit il est évident que vous obtiendrez un numéro de téléphone, puis vous écrivez le code évident. Ou il n'est pas évident de savoir comment vous obtiendrez un numéro de téléphone, puis vous écrivez une méthode pour cela.

Dans votre cas, il n'est pas évident de savoir comment obtenir le numéro de téléphone, vous devez donc écrire une méthode pour cela. L'implémentation n'est pas évidente, mais c'est pourquoi vous la mettez dans une méthode distincte, vous n'avez donc à la traiter qu'une seule fois. Un commentaire serait utile car l'implémentation n'est pas évidente.

La méthode "isApplicationInProduction" est tout à fait inutile. L'appeler à partir de votre méthode getPhonenumber ne rend pas l'implémentation plus évidente et rend simplement plus difficile de comprendre ce qui se passe.

N'écrivez pas de petites fonctions. Écrivez des fonctions qui ont un objectif bien défini et qui répondent à cet objectif.

PS. Je n'aime pas du tout l'implémentation. Il suppose que l'absence de numéro de téléphone signifie qu'il s'agit d'une version de développement. Donc, si le numéro de téléphone est absent de la production, non seulement vous ne le gérez pas, mais vous substituez un numéro de téléphone aléatoire. Imaginez que vous avez 10 000 clients, 17 n'ont pas de numéro de téléphone et que vous avez des problèmes de production. Que vous soyez en production ou en développement doit être vérifié directement, pas dérivé d'autre chose.

23
gnasher729

Même en ignorant le fait qu'aucune des deux implémentations n'est si bonne que cela, je note que c'est essentiellement une question de goût au moins au niveau de l'abstraction des fonctions triviales à usage unique.

Le nombre de lignes n'est pas une mesure utile dans la plupart des cas.

300 (ou même 3000) lignes de code purement séquentiel tout à fait trivial (programme d'installation, ou quelque chose comme ça) sont rarement un problème (mais pourraient être mieux générées automatiquement ou en tant que table de données ou quelque chose), 100 lignes de boucles imbriquées avec beaucoup de complications les conditions de sortie et les calculs mathématiques que vous pourriez trouver dans l'élimination gaussienne ou l'inversion de matrice ou autres peuvent être beaucoup trop difficiles à suivre facilement.

Pour moi, je n'écrirais pas de fonction à usage unique à moins que la quantité de code requise pour déclarer la chose soit beaucoup plus petite que la quantité de code formant l'implémentation (sauf si j'avais des raisons comme dire de vouloir pouvoir faire facilement une injection de faute). Un seul conditionnel correspond rarement à ce projet de loi.

Maintenant, je viens d'un petit monde intégré de base, où nous devons également prendre en compte des choses comme la profondeur de pile et les frais généraux d'appel/retour (ce qui milite encore contre les sortes de petites fonctions qui semblent être préconisées ici), et cela pourrait biaiser ma conception décisions, mais si je voyais cette fonction originale dans une revue de code, il obtiendrait une flamme usenet à l'ancienne en réponse.

Le goût est le design est difficile à enseigner et ne vient vraiment qu'avec l'expérience, je ne suis pas sûr qu'il puisse être réduit à des règles sur les longueurs de fonction, et même la complexité cyclomatique a ses limites en tant que métrique (Parfois, les choses sont juste compliquées mais vous les abordez).
Cela ne veut pas dire que le code propre ne discute pas de bonnes choses, il le fait, et ces choses doivent être considérées, mais la coutume locale et ce que fait la base de code existante doivent également être prises en compte.

Cet exemple particulier me semble cueillir des détails triviaux, je serais plus préoccupé par des choses de niveau beaucoup plus élevé car cela importe beaucoup plus à la capacité de comprendre et de déboguer facilement un système.

16
Dan Mills

Ne mettez pas tout dans une grande boucle, mais ne le faites pas trop souvent non plus:

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

Le problème avec la grande boucle est qu'il est vraiment difficile de voir sa structure globale lorsqu'elle couvre de nombreux écrans. Essayez donc de retirer de gros morceaux, idéalement des morceaux qui ont une seule responsabilité et qui sont réutilisables.

Le problème avec la petite fonction ci-dessus, c'est que si l'atomicité et la modularité sont généralement bonnes, cela peut être poussé trop loin. Si vous n'utilisez pas la fonction ci-dessus, cela nuit à la lisibilité et à la maintenabilité du code. Pour explorer les détails, vous devez passer à la fonction au lieu de pouvoir lire les détails en ligne, et l'appel de fonction prend à peine moins d'espace que le détail lui-même.

Il y a clairement un équilibre à trouver entre les méthodes qui font trop et les méthodes qui font trop pe. Je ne décomposerais jamais une fonction minuscule comme ci-dessus à moins qu'elle ne soit appelée de plus d'un endroit, et même alors j'y réfléchirais à deux fois, car la fonction juste n'est pas si importante in termes de l'introduction d'une nouvelle logique et en tant que telle justifie à peine d'avoir sa propre existence.

15
Brad Thomas

Il semble que ce que vous voulez réellement soit le suivant:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER

Cela devrait être explicite pour quiconque le lit: définissez phoneNumber sur resourceId s'il est disponible, ou par défaut sur DEV_PHONE_NUMBER si ce n'est pas le cas.

Si vous vraiment voulez définir cette variable uniquement en production, vous devriez avoir une autre méthode utilitaire plus canonique à l'échelle de l'application (qui ne nécessite pas de paramètres) pour déterminer d'où vous exécutez. La lecture des en-têtes de ces informations n'a pas de sens.

Permettez-moi d'être franc: il me semble que votre environnement (langage/framework/conception de classe, etc.) n'est pas vraiment adapté au code "propre". Vous mélangez toutes sortes de choses possibles en quelques lignes de code qui ne devraient pas vraiment être rapprochées. Quelle activité a une seule fonction en sachant que resourceId==undef signifie que vous n'êtes pas en production, que vous utilisez un numéro de téléphone par défaut dans les systèmes hors production, que le resourceId est enregistré dans certains "en-têtes" et ainsi de suite? Je suppose que headers sont des en-têtes HTTP, donc vous laissez même la décision concernant l'environnement dans lequel vous vous trouvez à l'utilisateur final?

La prise en compte de certains éléments dans les fonctions ne vous aidera pas beaucoup avec ce problème sous-jacent.

Quelques mots clés à rechercher:

  • découplage
  • cohésion
  • injection de dépendance

Vous pouvez réaliser ce que vous voulez (dans d'autres contextes) avec zéro ligne de code, en déplaçant les responsabilités du code et en utilisant des cadres modernes (qui peuvent ou non exister pour votre environnement/langage de programmation).

D'après votre description ("300 lignes de code dans une fonction" principale ""), même le mot "fonction" (au lieu de méthode) me fait supposer qu'il n'y a aucun intérêt dans ce que vous essayez de réaliser. Dans cet environnement de programmation à l'ancienne (c'est-à-dire une programmation impérative de base avec peu de structure, certainement pas de classes significatives, pas de modèle de cadre de classe comme MVC ou quelque chose comme ça), il n'y a vraiment pas grand chose à faire quoi que ce soit. Vous ne sortirez jamais du carter sans changements fondamentaux. Au moins, votre patron semble vous permettre de créer des fonctions pour éviter la duplication de code, c'est une bonne première étape!

Je connais aussi bien le type de code que le type de programmeur que vous décrivez. Franchement, s'il s'agissait d'un collègue, mon conseil serait différent. Mais comme c'est votre patron, il est inutile que vous alliez vous battre à ce sujet. Non seulement votre patron peut vous passer outre, mais vos ajouts de code conduiront en effet à un code pire si seulement vous faites "votre truc" en partie, et votre patron (et probablement d'autres personnes) continue comme avant. Il peut être préférable pour le résultat final si vous vous adaptez à leur style de programmation (uniquement en travaillant sur cette base de code particulière, bien sûr), et essayez, dans ce contexte, d'en tirer le meilleur parti.

14
AnoE

"Nettoyer" est l'un des objectifs de l'écriture de code. Ce n'est pas le seul objectif. Un autre objectif valable est colocalité. En termes informels, la colocalité signifie que les personnes qui essaient de comprendre votre code n'ont pas besoin de sauter partout pour voir ce que vous faites. L'utilisation d'une fonction bien nommée au lieu d'une expression ternaire peut sembler une bonne chose, mais selon le nombre de ces fonctions et leur emplacement, cette pratique peut se transformer en une nuisance. Je ne peux pas vous dire si vous avez franchi cette ligne, sauf pour dire que si les gens se plaignent, vous devez écouter, surtout si ces gens ont leur mot à dire sur votre situation professionnelle.

13
user1172763

L'utilisation de petites fonctions est, en général, une bonne pratique. Mais idéalement, je pense que l'introduction d'une fonction devrait soit séparer de gros morceaux logiques, soit réduire la taille globale du code en le SECHANT. L'exemple que vous avez donné à la fois allonge le code et nécessite plus de temps pour qu'un développeur le lise, tandis que l'alternative courte n'explique pas que la valeur "resourceId" N'est présente qu'en production. Quelque chose de simple comme ça est à la fois facile à oublier et déroutant lorsque vous essayez de l'utiliser, en particulier si vous êtes encore nouveau dans la base de code.

Je ne dirai pas que vous devez absolument utiliser un ternaire, certaines personnes avec qui j'ai travaillé préfèrent la if () {...} else {...} légèrement plus longue, c'est surtout un choix personnel. J'ai tendance à préférer une approche "une ligne fait une chose", mais je m'en tiendrai à tout ce que la base de code utilise normalement.

Lorsque vous utilisez ternaire si la vérification logique rend la ligne trop longue ou trop compliquée, envisagez de créer une ou plusieurs variables bien nommées pour contenir la valeur.

// NOTE "resourceId" not present in dev build, use test data
let isProduction = 'resourceId' in headers;
let phoneNumber = isProduction ? headers.resourceId : DEV_PHONE_NUMBER;

Je voudrais également dire que si la base de code s'étend sur 300 fonctions de ligne, elle a besoin d'une subdivision. Mais je conseillerais l'utilisation de traits légèrement plus larges.

6
nepeo

L'exemple de code que vous avez donné, votre patron IS CORRECT. Une seule ligne claire est préférable dans ce cas.

En général, briser une logique complexe en morceaux plus petits est meilleur pour la lisibilité, la maintenance du code et la possibilité que les sous-classes aient un comportement différent (même si ce n'est que légèrement).

N'ignorez pas les inconvénients: surcharge de fonction, obscurcissement (la fonction ne fait pas ce que les commentaires et le nom de la fonction impliquent), logique de spaghetti complexe, le potentiel de fonctions mortes (à un moment donné ont été créées dans un but qui n'est plus appelé).

5
Phil M

Je peux penser à au moins deux arguments en faveur des fonctions longues:

  • Cela signifie que vous avez beaucoup de contexte autour de chaque ligne. Une façon de formaliser cela: dessinez le graphe de flux de contrôle de votre code. À un sommet (~ = ligne) entre l'entrée et la sortie de la fonction, vous connaissez tous les bords entrants. Plus la fonction est longue, plus il y a de tels sommets.

  • De nombreuses petites fonctions signifient qu'il existe un graphe d'appel plus grand et plus complexe. Choisissez une ligne aléatoire dans une fonction aléatoire et répondez à la question "dans quel (s) contexte (s) cette ligne est-elle exécutée?" Cela devient plus difficile à mesure que le graphe d'appel est grand et complexe, car vous devez regarder plus de sommets dans ce graphe.

Il existe également des arguments contre les fonctions longues - la testabilité unitaire vient à l'esprit. Utilisez t̶h̶e̶ ̶f̶o̶r̶c̶e̶ votre expérience lorsque vous choisissez entre l'un et l'autre.

Remarque: je ne dis pas que votre patron a raison, mais seulement que son point de vue n'est peut-être pas totalement dénué de valeur.


Je pense que mon avis est que le bon paramètre d'optimisation n'est pas la longueur de la fonction. Je pense qu'une desiderata plus utile à penser en termes de est la suivante: toutes choses étant égales par ailleurs, il est préférable de pouvoir lire dans le code une description de haut niveau de la logique métier et de la mise en œuvre. (Les détails d'implémentation de bas niveau peuvent toujours être lus si vous pouvez trouver le bit de code approprié.)


Commentant réponse de David Arno :

L'écriture de petites fonctions est pénible car elle vous oblige à vous déplacer dans chaque petite fonction pour voir ce que fait le code.

Si la fonction est bien nommée, ce n'est pas le cas. isApplicationInProduction va de soi et il ne devrait pas être nécessaire d'examiner le code pour voir ce qu'il fait. En fait, l'inverse est vrai: l'examen du code révèle moins l'intention que le nom de la fonction (c'est pourquoi votre patron doit recourir aux commentaires).

Le nom indique clairement ce que signifie la valeur de retour , mais il ne dit rien sur les effets d'exécuter le code (= ce que fait le code ). Les noms (uniquement) transmettent des informations sur l'intention , le code transmet des informations sur le comportement (dont certaines parties de l'intention peuvent parfois être déduites).

Parfois, vous en voulez un, parfois l'autre, donc cette observation ne crée pas une règle de décision unilatérale universellement valide.

Mettez tout dans une grande boucle principale même si la boucle principale est de plus de 300 lignes, il est plus rapide à lire

Il peut être plus rapide à parcourir, mais pour vraiment "lire" le code, vous devez être capable de l'exécuter efficacement dans votre tête. C'est facile avec de petites fonctions et c'est vraiment, vraiment difficile avec des méthodes qui sont longues de 100 lignes.

Je suis d'accord que vous devez l'exécuter dans votre tête. Si vous avez 500 lignes de fonctionnalités dans une grande fonction par rapport à de nombreuses petites fonctions, je ne comprends pas pourquoi cela devient plus facile.

Supposons le cas extrême de 500 lignes de code linéaire à fort effet secondaire, et vous voulez savoir si l'effet A se produit avant ou après l'effet B. Dans le cas de la grande fonction, utilisez Page Haut/Bas pour localiser deux lignes, puis comparez numéros de ligne. Dans le cas de nombreuses petites fonctions, vous devez vous rappeler où se produisent les effets dans l'arborescence des appels, et si vous oubliez, vous devez passer un temps non trivial à redécouvrir la structure de cet arbre.

Lorsque vous parcourez l'arborescence d'appels des fonctions de prise en charge, vous êtes également confronté au défi de déterminer quand vous êtes passé de la logique métier aux détails d'implémentation. Je prétends sans preuve * que plus le graphe d'appel est simple, plus il est facile de faire cette distinction.

(*) Au moins, je suis honnête à ce sujet ;-)

Encore une fois, je pense que les deux approches ont leurs forces et leurs faiblesses.

N'écrivez que de petites fonctions si vous devez dupliquer du code

Je ne suis pas d'accord. Comme le montre votre exemple de code, de petites fonctions bien nommées améliorent la lisibilité du code et doivent être utilisées chaque fois que [par exemple] vous n'êtes pas intéressé par le "comment", uniquement le "quoi" d'un élément de fonctionnalité.

Que vous soyez intéressé par le "comment" ou le "quoi" est fonction de l'objectif pour lequel vous lisez le code (par exemple, avoir une idée générale par rapport à la recherche d'un bogue). Le but pour lequel vous lisez le code n'est pas disponible lors de l'écriture du programme, et vous lirez très probablement le code à des fins différentes; différentes décisions seront optimisées pour différents objectifs.

Cela dit, c'est la partie de l'avis du patron avec laquelle je suis probablement le plus en désaccord.

N'écrivez pas de fonction avec le nom du commentaire, mettez votre ligne de code complexe (3-4 lignes) avec un commentaire ci-dessus. Comme cela, vous pouvez modifier directement le code défaillant

Je ne peux vraiment pas comprendre le raisonnement derrière celui-ci, en supposant qu'il soit vraiment sérieux. [...] Les commentaires ont un défaut fondamental: ils ne sont pas compilés/interprétés et ne peuvent donc pas être testés à l'unité. Le code est modifié et le commentaire est laissé seul et vous finissez par ne pas savoir ce qui est juste.

Les compilateurs ne comparent que les noms pour l'égalité, ils ne vous donnent jamais d'erreur MisleadingNameError. De plus, comme plusieurs sites d'appels peuvent appeler une fonction donnée par son nom, il est parfois plus difficile et plus sujet aux erreurs de changer un nom. Les commentaires n'ont pas ce problème. Cependant, ceci est quelque peu spéculatif; pour vraiment régler cela, il faudrait probablement des données pour savoir si les programmeurs sont plus susceptibles de mettre à jour les commentaires trompeurs par rapport aux noms trompeurs, et je n'ai pas cela.

2
Jonas Kölker