Lisibilité contre maintenabilité, cas particulier d'écriture d'appels de fonction imbriqués

Mon style de codage pour les appels de fonction imbriqués est le suivant:

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);

J'ai récemment changé pour un département où le style de codage suivant est très utilisé:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

Le résultat de ma méthode de codage est que, en cas de blocage d'une fonction, Visual Studio peut ouvrir le vidage correspondant et indiquer la ligne où le problème se produit (les violations d'accès me préoccupent tout particulièrement).

Je crains qu'en cas d'accident dû au même problème programmé de la première manière, je ne puisse pas savoir quelle fonction a provoqué l'accident.

D'autre part, plus vous mettez de traitement sur une ligne, plus vous obtenez de logique sur une page, ce qui améliore la lisibilité.

Est-ce que ma peur est correcte ou est-ce que je manque quelque chose, et en général, ce qui est préféré dans un environnement commercial? Lisibilité ou maintenabilité?

Je ne sais pas si c'est pertinent, mais nous travaillons en C ++ (STL)/C #.

57
décomposez-le en au moins deux petites fonctions, donnez-leur des noms propres, puis appelez cette fonction? Cette instruction peut être refactorisée en une fonction avec trois entrées et une sortie, et peut recevoir un nom raisonnable.
ajouté l'auteur Bigballs, source
@ t3chb0t Vous êtes libre de voter comme vous le souhaitez, mais veillez à encourager les bonnes questions utiles sur ce sujet sur ce site (et à décourager les mauvaises), à l'effet que le vote à la hausse ou à la baisse des questions est: pour indiquer si une question est utile et claire, le fait de voter pour d'autres raisons, telles que l'utilisation d'un vote comme moyen de formuler des critiques sur des exemples de codes postés pour aider le contexte de la question, n'est généralement pas utile pour maintenir la qualité du site. : softwareengineering.stackexchange.com/help/privileges/vote-d‌ own
ajouté l'auteur Ben Cottrell, source
ajouté l'auteur gnat, source
Pour ajouter à ce que @dfri a dit, étant donné que l'ordre d'évaluation n'est pas défini, cela peut également avoir des implications pour la sécurité des exceptions dont vous devez être conscient avant d'utiliser ce formulaire.
ajouté l'auteur Sean Burton, source
Notez que si cet exemple devait être appliqué à C ++ (car vous mentionnez que cela est utilisé dans votre projet), il ne s'agit pas uniquement d'une question de style, car ordre d'évaluation du HX Les invocations et GX peuvent changer dans la ligne unique, l'ordre d'évaluation des arguments de la fonction n'étant pas spécifié. Si, pour une raison quelconque, vous dépendez de l'ordre des effets secondaires (consciemment ou inconsciemment) dans les invocations, cette "refactorisation de style" risque de produire davantage qu'une simple lisibilité/maintenance.
ajouté l'auteur eric, source
Est-ce que le nom de variable result_g1 est ce que vous utiliseriez réellement ou est-ce que cette valeur représente réellement quelque chose avec un nom raisonnable; par exemple. percentIncreasePerSecond . Ce serait en fait mon test pour décider entre les deux
ajouté l'auteur Richard Tingle, source
ajouté l'auteur Robert Grant, source
La trace de la pile indique toujours où elle se bloque, même si cela peut devenir confus si, par exemple, au lieu d’avoir des appels à 3 fonctions différentes sur une ligne, vous avez 3 appels à la même fonction. J'ai tendance à utiliser ton style aussi, mais c'est un peu plus bruyant.
ajouté l'auteur Arun K S, source
Indépendamment de vos sentiments sur le style de codage, vous devriez suivre la convention qui est déjà en place à moins que ce ne soit clairement faux (il ne semble pas que ce soit le cas dans ce cas).
ajouté l'auteur PoulGrym, source
Au cas où vous vous le demanderiez, l'un des votes négatifs concerne les noms sans signification de votre code.
ajouté l'auteur t3chb0t, source
C # n'a-t-il pas de méthode pour la composition de fonction/la tuyauterie? Et les accidents ne fournissent-ils pas une trace de pile pour déterminer où se trouve la ligne problématique? J'avoue qu'avoir mis en évidence la ligne incriminée dans votre éditeur est une bonne chose, mais la trace devrait vous indiquer où se trouve l'échec ...
ajouté l'auteur Sujan, source
@gnat: vous faites référence à une question générale, alors que je m'intéresse particulièrement au cas mentionné d'appels de fonction imbriqués et aux conséquences en cas d'analyse de vidage sur incident, mais merci pour le lien, il contient quelques informations intéressantes.
ajouté l'auteur Dominique, source

9 Réponses

Si vous vous sentiez obligé d’élargir un revêtement comme

 a = F(G1(H1(b1), H2(b2)), G2(c1));

Je ne t'en voudrais pas. Ce n'est pas seulement difficile à lire, c'est difficile à déboguer.

