
Tko nikada nije doživio situaciju da morate ispraviti pogrešku i na kraju saznate da je pogreška na poslužitelju nedostajalo polje koje dolazi iz HTTP zahtjeva? Ili pogreška na klijentu, gdje je vaš Javascript kôd pokušavao pristupiti polju koje ne postoji na podacima koji su došli u HTTP odgovoru od poslužitelja? Puno puta ove probleme uzrokuje samo različit naziv ovog polja između koda na klijentu i poslužitelju.
Problem
Svatko tko radi i na pozadini i na strani web aplikacije mora tražiti i obrađivati podatke na strani poslužitelja, a zatim ih vratiti kako bi ih potrošila klijentska strana aplikacije. Bez obzira na koliko slojeva je vaša arhitektura podijeljena, uvijek ćete imati prednost između poslužitelja i klijenta, gdje HTTP zahtjevi i odgovori prenose podatke između te dvije strane u oba smjera.
I ovdje se ne radi samo o greškama s različitim imenima - nitko se ne može sjetiti cjelokupne strukture podataka svih entiteta aplikacije. Kada pišete kôd, uobičajeno je upisati .
(ili -&
gt; or
[“). Ako tamo ne napišete pogrešno ime, zaustavite se i zapitate se "Kako se, dovraga, zvalo to polje?". Nakon što provedete neko vrijeme pokušavajući se sjetiti, odustajete i odabirete najdosadniji put. Uzmete miš i počnete tražiti datoteku u kojoj definirate sva ona polja kojima trebate pristupiti.
Ponekad ne škodi samo guglati i tamo nađete odgovor na Stack Overflow s kodom, spreman za kopiranje. Ali kada trebate potražiti ovaj odgovor u svom projektu, velikom projektu, gdje se kod koji definira strukturu podataka kojem morate pristupiti nalazi u datoteci koju niste napisali vi ... vrijeme koje provedete na ovom putu može biti jedan ili dva reda veličine veći od vremena provedenog samo za pisanje pravog imena.
TypeScript u pomoć
Kad smo nekad pisali samo stari Javascript, nismo imali mogućnost izbjeći taj dosadni put u tim situacijama. No, krajem 2012. Anders Hejlsberg (otac jezika C #) i njegov tim stvorili su TypeScript. Njihova misija bila je olakšati stvaranje velikih Javascript projekata koji se mijenjaju.
Smiješno je to što je, iako je ovaj novi jezik bio superset Javascripta, njegov cilj bio omogućiti vam da radite samo podskup stvari koje ste nekada radili s Javascriptom. Ona dodaje nove značajke kao što su klasa, enums, sučelja, vrste parametara, i vrste povratka.
Ali također je uklonio mogućnosti , čak i stvari koje nisu bile loše, poput prosljeđivanja broja kao parametra document.getElementById()
i korištenja *
operatora s brojem i numeričkim nizom kao operanda. Ne možete više računati s implicitnim pretvorbama tipova, morate biti eksplicitni i koristiti .toString()
ili parseInt(str)
kada želite pretvorbu tipa. Ali najbolja stvar koju više ne možete učiniti je pristupiti polju koje ne postoji u objektu.
Dakle, kada se problem riješi, često mjesto zauzme novi. I tu je novi problem bio dupliciranje koda. Ljudi su načelo SUHO (Ne ponavljajte se) počeli zamijeniti WET principom (Napiši sve dvaput).
Dobra je praksa koristiti različite razrede u različitim slojevima, u različite svrhe, ali ovdje nije slučaj. Ako imate tri sloja (A -> B -> C), ne biste trebali imati određene strukture podataka s za svaki sloj (jedan za A, jedan za B i jedan za C), već za svaki rub između tih slojeva ( jedan između A i B i drugi između B i C). Ovdje, osim ako vaš back-end nije aplikacija Node.js, moramo duplicirati ove deklaracije strukture podataka jer smo na rubu između dva različita programska jezika.
Da bismo izbjegli da sve napišemo dva puta, ostaje nam samo jedna opcija ...
Generiranje koda
Jednog dana radio sam na .NET projektu s Entity Framework-om. Imao je dijagram modela u .edmx datoteci, a ako sam promijenio ovu datoteku, morao sam odabrati opciju za generiranje klasa za POCO entitete (obični stari CLR objekti).
Ovu generaciju koda izveo je T4, mehanizam predloška Visual Studija koji je radio s .tt datotekom kao predloškom za klasu C #. Pokrenuo je kôd koji čita datoteku .edmx modela i izlaže klase u .cs datotekama. Nakon što sam se toga sjetio, pomislio sam da bi to moglo biti rješenje za generiranje TypeScript sučelja i počeo sam pokušavati raditi.
Prvo sam pokušao napisati vlastiti predložak. Kad sam radio s tim i Entity Frameworkom, nikada nisam morao mijenjati .tt predložak. Tada sam otkrio da Visual Studio ne podržava isticanje sintakse u .tt datotekama - bilo je to poput programiranja u notepadu, ali još gore.
Osim što imam C # kôd generirajuće logike, s njim sam pomiješao i TypeScript kôd koji je trebalo generirati, poput ovog. Instalirao sam proširenje Visual Studio da bih dobio podršku za sintaksu, ali proširenje je definiralo boje sintakse samo za svijetlu temu Visual Studija, a koristim tamnu. Boje sintakse svijetle teme na tamnoj temi bile su nečitke, pa sam i ja morao promijeniti temu svog Visual Studija.
Sad je s isticanjem sintakse sve bilo u redu. Bilo je vrijeme da počnemo pisati neki kod. Potražio sam na Googleu radni primjer. Moja ideja bila je promijeniti ga za svoje potrebe nakon što sam ga pokrenuo, ali ... NIJE RADIO!
System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
Isprobao sam puno "radnih" primjera koji su pronađeni na Googleu, ali niti jedan nije uspio. Pomislio sam da možda problem nije u Visual Studiu ili u T4 Engineu - možda sam problem u tome što sam ga pogrešno koristio.
Tada me Google dobio po ovom pitanju u spremištu .NET Core i otkrio sam da to ne funkcionira s ASP.NET Core projektima. Ali ta je pogreška bila uobičajena pogreška u .NET svijetu, pa sam zaključio da bih mogao pokušati zaobići to rješenje. Potražio sam onu verziju System.Runtime.dll 4.2.1.0, našao sam je i pokušao je staviti u neke različite direktorije kako bih vidio može li je Visual Studio pronaći ... ali ništa nije uspjelo.
Konačno, upotrijebio sam Process Explorer da vidim koju je verziju System.Runtime Visual Studio učitao, i to verziju 4.0.0.0. Pokušao sam ga bindingRedirect
prisiliti da koristi istu verziju (kao što sam ovdje opisao), i uspjelo je! Nisam mogao vjerovati da više neću morati kopirati i ručno sinkronizirati svoje podatkovne strukture između poslužitelja i klijenta.
Počeo sam više o tome razmišljati, a mučila me još jedna pomisao ...
Je li to vrijedilo?
Radim u velikoj naftnoj tvrtki s puno naslijeđenih prijava. Prijatelj je morao raditi s virtualnim strojem jer je aplikacija koju je otklanjao pogreške ponekad radila samo u sustavu Windows XP. Druga aplikacija na kojoj sam morao raditi jedan dan radila je samo s Visual Studiom 2010. Druga koja je koristila ugovore koda radila je samo s Visual Studiom 2013 jer proširenje Code Contracts nije radilo u Visual Studio 2015 ili 2017.
Od 2012. godine, kada sam tamo počeo raditi do početka 2019. godine, nikada nisam imao priliku razviti novu aplikaciju. Sav moj rad uvijek je bio u neredima drugih programera. Prošle godine počeo sam proučavati više o softverskoj arhitekturi i pročitao sam knjigu "Čista arhitektura" strica Boba.
Sad kad sam započeo ovu novu godinu s ovom prilikom, prvi put u ovoj tvrtki izrađujem web aplikaciju od nule i želim napraviti dobar posao. Odabrao sam ASP.NET Core za svoj back-end, React za front-end i to će biti jedna od prvih aplikacija u ovoj tvrtki koja će se pokretati u Docker spremniku u našem novom Kubernetes klasteru.
Još jedan siromašni programer morat će raditi na ovom projektu u budućnosti, s mojim kodom i svim mojim neredom, i ne želim da se moraju nositi s lošim kodom. Želim da svi programeri nakon mene žele raditi na ovom projektu. To se neće dogoditi ako moraju izgubiti jedan dan posla samo da bi generiranje klijentskog koda pokrenuli iz pozadinskih struktura podataka. Tada bi me mrzili (a neki bi me već mrzili zbog stavljanja TypeScript koda u projekt dok je TypeScript još uvijek bio u verziji 0.9).
Kada pišemo kod koji nije naš, mi smo odgovorni da drugima olakšamo rad na njemu.Nakon razmišljanja o tome, došao sam do zaključka:
Trebali bismo izbjegavati ovisnosti o bilo čemu s čime se upravitelj paketa odabrane tehnologije ne može nositi.U ovom bih slučaju, osim ovisnosti o Visual Studiju i sustavu Windows, projekt učinio ovisnim i o ispravci programske pogreške koju bi Microsoft trebao popraviti (a čini se da nema nikakav prioritet). Stoga je najbolje duplicirati ovaj kôd i ručno ga sinkronizirati nego staviti ovisnost o ovaj T4 motor.
Odlučio sam koristiti .NET Core, ali ako neki programer u budućnosti želi raditi na ovom projektu koristeći Linux, ne mogu ih zaustaviti.
Konačno rješenje (TL; DR)
Duplicate code is bad, but dependency on third party tools is worse. So, what can we do to avoid duplication of data structures and not depend on any specific IDE / plugin / extension / tool for development?
It took me some time to realize that the only tool that I needed was there all this time, inside the language runtime: Reflection.
I realized I could write some code that runs on the startup of my back-end ASP.NET Core app only in development mode. This code could use reflection to read the metadata about names and types of all the data structures that I wanted to generate TypeScript interfaces. I just needed to map C# primitives to TypeScript primitives, write the .d.ts TypeScript definitions in a specific folder, and I’d be done.
Every time I changed some data structure in the back-end, it would override the interfaces definitions inside a .d.ts files when I ran the code to test it. When I got to the part of writing the client code to use the data structure that changed, the interfaces would already be updated.
This approach can be used by projects in .NET, Java, Python, and any other language that has support for code reflection, without adding a dependency on any IDE / plugin / extension / tool.
I wrote a simple example using C# with ASP.NET Core and published it on GitHub here. It just takes from all classes that inherit Microsoft.AspNetCore.Mvc.ControllerBase
and all types from parameters and returns types of public methods that have HttpGet
or HttpPost
attributes.
Here is what the generated interfaces look like:

You can generate other types of code too
I used it to generate interfaces and enums for data structures only, but think about the code below:

It’s much less of a pain to keep this code in sync with all the possible MVC controllers and actions than it was to keep the data structures in sync. But do I need to write this code by hand? Couldn’t it be generated too?
I can’t generate C# interfaces from C# concrete implementations, because I need the code to compile and run before I can use reflection to generate it. But with client code that needs to be kept in sync with server code, I can generate it. This way of code generation can be used beyond the data structure interfaces.
If you don’t like TypeScript…
It doesn’t need to be written with TypeScript. If you don’t like TypeScript and prefer to use plain Javascript, you can write your .js files and use TypeScript just as a tool (if you use Visual Studio Code you are already using it). That way, you can generate helper functions that convert your data structures to the same structures. It seems weird, but it would help the TypeScript Language Service to analyse your code and tell Visual Studio Code with fields that exist in each object, so it could help you to write your code.

Conclusion
We, as developers, have a responsibility to other developers that will have to work on our code. Don’t leave a mess for them to clean up, because they won’t (or at least they won’t want to!). They will likely only make it worse for the next one.
You should avoid at all costs any development and runtime dependencies that cannot be handled by the package manager. Don’t make your project the one that others developers will hate working on.
Thanks for reading!
PS 1: This repository with my code is just an example. The code that converts C# classes into TypeScript interfaces there is not good. You can do a lot better, and maybe we already have some NuGet package that do this.
PS 2: I love TypeScript. If you love TypeScript too, you may want to take a look at these links, from before it was announced by Microsoft in 2012:
- What’s Microsoft’s father of C#’s next trick? Microsoft Technical Fellow Anders Hejlsberg is working on something to do with JavaScript tools. Here are a few clues about his latest project.
- A HackerNews discussion: “Anders Hejlsberg Is Right: You Cannot Maintain Large Programs In JavaScript”
- A Channel9 video: “Anders Hejlsberg: Introducing TypeScript”