Ik houd niet van codegeneratie en meestal zie ik het als een "geur". Als u gebruikmaakt van het genereren van codes, is er een goede kans dat er iets mis is met uw ontwerp of oplossing! Dus misschien in plaats van een script te schrijven om duizenden regels code te genereren, moet je een stapje terug doen, opnieuw over je probleem nadenken en een betere oplossing bedenken. Met dat gezegd, er zijn situaties waarin het genereren van code een goede oplossing kan zijn.
In dit bericht zal ik het hebben over voor- en nadelen van het genereren van code en vervolgens laten zien hoe je T4-sjablonen, het ingebouwde hulpmiddel voor het genereren van code in Visual Studio, kunt gebruiken met een voorbeeld.
Ik schrijf een bericht over een concept waarvan ik denk dat het een slecht idee is, vaker wel dan niet en het zou onprofessioneel van me zijn als ik je een hulpmiddel overhandigde en je niet waarschuwde voor de gevaren ervan.
De waarheid is dat het genereren van code best spannend is: je schrijft een paar regels code en je krijgt veel meer in ruil daarvoor zou je misschien handmatig moeten schrijven. Dus het is gemakkelijk om er een one-size-fits-all val mee te maken:
"Als het enige hulpmiddel dat je hebt een hamer is, heb je de neiging om elk probleem als een spijker te zien" ". A. Maslow
Maar het genereren van code is bijna altijd een slecht idee. Ik verwijs u naar dit bericht, dat de meeste problemen die ik zie met het genereren van codes verklaart. Kort samengevat resulteert het genereren van code in inflexibele en moeilijk te onderhouden code.
Hier zijn enkele voorbeelden van waar u zou moeten zijn niet gebruik code generatie:
Ik zou Object Relational Mapping ook in de lijst plaatsen, omdat sommige ORM's sterk afhankelijk zijn van het genereren van code om het persistentiemodel te maken van een conceptueel of fysiek gegevensmodel. Ik heb een aantal van deze hulpmiddelen gebruikt en een flinke dosis pijn gedaan om de gegenereerde code aan te passen. Dat gezegd hebbende, veel ontwikkelaars lijken ze echt leuk te vinden, dus ik heb dat gewoon weggelaten (of niet ?!);)
Hoewel sommige van deze 'hulpprogramma's' sommige van de programmeerproblemen oplossen en de vereiste inspanningen en kosten van softwareontwikkeling verminderen, zijn er enorme onderhoudskosten in het gebruik van codegeneratie die u vroeg of laat zullen bijten en hoe meer gegenereerde code die u heeft, des te meer pijn het gaat doen.
Ik weet dat veel ontwikkelaars grote fans zijn van het genereren van code en elke dag een nieuw codegeneratiescript schrijven. Als je in dat kamp bent en denkt dat het een geweldig hulpmiddel is voor veel problemen, ga ik niet met je ruzie maken. Het gaat er tenslotte niet om dat bewijzen dat het genereren van code een slecht idee is.
Maar heel zelden kom ik in een situatie terecht waarin codegeneratie geschikt is voor het probleem en de alternatieve oplossingen moeilijker of lelijker zijn.
Hier zijn een paar voorbeelden van waar codegeneratie een goede match kan zijn:
Zoals hierboven vermeld, zorgt het genereren van code voor inflexibele en moeilijk te onderhouden code; dus als de aard van het probleem dat u oplost statisch is en geen frequent onderhoud vereist, dan is het genereren van code een goede oplossing!
Alleen omdat uw probleem in een van de bovenstaande categorieën past, betekent dit niet dat het genereren van codes hiervoor geschikt is. U moet nog steeds proberen alternatieve oplossingen te evalueren en uw opties af te wegen.
En als je gaat voor het genereren van code, zorg er dan voor dat je nog steeds unit tests schrijft. Om een of andere reden denken sommige ontwikkelaars dat de gegenereerde code geen unit testing vereist. Misschien denken ze dat het wordt gegenereerd door computers en computers geen fouten maken! Ik denk dat de gegenereerde code evenveel (zo niet meer) geautomatiseerde verificatie vereist. Ik persoonlijk TDD mijn codegeneratie: ik schrijf eerst de tests, voer ze uit om ze te zien falen, genereer dan de code en zie de tests slagen.
Er is een geweldige code generatie-engine in Visual Studio genaamd Text Template Transformation Toolkit (AKA, T4).
Van MSDN:
Tekstsjablonen bestaan uit de volgende delen:
In plaats van te praten over hoe T4 werkt, zou ik een echt voorbeeld willen gebruiken. Dus hier is een probleem waar ik een tijdje geleden mee geconfronteerd ben waarvoor ik T4 gebruikte. Ik heb een open source .NET-bibliotheek genaamd Humanizer. Een van de dingen die ik in Humanizer wilde aanbieden, was een vloeiende ontwikkelaarsvriendelijke API om mee te werken Datum Tijd
.
Ik heb een aantal varianten van de API overwogen en heb hier uiteindelijk voor gekozen:
In.januari // Geeft 1 januari van het lopende jaar In.FebruaryOf (2009) // Geeft als resultaat 1 februari van 2009 On.January.The4th // Geeft als resultaat 4 januari van het lopende jaar On.Februari.De (12) // Retourneert 12 februari van het lopende jaar In.One.Second // DateTime.UtcNow.AddSeconds (1); In.Twee minuten.Minuten // Met bijbehorende Van-methode In.Three.Hours // Met bijbehorende Van-methode In.Five.Days // Met bijbehorende Van-methode In.Six.Weeks // Met bijbehorende Van-methode In.Seven.Months / / Met bijbehorende Van-methode In.Eight.Years // Met bijbehorende Van-methode In.Two.SecondsFrom (DateTime dateTime)
Nadat ik wist hoe mijn API eruit zou zien, dacht ik aan een paar verschillende manieren om dit aan te pakken en een paar objectgeoriënteerde oplossingen aan te scherpen, maar ze hadden allemaal een behoorlijk beetje boilerplate code nodig en degenen die dat niet deden, wilden niet geef me de schone openbare API die ik wilde. Dus besloot ik om met codegeneratie te gaan.
Voor elke variatie heb ik een apart T4-bestand gemaakt:
In januari
en In.FebrurayOf ()
enzovoorts.On.January.The4th
, On.February.The (12)
enzovoorts. In.One.Second
, In.TwoSecondsFrom ()
, In.Three.Minutes
enzovoorts. Hier zal ik bespreken Op dagen
. De code wordt hier voor uw referentie gekopieerd:
<#@ 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" #> systeem gebruiken; naamruimte Humanizer public partial class On <# const int leapYear = 2012; for (int month = 1; month <= 12; month++) var firstDayOfMonth = new DateTime(leapYear, month, 1); var monthName = firstDayOfMonth.ToString("MMMM");#> ////// Biedt vloeiende toegang tot datum voor <#= monthName #> /// openbare les <#= monthName #> ////// De negende dag van <#= monthName #> van het lopende jaar /// public static DateTime The (int dayNumber) retourneer nieuwe DateTime (DateTime.Now.Year, <#= month #>, dayNumber); <#for (int day = 1; day <= DateTime.DaysInMonth(leapYear, month); day++) var ordinalDay = day.Ordinalize();#> ////// De <#= ordinalDay #> dag van <#= monthName #> van het lopende jaar /// public static DateTime The<#= ordinalDay #> krijg retourneer nieuwe DateTime (DateTime.Now.Year, <#= month #>, <#= day #>); <##> <##>
Als je deze code uitcheckt in Visual Studio of wilt werken met T4, zorg er dan voor dat je de Tangible T4 Editor voor Visual Studio hebt geïnstalleerd. Het biedt IntelliSense, T4 Syntax-Highlighting, Advanced T4 Debugger en T4 Transform on Build.
De code lijkt misschien een beetje eng in het begin, maar het is gewoon een script dat erg op de ASP-taal lijkt. Na het opslaan, genereert dit een klasse genaamd Op
met 12 subklassen, één per maand (bijvoorbeeld, januari-
, februari
enz.), elk met openbare statische eigenschappen die een specifieke dag in die maand retourneren. Laten we de code uit elkaar halen en zien hoe het werkt.
De syntaxis van richtlijnen is als volgt: <#@ DirectiveName [AttributeName = "AttributeValue"]… #>
. Je kunt hier meer over richtlijnen lezen.
Ik heb de volgende richtlijnen in de code gebruikt:
<#@ template debug="true" hostSpecific="true" #>
De Template-instructie heeft verschillende attributen waarmee u verschillende aspecten van de transformatie kunt specificeren.
Als het debug
kenmerk is waar
, het tussencodebestand bevat informatie waarmee de foutopsporingsfunctie de positie in uw sjabloon nauwkeuriger kan identificeren waar een pauze of uitzondering heeft plaatsgevonden. Ik laat dit altijd zo waar
.
<#@ output extension=".cs" #>
De Output-instructie wordt gebruikt om de extensie en de codering van het getransformeerde bestand te definiëren. Hier hebben we de extensie ingesteld op .cs
wat betekent dat het gegenereerde bestand in C # staat en de bestandsnaam zal zijn On.Days.cs
.
<#@ assembly Name="System.Core" #>
Hier laden we System.Core
zodat we het verderop in de codeblokken kunnen gebruiken.
De Assembly-instructie laadt een assembly zodat uw sjablooncode de typen kan gebruiken. Het effect is vergelijkbaar met het toevoegen van een assemblyverwijzing in een Visual Studio-project.
Dit betekent dat u ten volle kunt profiteren van het .NET-framework in uw T4-sjabloon. U kunt bijvoorbeeld ADO.NET gebruiken om een database te raken, sommige gegevens uit een tabel te lezen en die te gebruiken voor het genereren van codes.
Verderop heb ik de volgende regel:
<#@ assembly name="$(SolutionDir)Humanizer\bin\Debug\Humanizer.dll" #>
Dit is een beetje interessant. In de On.Days.tt
template Ik gebruik de Ordinalize-methode van Humanizer die een nummer omzet in een ordinale reeks, die wordt gebruikt om de positie aan te duiden in een geordende volgorde zoals 1e, 2e, 3e, 4e. Dit wordt gebruikt om te genereren De 1e
, De 2e
enzovoorts.
Uit het MSDN-artikel:
De naam van de assembly moet een van de volgende zijn:
system.xml.dll
. U kunt ook de lange vorm gebruiken, zoals name = "System.Xml, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089". Zie voor meer informatie AssemblyName
.System.Core
leeft in GAC, dus we kunnen gewoon zijn naam gebruiken; maar voor Humanizer moeten we het absolute pad bieden. Natuurlijk wil ik mijn lokale pad niet hardcoderen, dus ik gebruikte het $ (SolutionDir)
die wordt vervangen door het pad waarin de oplossing leeft tijdens het genereren van de code. Op deze manier werkt de codegeneratie prima voor iedereen, ongeacht waar ze de code bewaren.
<#@ import namespace="System" #>
Met de importrichtlijn kunt u verwijzen naar elementen in een andere naamruimte zonder een volledig gekwalificeerde naam op te geven. Het is het equivalent van de gebruik makend van
verklaring in C # of invoer
in Visual Basic.
Op de top definiëren we alle namespaces die we nodig hebben in de codeblokken. De importeren
blokken die je ziet, er zijn meestal ingevoegd door T4 Tangible. Het enige dat ik heb toegevoegd was:
<#@ import namespace="Humanizer" #>
Dus ik kan later schrijven:
var ordinalDay = day.Ordinalize ();
Zonder de importeren
verklaring en specificatie van de bijeenkomst
per pad, in plaats van een C # -bestand, zou ik een compileerfout hebben gekregen die klaagde over het niet vinden van de Ordinalize
methode op integer.
Een tekstblok voegt tekst direct in het uitvoerbestand in. Bovenaan heb ik een paar regels C # -code geschreven die direct in het gegenereerde bestand worden gekopieerd:
systeem gebruiken; naamruimte Humanizer public partial class On
Verderop, tussen controleblokken, heb ik een aantal andere tekstblokken voor API-documentatie, methoden en ook voor het sluiten van haakjes.
Besturingsblokken zijn secties van programmacode die worden gebruikt om de sjablonen te transformeren. De standaardtaal is C #.
Notitie: De taal waarin u de code in de besturingsblokken schrijft, is niet gerelateerd aan de taal van de gegenereerde tekst.
Er zijn drie verschillende soorten besturingsblokken: standaard, expressie en klassenfunctie.
Van MSDN:
<# Standard control blocks #>
kan uitspraken bevatten.<#= Expression control blocks #>
kan uitdrukkingen bevatten.<#+ Class feature control blocks #>
kan methoden, velden en eigenschappen bevatten.Laten we eens kijken naar de besturingsblokken die we in de voorbeeldsjabloon hebben:
<# const int leapYear = 2012; for (int month = 1; month <= 12; month++) var firstDayOfMonth = new DateTime(leapYear, month, 1); var monthName = firstDayOfMonth.ToString("MMMM");#> ////// Biedt vloeiende toegang tot datum voor <#= monthName #> /// openbare les <#= monthName #> ////// De negende dag van <#= monthName #> van het lopende jaar /// public static DateTime The (int dayNumber) retourneer nieuwe DateTime (DateTime.Now.Year, <#= month #>, dayNumber); <#for (int day = 1; day <= DateTime.DaysInMonth(leapYear, month); day++) var ordinalDay = day.Ordinalize();#> ////// De <#= ordinalDay #> dag van <#= monthName #> van het lopende jaar /// public static DateTime The<#= ordinalDay #> krijg retourneer nieuwe DateTime (DateTime.Now.Year, <#= month #>, <#= day #>); <##> <##>
Voor mij persoonlijk is het meest verwarrende aan T4 de openings- en sluitingsbesturingsblokken, omdat ze nogal gemengd worden met de haakjes in het tekstblok (als je code genereert voor een accolade-haakstaal zoals C #). Ik vind de gemakkelijkste manier om hiermee om te gaan, is om te sluiten (#>
) het besturingsblok zodra ik open (<#
) en schrijf de code vervolgens in.
Aan de bovenkant, binnen het standaardbesturingsblok, definieer ik schrikkeljaar
als een constante waarde. Dit is zodat ik een bericht kan genereren voor 29 februari. Vervolgens herhaal ik meer dan 12 maanden voor elke maand om de firstDayOfMonth
en de monthName
. Ik sluit vervolgens het besturingsblok om een tekstblok voor de maandklasse en de XML-documentatie te schrijven. De monthName
wordt gebruikt als een klassenaam en in XML-opmerkingen (met behulp van blokken voor expressiecontrole). De rest is gewoon normale C # -code waar ik je niet mee vervul.
In deze post heb ik het gehad over het genereren van codes, een paar voorbeelden gegeven van wanneer het genereren van code gevaarlijk of nuttig kon zijn en ook liet zien hoe je T4-sjablonen kunt gebruiken om code te genereren van Visual Studio met een echt voorbeeld.
Als je meer wilt weten over T4, kun je veel geweldige content vinden op de blog van Oleg Sych.