Pourquoi?

  1. C'est dense
  2. Certains débogueurs ne mettent en évidence que le tout à la fois
  3. il est libre de noms descriptifs

Si vous développez avec des résultats intermédiaires, vous obtenez

 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);

and it's still hard to read. Pourquoi? It solves two of the problems and introduces a fourth:

  1. C'est dense
  2. Certains débogueurs ne mettent en évidence que le tout en une fois
  3. il est libre de noms descriptifs
  4. Il est encombré de noms non descriptifs

Si vous le développez avec des noms qui ajoutent une nouvelle, bonne signification sémantique, encore mieux! Un bon nom m'aide à comprendre.

 var temperature = H1(b1);
 var humidity = H2(b2);
 var precipitation = G1(temperature, humidity);
 var dewPoint = G2(c1);
 var forecast = F(precipitation, dewPoint);

Maintenant au moins cela raconte une histoire. Cela corrige les problèmes et est clairement meilleur que tout ce qui est proposé ici, mais il vous oblige à donner les noms.

Si vous le faites avec des noms dénués de sens, tels que result_this et result_that , parce que vous ne pouvez tout simplement pas penser à de bons noms, je préférerais vraiment que vous épargniez le fouillis de noms dénué de sens et que vous développiez en utilisant de bons vieux espaces blancs:

int a = 
    F(
        G1(
            H1(b1), 
            H2(b2)
        ), 
        G2(c1)
    )
;

Il est tout aussi lisible, sinon plus, que celui qui porte des noms de résultats sans signification (non que ces noms de fonctions soient si géniaux).

  1. C'est dense
  2. Certains débogueurs ne mettent en évidence que le tout en une fois
  3. il est libre de noms descriptifs
  4. il est encombré de noms non descriptifs

Quand vous ne pouvez pas penser à de bons noms, c'est ce qu'il y a de mieux.

Pour une raison quelconque, les débogueurs adorent les nouvelles lignes , vous devriez donc constater que le débogage n'est pas difficile:

enter image description here

Si cela ne vous suffit pas, imaginez que G2() a été appelé à plusieurs endroits, puis cela est arrivé:

Exception in thread "main" java.lang.NullPointerException
    at composition.Example.G2(Example.java:34)
    at composition.Example.main(Example.java:18)

Je pense que c'est bien que, puisque chaque appel G2() serait sur sa propre ligne, ce style vous mènera directement à l'appel incriminé dans main.

Donc, s'il vous plaît, n'utilisez pas les problèmes 1 et 2 comme excuse pour nous en tenir au problème 4. Utilisez les bons noms quand vous pouvez y penser. Évitez les noms sans signification quand vous ne pouvez pas.

Courses de légèreté dans commentaire/a> fait remarquer à juste titre que ces fonctions sont artificielles et qu’elles ont des noms pauvres morts. Voici donc un exemple d'application de ce style à un code de la nature:

var user = db.t_ST_User.Where(_user => string.Compare(domain,  
_user.domainName.Trim(), StringComparison.OrdinalIgnoreCase) == 0)
.Where(_user => string.Compare(samAccountName, _user.samAccountName.Trim(), 
StringComparison.OrdinalIgnoreCase) == 0).Where(_user => _user.deleted == false)
.FirstOrDefault();

Je déteste regarder ce flot de bruit, même lorsque l’ajout de mots n’est pas nécessaire. Voici à quoi ça ressemble dans ce style:

var user = db
    .t_ST_User
    .Where(
        _user => string.Compare(
            domain, 
            _user.domainName.Trim(), 
            StringComparison.OrdinalIgnoreCase
        ) == 0
    )
    .Where(
        _user => string.Compare(
            samAccountName, 
            _user.samAccountName.Trim(), 
            StringComparison.OrdinalIgnoreCase
        ) == 0
    )
    .Where(_user => _user.deleted == false)
    .FirstOrDefault()
;

Comme vous pouvez le constater, ce style fonctionne bien avec le code fonctionnel qui se déplace dans l'espace orienté objet. Si vous pouvez proposer de bons noms pour le faire dans un style intermédiaire, vous aurez plus de pouvoir. Jusque-là, je l'utilise. Mais dans tous les cas, s'il vous plaît, trouvez un moyen d'éviter les noms de résultats dénués de sens. Ils me font mal aux yeux.

