La génération de code source est-elle un anti-motif?


118

Si quelque chose peut être généré, alors ce sont des données, pas du code.

Dans ces conditions, l’idée même de la génération de code source n’est-elle pas un malentendu? Autrement dit, s'il existe un générateur de code pour quelque chose, pourquoi ne pas en faire une fonction appropriée pouvant recevoir les paramètres requis et effectuer l'action correcte que le code "aurait généré" aurait fait?

Si cela est fait pour des raisons de performances, cela ressemble à une lacune du compilateur.

Si cela est fait pour relier deux langues, cela ressemble à un manque de bibliothèque d’interface.

Est-ce que j'ai râté quelque chose?

Je sais que ce code est aussi une donnée. Ce que je ne comprends pas, c'est pourquoi générer du code source ? Pourquoi ne pas en faire une fonction capable d’accepter des paramètres et d’agir sur ceux-ci?


11
Un terme associé à la génération de code est la métaprogrammation
UselesssCat

4
en.wikipedia.org/wiki/Code_as_data , Lisp, FP, scripts, métaprogrammation, l' architecture Harvard / modifiée Von Neumann , etc. Il a été couvert ad nauseam . tl; dr la distinction "code source" vs "code de sortie", "code" vs "données" etc. sont destinés à simplifier les choses. Ils ne devraient jamais être dogmatiques .
vaxquis

9
@Utku, les meilleures raisons de générer du code sont souvent liées à la volonté de fournir une description de niveau supérieur à celle que votre langage actuel peut exprimer . Que le compilateur puisse ou non créer un code efficace n’a vraiment rien à voir avec cela. Considérons des générateurs d’analyseurs - un lexer généré par flexou un analyseur généré par bisonsera presque certainement plus prévisible, plus correct et souvent plus rapide à exécuter que les équivalents écrits à la main en C; et construit à partir de beaucoup moins de code (donc aussi moins de travail à maintenir).
Charles Duffy

1
Vous venez peut-être d'une langue qui ne comporte pas beaucoup d'éléments fonctionnels, mais dans de nombreuses langues, les fonctions sont de première classe - vous pouvez les transmettre. Ainsi, dans ces types de langage, le code est constitué de données, et vous pouvez le traiter comme tel.
Restioson

1
@Restioson dans un code de langage fonctionnel n'est pas une donnée. Les fonctions de première classe signifient exactement cela: les fonctions sont des données. Et pas nécessairement des données particulièrement bonnes: vous ne pouvez pas nécessairement les muter juste un peu (comme muter toutes les additions dans les fonctions en soustractions, par exemple). Le code est une donnée en langage homoiconique. (La plupart des langages homoiconiques ont des fonctions de première classe. Mais l'inverse n'est pas vrai.).
Lyndon White

Réponses:


150

La génération de code source est-elle un anti modèle?

Techniquement, si nous générons du code, ce n'est pas la source même s'il s'agit d'un texte lisible par l'homme. Le code source est un code original, généré par une intelligence humaine ou autre, non traduit mécaniquement et non immédiatement reproductible à partir de (véritable) source (directement ou indirectement).

Si quelque chose peut être généré, alors ce sont des données, pas du code.

Je dirais que tout est des données de toute façon. Même le code source. Surtout le code source! Le code source n'est que des données dans un langage conçu pour accomplir des tâches de programmation. Ces données doivent être traduites, interprétées, compilées, générées selon les besoins sous d'autres formes - de données - dont certaines sont exécutables.

Le processeur exécute les instructions en dehors de la mémoire. La même mémoire utilisée pour les données. Avant que le processeur n'exécute des instructions, le programme est chargé en mémoire sous forme de données .

Donc, tout est données , même le code .

Étant donné que [le code généré est des données], cette idée de génération de code n'est-elle pas un malentendu?

Il est parfaitement correct d’avoir plusieurs étapes de compilation, dont l’une peut être la génération de code intermédiaire sous forme de texte.

Autrement dit, s'il existe un générateur de code pour quelque chose, pourquoi ne pas en faire une fonction appropriée pouvant recevoir les paramètres requis et effectuer l'action correcte que le code "aurait généré" aurait fait?

C'est une façon, mais il y en a d'autres.


La sortie de la génération de code est du texte, conçu pour être utilisé par un humain.

Toutes les formes de texte ne sont pas destinées à la consommation humaine. En particulier, le code généré (sous forme de texte) est généralement destiné à la consommation du compilateur et non à la consommation humaine.


Le code source est considéré comme l’original: le maître - ce que nous éditons et développons; ce que nous archivons en utilisant le contrôle de code source. Le code généré, même lorsqu'un texte lisible par l'homme, est généralement régénéré à partir du code source d' origine . En règle générale, le code généré ne doit pas nécessairement être sous contrôle de code source car il est régénéré pendant la construction.


1
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft

65

Raisonnement pratique

OK, je sais que ce code est aussi une donnée. Ce que je ne comprends pas, c'est pourquoi générer du code source?

À partir de cette édition, je suppose que vous posez une question plutôt pratique que informatique théorique.

La raison classique pour générer du code source dans des langages statiques tels que Java était que ces langages ne venaient tout simplement pas avec des outils faciles à utiliser en langage pour faire des choses très dynamiques. Par exemple, à l'époque formatrice de Java, il n'était tout simplement pas possible de créer facilement une classe avec un nom dynamique (correspondant à un nom de table depuis une base de données) et des méthodes dynamiques (attributs correspondants de cette table) avec des types de données dynamiques les types desdits attributs). D'autant que Java attache beaucoup d'importance, voire garantit, à pouvoir détecter les erreurs de type au moment de la compilation.

Ainsi, dans un tel contexte, un programmeur ne peut créer que du code Java et écrire beaucoup de lignes de code manuellement. Souvent, le programmeur s'aperçoit que chaque fois qu'une table est modifiée, il doit revenir en arrière et modifier le code afin qu'il corresponde; et s'il oublie ça, de mauvaises choses arrivent. Par conséquent, le programmeur en arrivera au point où il rédigera des outils qui le font pour lui. Et par conséquent, la route commence pour une génération de code de plus en plus intelligente.

(Oui, vous pouvez générer le bytecode à la volée, mais programmer un tel programme en Java ne serait pas quelque chose qu'un programmeur aléatoire ferait entre deux lignes d'écriture de code de domaine.)

Comparez cela à des langages très dynamiques, par exemple Ruby, que je considérerais comme l'antithèse de Java à presque tous les égards (notez que je le dis sans valoriser l'une ou l'autre approche; ils sont simplement différents). Ici, il est 100% normal et standard de générer dynamiquement des classes, des méthodes, etc. au moment de l’exécution, et surtout, le programmeur peut le faire trivialement dans le code, sans passer au niveau "méta". Oui, Ruby on Rails est associé à la génération de code, mais nous avons constaté dans notre travail que nous l'utilisions comme une sorte de "mode tutoriel" avancé pour les nouveaux programmeurs, mais au bout d'un moment cela devient superflu (car il y a si peu de code). pour écrire dans cet écosystème que, lorsque vous savez ce que vous faites, l'écrire manuellement devient plus rapide que le nettoyage du code généré).

Ce ne sont que deux exemples pratiques du "monde réel". Ensuite, vous avez des langues comme LISP où le code est littéralement des données. Par contre, dans les langages compilés (sans moteur d’exécution tel que Java ou Ruby), il n’existe (ou n’ai pas suivi les fonctionnalités modernes du C ++ ...) tout simplement aucun concept de définition de noms de classe ou de méthode au moment de l’exécution, La génération de code est donc l’outil de choix pour la plupart des choses (d’autres exemples plus spécifiques à C / C ++ seraient des choses comme flex, yacc, etc.).


1
Je pense que c'est mieux que les réponses plus votées. En particulier, l'exemple mentionné avec Java et la programmation de base de données permet de mieux comprendre pourquoi la génération de code est utilisée et constitue un outil valide.
Panzercrisis

De nos jours, est-il possible en Java de créer des tables dynamiques à partir d'une base de données? Ou seulement en utilisant un ORM?
Noumenon

"(ou étais, je n'ai pas suivi les fonctionnalités modernes du C ++ ...)" cela a sûrement été possible en C ++ depuis plus de deux décennies grâce aux indicateurs de fonction? Je ne l'ai pas testé mais je suis sûr qu'il devrait être possible d'allouer un tableau de caractères, de le remplir avec du code machine, puis de placer un pointeur sur le premier élément en un pointeur de fonction, puis de l'exécuter? (En supposant que la plate-forme cible ne dispose pas d'une mesure de sécurité pour vous empêcher de le faire, ce qui pourrait bien être le cas.)
Pharap

1
"alloue un tableau de caractères, remplis-le de code machine, puis convertis un pointeur sur le premier élément en un pointeur de fonction, puis lance-le?" En plus d'être un comportement indéfini, c'est l'équivalent C ++ de "générer le code-octet à la volée". Il entre dans la même catégorie de "non pris en compte par les programmeurs ordinaires"
Caleth

1
@Pharap, "cela a sûrement été possible en C ++ depuis plus de deux décennies" ... j'ai du rire un peu; il y a environ deux décennies que j'ai codé pour la dernière fois en C ++. :) Mais ma phrase sur C ++ a été mal formulée de toute façon. Je l'ai un peu changé, il devrait être plus clair ce que je voulais dire maintenant.
AnoE

