Je n'aime pas la génération de code et généralement, je le vois comme une "odeur". Si vous utilisez une génération de code, il y a de fortes chances que votre conception ou solution présente un problème! Alors peut-être qu'au lieu d'écrire un script pour générer des milliers de lignes de code, vous devriez prendre du recul, repenser votre problème et trouver une meilleure solution. Cela dit, il existe des situations où la génération de code pourrait être une bonne solution.
Dans cet article, je parlerai des avantages et des inconvénients de la génération de code, puis vous montrerai comment utiliser les modèles T4, l'outil de génération de code intégré à Visual Studio, à l'aide d'un exemple..
J'écris un article sur un concept que je considère comme une mauvaise idée. Le plus souvent, il ne serait pas professionnel de ma part de vous donner un outil sans vous avertir de ses dangers..
La vérité est que la génération de code est très excitante: vous écrivez quelques lignes de code et vous obtenez beaucoup plus en retour que vous auriez peut-être à écrire manuellement. Il est donc facile de tomber dans un piège unique:
"Si le seul outil dont vous disposez est un marteau, vous avez tendance à voir chaque problème comme un clou" ". A. Maslow
Mais la génération de code est presque toujours une mauvaise idée. Je vous renvoie à ce message, qui explique la plupart des problèmes que je vois avec la génération de code. En bref, la génération de code se traduit par un code inflexible et difficile à gérer.
Voici quelques exemples de lieux où vous devriez ne pas Utiliser la génération de code:
J'allais également faire figurer le mappage relationnel-objet dans la liste, car certains ORM s'appuient fortement sur la génération de code pour créer le modèle de persistance à partir d'un modèle de données conceptuel ou physique. J'ai utilisé certains de ces outils et j'ai beaucoup souffert pour personnaliser le code généré. Cela dit, beaucoup de développeurs semblent vraiment les aimer, alors je l’ai laissé de côté (ou l’ai-je fait?!);)
Bien que certains de ces "outils" résolvent certains problèmes de programmation et réduisent les efforts et le coût de développement de logiciels requis, il existe un coût de maintenance caché énorme lié à l'utilisation de la génération de code, qui finira tôt ou tard par vous nuire le code généré que vous avez, plus cela va faire mal.
Je sais que beaucoup de développeurs sont de grands fans de la génération de code et écrivent chaque jour un nouveau script de génération de code. Si vous êtes dans ce camp et pensez que c'est un excellent outil pour beaucoup de problèmes, je ne vais pas me disputer avec vous. Après tout, cet article ne vise pas à prouver que la génération de code est une mauvaise idée.
Très rarement cependant, je me trouve dans une situation où la génération de code convient bien au problème à résoudre et où les solutions alternatives seraient plus difficiles ou plus laides..
Voici quelques exemples de situations dans lesquelles la génération de code pourrait bien convenir:
Comme mentionné ci-dessus, la génération de code rend le code inflexible et difficile à maintenir; Par conséquent, si la nature du problème que vous résolvez est statique et ne nécessite pas de maintenance fréquente, la génération de code peut être une bonne solution.!
Ce n’est pas parce que votre problème s’inscrit dans l’une des catégories ci-dessus que la génération de code convient parfaitement. Vous devriez quand même essayer d'évaluer des solutions alternatives et peser vos options.
En outre, si vous optez pour la génération de code, veillez à toujours écrire des tests unitaires. Pour une raison quelconque, certains développeurs pensent que le code généré ne nécessite pas de tests unitaires. Peut-être pensent-ils que cela est généré par des ordinateurs et que les ordinateurs ne font pas d'erreur! Je pense que le code généré nécessite tout autant (sinon plus) une vérification automatisée. Personnellement, j'ai créé ma code: j'écris d'abord les tests, je les lance pour les voir échouer, puis je génère le code et je vois les tests réussis.
Il existe un moteur de génération de code génial dans Visual Studio appelé Kit de transformation de modèles de texte (AKA, T4)..
De MSDN:
Les modèles de texte sont composés des parties suivantes:
Au lieu de parler du fonctionnement de T4, je voudrais utiliser un exemple concret. Donc, voici un problème que j'ai rencontré il y a un moment pour lequel j'ai utilisé T4. J'ai une bibliothèque open source .NET appelée Humanizer. L’une des choses que je souhaitais fournir dans Humanizer était une API facile à utiliser pour les développeurs pour travailler avec Date et heure
.
J'ai envisagé quelques variantes de l'API et à la fin, je me suis décidé pour cela:
In.January // Renvoie le 1er janvier de l'année en cours In.FebruaryOf (2009) // Renvoie le 1er février 2009 Le.Janvier.Le 4ème // Renvoie le 4 janvier de l'année en cours On.Février.Le (12) // Renvoie le 12 février de l'année en cours In.One.Second // DateTime.UtcNow.AddSeconds (1); In.Two.Minutes // Avec la méthode From correspondante In.Three.Hours // Avec la méthode From correspondante In.Five.Days // Avec la méthode From correspondante In.Six.Weeks // Avec la méthode From correspondante In.Seven.Months / / Avec la méthode From correspondante In.Eight.years // Avec la méthode From correspondante In.Two.SecondsFrom (DateTime dateTime)
Une fois que je savais à quoi allait ressembler mon API, j’ai réfléchi à différentes façons de résoudre ce problème et proposé quelques solutions orientées objet, mais toutes nécessitaient un peu de code passe-partout et celles qui ne le faisaient pas ne le feraient pas. donnez-moi l'API publique propre que je voulais. J'ai donc décidé d'aller avec la génération de code.
Pour chaque variante, j'ai créé un fichier T4 distinct:
En janvier
et In.FebrurayOf ()
etc.On.January.The4th
, Le.février.le (12)
etc. En une seconde
, In.TwoSecondsFrom ()
, Trois minutes
etc. Ici je vais discuter Un jour
. Le code est copié ici pour votre référence:
<#@ template debug="true" hostSpecific="true" #> <#@ output extension=".cs" #> <#@ Assembly Name="System.Core" #> <#@ Assembly Name="System.Windows.Forms" #> <#@ assembly name="$(SolutionDir)Humanizer\bin\Debug\Humanizer.dll" #> <#@ import namespace="System" #> <#@ import namespace="Humanizer" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Diagnostics" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Collections" #> <#@ import namespace="System.Collections.Generic" #> en utilisant le système; espace de noms Humanizer classe partielle publique On <# const int leapYear = 2012; for (int month = 1; month <= 12; month++) var firstDayOfMonth = new DateTime(leapYear, month, 1); var monthName = firstDayOfMonth.ToString("MMMM");#> ////// Fournit des accesseurs de date courants pour <#= monthName #> /// classe publique <#= monthName #> ////// Le nième jour de <#= monthName #> de l'année en cours /// static DateTime public The (int dayNumber) renvoie le nouveau DateTime (DateTime.Now.Year, <#= month #>, dayNumber); <#for (int day = 1; day <= DateTime.DaysInMonth(leapYear, month); day++) var ordinalDay = day.Ordinalize();#> ////// Le <#= ordinalDay #> jour de <#= monthName #> de l'année en cours /// statique DateTime public le<#= ordinalDay #> get retour new DateTime (DateTime.Now.Year, <#= month #>, <#= day #>) <##> <##>
Si vous extrayez ce code dans Visual Studio ou souhaitez utiliser T4, assurez-vous d'avoir installé Tangible T4 Editor pour Visual Studio. Il fournit IntelliSense, la surbrillance de la syntaxe T4, le débogueur T4 avancé et la transformation T4 lors de la génération..
Le code peut sembler un peu effrayant au début, mais c'est juste un script très similaire au langage ASP. Lors de la sauvegarde, cela générera une classe appelée Sur
avec 12 sous-classes, une par mois (par exemple, janvier
, février
etc), chacune avec des propriétés statiques publiques qui renvoient un jour spécifique de ce mois. Brisons le code et voyons comment cela fonctionne.
La syntaxe des directives est la suivante: <#@ DirectiveName [AttributeName = "AttributeValue"]… #>
. Vous pouvez en savoir plus sur les directives ici.
J'ai utilisé les directives suivantes dans le code:
<#@ template debug="true" hostSpecific="true" #>
La directive Template possède plusieurs attributs qui vous permettent de spécifier différents aspects de la transformation..
Si la déboguer
attribut est vrai
, le fichier de code intermédiaire contiendra des informations permettant au débogueur d'identifier plus précisément la position dans votre modèle où une interruption ou une exception s'est produite. Je laisse toujours ça comme vrai
.
<#@ output extension=".cs" #>
La directive Output permet de définir l’extension du nom de fichier et le codage du fichier transformé. Ici nous mettons l'extension à .cs
ce qui signifie que le fichier généré sera en C # et que le nom du fichier sera On.Days.cs
.
<#@ assembly Name="System.Core" #>
Ici nous sommes en train de charger System.Core
afin que nous puissions l'utiliser dans les blocs de code plus bas.
La directive Assembly charge un assembly afin que votre code de modèle puisse utiliser ses types. L'effet est similaire à l'ajout d'une référence d'assembly dans un projet Visual Studio..
Cela signifie que vous pouvez tirer pleinement parti du framework .NET dans votre modèle T4. Par exemple, vous pouvez utiliser ADO.NET pour accéder à une base de données, lire des données d’une table et les utiliser pour la génération de code..
Plus bas, j'ai la ligne suivante:
<#@ assembly name="$(SolutionDir)Humanizer\bin\Debug\Humanizer.dll" #>
C'est un peu intéressant. dans le On.Days.tt
template J'utilise la méthode Ordinalize de Humanizer qui convertit un nombre en chaîne ordinale, utilisée pour désigner la position dans une séquence ordonnée telle que 1st, 2nd, 3rd, 4th. Ceci est utilisé pour générer Le 1er
, Le 2ème
etc.
De l'article MSDN:
Le nom de l'assembly doit être l'un des suivants:
System.Xml.dll
. Vous pouvez également utiliser la forme longue, telle que name = "System.Xml, Version = 4.0.0.0, Culture = neutre, PublicKeyToken = b77a5c561934e089". Pour plus d'informations, voir AssemblyName
.System.Core
vit à GAC, nous pourrions donc facilement utiliser son nom; mais pour Humanizer, nous devons fournir le chemin absolu. Évidemment, je ne veux pas coder en dur mon chemin local, alors j’ai utilisé $ (SolutionDir)
qui est remplacé par le chemin d'accès de la solution lors de la génération du code. De cette façon, la génération de code fonctionne bien pour tout le monde, peu importe où ils conservent le code..
<#@ import namespace="System" #>
La directive import vous permet de faire référence à des éléments d'un autre espace de noms sans fournir de nom complet. C'est l'équivalent du en utilisant
déclaration en C # ou importations
en Visual Basic.
En haut, nous définissons tous les espaces de noms nécessaires dans les blocs de code. le importation
Les blocs que vous voyez sont principalement insérés par T4 Tangible. La seule chose que j'ai ajoutée était:
<#@ import namespace="Humanizer" #>
Donc je peux écrire plus tard:
var ordinalDay = day.Ordinalize ();
Sans le importation
déclaration et spécifiant la Assemblée
par chemin, au lieu d’un fichier C #, j’aurais eu une erreur de compilation en me plaignant de ne pas trouver le Ordinaliser
méthode sur entier.
Un bloc de texte insère du texte directement dans le fichier de sortie. En haut, j'ai écrit quelques lignes de code C # qui sont directement copiées dans le fichier généré:
en utilisant le système; espace de noms Humanizer classe partielle publique On
Plus bas, entre les blocs de contrôle, j'ai quelques autres blocs de texte pour la documentation de l'API, les méthodes et aussi pour la fermeture des crochets.
Les blocs de contrôle sont des sections de code de programme utilisées pour transformer les modèles. La langue par défaut est C #.
Remarque: La langue dans laquelle vous écrivez le code dans les blocs de contrôle n'est pas liée à la langue du texte généré.
Il existe trois types différents de blocs de contrôle: Standard, Expression et Class Feature..
De MSDN:
<# Standard control blocks #>
peut contenir des déclarations.<#= Expression control blocks #>
peut contenir des expressions.<#+ Class feature control blocks #>
peut contenir des méthodes, des champs et des propriétés.Jetons un coup d'oeil aux blocs de contrôles que nous avons dans le modèle d'exemple:
<# const int leapYear = 2012; for (int month = 1; month <= 12; month++) var firstDayOfMonth = new DateTime(leapYear, month, 1); var monthName = firstDayOfMonth.ToString("MMMM");#> ////// Fournit des accesseurs de date courants pour <#= monthName #> /// classe publique <#= monthName #> ////// Le nième jour de <#= monthName #> de l'année en cours /// static DateTime public The (int dayNumber) renvoie le nouveau DateTime (DateTime.Now.Year, <#= month #>, dayNumber); <#for (int day = 1; day <= DateTime.DaysInMonth(leapYear, month); day++) var ordinalDay = day.Ordinalize();#> ////// Le <#= ordinalDay #> jour de <#= monthName #> de l'année en cours /// statique DateTime public le<#= ordinalDay #> get retour new DateTime (DateTime.Now.Year, <#= month #>, <#= day #>) <##> <##>
Pour moi personnellement, la chose la plus déroutante à propos de T4 est les blocs de contrôle d'ouverture et de fermeture, car ils se mélangent un peu avec les crochets du bloc de texte (si vous générez du code pour un langage d'accolade comme C #). Je trouve que le moyen le plus simple de régler ce problème est de fermer (#>
) le bloc de contrôle dès mon ouverture (<#
) puis écrivez le code à l'intérieur.
En haut, à l'intérieur du bloc de contrôle standard, je définis année bissextile
comme une valeur constante. C'est pour que je puisse générer une entrée pour le 29 février. Ensuite, j'itère sur 12 mois pour chaque mois obtenir le premier jour du mois
et le monthName
. Je ferme ensuite le bloc de contrôle pour écrire un bloc de texte pour la classe de mois et sa documentation XML. le monthName
est utilisé comme nom de classe et dans les commentaires XML (à l'aide de blocs de contrôle d'expression). Le reste n'est que du code C # normal avec lequel je ne vais pas vous ennuyer.
Dans cet article, j'ai parlé de la génération de code, fourni quelques exemples de situations dans lesquelles la génération de code pouvait être dangereuse ou utile, et également expliqué comment utiliser des modèles T4 pour générer du code à partir de Visual Studio à l'aide d'un exemple réel..
Si vous souhaitez en savoir plus sur le T4, vous pouvez trouver beaucoup de contenu sur le blog de Oleg Sych..