111
ajouté
@ jpmc26 Mon problème est que l'ajout de trop d'espaces dans une ligne de code diminue l'importance des espaces entre les lignes de code. Parmi les autres problèmes, citons: avoir l'air laid et 8 fois moins de code s'ajustant à l'écran. Ces inconvénients notables l'emportent largement sur les supposés positifs.
ajouté l'auteur redsquare, source
La version développée a l'air moche. Il y a trop où y at-il des choses qui ne sont pas si fécondes que nous ne les avons pas?
ajouté l'auteur redsquare, source
J'ajoute à votre message: J'ai une petite règle générale: Si vous ne pouvez pas le nommer, cela peut indiquer que ce n'est pas bien défini. Je l'utilise sur des entités, des propriétés , variables, modules, menus, classes d’aide, méthodes, etc. Cette règle minuscule a souvent révélé une grave erreur de conception. D'une certaine manière, une bonne dénomination contribue non seulement à la lisibilité et à la facilité de maintenance, mais elle vous aide également à vérifier la conception. Bien sûr, il y a des exceptions à chaque règle simple.
ajouté l'auteur Coppermill, source
D'accord. La seule fois où j'utiliserais G1 serait si j'émulais une équation mathématique standard qui utilisait ces termes. Je pourrais utiliser des acronymes pour les variables intermédiaires, en fonction des noms des fonctions qu’elles ont appelées, si cela était clair dans le contexte.
ajouté l'auteur mjturner, source
Le blanc est une technique géniale (précieuse en SQL par exemple), mais il a tendance à utiliser le plus grand nombre de lignes de code, et peut même devenir très subtile à lire et très délicate à construire le formatage (à moins que l'EDI ne fonctionne avec vous ). Dans un langage impératif, j'utiliserais encore une variable intermédiaire pour évaluer G1 .
ajouté l'auteur mjturner, source
"Un débogueur ne va tout mettre en évidence à la fois." - Cela dépend beaucoup du débogueur. C’est un bon moment depuis que j’utilise VS (ce qui est vraisemblablement ce que OP utilise), mais d’aussi loin que je me souvienne, le débogueur fonctionne très bien avec les sous-expressions.
ajouté l'auteur Mark Ransom, source
@CandiedOrange Ce que je peux trouver de mieux dans un pincement (notez la sous-expression en surbrillance). De plus, les sous-expressions peuvent être inspectées/calculées et les points de rupture doivent être définis.
ajouté l'auteur Mark Ransom, source
@MateenUlhaq Ce que vous mentionnez explique en partie pourquoi cet article préfère les bons noms pour les valeurs intermédiaires. Mais en leur absence, les séquences d'appels denses sont beaucoup plus lisibles lorsque les espaces blancs les rendent plus apparents. Il vaut beaucoup mieux faire face à un certain défilement vertical que d’essayer d’analyser mentalement cette complexité. Garder votre position tout en sautillant entre ce code et les définitions de fonctions réelles est également plus difficile avec la version dense. Et des noms plus longs (comme Lightness mentionne) feront probablement que la ligne se répande sur la largeur de l'écran.
ajouté l'auteur jmibanez, source
@MateenUlhaq Le seul espace supplémentaire, à savoir quelques lignes et une indentation, est placé soigneusement à des limites significatives . Votre commentaire place plutôt les espaces blancs à des limites non significatives. Je vous suggère de regarder de plus près et plus ouvert.
ajouté l'auteur jmibanez, source
Je viens encore de traverser cela, et je voudrais souligner qu’il existe une sorte de troisième option pour votre dernier exemple: créer une méthode d’utilité. Par exemple. public static bool TrimmedIgnoreCaseEquals (cette chaîne s1, chaîne s2) {return 0 == String.Compare (s1.Trim (), s2, StringComparison.OrdinalIgnoreCase); } . Cela réduit chaque ligne à Où (_user => _user.domainName.TrimmedIgnoreCaseEquals (domaine)) et similaire, faisant abstraction de la logique. Je me trouve très souvent à l'aide de "fonctions utilitaires" , vous n'avez donc pas nécessairement besoin de variables pour nommer des étapes intermédiaires.
ajouté l'auteur jmibanez, source
@KonradRudolph Dans votre deuxième image, elle ne montre que la valeur de i , qui est une variable spécifique. Je ne pense pas que cela montrera la valeur de retour d'une fonction qui n'est assignée nulle part. Vous pouvez l'invoquer via Immediate Window, mais si c'est une fonction coûteuse, vous ne voudrez peut-être pas le faire. Pour certaines fonctions, Immediate Window est parfois un peu à craquer et échoue.
ajouté l'auteur jmibanez, source
@CandiedOrange Dans un monde idéal, bien sûr. En pratique, c'est parfois ce que vous pouvez faire de mieux, surtout avec un temps limité. Même si le nom de la fonction n’est pas idéal, le déplacement de la logique vers une solution de lecture est suffisamment impressionnant pour qu’il soit encore meilleur. Je n'aime pas vraiment le nom non plus, mais je n'ai pas pensé à un meilleur. Faites-moi savoir si vous le faites.
ajouté l'auteur jmibanez, source
@CandiedOrange Eh bien, votre préférence, mais je dirais que le nom est significatif , même s'il est prolixe. Cela arrive souvent avec les méthodes "utilitaires". Ils n’ont aucune signification particulière dans le domaine du problème que vous essayez de résoudre, mais ils ont une signification en termes de logique de programmation que vous devez implémenter. Le compactage d'éléments de la logique de programmation en appels à une seule méthode rend les abstractions mentales plus évidentes et permet à la logique de domaine de se distinguer davantage, de la même manière que les variables intermédiaires. C'est une grande victoire pour la lisibilité.
ajouté l'auteur jmibanez, source
Contrairement à @MateenUlhaq, je suis assez confiant sur les espaces dans cet exemple particulier avec de tels noms de fonction, mais avec des noms de fonction réels (qui ont plus de deux caractères de long, non?) J'irais pour.
ajouté l'auteur Lightness Races in Orbit, source
dense n'est pas une mauvaise chose si les variables et les noms de fonction impliqués sont descriptifs. Ce ne sont pas
ajouté l'auteur Thorbjørn Ravn Andersen, source
D'accord, mais (1) prévision (précipitations (température (b1), humidité (b2)), point de rosée (c1)) raconte la même chose si vous appelez votre fonction de manière appropriée. (2) Les débogueurs affichent aussi la trace de la pile, vous pouvez facilement identifier la portée.
ajouté l'auteur dalesmithtx, source
@ IdanArye Je veux dire, si je le vois, je me concentre dessus. Me montrer un mauvais nom ne me fait pas ignorer le nom. C'est à peu près la thèse centrale de cette réponse.
ajouté l'auteur candied_orange, source
@ jpmc26 true. mais "TrimmedIgnoreCaseEquals ()"? Je suis tout à fait en faveur de cacher des détails avec de bons noms qui expriment une intention et qui garantissent que ce que vous trouvez derrière eux n'est pas une surprise, mais ils devraient être plus qu'un mélange de noms ou d'une structure, c'est tout ce qui est communiqué. Les noms sémantiquement significatifs sont des abstractions beaucoup plus puissantes qui font passer leur idée sans m'obliger à m'en tenir à une implémentation particulière.
ajouté l'auteur candied_orange, source
@IdanArye Si vous voulez savoir ce que je pense des motos, ne me demandez pas ce que je pense du transport personnel. Je travaille avec ce que tu me donnes.
ajouté l'auteur candied_orange, source
@ jpmc26 le thème de cette réponse est que si je ne peux pas avoir un nom significatif, je préfère avoir un espace blanc bien structuré qu'un bruit dénué de sens.
ajouté l'auteur candied_orange, source
@ jpmc26 plutôt qu'une fonction d'utilité publique qui n'a pas de contexte et doit donc être comme suit une fonction privée localisée dans ce contexte nommé isSame (String expect, String userData) .
ajouté l'auteur candied_orange, source
@ KonradRudolph et théoriquement, vous avez raison bien sûr. A quoi je dis: "Des photos ou ça ne s'est pas passé!"
ajouté l'auteur candied_orange, source
@ Akavall tout à fait vrai mais toujours pas la peine de vivre avec une mauvaise réputation. Pensez-en un bon ou supprimez votre mauvais avant de vous enregistrer. Sans un bon nom, vous ne faites que vérifier le code de débogage.
ajouté l'auteur candied_orange, source
@ Alireza c'est perspicace. J'ai trouvé d'autres raisons d'inclure un échec du vocabulaire personnel, des collisions de noms avec la même idée exprimée d'une manière différente et un désir de sommeil. Les noms sont difficiles. Ils sont loin d’être faciles à négliger, mais ils rapportent gros quand ils sont bien faits.
ajouté l'auteur candied_orange, source
@ KonradRudolph nice! Hmm, je pense que nous supprimons 2000things.com avec tout le trafic. A défaut de charger.
ajouté l'auteur candied_orange, source
@Steve et je ne vous dis pas de ne pas le faire. Je prie pour un nom significatif. J'ai trop souvent vu le style intermédiaire se faire sans réfléchir. Les mauvais noms me brûlent le cerveau beaucoup plus que le code rare par ligne. Je ne laisse pas des considérations de largeur ou de longueur me motiver à rendre mon code dense ou mes noms courts. Je les laisse me motiver à me décomposer davantage. Si les bons noms ne se produisent pas, envisagez ce travail pour éviter le bruit dénué de sens.
ajouté l'auteur candied_orange, source
Les gens sont tellement concentrés sur les noms et oublient que peut-être, peut-être, les demandeurs utilisent des noms génériques (a, b, c, d/x, y, z, w/foo, bar, baz, qux) pour leurs fonctions et leurs variables car ils créent de petits extraits de code pour démontrer les propriétés de code très spécifiques qu'ils demandent?
ajouté l'auteur InsertUser, source
@CandiedOrange Si je vous demande votre avis sur une moto, je ne vous épaterai pas avec le récit détaillé de ma journée au parc des expositions où je l'ai vue, ce que j'avais pour le déjeuner et ce que je portais ce jour-là. Allez-vous supposer que, juste parce que j'ai omis ces détails, je ne déteste rien et ne porte rien, et je me concentre sur cela parce que ma faim et ma nudité en public sont des problèmes plus urgents que les spécificités de certaines motos?
ajouté l'auteur InsertUser, source
@CandiedOrange Mais ce ne sont même pas des noms - ce ne sont que des espaces réservés pour les noms. Pour avoir de bons noms, vous avez besoin d'un contexte, et nous ne connaissons pas le contexte de ce code. Nous ne connaissons pas le domaine du projet. Nous ne savons même pas si c'est frontend, backend, incorporé, script ou autre. Le demandeur n'a pas précisé cela parce que cela n'était pas pertinent - il souhaitait se concentrer sur les valeurs intermédiaires par rapport aux expressions imbriquées et a construit un petit exemple.
ajouté l'auteur InsertUser, source
Je suis tout pour les noms significatifs. Mais je vais supposer que nos fonctions "H" et "G" sont simplement ce que le demandeur a décidé d'utiliser pour les méthodes génériques afin d'expliquer sa question, par opposition à "foo" et "bar".
ajouté l'auteur Ellesedil, source
Je ne suis pas un grand fan de la version étendue. Si vous avez défini une variable, il est très facile d’enregistrer ou de placer une trace de débogage autour de celle-ci. Quand tout se passe dans un appel de fonction; il est plus difficile de se connecter/déboguer.
ajouté l'auteur Akavall, source

D'un autre côté, plus le traitement que vous mettez sur une ligne est important, plus vous obtenez de logique sur une page, ce qui améliore la lisibilité.

Je suis totalement en désaccord avec cela. En regardant simplement vos deux exemples de code, cela est incorrect:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

est entendu à lire. "Lisibilité" ne signifie pas densité d'informations; cela signifie "facile à lire, à comprendre et à maintenir".

Parfois, le code est simple et il est logique d’utiliser une seule ligne. D'autres fois, cela rend simplement la lecture plus difficile, sans autre avantage évident que de regrouper davantage de données sur une seule ligne.

Cependant, je vous demanderais également de dire que "facile à diagnostiquer les accidents" signifie que le code est facile à gérer. Le code qui ne plante pas est beaucoup plus facile à maintenir. "Facile à entretenir" est obtenu principalement via le code, facile à lire et à comprendre, accompagné d’un bon ensemble de tests automatisés.

Ainsi, si vous transformez une expression unique en une expression multiligne comportant de nombreuses variables simplement parce que votre code se bloque souvent et que vous avez besoin de meilleures informations de débogage, arrêtez de le faire et rendez le code plus robuste à la place. Vous devriez préférer écrire du code ne nécessitant pas de débogage plutôt que du code facile à déboguer.

50
ajouté
@JRE "Ils ne sont pas non plus autorisés à produire des déchets. Ils doivent gérer les mauvaises choses avec élégance et continuer à courir." est une contradiction en soi. Vous pouvez soit continuer à courir dans toutes les situations (lancer un gros bloc catch try au niveau supérieur et installer quelques en-têtes), soit vous assurer de ne pas causer d'incohérences. Il y a simplement des cas où vous devez choisir l'un ou l'autre.
ajouté l'auteur Kyle Hodgson, source
@leftaroundabout: Pour moi, le problème est qu'il n'est pas évident de savoir si G1 prend 3 paramètres ou seulement 2 et G2 est un autre paramètre pour F . Je dois plisser les yeux et compter les parenthèses.
ajouté l'auteur Owen, source
Bien que je sois d’accord pour dire que F (G1 (H1 (b1), H2 (b2)), G2 (c1)) est difficile à lire, cela n’a rien à voir avec un entassement trop dense. (Vous n'êtes pas sûr de vouloir dire cela, mais cela pourrait être interprété de cette façon.) L'imbrication de trois ou quatre fonctions sur une seule ligne peut être parfaitement lisible, en particulier si certaines fonctions sont de simples opérateurs infixes. Ce sont les noms non descriptifs qui sont le problème ici, mais ce problème est encore pire dans la version multiligne, où encore plus de noms non descriptifs sont introduits. L'ajout d'un passe-partout ne facilite presque jamais la lisibilité.
ajouté l'auteur zarose, source
@MatthieuM. Cela peut être un problème, bien que si les fonctions sont bien connues, il est souvent évident de savoir combien de arguments sont nécessaires. Comme je l’ai dit plus tôt, pour les fonctions infixes , il est clair qu’elles prennent deux arguments. (En outre, la syntaxe entre parenthèses-tuples utilisée par la plupart des langages aggrave ce problème; dans un langage qui préfère Currying, il est automatiquement plus clair: F (G1 (H1 b1) (H2 b2)) (G2 c1) .)
ajouté l'auteur zarose, source
Question: Diriez-vous toujours qu’il est difficile de lire si les arguments de F sont répartis sur plusieurs lignes? F ( G1 (H1 (b1), H2 (b2)), G2 (c1) ); (édition rapide: la réponse de CandiedOrange en est une version plus extrême )
ajouté l'auteur Izkata, source
Personnellement, je préfère la forme la plus compacte, dans la mesure où elle est stylée comme dans mon commentaire précédent, car elle garantit moins d'état pour le suivi mental de - result_h1 ne peut pas être réutilisé s'il n'existe pas, et la plomberie entre les 4 variables est évidente.
ajouté l'auteur Izkata, source
@marcus De plus, que se passe-t-il si un test échoue? Ne pas allez-vous le déboguer?
ajouté l'auteur R. Schmitz, source
Moi aussi, je pensais que cela allait être une bonne réponse, mais ensuite, il y a eu la voie "juste ne pas écrire les bugs", ce que je ne peux pas faire passer.
ajouté l'auteur bpktoo, source
J'ai trouvé que le code facile à déboguer est généralement du code qui n'a pas besoin de débogage.
ajouté l'auteur Rob K, source
@JRE Je sais que ces emplois de codage existent et je me considère vraiment chanceux que le mien n'en soit pas un. Un respect fou pour tous ceux qui prospèrent dans votre environnement. Mon commentaire portait sur cette réponse dans ce contexte, cependant. La question indique clairement que le crash est autorisé et, en tant que tel, je ne pense pas que cette réponse rende la situation juste.
ajouté l'auteur mx1010, source
Je voudrais vous inviter à voter, sauf que je suis nerveux au sujet de bs comme "Un code qui ne plante pas est beaucoup plus facile à maintenir". Je préfère conserver un code qui se bloque avec une cause évidente de code qui ne se bloque pas, mais fournit simplement un résultat final erroné.
ajouté l'auteur mx1010, source
@ DonFusili: Comptez-vous chanceux que vous avez la possibilité de planter. Mes tâches ne sont pas autorisées à se bloquer, et elles ne sont pas non plus autorisées à produire des déchets. Ils doivent faire face aux mauvaises choses avec élégance et continuer à courir. Ils sont autorisés à se plaindre dans les journaux pour que je puisse savoir plus tard quelle mauvaise chose s'est produite et comment mon code l'a traitée.
ajouté l'auteur JRE, source

Votre premier exemple, le formulaire d'affectation unique, est illisible car les noms choisis n'ont aucune signification. Cela pourrait être un artefact de vouloir ne pas divulguer d’informations internes de votre part, le vrai code pourrait bien se passer à cet égard, on ne peut pas le dire. Quoi qu’il en soit, il est long en raison de la densité d’informations extrêmement faible, ce qui ne se prête généralement pas à une compréhension aisée.

Votre deuxième exemple est condensé à un degré absurde. Si les fonctions avaient des noms utiles, cela pourrait être correct et bien lisible car il n'y en trop , mais cela est source de confusion dans l'autre sens.

Après avoir introduit des noms significatifs, vous pouvez chercher si l’une des formes semble naturelle ou si il existe un milieu d’or à viser.

Maintenant que vous avez du code lisible, la plupart des bugs seront évidents, et les autres auront au moins plus de mal à vous cacher.

25
ajouté

Comme toujours, en termes de lisibilité, l’échec est dans les extrêmes . Vous pouvez prendre n'importe quel bon conseil en matière de programmation, en faire une règle religieuse et l'utiliser pour produire du code totalement illisible. (Si vous ne me croyez pas à ce sujet, consultez ces deux gagnants du IOCCC borsanyi et goren et examinez comment ils utilisent les fonctions pour rendre le code totalement illisible. Conseil: Borsanyi utilise exactement une fonction, goren beaucoup, beaucoup plus ...)

Dans votre cas, les deux extrêmes sont 1) l’utilisation d’énoncés simples, et 2) la fusion de tous les éléments en énoncé volumineux, concis et complexes. Quelle que soit l'approche adoptée à l'extrême, votre code est illisible.