44

pourquoi générer du code?

Parce que la programmation avec des cartes perforées (ou des codes alt dans le bloc-notes ) est une douleur.

Si cela est fait pour des raisons de performances, cela ressemble à une lacune du compilateur.

Vrai. Je me fiche de la performance à moins d'y être forcé.

Si cela est fait pour relier deux langues, cela ressemble à un manque de bibliothèque d’interface.

Hmm, je ne sais pas de quoi vous parlez.

Voici ce qui se passe: le code source généré et conservé est toujours et toujours pénible. Il existe pour une seule raison. Quelqu'un veut travailler dans une langue alors que quelqu'un d'autre insiste pour travailler dans une autre et aucun d'entre eux ne peut s'embarrasser pour savoir comment interopérer entre eux afin que l'un d'eux sache comment transformer sa langue préférée en langue imposée afin de pouvoir faire quoi Ils veulent.

Ce qui est bien jusqu'à ce que je dois le maintenir. À quel point vous pouvez tous aller mourir.

Est-ce un modèle anti? Soupir, non. De nombreuses langues n'existeraient même pas si nous n'étions pas disposés à dire au revoir aux faiblesses des langues précédentes et générer le code des anciennes langues correspond au nombre de nouvelles langues qui commencent.

C'est une base de code qui reste dans un patchwork de monstres Frankenstein à moitié converti que je ne supporte pas. Le code généré est un code intouchable. Je déteste regarder du code intouchable. Pourtant, les gens continuent à l'enregistrer. POURQUOI? Vous pourriez aussi bien vérifier l'exécutable.

Eh bien maintenant je peste. Mon point est que nous "générons tous du code". C'est lorsque vous traitez le code généré comme un code source que vous me rendez fou. Parce qu'il semble que le code source ne le rend pas le code source.


41
Si vous le générez, ce n'est pas du code source. C'est un code intermédiaire. Je vais aller pleurer maintenant.
candied_orange

65
ARG !!! Peu importe à quoi ça ressemble !!! Texte, binaire, ADN, si ce n'est pas la SOURCE, ce n'est pas ce que vous devez toucher lorsque vous apportez des modifications. Ce n'est pas l'affaire de tous si mon processus de compilation comporte 42 langages intermédiaires. Arrête de les toucher. Arrêtez de les enregistrer. Effectuez vos modifications à la source.
candied_orange

24
XML est un texte et n'est clairement pas destiné à la consommation humaine. :-)
Nick Keighley

38
@utku: "Si quelque chose n'est pas destiné à être consommé par un humain, il ne devrait pas s'agir de texte": je ne suis absolument pas d'accord. Certains contre-exemples me viennent à l’esprit: le protocole HTTP, les encodages MIME, les fichiers PEM - à peu près tout ce qui utilise base64 n’importe où. Il existe de nombreuses raisons de coder des données dans un flux sécurisé de 7 bits, même si aucun humain ne devrait les voir. Sans parler de l'espace beaucoup plus vaste de choses avec lesquelles un humain ne devrait normalement jamais interagir, mais qu'il souhaite parfois utiliser: fichiers journaux, /etc/fichiers sous Unix, etc.
Daniel Pryden

12
Je ne pense pas que "programmer avec des cartes perforées" signifie ce que vous pensez que cela signifie. Je suis là, je l' ai fait, et oui, il était une douleur; mais il n'a aucun lien avec le "code généré". Un jeu de cartes perforées est juste un autre type de fichier - comme un fichier sur disque, un fichier sur bande ou un fichier sur une carte SD. De retour dans la journée, nous écrivions des données sur des jeux de cartes et les lisions. Donc, si la raison pour laquelle nous générons du code est parce que la programmation avec des cartes perforées est une douleur, cela implique alors que la programmation avec n'importe quel type de stockage de données est une douleur.
Salomon Slow

41

pourquoi générer du code source

Les cas d’utilisation les plus fréquents des générateurs de code avec lesquels je devais travailler étaient des générateurs qui

  • a pris une méta-description de haut niveau pour un type de modèle de données ou un schéma de base de données en entrée (peut-être un schéma relationnel ou un type de schéma XML)

  • et produit le code CRUD de la plaque de la chaudière pour les classes d’accès aux données en sortie, et peut-être des éléments supplémentaires comme des instructions SQL ou une documentation correspondante.

L'avantage ici est que d'une ligne d'une spécification d'entrée courte, vous obtenez 5 à 10 lignes de code débogable, sûr pour le type et sans bug (en supposant que la sortie du générateur de code soit mature) que vous auriez autrement dû implémenter et gérer manuellement. Vous pouvez imaginer à quel point cela réduit les efforts de maintenance et d'évolution.

Permettez-moi également de répondre à votre question initiale

La génération de code source est-elle un anti modèle?

Non, pas de génération de code source en soi, mais il y a en effet des pièges. Comme indiqué dans The Pragmatic Programmer , il convient d'éviter l'utilisation d'un générateur de code lorsqu'il génère un code difficile à comprendre . Sinon, les efforts accrus d'utilisation ou de débogage de ce code peuvent facilement compenser les efforts économisés en n'écrivant pas le code manuellement.

J'aimerais également ajouter qu'il est généralement utile de séparer physiquement les parties de code générées du code écrit manuellement de manière à ce que la régénération ne remplace pas les modifications manuelles. Cependant, j’ai aussi traité plus d’une fois de la situation dans laquelle la tâche consistait à migrer du code écrit dans l’ancien langage X vers un autre, un langage plus moderne, avec l’intention de le maintenir ensuite dans le langage Y. C’est un usage valable cas pour la génération de code ponctuel.


Je suis d'accord avec cette réponse. En utilisant quelque chose comme Torque for java, je peux créer automatiquement des fichiers source Java, avec des champs correspondant à la base de données SQL. Cela rend les opérations crud beaucoup plus faciles. Le principal avantage est la sécurité de type, notamment le fait de ne pouvoir référencer que des champs existants dans la base de données (Merci de compléter automatiquement).
MT Whileed

Oui, pour les langages à typage statique, c’est l’essentiel: vous pouvez vous assurer que votre code manuscrit correspond réellement au code généré.
Paŭlo Ebermann

"migrer du code écrit dans une langue ancienne" - même dans ce cas, la génération de code unique peut être très pénible. Par exemple, après certaines modifications manuelles, vous détectez un bogue dans le générateur et devez rétablir la génération après le correctif. Heureusement, les git ou similaires peuvent généralement soulager la douleur.
Maaartinus

13

pourquoi générer du code source?

J'ai rencontré deux cas d'utilisation pour le code généré (au moment de la construction et qui n'a jamais été archivé):

  1. Génère automatiquement du code passe-partout, tel que des getters / setters, toString, equals et hashCode, à partir d'un langage conçu pour spécifier ce type d'éléments (par exemple, le projet lombok pour Java)
  2. Génère automatiquement des classes de type DTO à partir de certaines spécifications d'interface (REST, SOAP, etc.) à utiliser ensuite dans le code principal. Ceci est similaire à votre problème de passerelle linguistique, mais finit par être plus propre et plus simple, avec une meilleure gestion des types que d'essayer d'implémenter la même chose sans classes générées.

15
Code hautement répétitif dans des langages inexpressifs. Par exemple, je devais écrire le code voulant que l'essentiel fît la même chose sur de nombreuses structures de données similaires mais non identiques. Cela aurait probablement pu être fait avec quelque chose comme un modèle C ++ (non ce n'est pas cette génération de code?). Mais j'utilisais C. La génération de code m'a permis d'écrire beaucoup de code presque identique.
Nick Keighley

1
@ NickKeighley Votre chaîne d'outils ne vous permettait peut-être pas d'utiliser un autre langage plus approprié?
Wilson

7
Vous n'avez généralement pas à choisir votre langue d'implémentation. Le projet était en C, ce n'était pas une option.
Nick Keighley

1
@Wilson, les langages les plus expressifs utilisent souvent la génération de code (macros lisp, ruby ​​on rails, par exemple), ils n'ont simplement pas besoin d'être sauvegardés sous forme de texte entre-temps.
Pete Kirkham

4
Oui, la génération de code est essentiellement de la méta-programmation. Des langages comme Ruby vous permettent de faire de la méta-programmation dans le langage lui-même, mais pas le C, vous devez donc utiliser la génération de code.
Sean Burton

13

Sussmann avait beaucoup à dire sur ce sujet dans son ouvrage classique "Structure et interprétation des programmes informatiques", principalement sur la dualité code-données.

Pour moi, la principale utilisation de la génération de code ad hoc est d'utiliser un compilateur disponible pour convertir un petit langage spécifique à un domaine en quelque chose que je peux lier à mes programmes. Pensez BNF, pensez ASN1 (en fait, non, c'est moche), pensez aux tableurs du dictionnaire de données.

