Dois-je refactoriser de grandes fonctions qui consistent principalement en une expression régulière? [fermé]

15

Je viens d'écrire une fonction qui couvre environ 100 lignes. En entendant cela, vous êtes probablement tenté de me parler des responsabilités individuelles et de m'exhorter à refactoriser. C'est aussi mon instinct, mais voici le problème: la fonction fait une chose. Il effectue une manipulation de chaîne complexe, et le corps de la fonction se compose principalement d'une expression régulière verbeuse, divisée en plusieurs lignes documentées. Si je divisais l'expression régulière en plusieurs fonctions, je sens que je perdrais en fait la lisibilité, car je change effectivement de langue et je ne serai pas en mesure de profiter de certaines fonctionnalités qu'offrent les expressions régulières. Voici maintenant ma question:

En ce qui concerne la manipulation de chaînes avec des expressions régulières, les grands corps de fonction sont-ils toujours un anti-modèle? Il semble que les groupes de capture nommés servent un objectif très similaire aux fonctions. Soit dit en passant, j'ai des tests pour chaque flux à travers l'expression régulière.

DudeOnRock
la source
3
Je ne pense pas qu'il y ait quelque chose de mal avec votre fonction, étant donné qu'une grande partie est de la documentation . Cependant, il peut y avoir un problème de maintenabilité avec l'utilisation d'une grande expression régulière.
Joel Cornett
2
Êtes-vous sûr qu'un regex géant est la meilleure solution à votre problème? Avez-vous envisagé des alternatives plus simples, comme une bibliothèque d'analyseur ou le remplacement d'un format de fichier personnalisé par un format standard (XML, JSON, etc.)?
lortabac
2
Existe-t-il d'autres fonctions utilisant une version modifiée / améliorée / simplifiée de cette expression régulière? Ce serait un indicateur important qu'une refactorisation devrait avoir lieu. Sinon, je le laisserais tel quel. Avoir besoin d'une manipulation de chaîne complexe comme celle-ci est un drapeau jaune à part entière (enfin je ne connais pas le contexte, donc juste jaune), et refactoriser la fonction vers le bas me semble plus comme un rituel pour racheter de la culpabilité que l'on ressent it;)
Konrad Morawski
8
Comment une expression rationnelle de 100 lignes peut-elle faire seulement 1 chose?
Pieter B
@lortabac: L'entrée est du texte généré par l'utilisateur (prose.)
DudeOnRock

Réponses:

36

Ce que vous rencontrez est la dissonance cognitive qui provient de l'écoute de personnes qui favorisent l'adhésion servile aux directives sous le couvert de «meilleures pratiques» par rapport à une prise de décision raisonnée.