En tant que programmeur, votre tâche est de trouver un équilibre . Pour chaque déclaration que vous écrivez, votre tâche est de répondre à la question: "Cette déclaration est-elle facile à comprendre et permet-elle de rendre ma fonction lisible?"


Le fait est qu’il n’existe pas une complexité d’énoncé mesurable qui puisse déterminer ce qu’il est bon d’inclure dans un seul énoncé. Prenons par exemple la ligne:

double d = sqrt(square(x1 - x0) + square(y1 - y0));

C'est une déclaration assez complexe, mais tout programmeur digne de ce nom devrait être capable de comprendre immédiatement ce que cela fait. C'est un modèle assez bien connu. En tant que tel, il est beaucoup plus lisible que l’équivalent

double dx = x1 - x0;
double dy = y1 - y0;
double dxSquare = square(dx);
double dySquare = square(dy);
double dSquare = dxSquare + dySquare;
double d = sqrt(dSquare);

qui brise le modèle bien connu en un nombre apparemment insignifiant de simples étapes. Cependant, la déclaration de votre question

var a = F(G1(H1(b1), H2(b2)), G2(c1));

Cela me semble trop compliqué, même s'il s'agit d'une opération en moins que le calcul de la distance . Bien entendu, c’est une conséquence directe de mon ignorance de F() , G1() , G2() , H1() ou H2() . Je pourrais en décider autrement si j'en savais plus. Mais c’est précisément le problème: la complexité souhaitable d’une déclaration dépend fortement du contexte et des opérations. Et vous, en tant que programmeur, devez examiner ce contexte et décider quoi inclure dans une seule déclaration. Si vous vous souciez de la lisibilité, vous ne pouvez pas décharger cette responsabilité d'une règle statique.

