Cara mudah untuk mendapatkan antaramuka TypeScript dari kod C #, Java, atau Python di mana-mana IDE

Siapa yang tidak pernah mengalami situasi di mana anda harus memperbaiki bug dan pada akhirnya anda mengetahui bahawa ralat pada pelayan adalah medan yang hilang yang datang dari permintaan HTTP? Atau ada kesalahan pada klien, di mana kod Javascript anda cuba mengakses medan yang tidak ada pada data yang datang dalam respons HTTP dari pelayan? Sering kali, masalah ini disebabkan oleh nama yang berbeza untuk bidang ini antara kod pada klien dan pelayan.

Masalah

Setiap orang yang bekerja baik di bahagian belakang dan depan aplikasi web harus membuat pertanyaan dan memproses data di sisi pelayan dan kemudian mengembalikan data ini untuk dimakan oleh pihak aplikasi. Tidak kira berapa lapisan arsitektur anda dibahagikan, anda akan selalu mempunyai kelebihan antara pelayan dan klien, di mana permintaan dan respons HTTP membawa data antara kedua-dua belah pihak ke dua arah.

Dan ini bukan hanya mengenai bug dengan nama yang berbeza - tidak ada yang dapat mengingat keseluruhan struktur data dari semua entiti aplikasi. Semasa anda menulis kod, adalah biasa untuk menaip .(atau -> or[“). Sekiranya anda tidak menulis nama yang salah di sana, anda berhenti dan bertanya kepada diri sendiri "Apa sih nama itu?" Setelah anda meluangkan masa untuk mengingat, anda berputus asa dan memilih jalan yang paling membosankan. Anda menggunakan tetikus dan mula mencari fail di mana anda menentukan semua bidang yang perlu anda akses.

Bahagian penulisan kod yang membosankan adalah apabila anda tidak dapat mengetahui sendiri apakah kod yang betul yang perlu anda tulis.

Kadang-kadang tidak ada salahnya hanya google dan anda menemui jawapan Stack Overflow dengan kod di sana, siap untuk disalin. Tetapi apabila anda harus mencari jawapan ini di dalam projek anda, sebuah projek besar, di mana kod yang menentukan struktur data yang harus anda akses terdapat dalam fail yang tidak ditulis oleh anda ... masa yang anda habiskan untuk jalan ini dapat menjadi satu atau dua urutan besarnya lebih besar daripada masa yang dihabiskan hanya dengan menulis nama yang betul.

TypeScript untuk menyelamatkan

Ketika kita biasa menulis tulisan Javascript biasa, kita tidak mempunyai pilihan untuk mengelakkan jalan yang membosankan ini dalam situasi ini. Tetapi kemudian, pada akhir tahun 2012, Anders Hejlsberg (bapa bahasa C #) dan pasukannya membuat TypeScript. Misi mereka adalah untuk mempermudah membuat projek Javascript yang berskala besar.

Bahagian yang lucu adalah bahawa, walaupun bahasa baru ini adalah superset Javascript, objektifnya adalah untuk membolehkan anda melakukan hanya sebahagian daripada perkara yang biasa anda lakukan dengan Javascript. Ia menambah ciri baru seperti kelas, enum, antaramuka, jenis parameter, dan jenis pengembalian.

Tetapi itu juga menghilangkan kemungkinan , bahkan hal-hal yang tidak terlalu buruk, seperti meneruskan angka sebagai parameter ke document.getElementById(), dan menggunakan *operator dengan nombor dan rentetan angka sebagai operan. Anda tidak dapat menghitung dengan penukaran jenis tersirat lagi, anda harus jelas dan menggunakan .toString()atau parseInt(str)ketika anda menginginkan penukaran jenis. Tetapi perkara terbaik yang tidak dapat anda lakukan lagi ialah mengakses medan yang tidak ada dalam objek.

Oleh itu, apabila masalah diselesaikan, masalah baru sering berlaku. Dan di sini masalah baru adalah penduaan kod. Orang mula mengganti prinsip KERING (Jangan Ulangi Diri Anda) dengan prinsip WET (Tulis Semuanya Dua Kali).

Merupakan amalan yang baik untuk menggunakan kelas yang berlainan dalam lapisan yang berlainan, untuk tujuan yang berbeza, tetapi ini tidak berlaku di sini. Sekiranya anda mempunyai tiga lapisan (A -> B -> C), anda tidak seharusnya mempunyai struktur data khusus untuk setiap lapisan (satu untuk A, satu untuk B dan satu untuk C), tetapi untuk setiap tepi antara lapisan tersebut ( satu antara A dan B dan yang lain antara B dan C). Di sini, melainkan jika bahagian belakang anda adalah aplikasi Node.js, kita harus menduplikasi deklarasi struktur data ini kerana kita berada di antara dua bahasa pengaturcaraan yang berbeza.

Untuk mengelakkan menulis semuanya dua kali, kami hanya tinggal satu pilihan…

Penjanaan Kod

Suatu hari saya mengusahakan projek .NET dengan Entity Framework. Ia mempunyai gambarajah model dalam fail .edmx, dan jika saya menukar fail ini, saya harus memilih pilihan untuk menghasilkan kelas untuk entiti POCO (Plain Old CLR Objects).

Penjanaan kod ini dilakukan oleh T4, mesin templat Visual Studio yang berfungsi dengan fail .tt sebagai templat untuk kelas C #. Ia menjalankan kod yang membaca fail model .edmx dan mengeluarkan kelas dalam fail .cs. Setelah mengingatnya, saya fikir ia boleh menjadi penyelesaian untuk menghasilkan antaramuka TypeScript dan saya mula berusaha menjadikannya berfungsi.

Pertama, saya cuba menulis templat saya sendiri. Semasa saya bekerja dengan ini dan Entity Framework, saya tidak perlu menukar templat .tt. Kemudian saya mendapat tahu bahawa Visual Studio tidak menyokong penyorotan sintaks dalam fail .tt - seperti pengaturcaraan di notepad tetapi lebih teruk lagi.

Selain mempunyai kod C # logik generasi, saya juga telah mencampurkannya dengan kod TypeScript yang harus dihasilkan, seperti ini. Saya memasang pelanjutan Visual Studio untuk mendapatkan sokongan sintaks, tetapi peluasan warna sintaks yang ditentukan hanya untuk tema cahaya Visual Studio, dan saya menggunakan yang gelap. Warna sintaks tema cahaya pada tema gelap tidak dapat dibaca, jadi saya juga harus menukar tema Visual Studio saya.

Kini dengan sorotan sorotan semuanya baik. Sudah tiba masanya untuk mula menulis beberapa kod. Saya mencari di google untuk contoh yang berfungsi. Idea saya adalah untuk mengubahnya untuk keperluan saya setelah saya berjaya, tetapi ... TIDAK BEKERJA!

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.

Saya mencuba banyak contoh "berfungsi" yang dijumpai di google, tetapi tidak ada yang berjaya. Saya fikir mungkin masalahnya bukan dengan Visual Studio atau dengan T4 Engine - mungkin masalahnya adalah saya, salah menggunakannya.

Kemudian google mengemukakan saya mengenai isu ini di repositori Core NET dan saya mendapati bahawa ia tidak berfungsi dengan projek Core ASP.NET. Tetapi ralat ini adalah kesalahan biasa di dunia .NET, jadi saya fikir saya boleh berusaha untuk menyelesaikannya. Saya mencari versi 4.2.1.0 dari System.Runtime.dll, saya menjumpainya, dan saya cuba memasukkannya ke dalam beberapa direktori yang berbeza untuk melihat apakah Visual Studio dapat menemuinya ... tetapi tidak ada yang berjaya.

Akhirnya, saya menggunakan Process Explorer untuk melihat versi System.Runtime Visual Studio yang dimuat, dan versi 4.0.0.0. Saya cuba menggunakan a bindingRedirectuntuk memaksanya menggunakan versi yang sama (seperti yang saya jelaskan di sini), dan ia berjaya! Saya tidak percaya bahawa saya tidak perlu menggandakan dan menyegerakkan struktur data saya secara manual antara pelayan dan pelanggan lagi.

Saya mula memikirkannya lebih banyak, dan pemikiran lain mengganggu saya ...

Adakah ianya berbaloi?

Saya bekerja untuk syarikat minyak besar, dengan banyak aplikasi warisan. Seorang rakan terpaksa bekerja dengan mesin maya kerana aplikasi yang dia debug kadang-kadang hanya berfungsi di Windows XP. Aplikasi lain yang saya terpaksa bekerja pada satu hari hanya berfungsi dengan Visual Studio 2010. Aplikasi lain yang menggunakan Code Contracts hanya berfungsi dengan Visual Studio 2013 kerana peluasan Code Contracts tidak berfungsi di Visual Studio 2015 atau 2017.

Sejak tahun 2012 ketika saya mula bekerja di sana hingga awal tahun 2019, saya tidak pernah berpeluang mengembangkan aplikasi baru. Semua kerja saya selalu dilakukan dengan gangguan pemaju yang lain. Tahun lalu saya mula belajar lebih banyak mengenai seni bina perisian, dan saya membaca buku "Clean Architecture" Uncle Bob.

Sekarang saya memulakan tahun baru ini dengan peluang ini, untuk pertama kalinya di syarikat ini saya membuat aplikasi web dari awal dan saya mahu melakukan pekerjaan dengan baik. Saya memilih ASP.NET Core untuk back-end saya, React untuk front-end, dan ia akan menjadi salah satu aplikasi pertama di syarikat ini yang berjalan dalam kontena Docker di kluster Kubernetes baru kami.

Seorang lagi pemaju yang lemah perlu mengusahakan projek ini pada masa akan datang, dengan kod saya dan semua kekacauan saya, dan saya tidak mahu mereka berurusan dengan kod buruk. Saya mahu semua pembangun selepas saya mahu mengusahakan projek ini. Ini tidak akan berlaku sekiranya mereka harus kehilangan satu hari kerja hanya untuk mendapatkan generasi kod pelanggan dari struktur data belakang yang berfungsi. Mereka kemudian akan membenci saya (dan ada di antara mereka yang sudah membenci saya kerana memasukkan kod TypeScript dalam projek ketika TypeScript masih dalam versi 0.9).

Apabila kita menulis kod yang bukan milik kita, kita mempunyai tanggungjawab untuk memudahkan orang lain mengusahakannya.

Setelah memikirkannya, saya membuat kesimpulan:

Kita harus mengelakkan pergantungan pada apa sahaja yang tidak dapat ditangani oleh pengurus pakej teknologi pilihan.

Dalam kes ini, selain ketergantungan pada Visual Studio dan Windows, saya akan membuat projek bergantung pada bug fix yang perlu diperbaiki oleh Microsoft (dan nampaknya ia tidak mempunyai keutamaan). Oleh itu, lebih baik menduplikasi kod ini dan menyegerakkannya secara manual daripada meletakkan kebergantungan pada enjin T4 ini.

Saya memilih untuk menggunakan .NET Core, tetapi jika beberapa pembangun pada masa akan datang ingin mengerjakan projek ini menggunakan Linux, saya tidak dapat menghentikannya.

Penyelesaian terakhir (TL; DR)

Kod pendua tidak betul, tetapi ketergantungan pada alat pihak ketiga lebih teruk. Oleh itu, apa yang dapat kita lakukan untuk mengelakkan pertindihan struktur data dan tidak bergantung pada IDE / plugin / ekstensi / alat khusus untuk pembangunan?

Saya memerlukan sedikit masa untuk menyedari bahawa satu-satunya alat yang saya perlukan ada selama ini, di dalam jangka masa bahasa: Refleksi .

Saya menyedari bahawa saya dapat menulis beberapa kod yang dijalankan semasa permulaan aplikasi Core ASP.NET belakang saya hanya dalam mod pembangunan. Kod ini dapat menggunakan refleksi untuk membaca metadata mengenai nama dan jenis semua struktur data yang ingin saya hasilkan antaramuka TypeScript. Saya hanya perlu memetakan C # primitif ke primitif TypeScript, menulis definisi TypeScript dalam folder tertentu, dan saya akan selesai.

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”