Les langages spécifiques à un domaine trivial peuvent représenter un gain de temps considérable, et la sortie de quelque chose qui peut être compilé par des outils de langage standard est la voie à suivre lors de la création de telles choses, que préféreriez-vous éditer, un analyseur syntaxique piraté à la main non trivial dans la langue maternelle de votre choix? écriture, ou la BNF pour une auto généré?

En envoyant du texte qui est ensuite envoyé à un compilateur système, je reçois toute cette optimisation des compilateurs et la configuration spécifique du système sans avoir à y penser.

J'utilise efficacement le langage de saisie du compilateur comme une simple représentation intermédiaire. Quel est le problème? Les fichiers texte ne sont pas intrinsèquement du code source, ils peuvent être un IR pour un compilateur , et s’ils ressemblent à C, C ++, Java ou autre chose, peu importe.

Maintenant, si vous avez du mal à penser que vous pourriez éditer le résultat de l’analyseur de langage jouet, ce qui décevra manifestement la prochaine fois que quelqu'un éditera les fichiers de langue d'entrée et les reconstruira, la solution consiste à ne pas valider l'IR généré automatiquement dans le référentiel. (et évitez d’avoir de telles personnes dans votre groupe de développeurs, elles sont généralement plus heureuses dans le marketing).

Ce n'est pas vraiment un manque d'expressivité dans nos langues, mais une expression du fait que vous pouvez parfois obtenir (ou masquer) des parties de la spécification sous une forme qui peut être automatiquement convertie en code, et qui en engendrera généralement beaucoup moins. bugs et être beaucoup plus facile à entretenir. Si je peux donner à nos équipes de test et de configuration une feuille de calcul, ils peuvent la modifier et un outil qu'ils utilisent ensuite, qui extrait ces données et crée un fichier hexadécimal complet pour la mémoire flash de mon calculateur, ce qui représente un gain de temps considérable par rapport à la traduction manuelle la dernière configuration en un ensemble de constantes dans la langue du jour (complète avec des fautes de frappe).

Même chose avec la construction de modèles dans Simulink, puis la génération de C avec RTW, puis la compilation pour cibler avec l’outil qui convient, l’intermédiaire C est illisible, et alors? Le matériel de haut niveau Matlab RTW doit uniquement connaître un sous-ensemble de C, et le compilateur C prend en charge les détails de la plate-forme. Le seul moment où un humain doit se frayer un chemin à travers le C généré est lorsque les scripts RTW ont un bogue, et ce genre de choses est beaucoup plus facile à déboguer avec un IR lisible par un humain nominalement qu'avec un arbre d'analyse binaire.

Vous pouvez bien sûr écrire ce genre de choses pour sortir du bytecode ou même du code exécutable, mais pourquoi voudriez-vous le faire? Nous avons des outils pour convertir un IR à ces choses.


C’est bien, mais j’ajouterais qu’il ya un compromis à faire pour déterminer quel IR utiliser: utiliser C comme IR facilite certaines choses et rend plus difficile d’autres, par rapport au langage assembleur x86. Le choix est encore plus important lorsque vous choisissez entre, par exemple, le code de langage Java et le bytecode Java, car de nombreuses autres opérations n'existent que dans l'un ou l'autre langage.
Daniel Pryden

2
Mais le langage d'assemblage X86 génère une IR faible lorsque vous ciblez un noyau ARM ou PPC! Toutes les choses sont un compromis en ingénierie, c’est pourquoi ils l’appellent ingénierie. On aurait pu espérer que les possibilités du bytecode Java étaient un sur-ensemble strict des possibilités du langage Java, et que cela est généralement vrai à mesure que vous vous rapprochez du métal, quels que soient l'outil utilisé et l'endroit où vous injectez l'IR.
Dan Mills

Oh, je suis tout à fait d’accord: mon commentaire faisait suite à votre dernier paragraphe, dans lequel vous demandiez pourquoi vous produisiez du bytecode ou quelque chose de plus bas niveau - parfois vous avez besoin du niveau inférieur. (En Java en particulier, il y a beaucoup de choses utiles que vous pouvez faire avec le bytecode que vous ne pouvez pas faire dans le langage Java lui-même.)
Daniel Pryden

2
Je ne suis pas en désaccord avec cela, mais l’utilisation d’un IR plus proche du métal a un coût, non seulement en réduction de la généralité, mais aussi en raison du fait que vous êtes généralement responsable d’une plus grande optimisation vraiment dérangeante des bas niveaux. Le fait que nous pensions généralement de nos jours en termes d'optimisation du choix de l'algorithme plutôt que de la mise en œuvre est un reflet du chemin parcouru par les compilateurs. Parfois, vous devez vraiment vous approcher du métal, mais réfléchissez-y à deux fois avant de jeter les compilateurs. capacité à optimiser en utilisant un niveau IR trop bas.
Dan Mills

1
"Ils sont généralement plus heureux de travailler dans le marketing" Catty, mais drôle.
dmckee

13

Réponse pragmatique: la génération de code est-elle nécessaire et utile? Fournit-il quelque chose de vraiment très utile et nécessaire pour la base de code propriétaire, ou semble-t-il simplement créer une autre façon de faire les choses d'une manière qui contribue davantage à la surcharge intellectuelle pour des résultats sous-optimaux?

OK, je sais que ce code est aussi une donnée. Ce que je ne comprends pas, c'est pourquoi générer du code? Pourquoi ne pas en faire une fonction capable d’accepter des paramètres et d’agir sur ceux-ci?

Si vous devez poser cette question et qu'il n'y a pas de réponse claire, la génération de code est probablement superflue et ne fait que contribuer à l'exotisme et à une charge intellectuelle considérable pour votre base de code.

Pendant ce temps , si vous prenez quelque chose comme OpenShadingLanguage: https://github.com/imageworks/OpenShadingLanguage

... alors ces questions ne doivent pas être soulevées car elles sont immédiatement répondues par les résultats impressionnants.

OSL utilise la structure de compilateur LLVM pour convertir les réseaux de shader en code machine à la volée (juste à temps, ou "JIT"), ce qui optimise considérablement les shaders et les réseaux avec une connaissance complète des paramètres du shader et d'autres valeurs d'exécution qui ne pourraient pas ont été connus lorsque les shaders ont été compilés à partir du code source. En conséquence, nous constatons que nos réseaux d’ombrage OSL s'exécutent 25% plus rapidement que les systèmes d’ombrage équivalents fabriqués à la main en C! (C'est comme ça que nos vieux shaders travaillaient dans notre moteur de rendu.)

Dans un tel cas, vous n'avez pas besoin de mettre en doute l'existence du générateur de code. Si vous travaillez dans ce type de domaine VFX, votre réponse immédiate est généralement plus orientée: "tais-toi et prends mon argent!" ou "wow, nous devons aussi faire quelque chose comme ça."


traduire les réseaux de shader en code machine . Cela ressemble à un compilateur plutôt qu’à un générateur de code, non?
Utku

2
Il s’agit d’un réseau nodal auquel l’utilisateur se connecte et génère un code intermédiaire compilé JIT par LLVM. La distinction entre compilateur et générateur de code est un peu floue. Avez-vous réfléchi davantage aux lignes des fonctionnalités de génération de code dans des langages tels que les modèles en C ++ ou le pré-processeur C?

Je pensais à tout générateur qui produirait du code source.
Utku

Je vois, où la production est toujours destinée à la consommation humaine, je suppose. OpenSL génère également du code source intermédiaire, mais il s'agit d'un code de bas niveau proche de l'assemblage pour la consommation de LLVM. Ce n'est généralement pas le code qui doit être maintenu (les programmeurs conservent les nœuds utilisés pour générer le code). La plupart du temps, je pense que ces types de générateurs de code risquent davantage d’être utilisés abusivement que d’utiles pour justifier leur valeur, en particulier si vous devez régénérer constamment le code dans le cadre de votre processus de construction. Parfois, ils ont toujours une place réelle pour remédier aux lacunes ...

... des langues disponibles lorsqu'elles sont utilisées pour un domaine particulier. QT a l’un de ces controversés avec son compilateur de méta-objets (MOC). Le MOC réduit le standard habituel dont vous auriez normalement besoin pour fournir les propriétés et la réflexion, ainsi que les signaux et les créneaux, etc. en C ++, mais pas dans une mesure suffisante pour justifier clairement son existence. Je pense souvent que QT aurait pu être mieux sans le lourd fardeau de la génération de code du MOC.

8

Non, la génération de code intermédiaire n'est pas un anti-motif. La réponse à l’autre partie de votre question, "Pourquoi le faire?", Est une question très large (et séparée), bien que je donnerai quand même quelques raisons.

Ramifications historiques de ne jamais avoir de code intermédiaire lisible par l'homme

Prenons C et C ++ comme exemples, car ils font partie des langages les plus célèbres.

Vous devez prendre note que la procédure logique de compilation du code C ne génère pas de code machine, mais un code assembleur lisible par l’homme. De même, les anciens compilateurs C ++ compilaient physiquement le code C ++ en code C. Dans cette chaîne d'événements, vous pouvez compiler du code 1 lisible par l'homme au code 2 lisible par l'homme au code 3 lisible par l'homme au code machine. "Pourquoi?" Pourquoi pas?