17
ajouté

@ Dominique, je pense que dans l'analyse de votre question, vous faites l'erreur de dire que "lisibilité" et "maintenabilité" sont deux choses distinctes.

Est-il possible d'avoir du code maintenable mais illisible? À l'inverse, si le code est extrêmement lisible, pourquoi deviendrait-il impossible à maintenir parce qu'il est lisible? Je n'ai jamais entendu parler d'un programmeur ayant joué ces facteurs l'un contre l'autre, devant choisir l'un ou l'autre!

Pour ce qui est de décider d’utiliser ou non des variables intermédiaires pour les appels de fonction imbriqués, dans le cas de 3 variables données, d’appels vers 5 fonctions distinctes et de certains appels imbriqués 3 profonds, j’utiliserais au moins certains . variables intermédiaires pour décomposer cela, comme vous l'avez fait.

Mais je ne vais certainement pas jusqu'à dire que les appels de fonction ne doivent jamais être imbriqués. C'est une question de jugement dans les circonstances.

Je dirais que les points suivants portent sur le jugement:

  1. Si les fonctions appelées représentent des opérations mathématiques standard, elles sont plus susceptibles d'être imbriquées que des fonctions représentant une logique de domaine obscure dont les résultats sont imprévisibles et ne peuvent pas nécessairement être évalués mentalement par le lecteur.

  2. Une fonction avec un seul paramètre est plus capable de participer à un nid (en tant que fonction interne ou externe) qu’une fonction avec plusieurs paramètres. Le fait de mélanger des fonctions d'arités différentes à différents niveaux de nidification a tendance à laisser le code ressembler à l'oreille d'un cochon.

  3. Un nid de fonctions que les programmeurs sont habitués à voir exprimés d’une manière particulière - peut-être parce qu’elle représente une technique mathématique standard ou une équation, qui a une implémentation standard - peut être plus difficile lire et vérifier s'il est décomposé en variables intermédiaires.

  4. Un petit nid d'appels de fonction qui exécute une fonctionnalité simple et qui est déjà lisible, puis est décomposé et atomisé de manière excessive, est susceptible d'être plus difficile à lire que celui qui n'était pas décomposé du tout.