Vous avez clairement fait vos devoirs:

  • Le but de la fonction est compris.
  • Le fonctionnement de sa mise en œuvre est compris (c'est-à-dire lisible).
  • Il existe des tests de couverture complète de la mise en œuvre.
  • Ces tests réussissent, ce qui signifie que vous pensez que la mise en œuvre est correcte.

Si l'un de ces points n'était pas vrai, je serais le premier à dire que votre fonction a besoin de travail. Il y a donc un vote pour laisser le code tel quel.

Le deuxième vote vient de l'examen de vos options et de ce que vous obtenez (et perdez) de chacune:

  • Refactor. Cela vous permet de vous conformer à l'idée que quelqu'un a de la durée d'une fonction et sacrifie la lisibilité.
  • Ne fais rien. Cela maintient la lisibilité existante et sacrifie la conformité avec l'idée de quelqu'un de la durée d'une fonction.

Cette décision revient à laquelle vous accordez le plus d'importance: la lisibilité ou la longueur. Je tombe dans le camp qui croit que la longueur est agréable mais la lisibilité est importante et prendra ce dernier au cours de la première semaine.

Conclusion: s'il n'est pas cassé, ne le réparez pas.

Blrfl
la source
10
+1 pour "S'il n'est pas cassé, ne le réparez pas."
Giorgio
En effet. Les règles de Sandy Metz ( gist.github.com/henrik/4509394 ) sont agréables et tout, mais sur youtube.com/watch?v=VO-NvnZfMA4#t=1379, elle explique comment elles sont devenues et pourquoi les gens prennent les beaucoup trop au sérieux.
Amadan
@Amdan: Avec le contexte supplémentaire de la vidéo, ce que Metz a fait est logique. Sa recommandation à un client était intentionnellement extrême à une extrémité pour contrer un comportement extrême à l'autre extrémité afin de le faire glisser vers le milieu plus raisonnable. Le reste de cette discussion se résume à l'idée maîtresse de ma réponse: le raisonnement, et non la foi, est le moyen de déterminer la meilleure ligne de conduite.
Blrfl
19

Honnêtement, votre fonction peut "faire une chose", mais comme vous l'avez dit vous-même

Je pourrais commencer à diviser l'expression régulière en plusieurs fonctions,

ce qui signifie que votre code ex reg fait beaucoup de choses. Et je suppose qu'il pourrait être décomposé en unités plus petites et testables individuellement. Cependant, si c'est une bonne idée, il n'est pas facile de répondre (surtout sans voir le code réel). Et la bonne réponse peut être ni "oui" ni "non", mais "pas encore, mais la prochaine fois vous devrez changer quelque chose dans cette exp reg".

mais j'ai l'impression que je perdrais en fait la lisibilité de cette façon, car je change effectivement de langue

Et c'est le point central - vous avez un morceau de code écrit en langage ex reg . Ce langage ne fournit pas de bon moyen d'abstraction en soi (et je ne considère pas les "groupes de capture nommés" comme un remplacement pour les fonctions). Donc, la refactorisation "dans la langue reg ex" n'est pas vraiment possible, et l'entrelacement de plus petites exp reg avec la langue hôte peut ne pas réellement améliorer la lisibilité (au moins, vous le sentez , mais vous avez des doutes, sinon vous n'auriez pas posté la question) . Voici donc mon conseil

  • montrez votre code à un autre développeur avancé (peut-être sur /codereview// ) pour vous assurer que les autres pensent la lisibilité comme vous le faites. Soyez ouvert à l'idée que d'autres ne trouveront peut-être pas une exp de 100 lignes aussi lisible que vous. Parfois, la notion de «ce n'est pas facilement cassable en petits morceaux» peut être surmontée par une deuxième paire d'yeux.

  • observez l’évolutivité réelle - votre exp brillant est-il toujours aussi beau lorsque de nouvelles exigences arrivent et vous devez les implémenter et les tester? Tant que votre exp exp fonctionne, je ne le toucherais pas, mais chaque fois que quelque chose doit être changé, je reconsidérerais si c'était vraiment une bonne idée de tout mettre dans ce gros bloc - et (sérieusement!) Repenser si la scission en des morceaux plus petits ne seraient pas une meilleure option.

  • observer la maintenabilité - pouvez-vous déboguer efficacement le reg exp dans la forme actuelle très bien? Surtout après avoir changé quelque chose, et maintenant vos tests vous disent que quelque chose ne va pas, avez-vous un débogueur reg exp vous aidant à trouver la cause première? Si le débogage devient difficile, ce serait également l'occasion de reconsidérer votre conception.

Doc Brown
la source
Je dirais que les groupes de capture nommés (les groupes de capture en général, vraiment) sont les plus similaires aux variables finales / à écriture unique, ou peut-être aux macros. Ils vous permettent de référencer des parties spécifiques de la correspondance, soit à partir de l'objet de correspondance renvoyé par le processeur d'expression régulière, soit ultérieurement dans l'expression régulière elle-même.
JAB
4

Parfois, une fonction plus longue qui fait une chose est la façon la plus appropriée de gérer une unité de travail. Vous pouvez facilement accéder à des fonctions très longues lorsque vous commencez à traiter une requête dans une base de données (en utilisant votre langage de requête préféré). Rendre une fonction (ou méthode) plus lisible tout en la limitant à son objectif déclaré est ce que je considérerais comme le résultat le plus souhaitable d'une fonction.

La longueur est une "norme" arbitraire en ce qui concerne la taille du code. Lorsqu'une fonction de 100 lignes en C # peut être considérée comme longue, elle serait minuscule dans certaines versions d'assemblage. J'ai vu des requêtes SQL qui étaient bien dans la plage de 200 lignes de code qui ont renvoyé un ensemble de données très compliqué pour un rapport.

Un code pleinement fonctionnel , c'est aussi simple que vous pouvez raisonnablement en faire l'objectif.

Ne le changez pas simplement parce qu'il est long.

Adam Zuckerman
la source
3

Vous pouvez toujours diviser l'expression régulière en sous-expressions rationnelles et composer progressivement l'expression finale. Cela pourrait aider à la compréhension d'un très grand modèle, en particulier si le même sous-modèle est répété plusieurs fois. Par exemple en Perl;

my $start_re = qr/(?:\w+\.\w+)/;
my $middle_re = qr/(?:DOG)|(?:CAT)/;
my $end_re = qr/ => \d+/;

my $final_re = $start_re . $middle_re . $end_re;
# or: 
# my $final_re = qr/${start_re}${middle_re}${end_re}/
Rory Hunter
la source
J'utilise le drapeau verbeux, ce qui est encore plus pratique que ce que vous proposez.
DudeOnRock
1

Je dirais le casser s'il est cassable. du point de vue de la maintenabilité et peut-être de la reproductibilité, il est logique de la casser, mais bien sûr, vous devez tenir compte de la nature de votre fonction et de la façon dont vous obtenez des informations et de ce qu'elle va rendre.

Je me souviens que je travaillais sur l'analyse de streaming de données en morceaux dans des objets, donc ce que j'ai fait essentiellement, je l'ai divisé en deux parties principales, l'une construisait une unité complète de String à partir du texte codé et dans la deuxième partie, analysait ces unités dans un dictionnaire de données et organisait eux (pourrait être une propriété aléatoire pour un objet différent) et que la mise à jour ou la création d'objets.

De plus, je pouvais diviser chaque partie principale en plusieurs fonctions plus petites et plus spécifiques.A la fin, j'avais 5 fonctions différentes pour faire le tout et je pouvais réutiliser certaines des fonctions à différents endroits.

arfo
la source
1

Une chose que vous avez peut-être ou non envisagée est d'écrire un petit analyseur dans la langue que vous utilisez au lieu d'utiliser une expression régulière dans cette langue. Cela peut être plus facile à lire, à tester et à maintenir.

Thomas Eding
la source
J'y ai pensé moi-même. Le problème est que l'entrée est en prose et je prends des repères de contexte et de formatage. S'il est possible d'écrire un analyseur pour quelque chose comme ça, j'aimerais en savoir plus! Je n'ai rien trouvé moi-même.
DudeOnRock
1
Si une expression régulière peut l'analyser, vous pouvez l'analyser. Votre réponse me semble que vous ne connaissez peut-être pas bien l'analyse. Si tel est le cas, vous voudrez peut-être vous en tenir à l'expression régulière. Soit cela, soit apprenez une nouvelle compétence.
Thomas Eding
J'adorerais apprendre une nouvelle compétence. Avez-vous de bonnes ressources à proposer? Je m'intéresse également à la théorie derrière.
DudeOnRock
1

Les regex géants sont un mauvais choix dans la plupart des cas. D'après mon expérience, ils sont souvent utilisés parce que le développeur n'est pas familier avec l'analyse (voir la réponse de Thomas Eding ).

Quoi qu'il en soit, supposons que vous souhaitiez vous en tenir à une solution basée sur les regex.

Comme je ne connais pas le code réel, j'examinerai les deux scénarios possibles:

  • Le regex est simple (beaucoup de correspondance littérale et peu d'alternatives)

    Dans ce cas, les fonctionnalités avancées offertes par une seule expression régulière ne sont pas indispensables. Cela signifie que vous bénéficierez probablement de la scission.

  • Le regex est complexe (beaucoup d'alternatives)

    Dans ce cas, vous ne pouvez pas avoir une couverture de test complète, car vous avez probablement des millions de flux possibles. Donc, pour le tester, vous devez le diviser.

Je pourrais manquer d'imagination, mais je ne peux penser à aucune situation du monde réel où une expression régulière de 100 lignes est une bonne solution.

lortabac
la source