Si quelque chose peut être généré, alors ce sont des données, pas du code.
Cela étant, cette idée 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, alors pourquoi ne pas faire de ce quelque chose une fonction appropriée qui peut recevoir les paramètres requis et faire la bonne action 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 des données. Ce que je ne comprends pas, c'est pourquoi générer du code source? Pourquoi ne pas en faire une fonction qui peut accepter des paramètres et agir sur eux?
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 source même si c'est du texte lisible par l'homme. Le code source est un code original, généré par une véritable intelligence humaine ou autre, non traduit mécaniquement et pas immédiatement reproductible à partir de la (vraie) 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 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 dans d'autres formes - de données - dont certaines peuvent être exécutables.
Le processeur exécute les instructions hors de la mémoire. La même mémoire utilisée pour les données. Avant que le processeur n'exécute les instructions, le programme est chargé en mémoire en tant que données .
Donc, tout est des données , même du code .
Étant donné que [le code généré est des données], toute cette idée de la 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, alors pourquoi ne pas faire de ce quelque chose une fonction appropriée qui peut recevoir les paramètres requis et faire la bonne action 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, qui est quelque chose conçu pour être utilisé par un humain.
Tous les formulaires de texte ne sont pas destinés à 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 du code source. Le code généré, même lorsqu'il est lisible par l'homme, est généralement régénéré à partir du code source d'origine . Le code généré, de manière générale, n'a pas besoin d'être sous contrôle de code source car il est régénéré pendant la construction.
OK, je sais que le code est aussi des données. Ce que je ne comprends pas, c'est pourquoi générer du code source?
À partir de ce montage, je suppose que vous posez des questions à un niveau plutôt pratique, et non théorique en informatique.
La raison classique pour générer du code source dans des langages statiques comme Java était que de tels langages n'étaient tout simplement pas vraiment fournis avec des outils en langage faciles à utiliser pour faire des choses très dynamiques. Par exemple, à l'époque formative de Java, il n'était tout simplement pas possible de créer facilement une classe avec un nom dynamique (correspondant à un nom de table à partir d'une base de données) et des méthodes dynamiques (correspondant aux attributs de cette table) avec des types de données dynamiques (correspondant les types desdits attributs). Surtout puisque Java met beaucoup d'importance, non, des garanties, pour pouvoir attraper des erreurs de type lors de la compilation.
Ainsi, dans un tel paramètre, un programmeur ne peut créer que du code Java et écrire beaucoup de lignes de code manuellement. Souvent, le programmeur trouvera que chaque fois qu'une table change, il doit revenir en arrière et changer le code pour correspondre; et s'il l'oublie, de mauvaises choses se produisent. Par conséquent, le programmeur arrivera au point où il écrit des outils qui le font pour lui. Et donc la route commence vers une génération de code toujours plus intelligente.
(Oui, vous pourriez générer le bytecode à la volée, mais programmer une telle chose dans Java ne serait pas quelque chose qu'un programmeur aléatoire ferait juste entre l'écriture de quelques lignes de code de domaine.)
Comparez cela à des langages très dynamiques, par exemple Ruby, que je considérerais comme l'antithèse de Java à bien des égards (notez que je dis cela 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 directement dans le code, sans passer par un niveau "méta". Oui, des choses comme Ruby sur Rails viennent avec la génération de code, mais nous avons trouvé dans notre travail que nous l'utilisons essentiellement comme une sorte de "mode tutoriel" avancé pour les nouveaux programmeurs, mais après cela devient superflu (car il y a si peu de code à écrire dans cet écosystème que lorsque vous savez ce que vous faites, l'écrire manuellement devient plus rapide que de nettoyer le 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 is data, littéralement. D'autre part, dans les langages compilés (sans moteur d'exécution comme Java ou Ruby), il n'y a (ou n'était, je n'ai pas suivi les fonctionnalités C++ modernes ...) tout simplement pas de concept de définition de classe ou les noms de méthode lors de l'exécution, donc la génération de code, le processus de construction est l'outil de choix pour la plupart des choses (d'autres exemples plus spécifiques C/C++ seraient des choses comme flex, yacc etc.).
pourquoi générer du code?
Parce que la programmation avec des cartes perforées (ou 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 tu parles.
Regardez, c'est comme ça: Le code source généré et conservé est toujours et à jamais une douleur dans le cul. Il existe pour une seule raison. Quelqu'un veut travailler dans une langue tandis que quelqu'un d'autre insiste pour travailler dans une autre et ni l'un ni l'autre ne peut être dérangé pour comprendre comment interagir entre eux, alors l'un d'eux trouve comment transformer sa langue préférée en langue imposée afin qu'il puisse faire quoi Ils veulent.
Ce qui est bien jusqu'à ce que je doive le maintenir. À quel point vous pouvez tous aller mourir.
Est-ce un motif anti? Soupir, non. De nombreuses langues n'existeraient même pas si nous ne voulions pas dire adieu aux lacunes des langues précédentes et générer le code des langues plus anciennes est le nombre de nouvelles langues qui commencent.
C'est une base de code qui est laissée dans un patchwork de monstres Frankenstein à moitié converti que je ne peux pas supporter. Le code généré est un code intouchable. Je déteste regarder du code intouchable. Pourtant, les gens continuent de vérifier. POURQUOI? Vous pourriez tout aussi bien archiver l'exécutable.
Eh bien maintenant je me déchaîne. Mon point est que nous "générons tous du code". C'est lorsque vous traitez le code généré comme du code source que vous me rendez fou. Ce n'est pas parce que le code source en fait que c'est du code source.
pourquoi générer du code source
Le cas d'utilisation le plus fréquent pour les générateurs de code avec lesquels j'ai dû travailler dans ma carrière était les générateurs qui
a pris une méta-description de haut niveau pour une sorte de modèle de données ou de schéma de base de données en entrée (peut-être un schéma relationnel ou une sorte de schéma XML)
et produit du code CRUD de plaque chauffante pour les classes d'accès aux données en sortie, et peut-être des choses supplémentaires comme les SQL ou la documentation correspondants.
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, sans danger pour les types, sans bogue (en supposant que la sortie des générateurs de code est mature) que vous auriez 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 modèle anti
Non, pas la génération de code source en soi, mais il y a effectivement des écueils. Comme indiqué dans The Pragmatic Programmer , il convient d'éviter l'utilisation d'un générateur de code lorsqu'il produit du code difficile à comprendre . Sinon, les efforts accrus pour utiliser ou déboguer ce code peuvent facilement l'emporter sur l'effort enregistré en n'écrivant pas le code manuellement.
Je voudrais également ajouter que c'est la plupart du temps une bonne idée de séparer physiquement les parties générées du code du code écrit manuellement de manière à ce que la régénération n'écrase aucune modification manuelle. Cependant, j'ai également traité plus d'une fois la situation où la tâche consistait à migrer du code écrit en ancien langage X vers un autre langage plus moderne Y, avec l'intention de faire ensuite la maintenance en langage Y. Ceci est une utilisation valide cas pour la génération de code unique.
Sussmann avait beaucoup de choses intéressantes à dire sur de telles choses dans son 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 adhoc consiste à 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, ne le faites pas, c'est moche), pensez aux feuilles de calcul du dictionnaire de données.
Les langues spécifiques à un domaine trivial peuvent être un énorme gain de temps, et produire 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 vous préférez modifier, un analyseur non piraté à la main dans la langue maternelle que vous êtes l'écriture, ou le BNF pour un généré automatiquement?
En générant du texte qui est ensuite envoyé à un compilateur système, j'obtiens toute l'optimisation des compilateurs et la configuration spécifique au système sans avoir à y penser.
J'utilise efficacement le langage d'entrée du compilateur comme une autre 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 se présentent comme C ou C++ ou Java ou autre chose, peu importe ?
Maintenant, si vous êtes difficile à penser vous pouvez modifier la SORTIE de l'analyseur de langage jouet, ce qui décevra clairement la prochaine fois que quelqu'un éditera les fichiers de langue d'entrée et reconstruira, la réponse est de ne pas valider la génération automatique IR vers le référentiel, faites-le générer par votre chaîne d'outils (Et évitez d'avoir de telles personnes dans votre groupe de développeurs, elles sont généralement plus heureuses de travailler dans le marketing).
Ce n'est pas tant un échec de l'expressivité dans nos langues, que l'expression du fait que parfois vous pouvez obtenir (ou masser) des parties de la spécification sous une forme qui peut être automatiquement convertie en code, et qui sera généralement beaucoup moins bugs et être beaucoup plus facile à maintenir. Si je peux donner à nos gars de test et de configuration une feuille de calcul, ils peuvent Tweak et un outil qu'ils exécutent ensuite qui prend ces données et crache un fichier hexadécimal complet pour le flash sur mon ECU alors c'est un gain de temps énorme par rapport à la traduction manuelle de la dernière configuration en un ensemble de constantes dans la langue du jour (Complet avec les 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 n'importe quel outil logique, le C intermédiaire est illisible, alors quoi? Le truc Matlab RTW de haut niveau n'a besoin que de connaître un sous-ensemble de C, et le compilateur C s'occupe des détails de la plate-forme. Le seul moment où un humain doit parcourir le C généré est lorsque les scripts RTW ont un bogue, et ce genre de chose est beaucoup plus facile à déboguer avec un IR lisible nominalement humain, puis avec juste un arbre d'analyse binaire.
Vous pouvez bien sûr écrire de telles choses sur du bytecode ou même du code exécutable, mais pourquoi feriez-vous cela? Nous avons des outils pour convertir un IR à ces choses.
Réponse pragmatique: la génération de code est-elle nécessaire et utile? Fournit-il quelque chose qui est 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 à une surcharge intellectuelle pour des résultats sous-optimaux?
OK, je sais que le code est aussi des données. Ce que je ne comprends pas, c'est pourquoi générer du code? Pourquoi ne pas en faire une fonction qui peut accepter des paramètres et agir sur eux?
Si vous devez poser cette question et qu'il n'y a pas de réponse claire, alors la génération de code est probablement superflue et ne fait que contribuer à l'exotisme et à beaucoup de frais généraux intellectuels dans votre base de code.
Pendant ce temps, si vous prenez quelque chose comme OpenShadingLanguage: https://github.com/imageworks/OpenShadingLanguage
... alors ces questions n'ont pas à être soulevées car elles sont immédiatement répondues par les résultats impressionnants.
OSL utilise la structure du compilateur LLVM pour traduire les réseaux de shaders en code machine à la volée (juste à temps, ou "JIT"), et dans le processus optimise fortement 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 pouvaient pas ont été connus lorsque les shaders ont été compilés à partir du code source. En conséquence, nous voyons nos réseaux d'ombrage OSL s'exécuter 25% plus rapidement que les shaders équivalents fabriqués à la main en C! (C'est ainsi que nos anciens shaders fonctionnaient dans notre moteur de rendu.)
Dans un tel cas, vous n'avez pas besoin de remettre en question 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 sur les lignes de "tais-toi et prends mon argent!" ou, "wow, nous devons aussi faire quelque chose comme ça."
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 jamais archivé):
Non, la génération de code intermédiaire n'est pas un anti-modèle. La réponse à l'autre partie de votre question, "Pourquoi le faire?", Est une question très large (et distincte), mais je vais quand même donner quelques raisons.
Prenons C et C++ comme exemples, car ils font partie des langages les plus connus.
Vous devez noter que la procession logique de la compilation du code C génère non pas du code machine, mais plutôt du code Assembly lisible par l'homme. De même, les anciens compilateurs C++ utilisés pour compiler physiquement le code C++ en code C. Dans cette chaîne d'événements, vous pouvez compiler du code lisible par l'homme 1 au code lisible par l'homme 2 au code lisible par l'homme 3 au code machine. "Pourquoi?" Pourquoi pas?
Si un code intermédiaire lisible par l'homme n'a jamais été généré, nous ne pourrions même pas avoir C ou C++. C'est certainement une possibilité; les gens empruntent le chemin de la moindre résistance à leurs objectifs, et si un autre langage gagnait Steam en premier à cause de la stagnation du développement de C, C pourrait être mort alors qu'il était encore jeune. Bien sûr, vous pourriez dire "Mais alors peut-être que nous utiliserions un autre langage, et ce serait peut-être mieux." Peut-être, ou peut-être que ce serait pire. Ou peut-être que nous serions tous encore en train d'écrire à l'Assemblée.
J'ai déjà travaillé sur des projets où le code doit être généré sur la base de données ou d'informations dans un autre document. Par exemple, un projet avait tous ses messages réseau et données constantes définis dans une feuille de calcul et un outil qui passerait par la feuille de calcul et générerait un lot de C++ et Java = code qui nous a permis 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'était des centaines (peut-être même des milliers, pas sûr) de structures et 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 s'il a été refait dans quelque chose comme Rhapsody, alors nous avons quand même du code généré à partir de Rhapsody.
De plus, avoir toutes ces données dans une feuille de calcul était bon dans un sens: cela nous permettait de représenter les données d'une manière que nous ne pourrions pas avoir si elles n'étaient que dans des fichiers de code source.
Quand j'ai fait un peu de travail dans la construction du compilateur, j'ai utilisé l'outil Antlr pour faire mon lexing et mon analyse. J'ai spécifié une grammaire de langage, puis j'ai utilisé l'outil pour cracher une tonne de code en C++ ou Java, puis j'ai utilisé ce code généré à côté de mon propre code et l'ai inclus dans la build.
Sinon, comment cela aurait-il pu être fait? Vous pourriez peut-être trouver une autre façon; il y a probablement d'autres façons. Mais pour ce travail, les autres moyens n'auraient pas été meilleurs que le code Lex/parse généré que j'avais.
Une réponse un peu plus pragmatique, en se concentrant sur pourquoi et non sur ce qui est et n'est pas du code source. Notez que la génération de code source fait partie du processus de génération dans tous ces cas - les fichiers générés ne doivent donc pas trouver leur chemin dans le contrôle de code source.
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.
Prenez TypeScript - les navigateurs ne peuvent pas l'interpréter, donc le processus de construction utilise un transpiler (traducteur de code en code) pour générer JavaScript. En fait, de nombreux langages compilés nouveaux ou ésotériques commencent par être transpilés en C avant d'obtenir un compilateur approprié.
Pour les projets intégrés (pensez IoT) écrits en C et utilisant un seul binaire (RTOS ou pas de système d'exploitation), il est assez facile de générer un tableau C avec les données à compiler comme si le code source normal, comme opposé à les lier directement comme ressources.
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 par nécessité une structure de valeur-clé - ce qui signifie que vous avez besoin de beaucoup de code passe-partout, vous manquez quelques vérifications au moment de la compilation (sur les clés et les types de valeurs en particulier), obtenez de meilleures performances et pas de complétion de code. Imaginez tous ces void*
en C ou cet énorme std::variant
en C++ (si vous avez C++ 17), certains langages peuvent ne pas avoir une telle fonctionnalité du tout.
Ce qui vous manque, c'est réutiliser.
Nous avons un outil incroyable pour transformer le texte du code source en binaire, appelé un compilateur. Ses entrées sont bien définies (généralement!), Et il a fallu beaucoup de travail pour affiner la façon dont il optimise. Si vous voulez réellement utiliser le compilateur pour effectuer certaines opérations, vous voulez utiliser un compilateur existant et non pas écrire le vôtre.
Beaucoup de gens inventent de nouveaux langages de programmation et écrivent leurs propres compilateurs. À peu près sans exception, ils le font tous parce qu'ils aiment le défi, pas parce qu'ils ont besoin les fonctionnalités que ce langage offre. Tout ce qu'ils font pourrait se faire dans une autre langue; ils créent simplement un nouveau langage parce qu'ils aiment ces fonctionnalités. Ce qui ne les obtiendra pas, c'est un compilateur optimisé, 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 seulement quelque chose que les humains lisent et écrivent. Les ordinateurs sont parfaitement à l'aise avec le texte aussi. En fait, les formats comme XML (et d'autres formats associés) réussissent parce que ils utilisent du texte brut. Les formats de fichiers binaires sont souvent obscurs et mal documentés, et un lecteur ne peut pas facilement découvrir comment ils fonctionnent. XML est relativement auto-documenté, ce qui permet aux gens d'écrire plus facilement du code qui utilise 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 fonctionnalités pour vous faciliter la vie. C'est peut-être un outil de mise en page GUI. Ce sont peut-être les interfaces signaux-et-slots que Qt fournit. C'est peut-être la façon dont TI's Code Composer Studio vous permet de configurer le périphérique avec lequel vous travaillez et de tirer les bonnes bibliothèques dans la build. Peut-être qu'il prend un dictionnaire de données et les typedefs auto-générés et les définitions de variables globales (oui, c'est encore beaucoup dans les logiciels embarqués). Quoi qu'il en soit, la façon la plus efficace de tirer parti de votre compilateur existant est de créer un outil qui prendra votre configuration il est et produit automatiquement le code dans la langue de votre choix.
C'est facile à développer et à tester, car vous savez ce qui se passe et vous pouvez lire le code source qu'il crache. Vous n'avez pas besoin de passer des années-homme à construire un compilateur pour rivaliser avec GCC. Vous n'avez pas besoin d'apprendre une nouvelle langue complète, ni d'exiger d'autres personnes. Tout ce que vous avez à faire est d'automatiser cette petite zone et tout le reste reste le même. Travail accompli.
La génération de code source est-elle un anti modèle?
C'est une solution de contournement pour un langage de programmation insuffisamment expressif. Il n'est pas nécessaire de générer du code dans un langage qui contient une méta-programmation intégrée adéquate.
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 dans Selenium pour réellement exécuter l'action lorsque le framework est en mode relecture. Si je n'ai pas utilisé la génération de code , Je devrais m'assurer manuellement que les deux sont toujours synchronisés, ce qui est lourd et constitue également une duplication logique d'une certaine manière.
Si toutefois on utilise la génération de code source pour remplacer des fonctionnalités comme les génériques, alors c'est anti-modèle.
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 proposer du HTML.
Je crois qu'il était important que le HTML soit simple et statique - cela facilitait la création de navigateurs, il permettait de démarrer les navigateurs mobiles tôt, etc. Comme l'ont montré d'autres expériences (applets Java, Flash) - des langages plus complexes et puissants entraînent plus de problèmes . Il s'avère que les utilisateurs sont réellement menacés par Java et la visite de ces sites Web étaient aussi sûrs que d'essayer les fissures de jeu téléchargées via DC++. Le HTML simple, d'autre part, est suffisamment inoffensif pour nous permettre de consultez tout site ayant une croyance raisonnable en la sécurité de notre appareil.
Cependant, HTML ne serait pas du tout où il se trouve actuellement s'il n'était pas généré par ordinateur. Ma réponse n'apparaîtrait même pas sur cette page jusqu'à ce que quelqu'un la réécrive manuellement de 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, alors pourquoi ne pas faire de ce quelque chose une fonction appropriée qui peut recevoir les paramètres requis et faire la bonne action que le code "aurait généré" aurait fait?
Pouvez-vous imaginer une meilleure façon d'afficher la question et toutes les réponses et commentaires à l'utilisateur qu'en utilisant HTML comme code intermédiaire généré?
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:
Quant à votre "pourquoi ne pas simplement en faire une fonction et lui passer directement des 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 contre eux.
Parfois, votre langage de programmation n'a tout simplement pas les fonctionnalités que vous souhaitez, ce qui rend impossible d'écrire des fonctions ou des 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 alors générer le code requis dans le cadre de votre processus de génération, que vous #include
dans le fichier source réel.
Comment le sais-je? Parce que c'est une solution que j'ai trouvée à plusieurs reprises en travaillant avec différents systèmes, plus récemment SourcePawn. Un simple script Python qui analyse une simple ligne de code source et produit deux ou trois lignes de code généré est bien mieux que de créer manuellement le code généré, lorsque vous vous retrouvez avec deux douzaines de ces lignes ( création de tous mes cvars).
Démonstration/exemple de code source disponible si les gens le souhaitent.
Toute génération de code source ne consiste pas à générer du code, puis à ne jamais le toucher; puis le régénérer à partir de la source d'origine lorsqu'il a besoin d'être mis à jour.
Parfois, vous générez du code une seule fois, puis supprimez la source d'origine et, en poursuivant, maintenez la nouvelle source.
Cela se produit parfois lors du portage de code d'une langue vers une autre. Surtout si l'on ne s'attend pas à vouloir porter plus tard de nouvelles modifications dans l'original (par exemple, l'ancien code de langue ne sera pas conservé, ou il est réellement 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 pour ce faire, ne peut réellement traduire que 90% du code correctement. puis les 10% restants doivent être réparés à la main. Ce qui est beaucoup plus rapide que de traduire 100% à la main.
Ces générateurs de code sont souvent très différents du type de générateurs de code traducteurs en langage complet (comme Cython ou f2c
) produire. Puisque le but est de faire maintenir le code une fois. Ils sont souvent faits en 1, pour faire exactement ce qu'ils doivent. À bien des égards, c'est la version de niveau supérieur de l'utilisation d'un regex/find-replace pour porter le code. "Portage assisté par outil", pourrait-on dire.
Il est étroitement lié si vous générez le code à partir d'une source à laquelle vous ne souhaitez plus accéder. Par exemple. Si les actions nécessaires pour générer le code ne sont pas répétables ou cohérentes, ou les exécuter coûte cher. Je travaille actuellement sur une paire de projets: DataDeps.jl et DataDepsGenerators.jl .
DataDeps.jl aide les utilisateurs à télécharger des données (comme les jeux de données ML standard). Pour ce faire, il a besoin de ce que nous appelons un RegistrationBlock. Il s'agit d'un code spécifiant certaines métadonnées, comme l'emplacement de téléchargement des fichiers, une somme de contrôle et un message expliquant à l'utilisateur les termes/coditions/quel est le statut de licence sur les données.
Écrire ces blocs peut être ennuyeux. Et ces informations sont souvent disponibles en (structurées ou non structurées) à partir des sites Web sur lesquels les données sont hébergées. Donc DataDepsGenerators.jl, utilise un Webscraper pour générer le RegistrationBlockCode, pour certains sites qui hébergent beaucoup de données.
Il pourrait ne pas les générer correctement. Ainsi, 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 veulent s'assurer qu'ils n'ont pas raté les informations de licence par exemple.
Surtout, les utilisateurs/développeurs travaillant avec DataDeps.jl n'ont pas besoin d'installer ou d'utiliser le Webscraper pour utiliser le code RegistrationBlock qui a été généré. (Et ne pas avoir besoin de télécharger et d'installer un Web-Scraper permet de gagner un peu de temps. En particulier pour les exécutions CI)
Générer une fois le code source n'est pas un contre-modèle. et il ne peut normalement pas être remplacé par une métaprogrammation.
Il existe différentes manières d'utiliser la génération de code. Ils pourraient être divisés en trois grands groupes:
Je suppose que vous parlez du troisième type de code généré, car c'est la forme la plus controversée. Dans les deux premiers formulaires, le code généré est une étape intermédiaire qui est très distinctement séparée du code source. Mais dans le troisième formulaire, 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 modifiez pas ce code". Cela ouvre toujours le risque que les développeurs éditent le code généré 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 typé statiquement. Par exemple, lors de l'intégration avec des entités ORM, il est vraiment utile d'avoir des wrappers fortement typés pour les tables de base de données. Bien sûr pourrait gérer l'intégration dynamiquement au moment de l'exécution, mais vous perdriez la sécurité des types et la prise en charge 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 niveau de l'é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 typés dynamiquement, 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, alors pourquoi ne pas faire de ce quelque chose une fonction appropriée qui peut recevoir les paramètres requis et faire la bonne action que le code "aurait généré" aurait fait?
Parce que la sécurité des types et l'achèvement du code sont des fonctionnalités que vous souhaitez au moment de la compilation (et lors de l'écriture de code dans un IDE), mais les fonctions régulières ne sont exécutées qu'au moment de l'exécution.
Il pourrait y avoir un juste milieu cependant: F # prend en charge le concept de fournisseurs de types qui est fondamentalement des interfaces fortement typées générées par programmation au moment de la compilation. Ce concept pourrait probablement remplacer de nombreuses utilisations de la génération de code et fournir une séparation plus nette des problèmes.
La génération de code "source" est une indication d'une lacune du langage généré. L'utilisation d'outils pour surmonter cela est-elle un anti-modèle? Absolument pas - laissez-moi vous expliquer.
La génération de code est généralement utilisée car il existe une définition de niveau supérieur qui peut décrire le code résultant beaucoup moins verbeux que le langage de niveau inférieur. La génération de code facilite donc l'efficacité et la concision.
Lorsque j'écris en c ++, je le fais car cela me permet d'écrire du code plus efficace que d'utiliser du code assembleur ou machine. Le code machine est 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 au domaine), il est possible d'écrire en lacet, mais peut-être en code restreint à une tâche spécifique. Cela rendra moins compliqué la génération du 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 du code rapide à partir d'une entrée plus simple à manipuler et à comprendre. Si cela est rempli , ne pas utiliser de générateur est un anti-modèle . Cet anti-modèle vient généralement de la notion que le code "pur" est "plus propre", de la même manière qu'un travailleur du bois ou un autre artisan peut envisager l'utilisation d'outils électriques ou l'utilisation de la CNC pour "générer" des pièces (pensez - marteau d'or ).
D'un autre côté, si la source du code généré est plus difficile à maintenir ou à générer du code qui n'est pas assez efficace, l'utilisateur tombe dans le piège d'utiliser les mauvais outils (parfois à cause du même Golden Hammer =).
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, et qui est très souvent du 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 les 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 des 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 se produit pas de nulle part.
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, une raison majeure pour 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.
La génération de code source signifie absolument que le code généré est des données. 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 connais qui sont intégrés dans le code source sont les informations graphiques sur les fenêtres (nombre et placement des différents 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 Mac d'origine (1984), les définitions de boîtes de dialogue et de fenêtres ont été créées à l'aide d'un éditeur de ressources qui conservait les données au format binaire. L'utilisation de ces ressources dans votre application était plus difficile qu'elle ne l'aurait été si le "format binaire" avait été Pascal.
Donc, non, la génération de code source n'est pas un anti-pattern, elle permet d'intégrer les données dans l'application, ce qui en facilite l'utilisation.
Le code et les données sont: Information.
Les données sont les informations exactement sous la forme dont vous avez besoin (et la 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 précisément, le code est une information permettant aux machines de décharger les humains du traitement des informations par eux-mêmes.
Décharger les humains du traitement de l'information est le motif le plus important. Les étapes intermédiaires sont acceptables tant qu'elles facilitent la vie. C'est pourquoi des outils intermédiaires de cartographie des informations existent. Comme les générateurs de code, les compilateurs, les transpilateurs, etc.
pourquoi générer du code source? Pourquoi ne pas en faire une fonction qui peut accepter des paramètres et agir sur eux?
Supposons que quelqu'un vous propose une telle fonction de mappage, dont l'implémentation vous est obscure. Tant que la fonction fonctionne comme promis, vous soucieriez-vous si en interne elle génère du code source ou non?
La génération de code est un anti-modèle lorsqu'elle coûte plus cher qu'elle ne le fait. 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 la création de stades pour A à B .
Le compromis est plus prohibitif contre la génération de code dans des langues qui ne disposent pas d'installations de méta-programmation (macros structurelles) en raison des complications et des insuffisances de la réalisation de la métaprogrammation grâce à la mise en scène du traitement de texte externe.
Le mauvais compromis pourrait également être lié à la quantité d'utilisation. Le langage A pourrait être sensiblement différent de B, mais l'ensemble du projet avec son générateur de code personnalisé n'utilise A qu'en un ou deux petits endroits, de sorte que la quantité totale de complexité (petits morceaux de A, plus le générateur de code A -> B, ainsi que la mise en scène de génération environnante) dépasse la complexité d'une solution qui vient d'être faite en B.
Fondamentalement, si nous nous engageons à générer du code, nous devrions probablement "aller grand ou rentrer à la maison": lui donner une sémantique substantielle, et l'utiliser beaucoup, ou ne pas déranger.
Je n'ai pas vu cela clairement énoncé (je l'ai vu touché 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.
La modification du code généré est l'un des anti-schémas les plus insidieux, maléfiques et horribles que vous rencontrerez jamais. Ne faites pas cela.
Au mieux, l'édition du code généré attire un tas de mauvais code dans votre projet (l'ensemble de code ENTIER est maintenant vraiment un CODE SOURCE - plus de données). Au pire, le code introduit dans votre programme est une redondance très redondante et mal nommée qui est presque complètement impossible à maintenir.
Je suppose qu'une troisième catégorie est le code que vous utilisez une fois (générateur gui?) Puis éditez pour vous aider à démarrer/apprendre. Ceci est un peu de chacun - cela PEUT être une bonne façon de commencer mais votre générateur GUI sera destiné à utiliser du code "Generatable" qui ne sera pas un bon début pour vous en tant que programmeur - En outre, vous pourriez être tenté de l'utiliser à nouveau pour une deuxième interface graphique, ce qui signifie tirer du code SOURCE redondant dans votre système.
Si votre outillage est suffisamment intelligent pour interdire toute modification du code généré, allez-y. Sinon, je dirais que c'est l'un des pires anti-modèles.
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 des données, votre proposition se réduit à "Si quelque chose peut être généré, alors cette chose n'est pas du code". Diriez-vous, alors, que le code d'assembly généré par un compilateur C n'est pas du code? Que se passe-t-il s'il 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 par une définition de "code". Sans devenir trop technique, une assez bonne définition aux fins de cette discussion serait "des instructions exécutables par machine pour effectuer un calcul".
Cela étant, cette idée 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", il ne devrait y avoir aucun problème conceptuel avec la génération de code en général.
Autrement dit, s'il existe un générateur de code pour quelque chose, alors pourquoi ne pas faire de ce quelque chose une fonction appropriée qui peut recevoir les paramètres requis et faire la bonne action que le code "aurait généré" aurait fait?
Eh bien, c'est une question entièrement différente, sur la raison pour l'utilisation de la génération de code, plutôt que sur sa nature. Vous proposez l'alternative qu'au lieu d'écrire ou d'utiliser un générateur de code, on écrit une fonction qui calcule le résultat directement. Mais dans quelle langue? Il est révolu le temps où quelqu'un écrivait directement dans le code machine, et si vous écrivez votre code dans un autre langage, vous dépendez d'un générateur de code sous la forme d'un compilateur et/ou d'un assembleur pour produire un programme qui s'exécute réellement.
Pourquoi, alors, préférez-vous écrire en Java ou C ou LISP ou quoi? 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 en font plus facile d'exprimer les détails du calcul que vous souhaitez 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 d'analyseurs et d'analyseurs tels que Lex
et 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 on le fait. Mais pour un problème d'une complexité importante, l'utilisation d'un langage spécial de niveau supérieur tel que Lex ou yacc facilite le code manuscrit à écrire, lire et maintenir. 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 des modèles C++ comme des exercices de génération de code; vous opposez-vous à ces derniers? Sinon, je pense que vous devrez effectuer une gymnastique mentale pour rationaliser l'acceptation de ceux-ci mais rejeter d'autres saveurs de génération de code.
Si cela est fait pour des raisons de performances, cela ressemble à une lacune du compilateur.
Pourquoi? Vous posez fondamentalement que l'on devrait avoir un programme universel auquel l'utilisateur alimente les données, certaines classées comme "instructions" et d'autres comme "entrée", et qui procède à effectuer le calcul et émettre plus de données que nous appelons "sortie". (D'un certain point de vue, on pourrait appeler un tel programme universel un "système d'exploitation".) Mais pourquoi supposez-vous qu'un compilateur devrait être aussi efficace pour optimiser un tel programme à usage général que pour optimiser un programme 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 avoir une bibliothèque d'interface universelle à un certain degré serait nécessairement une bonne chose. Peut-être bien, mais dans de nombreux cas, une telle bibliothèque serait grande et difficile à écrire et à entretenir, et peut-être même lente. Et si une telle bête n'existe pas en fait pour servir le problème particulier à résoudre, alors qui êtes-vous pour insister pour qu'elle soit créée, alors qu'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 des données. Ce que je ne comprends pas, c'est pourquoi générer du code source? Pourquoi ne pas en faire une fonction qui peut accepter des paramètres et agir sur eux?
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 demandez alors pourquoi les gens voudraient écrire des programmes en plusieurs langues, et surtout pourquoi ils pourraient vouloir mélanger des langues de niveaux subjectivement différents.
Mais j'en ai déjà parlé. On choisit une langue pour une tâche particulière en se basant en partie sur sa clarté et son expressivité pour cette tâche. Dans la mesure où un code plus petit a moins de bogues en moyenne et est plus facile à maintenir, il existe également un biais vers les langages de niveau supérieur, au moins pour les travaux à grande échelle. Mais un programme complexe implique de nombreuses tâches, et souvent certaines d'entre elles peuvent être traitées plus efficacement dans une langue, tandis que d'autres sont traitées plus efficacement ou de manière plus concise dans une autre. Utiliser le bon outil pour le travail signifie parfois utiliser la génération de code.
Répondre à la question dans le cadre de votre commentaire:
Le devoir du compilateur est de prendre un code écrit sous une forme lisible par l'homme et de le convertir en une forme lisible par machine. Par conséquent, si le compilateur ne peut pas créer un code efficace, le compilateur ne fait pas correctement son travail. Est-ce faux?
Un compilateur ne sera jamais optimisé pour votre tâche. La raison en est simple: il est optimisé pour effectuer des tâches many. C'est un outil à usage général utilisé par de nombreuses personnes pour de nombreuses tâches différentes. Une fois que vous savez quelle est votre tâche, vous pouvez aborder le code d'une manière spécifique au domaine, en faisant des compromis que les compilateurs n'ont pas pu.
Par exemple, j'ai travaillé sur un logiciel où un analyste peut avoir besoin d'écrire du code. Ils pourraient écrire leur algorithme en C++, et ajouter tous les contrôles de limites et astuces de mémorisation dont ils dépendent, mais cela nécessite de connaître un lot sur le fonctionnement interne du code. Ils préfèrent écrire quelque chose de simple et me laisser lancer un algorithme pour générer le code C++ final. Ensuite, je peux faire des astuces exotiques pour maximiser les performances comme une analyse statique que je ne m'attendrais jamais à ce que mes analystes endurent. La génération de code leur permet d'écrire d'une manière spécifique au domaine, ce qui leur permet de sortir le produit plus facilement que n'importe quel outil à usage général.
J'ai également 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 rendre la vie facile à ceux qui utilisent le logiciel, nous avons donc utilisé des quantités massives 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 C++ à usage général pour faire mon travail.
Cependant, il y a un hic. Il était énormément difficile de garantir que les erreurs étaient lisibles. Si vous avez déjà utilisé du 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 et d'arguments de modèle incompréhensibles pour comprendre ce qui s'est passé. Cet effet était si 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 contient une erreur. Allez sur cette ligne et plissez les yeux jusqu'à ce que vous réalisiez ce que vous a mal fait. "
Si nous avions utilisé la génération de code, nous aurions pu avoir des capacités de gestion des erreurs beaucoup plus puissantes, avec des erreurs lisibles par l'homme. C'est la vie.