14
ajouté
+1 à "Est-il possible d’avoir un code maintenable mais illisible?". C'était aussi ma première pensée.
ajouté l'auteur user130256, source

Les deux sont sous-optimaux. Considérer les commentaires.

// Calculating torque according to Newton/Dominique, 4th ed. pg 235
var a = F(G1(H1(b1), H2(b2)), G2(c1));

Ou des fonctions spécifiques plutôt que générales:

var a = Torque_NewtonDominique(b1,b2,c1);

Lors du choix des résultats à épeler, gardez à l'esprit le coût (copie par rapport à la référence, valeur L par rapport à la valeur r), la lisibilité et le risque individuellement pour chaque instruction.

Par exemple, déplacer de simples conversions unité/type sur leurs propres lignes n'a pas de valeur ajoutée, car elles sont faciles à lire et ont très peu de chances d'échouer:

var radians = ExtractAngle(c1.Normalize())
var a = Torque(b1.ToNewton(),b2.ToMeters(),radians);

En ce qui concerne votre souci d’analyser les vidages sur incident, la validation des entrées est généralement beaucoup plus importante. L’incident réel risque fort de se produire dans ces fonctions plutôt que par la ligne qui les appelle, et même si ce n’est pas le cas, il n’est généralement pas nécessaire de vous dire où exactement. les choses ont explosé. Il est bien plus important de savoir où les choses ont commencé à se dégrader que de savoir où elles ont finalement explosé, ce qui correspond aux captures de validation des entrées.