Si du code intermédiaire lisible par l'homme n'a jamais été généré, il est possible que nous n'ayons même pas du tout C ou C ++. C'est certainement une possibilité. les gens prennent le chemin de la moindre résistance à leurs objectifs, et si une autre langue gagnait du terrain en raison de la stagnation du développement du C, celui-ci aurait pu mourir alors qu'il était encore jeune. Bien sûr, vous pourriez dire "Mais alors, nous utiliserions peut-être un autre langage et ce serait peut-être mieux." Peut-être, ou peut-être que ce serait pire. Ou peut-être aurions-nous tous encore écrit en assemblée.

Pourquoi utiliser du code intermédiaire lisible par l'homme?

  1. Parfois, un code intermédiaire est souhaité afin que vous puissiez le modifier avant la prochaine étape de la construction. Je vais admettre que ce point est le plus faible.
  2. Parfois, c'est parce que le travail original n'a pas été fait dans un langage lisible par tout, mais dans un outil de modélisation graphique.
  3. Parfois, vous devez faire quelque chose de très répétitif, et le langage ne doit pas être adapté à ce que vous faites, car il s'agit d'un créneau ou d'une chose tellement complexe qu'il n'a pas vocation à accroître la complexité ou la grammaire du langage de programmation simplement pour tenir compte des besoins. toi.
  4. Parfois, vous devez faire quelque chose de très répétitif, et il n'y a aucun moyen d'obtenir ce que vous voulez dans le langage de manière générique; soit il ne peut pas être représenté par, soit en conflit avec la grammaire de la langue.
  5. L'un des objectifs des ordinateurs est de réduire l'effort humain. Parfois, le méta-code d'un code improbable (peu de risque de maintenance) peut être écrit pour générer votre code le plus long dix fois plus tard; si je peux le faire en 1 jour au lieu de 2 semaines et il est peu probable d'être maintenu jamais, alors je ferais mieux de générer - et la chance ce que quelqu'un 5 ans est maintenant ennuyé parce qu'ils ont effectivement ne besoin de le maintenir, puis ils peuvent passer les 2 semaines à l'écrire complètement s'ils le souhaitent, ou être ennuyés par une semaine de maintenance du code compliqué (mais nous avons encore une semaine d'avance à ce stade), et c'est si l' entretien doit être effectué du tout .
  6. Je suis sûr qu'il y a d'autres raisons que je néglige.

Exemple

J'ai déjà travaillé sur des projets où le code devait être généré à partir de données ou d'informations contenues dans un autre document. Par exemple, un projet avait tous ses messages réseau et ses données constantes définis dans un tableur et un outil qui parcourrait le tableur et générerait beaucoup de code C ++ et Java qui nous permettait de travailler avec ces messages.

Je ne dis pas que c'était la meilleure façon de mettre en place ce projet (je ne faisais pas partie de son démarrage), mais c'était ce que nous avions, et c'étaient des centaines (peut-être même des milliers, pas sûr) de structures, d'objets et de constantes qui étaient générés; à ce stade, il est probablement trop tard pour essayer de le refaire dans quelque chose comme Rhapsody. Mais même si cela avait été refait dans quelque chose comme Rhapsody, nous aurions quand même du code généré à partir de Rhapsody .

De plus, avoir toutes ces données dans un tableur était une bonne chose: cela nous permettait de représenter les données d'une manière que nous ne pourrions pas avoir si elles n'étaient que des fichiers de code source.

Exemple 2

Quand j'ai travaillé sur la construction du compilateur, j'ai utilisé l'outil Antlr pour faire mon lexing et mon analyse. J'ai spécifié une grammaire linguistique, puis j'ai utilisé l'outil pour cracher une tonne de code en C ++ ou en Java, puis ce code généré à côté de mon propre code et inclus dans la construction.

Sinon, comment cela aurait-il dû être fait? Peut-être pourriez-vous trouver un autre moyen; il y a probablement d'autres moyens. Mais pour ce travail, les autres méthodes n'auraient pas été meilleures que le code généré lex / parse que j'avais.


J’ai utilisé le code intermédiaire comme une sorte de format de fichier et de trace de débogage lorsque les deux systèmes étaient incompatibles, mais qu’ils possédaient une API stable, dans un langage de script très ésotérique. N'a pas été conçu pour être lu manuellement, mais aurait pu l'être de la même façon que XML aurait pu l'être. Mais cela est plus courant que vous ne le croyez après que toutes les pages Web fonctionnent de cette façon, comme quelqu'un l’a fait remarquer.
Joojaa

7

Ce qui vous manque, c'est la réutilisation .

Nous avons un outil extraordinaire pour transformer le texte du code source en binaire, appelé compilateur. Ses entrées sont bien définies (généralement!) Et il a fallu beaucoup de travail pour affiner son optimisation. Si vous voulez réellement utiliser le compilateur pour effectuer certaines opérations, vous voulez utiliser un compilateur existant et non écrire le vôtre.

Beaucoup de gens inventent de nouveaux langages de programmation et écrivent leurs propres compilateurs. Quasiment sans exception, ils le font tous parce qu'ils aiment le défi, non pas parce qu'ils ont besoin des fonctionnalités fournies par ce langage. Tout ce qu'ils font pourrait être fait dans une autre langue; ils créent simplement une nouvelle langue parce qu'ils aiment ces fonctionnalités. Cependant, ce qui ne les obtiendra pas, c'est un compilateur optimiseur bien réglé, rapide, efficace. Cela leur donnera quelque chose qui peut transformer du texte en binaire, bien sûr, mais ce ne sera pas aussi bon que tous les compilateurs existants .

Le texte n'est pas simplement quelque chose que les humains lisent et écrivent. Les ordinateurs sont parfaitement à la maison avec le texte aussi. En fait, les formats tels que XML (et autres formats associés) ont du succès car ils utilisent du texte brut. Les formats de fichiers binaires sont souvent obscurs et mal documentés, et un lecteur ne peut pas facilement savoir comment ils fonctionnent. XML est relativement auto-documenté, ce qui facilite la rédaction de code utilisant des fichiers au format XML. Et tous les langages de programmation sont configurés pour lire et écrire des fichiers texte.

Supposons donc que vous souhaitiez ajouter de nouvelles installations pour vous simplifier la vie. Peut-être que c'est un outil de présentation graphique. Peut-être que Qt fournit les interfaces de signaux et de slots . C'est peut-être ainsi que Code Composer Studio de TI vous permet de configurer le périphérique avec lequel vous travaillez et d'extraire les bonnes bibliothèques dans la construction. Peut-être prend-il un dictionnaire de données et génère-t-il des définitions de type et des variables globales auto-générées (oui, c’est toujours un problème dans les logiciels embarqués). Quoi qu'il en soit, le moyen le plus efficace d'exploiter votre compilateur existant consiste à créer un outil prenant en compte votre configuration de ce qu'il est et produisant automatiquement du code dans la langue de votre choix.

Il est facile à développer et à tester, car vous savez ce qui se passe et vous pouvez lire le code source qu'il a généré. Vous n'avez pas besoin de passer des années sur la construction d'un compilateur pour rivaliser avec GCC. Vous n'avez pas besoin d'apprendre une nouvelle langue complète ni de demander à d'autres personnes de le faire. Tout ce que vous avez à faire est d’automatiser ce petit domaine et tout le reste reste le même. Travail accompli.


Néanmoins, l'avantage du XML basé sur le texte est simplement que si nécessaire , il peut être lu et écrit par des humains (ils ne se gênent normalement pas une fois que cela fonctionne, mais le font certainement pendant le développement). En termes de performances et d’économie d’espace, les formats binaires sont généralement bien meilleurs (ce qui n’a très souvent aucune importance, car le goulot d’étranglement se situe ailleurs).
gauche vers

@leftaroundabout Si vous avez besoin de cette performance et de cette efficacité d'espace, bien sûr. La raison pour laquelle de nombreuses applications se sont tournées vers les formats XML ces derniers temps est que les performances et l’utilisation de l’espace ne sont plus les critères par excellence, et l’histoire a montré à quel point les formats de fichiers binaires sont mal gérés. (Anciens documents MS Word pour un exemple classique!) Le point reste cependant: le texte est aussi bien adapté à la lecture par ordinateur que par un être humain.
Graham

Bien sûr, un format binaire mal conçu peut en fait être moins performant qu’un format de texte correctement pensé, et même un format binaire correct n’est souvent pas beaucoup plus compact que le XML doté d’un algorithme de compression à usage général. Le meilleur des deux mondes est d'utiliser une spécification lisible par l'homme via des types de données algébriques et de générer automatiquement une représentation binaire efficace à partir de l'AST de ces types. Voir par exemple la bibliothèque à plat .
gauche du

7

Une réponse un peu plus pragmatique, centrée sur le pourquoi et non sur le code source. Notez que la génération de code source fait partie du processus de construction dans tous les cas. Par conséquent, les fichiers générés ne doivent pas se retrouver dans le contrôle de source.

Interoprabilité / simplicité

Prenons l'exemple des tampons de protocole de Google: vous écrivez une seule description de protocole de haut niveau qui peut ensuite être utilisée pour générer l'implémentation dans plusieurs langues - souvent différentes parties du système sont écrites dans différentes langues.

Mise en œuvre / raisons techniques

Prenez TypeScript - les navigateurs ne peuvent pas l’interpréter. Le processus de génération utilise donc un transpiler (traducteur de code) pour générer du JavaScript. En fait, de nombreux langages compilés nouveaux ou ésotériques commencent par transpiler en C avant d'obtenir un compilateur approprié.

Facilité d'utilisation

Pour les projets intégrés (think IoT) écrits en C et utilisant un seul binaire (RTOS ou aucun système d'exploitation), il est assez facile de générer un tableau C avec les données à compiler comme si c'était du code source normal, par opposition à les relier directement comme ressources.

Modifier

Développer sur protobuf: la génération de code permet aux objets générés d'être des classes de première classe dans n'importe quel langage. Dans un langage compilé, un analyseur générique renverrait nécessairement une structure clé-valeur - ce qui signifie que vous manquiez beaucoup de code standard, que vous manquiez certaines vérifications au moment de la compilation (sur les clés et les types de valeurs en particulier), pas de code complet. Imaginez tous ceux void*en C ou ceux qui sont énormes std::varianten C ++ (si vous avez C ++ 17), certaines langues peuvent ne pas avoir une telle fonctionnalité du tout.


Pour la première raison, je pense que l'idée du PO serait d'avoir une implémentation générique dans chaque langage (qui prend la description des tampons de protocole, puis analyse / consomme le format sur le fil). Pourquoi cela serait-il pire que de générer du code?
Paŭlo Ebermann

@ PaŭloEbermann, mis à part l'argument de performance habituel, une telle interprétation générique rendrait impossible l'utilisation de ces messages en tant qu'objets de première classe dans les langages compilés (et éventuellement interprétés) - en C ++, par exemple, un tel interpréteur renverrait nécessairement une structure clé-valeur . Bien sûr, vous pouvez ensuite intégrer ce kv à vos cours, mais cela peut se transformer en beaucoup de code passe-partout. Et il y a aussi l'achèvement du code aussi. Et compiler le temps de vérification - votre compilateur ne vérifiera pas si vos littéraux n'ont pas de fautes de frappe.
Jan Dorniak

Je suis d'accord ... pourriez-vous ajouter ceci dans la réponse?
Paŭlo Ebermann

@ PaŭloEbermann fait
Jan Dorniak

6

La génération de code source est-elle un anti modèle?

C'est un moyen de contourner un langage de programmation insuffisamment expressif. Il n'est pas nécessaire de générer du code dans un langage contenant une méta-programmation intégrée adéquate.


3
C'est également une solution de contournement pour avoir à écrire un compilateur de code objet complet et natif pour un langage plus expressif. Générez C, laissez un compilateur avec un bon optimiseur s'occuper du reste.
Blrfl

Pas toujours. Parfois, vous avez une ou plusieurs bases de données contenant des définitions pour, par exemple, des signaux sur un bus. Ensuite, vous souhaitez rassembler ces informations, éventuellement effectuer quelques vérifications de cohérence, puis écrire un code assurant la liaison entre les signaux provenant du bus et les variables que vous vous attendez à avoir dans votre code. Si vous pouvez me montrer un langage avec une méta-programmation qui facilite l'utilisation de feuilles Excel fournies par le client, d'une base de données et d'autres sources de données et crée le code dont j'ai besoin, avec les vérifications nécessaires sur la validité et la cohérence des données, puis tous les moyens me montrent.
codemonkey

@CodeMonkey: quelque chose comme l'implémentation ActiveRecord de Ruby on Rails me vient à l'esprit. Il n'est pas nécessaire de dupliquer le schéma de la table de base de données dans le code. Mappez simplement une classe sur une table et écrivez une logique métier en utilisant les noms de colonne comme propriétés. Je ne peux imaginer aucun type de modèle pouvant être généré par un générateur de code qui ne pourrait pas également être géré par la méta-programmation Ruby. Les modèles C ++ sont également extrêmement puissants, même s'ils sont un peu mystérieux. Les macros Lisp sont un autre puissant système de méta-programmation en langage.
kevin cline

@kevincline, ce que je voulais dire était un code basé sur certaines données de la base de données (pouvant être construite à partir de celle-ci), mais pas la base de données elle-même. C'est-à-dire que j'ai des informations sur les signaux que je reçois dans Excel Table A. J'ai une base de données B avec des informations sur ces signaux, etc. Maintenant, je veux une classe qui accède à ces signaux. Il n'y a pas de connexion à la base de données ou à la feuille Excel sur la machine qui exécute le code. Utiliser des modèles C ++ très compliqués pour générer ce code au moment de la compilation, au lieu d'un simple générateur de code. Je vais choisir codegen.
CodeMonkey

6

La génération de code source n'est pas toujours un anti-modèle. Par exemple, j'écris actuellement un framework qui, par spécification donnée, génère du code dans deux langages différents (Javascript et Java). Le framework utilise le Javascript généré pour enregistrer les actions du navigateur de l'utilisateur et utilise le code Java de Selenium pour exécuter l'action lorsque le framework est en mode de relecture. Si je n'utilisais pas la génération de code, je devrais m'assurer manuellement que les deux sont toujours synchronisés, ce qui est fastidieux et constitue en quelque sorte une duplication logique.

Si toutefois on utilise la génération de code source pour remplacer des fonctionnalités telles que les génériques, alors c'est anti-pattern.


Vous pouvez bien sûr écrire votre code une fois dans ECMAScript et l'exécuter à Nashorn ou à Rhino sur la machine virtuelle. Vous pouvez également écrire une machine virtuelle Java dans ECMAScript (ou essayer de compiler Avian sur WebAssembly à l’aide d’Emscripten) et exécuter votre code Java dans le navigateur. Je ne dis pas que ce sont de bonnes idées (enfin, probablement, des idées terribles: D), mais au moins, elles sont possibles sinon réalisables.
Jörg W Mittag

En théorie, c'est possible, mais ce n'est pas une solution générale. Que se passe-t-il si je ne peux pas utiliser l'une des langues dans une autre? Par exemple, une chose supplémentaire: je viens de créer un modèle Netlogo simple à l'aide de la génération de code et de disposer d'une documentation interactive du système, qui est toujours synchronisée avec l'enregistreur et le lecteur. Et en général, la création d’une exigence puis la génération de code permettent de synchroniser les éléments sémantiquement.
Hristo Vrigazov

6

Est-ce que j'ai râté quelque chose?

Peut-être un bon exemple où le code intermédiaire s'est avéré être la raison du succès? Je peux vous offrir du HTML.

Je pense qu'il était important que le HTML soit simple et statique: il facilitait la création de navigateurs, permettait de démarrer les navigateurs mobiles plus tôt, etc. . Il s'avère que les utilisateurs sont réellement menacés par les applets Java et la visite de tels sites Web était aussi sûre que d'essayer des jeux téléchargés via DC ++. Le langage HTML simple, en revanche, est suffisamment inoffensif pour nous permettre de consulter tout site ayant une confiance raisonnable en la sécurité de notre appareil.

Cependant, HTML ne serait nulle part où il est maintenant s'il n'était pas généré par ordinateur. Ma réponse n'apparaît même pas sur cette page tant que quelqu'un n'a pas réécrit manuellement la base de données dans un fichier HTML. Heureusement, vous pouvez créer du HTML utilisable dans presque tous les langages de programmation :)

Autrement dit, s'il existe un générateur de code pour quelque chose, pourquoi ne pas en faire une fonction appropriée pouvant recevoir les paramètres requis et effectuer l'action correcte que le code "aurait généré" aurait fait?

Pouvez-vous imaginer un meilleur moyen d’afficher la question, ainsi que toutes les réponses et commentaires à l’utilisateur, en utilisant HTML comme code intermédiaire généré?


Oui, je peux imaginer un meilleur moyen. Le code HTML est l'héritage d'une décision de Tim Berners-Lee d'autoriser la création rapide d'un navigateur Web en mode texte. C'était parfaitement correct à l'époque, mais nous ne ferions pas la même chose avec le recul. CSS a rendu inutiles tous les différents types d’éléments de présentation (DIV, SPAN, TABLE, UL, etc.).
kevin cline

@kevincline Je ne dis pas que HTML en tant que tel est sans défaut, je soulignais que l'introduction d'un langage de balisage (pouvant être généré par un programme) fonctionnait très bien dans ce cas.
Džuris

Donc, HTML + CSS est meilleur que le HTML. J'ai même écrit de la documentation interne pour certains projets sur lesquels j'ai travaillé directement en HTML + CSS + MathJax. Mais la plupart des pages Web que je visite semblent avoir été produites par des générateurs de code.
David K

3

pourquoi générer du code source?

Parce que c'est plus rapide et plus facile (et moins sujet aux erreurs) que d'écrire le code manuellement, en particulier pour les tâches fastidieuses et répétitives. Vous pouvez également utiliser l'outil de haut niveau pour vérifier et valider votre conception avant d'écrire une seule ligne de code.

Cas d'utilisation courants:

  • Outils de modélisation comme Rose ou Visual Paradigm;
  • MISE EN ÉVIDENCE er langues de niveau comme Embedded SQL ou un langage de définition d'interface qui doit être prétraité en quelque chose compilable;
  • Des générateurs de lexer et d’analyseur tels que flex / bison;

En ce qui concerne votre "pourquoi ne pas simplement en faire une fonction et lui passer directement les paramètres", notez qu’aucun de ces éléments n’est un environnement d’exécution en soi. Il n'y a aucun moyen de lier votre code avec eux.


2

Parfois, votre langage de programmation ne dispose tout simplement pas des installations souhaitées, ce qui rend impossible l’écriture de fonctions ou de macros pour faire ce que vous voulez. Ou peut-être que vous pourriez faire ce que vous voulez, mais le code pour l'écrire serait moche. Un simple script Python (ou similaire) peut ensuite générer le code requis dans le cadre de votre processus de construction, que vous intégrez ensuite #includedans le fichier source réel.

Comment je sais ça? Parce que c’est une solution que j’ai choisie plusieurs fois lorsque je travaille avec différents systèmes, notamment SourcePawn. Un simple script Python qui analyse une simple ligne de code source et génère deux ou trois lignes de code généré est nettement préférable à la création manuelle du code généré, lorsque vous vous retrouvez avec deux douzaines de lignes de ce type (créant tous mes cvars).

Code source démonstratif / exemple disponible si les gens le souhaitent.


1

La forme du texte est requise pour une consommation facile par les humains. Les ordinateurs traitent également le code sous forme de texte assez facilement. Par conséquent, le code généré doit être généré sous la forme la plus facile à générer et à utiliser par les ordinateurs, ce qui est très souvent un texte lisible.

Et lorsque vous générez du code, le processus de génération de code lui-même doit souvent être débogué - par des humains. C'est très, très utile si le code généré est lisible par l'homme afin que les humains puissent détecter les problèmes dans le processus de génération de code. Quelqu'un doit écrire le code pour générer du code, après tout. Cela ne vient pas de nulle part.


1

Générer du code, juste une fois

Toute la génération de code source ne consiste pas à générer du code et à ne jamais le toucher; puis le régénérer à partir de la source originale quand il a besoin de mise à jour.

Parfois, vous générez du code une seule fois, puis vous supprimez la source d'origine et vous conservez ensuite la nouvelle source.

Cela se produit parfois lors du transfert de code d'une langue à une autre. En particulier si on ne souhaite pas reporter ultérieurement les modifications apportées à l'original (par exemple, le code de l'ancienne langue ne sera pas conservé ou il est en fait complet (par exemple, dans le cas de certaines fonctionnalités mathématiques)).

Un cas courant est que l’écriture d’un générateur de code à cette fin pourrait ne traduire en réalité que 90% du code correctement. et ensuite, les 10% restants doivent être réparés à la main. Ce qui est beaucoup plus rapide que de traduire 100% à la main.

De tels générateurs de code sont souvent très différents du type de générateurs de code produits par les traducteurs en langage intégral (comme Cython ou f2c). Depuis l'objectif est de faire maintenir le code une fois. Ils sont souvent fabriqués en tant que 1, pour faire exactement ce qu'ils doivent faire. À bien des égards, il s’agit de la version de niveau supérieur de l’utilisation d’un code regex / find-replace pour le port. "Portage assisté par un outil" pourrait-on dire.

Générer du code, juste une fois, à partir, par exemple, d'un site web à gratter.

En relation étroite, si vous générez le code à partir d'une source à laquelle vous ne souhaitez plus avoir accès. Par exemple, si les actions nécessaires pour générer le code ne sont pas reproductibles, cohérentes ou si leur exécution coûte cher. Je travaille actuellement sur deux projets: DataDeps.jl et DataDepsGenerators.jl .

DataDeps.jl aide les utilisateurs à télécharger des données (comme des jeux de données ML standard). Pour ce faire, il faut ce que nous appelons RegistrationBlock. Il s’agit d’un code spécifiant des métadonnées, telles que le lieu de téléchargement des fichiers, une somme de contrôle et un message expliquant à l’utilisateur les termes / coditions / l’état de la licence des données.

Écrire ces blocs peut être ennuyant. Et cette information est souvent disponible dans (structurée ou non) à partir de sur les sites Web où les données sont hébergées. Donc, DataDepsGenerators.jl, utilise un webscraper pour générer le RegistrationBlockCode, pour certains sites hébergeant beaucoup de données.

Cela pourrait ne pas les générer correctement. Donc, le développeur utilisant le code généré peut et doit le vérifier et le corriger. Il y a de fortes chances qu'ils veuillent s'assurer qu'ils n'ont pas raté les informations de licence, par exemple.

Il est important de noter que les utilisateurs / devs travaillant avec DataDeps.jl n’ont pas besoin d’installer ou d’utiliser le navigateur Web pour utiliser le code RegistrationBlock généré. (Et ne pas avoir besoin de télécharger et d'installer un Web-Scraper permet d'économiser un peu de temps. En particulier pour les courses CI)

Générer du code source une fois n'est pas un anti-modèle. et il ne peut normalement pas être remplacé par une métaprogrammation.


"report" est un mot anglais qui signifie autre chose que "port again". Essayez de "reporter" pour rendre cette phrase plus claire. (Commentaires car trop petit pour un montage suggéré.)
Peter Cordes

Bonne prise @PeterCordes, j'ai reformulé.
Lyndon White

Plus rapide, mais potentiellement beaucoup moins gérable, en fonction de l’horrible code généré. Le Fortran en C était une chose à l’époque (les compilateurs C étaient plus largement disponibles, donc les gens utiliseraient f2c+ cc), mais le code résultant n’était pas vraiment un bon point de départ pour une version C du programme, AFAIK.
Peter Cordes

1
Potentiellement, potentiellement pas. Ce n’est pas la faute dans le concept de générateurs de code que certains générateurs génèrent du code non maintenable. En particulier, un outil fabriqué à la main, qui ne doit pas attraper tous les cas, permet souvent de créer un code parfait. Si 90% du code est simplement une liste de constantes de tableau par exemple, alors générer ces constructeurs de tableaux en un seul peut être fait de manière triviale très facilement et avec un minimum d'effort. (D'un autre côté, le code C produit par Cython ne peut pas être conservé par les humains. Parce qu'il n'est pas destiné à l'être. Tout comme vous dites pour le f2cretour du jour.)
Lyndon White

1
La grande table était l'argument le plus simple, le plus réduit. On peut dire la même chose pour convertir par exemple des boucles ou des conditions. Cela sedva en effet très loin, mais il faut parfois un peu plus de pouvoir expressif. La ligne entre la logique du programme et les données est souvent fine. Parfois, la distinction n'est pas utile. JSON est (/ était) juste le code constructeur de l'objet javascript. Dans mon exemple, je génère également du code de constructeur d'objet (s'agit-il de données? Peut-être (peut-être pas, car il comporte parfois des appels de fonction). Est-il préférable de le traiter comme un code? Oui.)
Lyndon White

1

La génération de code "source" est une indication d'une lacune du langage généré. Est-ce que l'utilisation d'outils pour surmonter ceci est un anti-modèle? Absolument pas, laissez-moi vous expliquer.

Généralement, la génération de code est utilisée car il existe une définition de niveau supérieur qui peut décrire le code résultant beaucoup moins verbeusement que le langage de niveau inférieur. La génération de code facilite donc l’efficacité et la concision.

Quand j'écris c ++, je le fais parce que cela me permet d'écrire du code plus efficace que d'utiliser du code assembleur ou machine. Le code machine est encore généré par le compilateur. Au début, c ++ était simplement un préprocesseur qui générait du code C. Les langages à usage général sont parfaits pour générer un comportement à usage général.

De la même manière, en utilisant un DSL (langage spécifique à un domaine), il est possible d'écrire des mots, mais peut-être que le code est restreint à une tâche spécifique. Cela rendra moins compliqué de générer le comportement correct du code. Rappelez-vous que le code est un moyen de se terminer . Ce qu’un développeur recherche, c’est un moyen efficace de générer un comportement.

Idéalement, le générateur peut créer un code rapide à partir d’une entrée plus simple à manipuler et à comprendre. Si cela est accompli, ne pas utiliser de générateur est un anti-motif . Cet anti-motif provient généralement de la notion que le code "pur" est "plus propre", de la même manière qu'un ouvrier du bois ou un autre artisan pourrait envisager l'utilisation d'outils électriques, ou l'utilisation de la CNC pour "générer" des pièces (think golden marteau ).

D'autre part, si la source du code généré est plus difficile à gérer ou à générer du code pas assez efficace, l'utilisateur tombe dans le piège qui consiste à utiliser les mauvais outils (parfois à cause du même marteau d'or ).


0

La génération de code source signifie absolument que le code généré est une donnée. Mais ce sont des données de première classe, des données que le reste du programme peut manipuler.

Les deux types de données les plus courants que je connaisse et qui sont intégrés au code source sont les informations graphiques sur les fenêtres (nombre et emplacement des divers contrôles) et les ORM. Dans les deux cas, l'intégration via la génération de code facilite la manipulation des données, car vous n'avez pas à passer par des étapes "spéciales" supplémentaires pour les utiliser.

Lorsque vous travaillez avec les Macs d'origine (1984), les définitions de dialogue et de fenêtre ont été créées à l'aide d'un éditeur de ressources dans lequel les données sont conservées au format binaire. L'utilisation de ces ressources dans votre application était plus difficile que si le "format binaire" avait été Pascal.

Donc, non, la génération de code source n'est pas un anti-motif, elle permet de rendre la partie données de l'application, ce qui facilite son utilisation.


0

La génération de code est un anti-modèle quand il en coûte plus que ce qu'il accomplit. Cette situation se produit lorsque la génération a lieu de A à B, où A est presque le même langage que B, mais avec quelques extensions mineures qui pourraient être effectuées simplement en codant en A avec moins d’effort que tous les outils personnalisés et l’établissement de la compilation de A à B. .

Le compromis est plus prohibitif vis-à-vis de la génération de code dans des langues dépourvues de fonctions de méta-programmation (macros structurelles) en raison des difficultés et des insuffisances liées à la réalisation de la métaprogrammation via la mise en scène du traitement de texte externe.

Le compromis médiocre pourrait également être lié à la quantité d'utilisation. La langue A peut être substantiellement différente de B, mais le projet dans son ensemble avec son générateur de code personnalisé n’utilise A que dans un ou deux petits endroits, de sorte que la complexité totale (petits bits de A, plus le générateur de code A -> B, plus la mise en scène environnante) dépasse la complexité d’une solution qui vient d’être réalisée en B.

Fondamentalement, si nous nous engageons à générer du code, nous devrions probablement «aller gros ou aller chez nous»: lui donner une sémantique substantielle, et l’utiliser beaucoup, ou ne pas déranger.


Pourquoi avez-vous supprimé le paragraphe "Quand Bjarne Stroustrup a pour la première fois implémenté C ++ ..."? Je pense que c'était intéressant.
Utku

@Utku D'autres réponses couvrent cela du point de vue de la compilation d'un langage complet et sophistiqué, dans lequel le reste d'un projet est entièrement écrit. Je ne pense pas que cela soit représentatif de la majorité de ce qu'on appelle la "génération de code".
Kaz

0

Je n'ai pas vu cela énoncé clairement (je l'ai vu évoqué par une ou deux réponses, mais cela ne semblait pas très clair)

La génération de code (comme vous l'avez dit, comme s'il s'agissait de données) n'est pas un problème, c'est un moyen de réutiliser un compilateur à des fins secondaires.

L’édition de code généré est l’un des anti-patterns les plus insidieux, pervers et horribles que vous rencontrerez jamais. Ne faites pas cela.

Au mieux, la modification du code généré génère un lot de codes de mauvaise qualité dans votre projet (l'ensemble ENTIER de code est désormais véritablement CODE SOURCE - plus de données). Dans le pire des cas, le code introduit dans votre programme est hautement redondant, avec des ordures mal nommées et presque impossibles à maintenir.

Je suppose qu'une troisième catégorie est le code que vous utilisez une fois (générateur d'interface graphique?) Puis que vous modifiez pour vous aider à démarrer / à apprendre. C’est un peu de chaque - cela peut être un bon moyen de commencer, mais votre générateur d’IUG sera destiné à utiliser du code "Generatable" qui ne sera pas un bon début pour vous en tant que programmeur - En plus, vous pourriez être tenté de le réutiliser pour une seconde interface graphique, ce qui signifie qu’il faut extraire du code SOURCE redondant dans votre système.

Si vos outils sont suffisamment intelligents pour interdire toute modification du code généré, lancez-vous. Sinon, je dirais que c'est l'un des pires anti-modèles du marché.


0

Le code et les données sont les suivants: Information.

Les données sont les informations exactement sous la forme dont vous avez besoin (et valeur). Le code est aussi une information, mais sous une forme indirecte ou intermédiaire. En substance, le code est également une forme de données.

Plus spécifiquement, le code est une information permettant aux machines de décharger des êtres humains du traitement d’informations par eux-mêmes.

Décharger des êtres humains du traitement de l'information est le motif le plus important. Les étapes intermédiaires sont acceptables dans la mesure où elles facilitent la vie. C'est pourquoi des outils de mappage d'informations intermédiaires existent. Comme les générateurs de code, les compilateurs, les transpileurs, etc.

pourquoi générer du code source? Pourquoi ne pas en faire une fonction capable d’accepter des paramètres et d’agir sur ceux-ci?

Supposons que quelqu'un vous propose une telle fonction de cartographie, dont l'implémentation vous est obscure. Tant que la fonction fonctionne comme promis, vous soucieriez-vous de générer du code source en interne ou non?


0

Si quelque chose peut être généré, alors ce sont des données, pas du code.

Dans la mesure où vous stipulez plus tard que le code est une donnée, votre proposition se réduit à "Si quelque chose peut être généré, alors ce n'est pas du code". Diriez-vous alors que le code assembleur généré par un compilateur C n'est pas un code? Et si cela coïncide exactement avec le code d'assemblage que j'écris à la main? Vous pouvez y aller si vous le souhaitez, mais je ne viendrai pas avec vous.

Commençons plutôt avec une définition de "code". Sans être trop technique, une définition plutôt bonne pour les besoins de cette discussion serait "des instructions exploitables par une machine pour effectuer un calcul".

Dans ces conditions, l’idée même de la génération de code source n’est-elle pas un malentendu?

Eh bien oui, votre proposition de départ est que le code ne peut pas être généré, mais je rejette cette proposition. Si vous acceptez ma définition de «code», la génération de code en général ne devrait poser aucun problème conceptuel.

Autrement dit, s'il existe un générateur de code pour quelque chose, pourquoi ne pas en faire une fonction appropriée pouvant recevoir les paramètres requis et effectuer l'action correcte que le code "aurait généré" aurait fait?

Eh bien, c’est une question tout à fait différente, sur la raison d’utiliser la génération de code, plutôt que sur sa nature. Vous proposez comme alternative que, au lieu d'écrire ou d'utiliser un générateur de code, on écrit une fonction qui calcule directement le résultat. Mais dans quelle langue? Il est révolu le temps où quiconque écrivait directement dans le code machine et si vous écrivez votre code dans une autre langue, vous dépendez d'un générateur de code sous la forme d'un compilateur et / ou d'un assembleur pour générer un programme qui s'exécute réellement.

Pourquoi, alors, préférez-vous écrire en Java, en C, en Lisp ou ailleurs? Même assembleur? J'affirme que c'est au moins en partie parce que ces langages fournissent des abstractions pour les données et les opérations qui facilitent l'expression des détails du calcul que vous voulez effectuer.

Il en va de même pour la plupart des générateurs de code de niveau supérieur. Les cas prototypiques sont probablement des générateurs de scanner et d’analyseur tels que lexet yacc. Oui, vous pouvez écrire un scanner et un analyseur directement en C ou dans un autre langage de programmation de votre choix (même du code machine brut), et parfois un. Mais pour un problème de complexité significative, l'utilisation d'un langage de niveau supérieur, tel que lex ou le yacc, facilite l'écriture, la lecture et la maintenance du code manuscrit. Habituellement beaucoup plus petit aussi.

Vous devriez également considérer ce que vous entendez exactement par "générateur de code". Je considérerais le prétraitement C et l’instanciation de modèles C ++ comme des exercices de génération de code; vous opposez-vous à cela? Sinon, je pense que vous devrez effectuer une gymnastique mentale pour rationaliser l'acceptation de celles-ci, mais en rejetant d'autres versions de la génération de code.

Si cela est fait pour des raisons de performances, cela ressemble à une lacune du compilateur.

Pourquoi? Vous pensez en principe qu’il faut un programme universel vers lequel l’utilisateur insère des données, certaines classées comme "instructions" et d’autres comme "entrée", et qui effectue le calcul et émet plus de données que nous appelons "sortie". (D'un certain point de vue, on pourrait appeler ce programme universel "système d'exploitation".) Mais pourquoi supposez-vous qu'un compilateur devrait être aussi efficace pour optimiser un programme aussi polyvalent que pour optimiser un logiciel plus spécialisé? programme? Les deux programmes ont des caractéristiques et des capacités différentes.

Si cela est fait pour relier deux langues, cela ressemble à un manque de bibliothèque d’interface.

Vous dites que comme si une bibliothèque d'interface universelle à un certain degré serait nécessairement une bonne chose. Peut-être que ce serait le cas, mais dans de nombreux cas, une telle bibliothèque serait volumineuse et difficile à écrire et à gérer, voire lente. Et si une telle bête n’existe pas en réalité pour résoudre le problème particulier qui se pose, alors qui souhaitez-vous qu’elle soit créée, quand une approche de génération de code peut résoudre le problème beaucoup plus rapidement et facilement?

Est-ce que j'ai râté quelque chose?

Plusieurs choses, je pense.

Je sais que ce code est aussi une donnée. Ce que je ne comprends pas, c'est pourquoi générer du code source? Pourquoi ne pas en faire une fonction capable d’accepter des paramètres et d’agir sur ceux-ci?

Les générateurs de code transforment le code écrit dans une langue en code dans une langue différente, généralement de niveau inférieur. Vous vous demandez alors pourquoi les gens voudraient écrire des programmes dans plusieurs langues et surtout pourquoi ils pourraient vouloir mélanger des langues de niveaux subjectivement différents.

Mais j'ai déjà abordé ce sujet. On choisit un langage pour une tâche particulière, basé en partie sur sa clarté et son expressivité pour cette tâche. Dans la mesure où le code plus petit contient en moyenne moins de bogues et est plus facile à gérer, il existe également un biais en faveur des langages de niveau supérieur, du moins pour les travaux à grande échelle. Cependant, un programme complexe implique de nombreuses tâches. Souvent, certaines d'entre elles peuvent être traitées plus efficacement dans une langue, alors que d'autres sont traitées de manière plus efficace ou plus concise dans une autre langue. Utiliser le bon outil pour le travail signifie parfois employer de la génération de code.


0

Répondre à la question dans le contexte de votre commentaire:

Le compilateur doit prendre un code écrit sous une forme lisible par l'homme et le convertir en un format lisible par une machine. Par conséquent, si le compilateur ne peut pas créer un code efficace, il ne fait pas son travail correctement. Est-ce faux?

Un compilateur ne sera jamais optimisé pour votre tâche. La raison en est simple: elle est optimisée pour effectuer de nombreuses tâches. C'est un outil à usage général utilisé par de nombreuses personnes pour différentes tâches. Une fois que vous savez quelle est votre tâche, vous pouvez aborder le code de manière spécifique à un domaine, en faisant des compromis que les compilateurs ne pourraient pas.

Par exemple, j'ai travaillé sur des logiciels pour lesquels un analyste peut avoir besoin d'écrire du code. Ils pourraient écrire leur algorithme en C ++ et ajouter toutes les astuces de vérification des liens et de mémorisation dont ils dépendent, mais cela nécessite de connaître beaucoup le fonctionnement interne du code. Ils préfèrent écrire quelque chose de simple et me laisser jeter un algorithme pour générer le code C ++ final. Je peux ensuite réaliser des astuces exotiques pour optimiser les performances, telles que l'analyse statique, que je ne m'attendrais jamais de la part de mes analystes. La génération de code leur permet d'écrire d'une manière spécifique à un domaine, ce qui leur permet d'obtenir le produit plus facilement que n'importe quel outil à usage général.

J'ai aussi fait exactement le contraire. J'ai un autre travail que j'ai fait qui avait un mandat "pas de génération de code". Nous voulions toujours faciliter la vie des utilisateurs du logiciel. Nous avons donc utilisé une quantité considérable de métaprogrammation de modèles pour que le compilateur génère le code à la volée. Ainsi, je n'avais besoin que du langage généraliste C ++ pour faire mon travail.

Cependant, il y a un piège. Il était extrêmement difficile de garantir la lisibilité des erreurs. Si vous avez déjà utilisé un code métaprogrammé de modèle auparavant, vous savez qu'une seule erreur innocente peut générer une erreur qui prend 100 lignes de noms de classe incompréhensibles et d'arguments de modèle pour comprendre ce qui s'est mal passé. Cet effet était tellement prononcé que le processus de débogage recommandé pour les erreurs de syntaxe était "Faites défiler le journal des erreurs jusqu'à ce que vous voyiez la première fois qu'un de vos propres fichiers comporte une erreur. Allez à cette ligne, a eu tort. "

Si nous avions utilisé la génération de code, nous aurions pu disposer de capacités de traitement d'erreur beaucoup plus puissantes, avec des erreurs lisibles par l'homme. C'est la vie.


0

Il y a différentes manières d'utiliser la génération de code. Ils pourraient être divisés en trois groupes principaux:

  • Génération de code dans une langue différente en tant que sortie d’une étape du processus de compilation. Pour le compilateur typique, il s'agirait d'un langage de niveau inférieur, mais il pourrait s'agir d'un autre langage de haut niveau, comme dans le cas des langages qui compilent en JavaScript.
  • Génération ou transformation de code dans le langage de code source en tant qu’étape du processus de compilation. C'est ce que font les macros .
  • Générer du code avec un outil séparément du processus de compilation habituel. Le résultat obtenu est un code qui vit sous forme de fichiers avec le code source standard et qui est compilé avec ce dernier. Par exemple, les classes d'entités d'un ORM peuvent être générées automatiquement à partir d'un schéma de base de données, ou les objets de transfert de données et les interfaces de service peuvent être générés à partir d'une spécification d'interface, telle qu'un fichier WSDL pour SOAP.

J'imagine que vous parlez du troisième type de code généré, puisqu'il s'agit de la forme la plus controversée. Dans les deux premières formes, le code généré est une étape intermédiaire qui est très nettement séparée du code source. Mais dans la troisième forme, il n'y a pas de séparation formelle entre le code source et le code généré, sauf que le code généré a probablement un commentaire qui dit "ne modifie pas ce code". Cela laisse quand même le risque aux développeurs d’éditer le code généré, ce qui serait vraiment moche. Du point de vue du compilateur, le code généré est le code source.

Néanmoins, de telles formes de code généré peuvent être vraiment utiles dans un langage à typage statique. Par exemple, lors de l'intégration avec des entités ORM, il est vraiment utile de disposer de wrappers fortement typés pour les tables de la base de données. Bien sûr, vous pouvez gérer l'intégration de manière dynamique au moment de l'exécution, mais vous perdriez la sécurité du type et le support des outils (complétion de code). Un avantage majeur du langage de type statique est la prise en charge du système de type au type d’écriture plutôt qu’au moment de l’exécution. (Inversement, ce type de génération de code n’est pas très répandu dans les langages à typage dynamique car, dans un tel langage, il n’offre aucun avantage par rapport aux conversions d’exécution.)

Autrement dit, s'il existe un générateur de code pour quelque chose, pourquoi ne pas en faire une fonction appropriée pouvant recevoir les paramètres requis et effectuer l'action correcte que le code "aurait généré" aurait fait?

Étant donné que la sécurité de type et l'achèvement de code sont des fonctionnalités souhaitées au moment de la compilation (et lors de l'écriture de code dans un IDE), les fonctions standard ne sont exécutées qu'au moment de l'exécution.

Cependant, il peut y avoir un terrain d’entente: F # supporte le concept de fournisseurs de types qui est essentiellement des interfaces fortement typées générées par programme lors de la compilation. Ce concept pourrait probablement remplacer de nombreuses utilisations de la génération de code et offrir une séparation plus nette des problèmes.


0

Les jeux d'instructions du processeur sont fondamentalement impératifs , mais les langages de programmation peuvent être déclaratifs . L'exécution d'un programme écrit dans un langage déclaratif nécessite inévitablement un certain type de génération de code. Comme mentionné dans cette réponse et d'autres, l'une des principales raisons de générer du code source dans un langage lisible par l'homme est de tirer parti des optimisations sophistiquées effectuées par les compilateurs.


-3

Si quelque chose peut être généré, alors ce sont des données, pas du code.

Vous l'avez mal compris. Il faut lire

Si quelque chose peut être introduit dans un générateur pour les interprétables , alors c'est du code, pas des données.

C'est le format source de cette étape de compilation, et le format du récepteur est toujours du code.


1
Mauvaise définition du code source . Le code source est principalement destiné aux humains qui y travaillent (et ce fait le définit, voyez aussi ce que sont les logiciels libres de la FSF). Le code d'assembleur généré avec gcc -fverbose-asm -O -Sn'est pas un code source (et il ne s'agit pas uniquement de données), même s'il s'agit d'une forme textuelle toujours alimentée par GNU aset parfois lue par des humains.
Basile Starynkevitch

En outre, de nombreuses implémentations de langages se compilent en code C , mais celui-ci généré n’est pas un code source authentique (par exemple, il est difficile à l’utilisateur de travailler dessus).
Basile Starynkevitch

Enfin, votre matériel (par exemple, votre puce AMD ou Intel, ou la carte mère de votre ordinateur) interprète le code machine (qui n'est évidemment pas un code source). BTW Le IBM1620 avait un code machine typable au clavier (BCD), mais ce fait ne le rendait pas "code source". Tout le code n'est pas la source.
Basile Starynkevitch

@ BasileStarynkevitch Ah, vous m'avez eu là. Je ne devrais pas essayer de trop compresser ma déclaration spirituelle, sinon ils changeront de signification. À droite, le code source devrait être le code le plus original introduit dans la première phase de compilation.
Bergi

Aucun code source n'est un code pour les humains. Il est aussi difficile et subjectif de définir une musique (ou un son). Il ne s'agit pas d'essayer de trouver le logiciel qui le consomme.
Basile Starynkevitch
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.