Latar belakang
Setengah tahun yang lalu, perusahaan kami memutuskan untuk beralih ke teknologi yang lebih baru dan lebih modis. Untuk melakukan ini, sekelompok spesialis dibentuk, yaitu untuk: memutuskan tumpukan teknologi, membuat jembatan ke kode Legacy berdasarkan tumpukan ini, dan akhirnya mentransfer beberapa modul lama ke rel baru. Saya beruntung masuk ke grup ini. Basis kode klien adalah sekitar satu juta baris kode. Kami memilih TypeScript sebagai bahasa. Mereka memutuskan untuk membuat substrat GUI pada vue bersamaan dengan
komponen-kelas-vue dan
IoC .
Tetapi ceritanya bukan tentang bagaimana kita menyingkirkan warisan kode, tetapi tentang satu insiden kecil yang menghasilkan perang pengetahuan yang sesungguhnya. Siapa peduli, selamat datang ke kucing.
Mengenal masalahnya
Beberapa bulan setelah dimulainya, kelompok kerja merasa nyaman dengan tumpukan baru dan berhasil mentransfer sebagian dari kode lama ke dalamnya. Anda bahkan dapat mengatakan bahwa ada beberapa massa daging yang kritis ketika Anda perlu berhenti, menarik napas dan melihat apa yang telah kami lakukan.
Tempat-tempat yang membutuhkan studi mendalam, seperti Anda ketahui, sudah cukup. Namun dari semua hal penting, ironisnya, tidak ada yang menangkap saya. Bukan sebagai pengembang. Tetapi salah satu yang tidak penting, sebaliknya, sedang menghantui. Saya sangat kesal sampai-sampai kami menggunakan data enumerasi. Tidak ada generalisasi. Anda akan bertemu dengan kelas yang terpisah dengan satu set metode yang diperlukan, maka Anda akan menemukan dua kelas, untuk hal yang sama, atau bahkan sesuatu yang misterius dan ajaib. Dan tidak ada yang bisa disalahkan. Langkah yang kami ambil untuk menyingkirkan warisan itu terlalu besar.
Setelah mengangkat topik transfer antar kolega, saya menerima dukungan. Ternyata bukan saja saya tidak puas dengan kurangnya pendekatan terpadu untuk bekerja dengan mereka. Pada saat itu, saya merasa bahwa dalam beberapa jam pengkodean saya akan mencapai hasil yang diinginkan dan mengajukan diri untuk memperbaiki situasi. Tapi betapa salahnya saya saat itu ...
// , . import {Enum} from "ts-jenum"; @Enum("text") export class State { static readonly NEW = new State("New"); static readonly ACTIVE = new State("Active"); static readonly BLOCKED = new State("Blocked"); private constructor(public text: string) { super(); } } // console.log("" + State.ACTIVE); // Active console.log("" + State.BLOCKED); // Blocked console.log(State.values()); // [State.NEW, State.ACTIVE, State.BLOCKED] console.log(State.valueOf("New")); // State.NEW console.log(State.valueByName("NEW")); // State.NEW console.log(State.ACTIVE.enumName); // ACTIVE
1. Dekorator
Di mana untuk memulai? Hanya satu hal yang terlintas di benak saya: anggap enumerasi mirip Jawa sebagai dasar. Tetapi karena saya ingin pamer kepada rekan-rekan saya, saya memutuskan untuk meninggalkan warisan klasik. Gunakan dekorator saja. Selain itu, dekorator dapat diterapkan dengan argumen untuk memberikan penghitungan fungsi yang dibutuhkan dengan mudah dan alami. Pengodean tidak memakan banyak waktu, dan setelah beberapa jam saya sudah memiliki sesuatu yang mirip dengan ini:
Dekorator export function Enum(idProperty?: string) { // tslint:disable-next-line return function <T extends Function, V>(target: T): T { if ((target as any).__enumMap__ || (target as any).__enumValues__) { const enumName = (target as any).prototype.constructor.name; throw new Error(`The enumeration ${enumName} has already initialized`); } const enumMap: any = {}; const enumMapByName: any = {}; const enumValues = []; // Lookup static fields for (const key of Object.keys(target)) { const value: any = (target as any)[key]; // Check static field: to be instance of enum type if (value instanceof target) { let id; if (idProperty) { id = (value as any)[idProperty]; if (typeof id !== "string" && typeof id !== "number") { const enumName = (target as any).prototype.constructor.name; throw new Error(`The value of the ${idProperty} property in the enumeration element ${enumName}. ${key} is not a string or a number: ${id}`); } } else { id = key; } if (enumMap[id]) { const enumName = (target as any).prototype.constructor.name; throw new Error(`An element with the identifier ${id}: ${enumName}.${enumMap[id].enumName} already exists in the enumeration ${enumName}`); } enumMap[id] = value; enumMapByName[key] = value; enumValues.push(value); Object.defineProperty(value, "__enumName__", {value: key}); Object.freeze(value); } } Object.freeze(enumMap); Object.freeze(enumValues); Object.defineProperty(target, "__enumMap__", {value: enumMap}); Object.defineProperty(target, "__enumMapByName__", {value: enumMapByName}); Object.defineProperty(target, "__enumValues__", {value: enumValues}); if (idProperty) { Object.defineProperty(target, "__idPropertyName__", {value: idProperty}); } // values(), valueOf , -. Object.freeze(target); return target; }; }
Dan di sini saya mengalami kegagalan pertama. Ternyata Anda tidak dapat mengubah jenisnya dengan bantuan dekorator. Microsoft bahkan memiliki daya tarik pada subjek ini:
Mutasi Dekorator Kelas . Ketika saya mengatakan bahwa Anda tidak dapat mengubah jenisnya, maksud saya IDE Anda tidak akan tahu apa-apa tentang ini dan tidak akan menawarkan petunjuk dan penyelesaian otomatis yang memadai. Dan Anda dapat mengubah jenis sebanyak yang Anda suka, hanya yang terbaik ...
2. Warisan
Karena saya tidak mencoba membujuk diri saya sendiri, tetapi saya harus kembali ke ide untuk membuat transfer berdasarkan kelas umum. Ya, dan apa yang salah dengan itu? Saya merasa terganggu oleh diri saya sendiri. Waktu hampir habis, orang-orang dari grup, Tuhan melarang, saya menghabiskan waktu di dekorator. Dimungkinkan untuk mengajukan enum secara umum dalam satu jam dan melanjutkan. Jadi begitulah. Dengan cepat melempar kode kelas dasar Enumerable dan mendesah, merasa lega. Saya melemparkan draft ke repositori umum dan meminta rekan saya untuk memeriksa solusinya.
Terhitung // : , - export class Enumerable<T> { constructor() { const clazz = this.constructor as any as EnumStore; if (clazz.__enumMap__ || clazz.__enumValues__ || clazz.__enumMapByName__) { throw new Error(`It is forbidden to create ${clazz.name} enumeration elements outside the enumeration`); } } static values<T>(): ReadonlyArray<T> { const clazz = this as any as EnumStore; if (!clazz.__enumValues__) { throw new Error(`${clazz.name} enumeration has not been initialized. It is necessary to add the decorator @Enum to the class`); } return clazz.__enumValues__; } static valueOf<T>(id: string | number): T { const clazz = this as any as EnumStore; if (!clazz.__enumMap__) { throw new Error(`${clazz.name} enumeration has not been initialized. It is necessary to add the decorator @Enum to the class`); } const value = clazz.__enumMap__[id]; if (!value) { throw new Error(`The element with ${id} identifier does not exist in the $ {clazz.name} enumeration`); } return value; } static valueByName<T>(name: string): T { const clazz = this as any as EnumStore; if (!clazz.__enumMapByName__) { throw new Error(`${clazz.name} enumeration has not been initialized. It is necessary to add the decorator @Enum to the class`); } const value = clazz.__enumMapByName__[name]; if (!value) { throw new Error(`The element with ${name} name does not exist in the ${clazz.name} enumeration`); } return value; } get enumName(): string { return (this as any).__enumName__; } toString(): string { const clazz = this.constructor as any as EnumStore; if (clazz.__idPropertyName__) { const self = this as any; return self[clazz.__idPropertyName__]; } return this.enumName; } }
Tetapi, tragedi itu semakin meningkat. TypeScript versi 2.6.2 diinstal pada mesin saya, versi di mana ada bug yang sangat berharga. Tak ternilai, karena itu bukan bug, tetapi fitcha. Sebuah suara dari kamar sebelah berteriak bahwa dia tidak akan melakukan apa-apa. Kesalahan saat mengkompilasi (
pengubahan ). Aku tidak bisa mempercayai telingaku sendiri, karena aku selalu menyusun proyek sebelum dorongan, bahkan jika itu adalah konsep. Dan suara hati berbisik: ini kegagalan, kawan.
Setelah percobaan singkat, saya menyadari bahwa itu adalah versi TypeScript. Ternyata jika nama generik kelas bertepatan dengan nama generik yang ditentukan dalam metode statis, maka kompilator menganggap ini sebagai satu jenis. Tapi apa pun itu, sekarang sudah menjadi bagian dari sejarah perang itu untuk pengetahuan tentang TypeScript.
Intinya: masalah dengan transfer apa adanya dan tetap. Kesedihanku ...
Catatan:
Saya tidak dapat mereproduksi perilaku ini sendiri sekarang dengan 2.6.2, mungkin saya membuat kesalahan dengan versi atau tidak menambahkan sesuatu dalam kasus uji. Dan permintaan untuk masalah di atas Izinkan anggota statis untuk referensi parameter tipe kelas ditolak.3. Fungsi casting
Terlepas dari kenyataan bahwa ada solusi bengkok, dengan indikasi eksplisit dari jenis kelas enumerasi dalam metode statis, misalnya, State.valueOf <State> (), itu tidak cocok untuk siapa pun, dan pertama-tama, saya. Untuk sementara, saya bahkan mengesampingkan transfer sialan dan kehilangan kepercayaan bahwa saya umumnya bisa menyelesaikan masalah ini.
Setelah mendapatkan kembali kekuatan moral saya, saya mencari trik TipScript di Internet, melihat siapa yang menderita apa, membaca lagi dokumentasi bahasa, untuk berjaga-jaga, dan memutuskan, untuk menghindarinya, untuk menyelesaikan pekerjaan. Tujuh jam percobaan terus-menerus, tidak mencari apa pun, bahkan untuk kopi, memberikan hasil. Hanya satu fungsi, yang terdiri dari satu baris kode, meletakkan semuanya di tempatnya.
export function EnumType<T>(): IStaticEnum<T> { return (<IStaticEnum<T>> Enumerable); } // IStaticEnum : export interface IStaticEnum<T> { new(): {enumName: string}; values(): ReadonlyArray<T>; valueOf(id: string | number): T; valueByName(name: string): T; }
Dan deklarasi enumerasi seperti Java sekarang terlihat seperti ini:
import {Enum, EnumType, IStaticEnum} from "ts-jenum"; @Enum("text") export class State extends EnumType<State>() { static readonly NEW = new State("New"); static readonly ACTIVE = new State("Active"); static readonly BLOCKED = new State("Blocked"); private constructor(public text: string) { super(); } } // console.log("" + State.ACTIVE); // Active console.log("" + State.BLOCKED); // Blocked console.log(State.values()); // [State.NEW, State.ACTIVE, State.BLOCKED] console.log(State.valueOf("New")); // State.NEW console.log(State.valueByName("NEW")); // State.NEW console.log(State.ACTIVE.enumName); // ACTIVE
Bukan tanpa rasa ingin tahu, dengan impor ekstra IStaticEnum, yang tidak digunakan di mana pun (lihat contoh di atas). Dalam versi TypeScript 2.6.2 yang sangat naas, Anda harus menentukannya secara eksplisit. Bug tentang topik di
sini .
Total
Jika Anda menderita untuk waktu yang lama, sesuatu akan berhasil. Tautan ke github dengan hasil pekerjaan yang dilakukan di
sini . Bagi saya sendiri, saya menemukan bahwa TypeScript adalah bahasa dengan fitur hebat. Ada begitu banyak peluang ini sehingga Anda bisa tenggelam di dalamnya. Dan siapa yang tidak mau tenggelam, belajar berenang. Jika Anda kembali ke topik transfer, Anda dapat melihat bagaimana orang lain bekerja dengannya:
Tulis tentang pekerjaan Anda, saya pikir komunitas akan tertarik. Terima kasih atas kesabaran dan minat Anda.