4
ajouté
Re le coût de passer un argument: Il existe deux règles d'optimisation. 1) Ne pas. 2) (réservé aux experts) Ne pas pour le moment .
ajouté l'auteur RubberDuck, source

Peu importe si vous êtes en C# ou C ++, tant que vous êtes dans une version de débogage, une solution possible consiste à encapsuler les fonctions.

var a = F(G1(H1(b1), H2(b2)), G2(c1));

Vous pouvez écrire une expression en ligne et toujours être pointé là où le problème se pose simplement en regardant la trace de la pile.

returnType F( params)
{
    returnType RealF( params);
}

Bien sûr, si vous appelez la même fonction plusieurs fois dans la même ligne, vous ne pouvez pas savoir quelle fonction, mais vous pouvez toujours l'identifier:

  • Examen des paramètres de la fonction
  • Si les paramètres sont identiques et que la fonction n'a pas d'effets secondaires, deux appels identiques deviennent deux appels identiques, etc.

Ce n'est pas une solution miracle, mais un pas si mal à mi-chemin.

Sans oublier que le groupe de fonctions d'encapsulation peut même être plus bénéfique pour la lisibilité du code:

type CallingGBecauseFTheorem( T b1, C b2)
{
     return G1( H1( b1), H2( b2));
}

var a = F( CallingGBecauseFTheorem( b1,b2), G2( c1));
1
ajouté

La lisibilité est la majeure partie de la maintenabilité. Doute moi? Choisissez un projet volumineux dans un langage que vous ne connaissez pas (idéalement à la fois le langage de programmation et le langage des programmeurs), et voyez comment vous y prendre pour le remanier ...

Je mettrais la lisibilité entre quelque part entre 80 et 90% de maintenabilité. L’autre 10 à 20% indique à quel point il est possible de procéder à une refactorisation.

Cela dit, vous passez effectivement 2 variables à votre fonction finale (F). Ces 2 variables sont créées en utilisant 3 autres variables. Vous auriez mieux fait de passer b1, b2 et c1 en F, si F existe déjà, puis créez D qui fait la composition pour F et renvoie le résultat. À ce stade, il s’agit simplement de donner à D un bon nom et le style que vous utiliserez n’importera pas.

Sur le même sujet, vous dites que plus de logique sur la page facilite la lisibilité. C'est incorrect, la métrique n'est pas la page, c'est la méthode, et la logique MOINS qu'une méthode contient contient, plus elle est lisible.

Lisible signifie que le programmeur peut conserver la logique (entrée, sortie et algorithme) en tête. Plus il en fait, MOINS un programmeur peut le comprendre. Renseignez-vous sur la complexité cyclomatique.

1
ajouté
Je suis d'accord avec tout ce que vous dites sur la lisibilité. Mais je ne suis pas d’accord pour dire que scinder une opération logique en méthodes distinctes, nécessairement la rend plus lisible que de la scinder en lignes séparées (les deux techniques qui peuvent , en cas de une logique simple moins lisible et plus encombrant le programme) - si vous enfoncez trop les méthodes, vous finissez par émuler des macros en langage assembleur et vous perdez de vue la façon dont elles s’intègrent. En outre, dans cette méthode distincte, vous rencontriez toujours le même problème: imbriquer les appels ou les décomposer en variables intermédiaires.
ajouté l'auteur mjturner, source
@Steve: Je n'ai pas dit de toujours le faire, mais si vous envisagez d'utiliser 5 lignes pour obtenir une valeur unique, il y a de bonnes chances qu'une fonction soit meilleure. En ce qui concerne les lignes multiples par rapport aux lignes complexes: si c'est une fonction avec un bon nom, les deux fonctionneront également.
ajouté l'auteur pihentagy, source

À mon avis, le code auto-documenté est préférable pour la maintenabilité et la lisibilité, quelle que soit la langue.

La déclaration donnée ci-dessus est dense, mais "auto-documentée":

double d = sqrt(square(x1 - x0) + square(y1 - y0));

Lorsque cassé en étapes (plus facile pour les tests, sûrement) perd tout le contexte, comme indiqué ci-dessus:

double dx = x1 - x0;
double dy = y1 - y0;
double dxSquare = square(dx);
double dySquare = square(dy);
double dSquare = dxSquare + dySquare;
double d = sqrt(dSquare);

Et évidemment, utiliser des noms de variables et de fonctions qui indiquent clairement leur objectif est inestimable.

Même les blocages "si" peuvent être bons ou mauvais en auto-documentation. Cela est grave car vous ne pouvez pas forcer facilement les 2 premières conditions à tester la troisième ... toutes ne sont pas liées:

if (Bill is the boss) && (i == 3) && (the carnival is next weekend)

Celui-ci a un sens plus "collectif" et est plus facile à créer des conditions de test:

if (iRowCount == 2) || (iRowCount == 50) || (iRowCount > 100)

Et cette déclaration est juste une chaîne aléatoire de caractères, vue d'un point de vue auto-documenté:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

En regardant la déclaration ci-dessus, la maintenabilité reste un défi majeur si les fonctions H1 et H2 modifient les mêmes "variables d'état du système" au lieu d'être unifiées en une seule fonction "H", car quelqu'un finira par modifier H1 sans même penser à la présence d'un Fonction H2 à regarder et pourrait casser H2.

Je pense qu'une bonne conception de code est très difficile car il n’existe pas de règles strictes pouvant être systématiquement détectées et appliquées.

1
ajouté