Código limpio para TypeScript - Parte 2

La primera parte , a juzgar por los comentarios, causó una opinión mixta, especialmente con respecto a la parte de enumeración. En algún lugar también puedo estar en desacuerdo, tanto con el autor del original como con algunos comentarios. Pero como se indica en la descripción inicial de la primera parte, el código limpio no es un dogma a seguir, son solo recomendaciones, cuya observancia eligen todos para satisfacer sus necesidades y puntos de vista.



Objetos y estructuras de datos


Usar inmunidad


El sistema de tipos en TypeScript le permite marcar propiedades individuales de una interfaz / clase como campos de solo lectura (solo lectura) . Esto le permite trabajar funcionalmente (la mutación inesperada es mala). Para escenarios más complejos, hay un tipo Readonly que toma un tipo T y marca todas sus propiedades de solo lectura utilizando tipos asignados (ver tipos asignados ).


Malo:


 interface Config { host: string; port: string; db: string; } 

Bueno


 interface Config { readonly host: string; readonly port: string; readonly db: string; } 

En el caso de una matriz, puede crear una matriz de solo lectura con ReadonlyArray<T> . que no permite cambios usando push() y fill() , pero puede usar concat() y slice() , no cambian los valores.


Malo:


 const array: number[] = [ 1, 3, 5 ]; array = []; // error array.push(100); // array will updated 

Bueno


 const array: ReadonlyArray<number> = [ 1, 3, 5 ]; array = []; // error array.push(100); // error 

Declarar argumentos de solo lectura TypeScript 3.4 es un poco más fácil .


 function hoge(args: readonly string[]) { args.push(1); // error } 

Preferencias const aserciones para valores literales.


Malo:


 const config = { hello: 'world' }; config.hello = 'world'; //   const array = [ 1, 3, 5 ]; array[0] = 10; //   //    function readonlyData(value: number) { return { value }; } const result = readonlyData(100); result.value = 200; //   

Bueno


 //     const config = { hello: 'world' } as const; config.hello = 'world'; //  //     const array = [ 1, 3, 5 ] as const; array[0] = 10; //  //        function readonlyData(value: number) { return { value } as const; } const result = readonlyData(100); result.value = 200; //  

Tipos vs. interfaces


Use tipos cuando necesite unión o intersección. Use la interfaz cuando quiera usar extends o implements . Sin embargo, no existe una regla estricta, use lo que funcione para usted. Para una explicación más detallada, vea estas respuestas sobre las diferencias entre type e interface en TypeScript.


Malo:


 interface EmailConfig { // ... } interface DbConfig { // ... } interface Config { // ... } //... type Shape = { // ... } 

Bueno


 type EmailConfig = { // ... } type DbConfig = { // ... } type Config = EmailConfig | DbConfig; // ... interface Shape { // ... } class Circle implements Shape { // ... } class Square implements Shape { // ... } 

Clases


Las clases deben ser pequeñas.


El tamaño de una clase se mide por su responsabilidad. Siguiendo el principio de responsabilidad exclusiva, la clase debe ser pequeña.


Malo:


 class Dashboard { getLanguage(): string { /* ... */ } setLanguage(language: string): void { /* ... */ } showProgress(): void { /* ... */ } hideProgress(): void { /* ... */ } isDirty(): boolean { /* ... */ } disable(): void { /* ... */ } enable(): void { /* ... */ } addSubscription(subscription: Subscription): void { /* ... */ } removeSubscription(subscription: Subscription): void { /* ... */ } addUser(user: User): void { /* ... */ } removeUser(user: User): void { /* ... */ } goToHomePage(): void { /* ... */ } updateProfile(details: UserDetails): void { /* ... */ } getVersion(): string { /* ... */ } // ... } 

Bueno


 class Dashboard { disable(): void { /* ... */ } enable(): void { /* ... */ } getVersion(): string { /* ... */ } } //  ,       // ... 

Alta cohesión bajo enlace


La cohesión determina el grado en que los miembros de una clase están relacionados entre sí. Idealmente, todos los campos de una clase deberían ser utilizados por cada método. Decimos que la clase está lo más conectada posible . En la práctica, sin embargo, esto no siempre es posible e incluso indeseable. Sin embargo, debe asegurarse de que la cohesión sea alta.


La conectividad también se refiere a cómo dos clases están relacionadas o dependen unas de otras. Las clases se consideran libremente si los cambios en una de ellas no afectan a la otra.


Malo:


 class UserManager { // :         . //   ,    ,    //      ,     , //       `emailSender`. constructor( private readonly db: Database, private readonly emailSender: EmailSender) { } async getUser(id: number): Promise<User> { return await db.users.findOne({ id }); } async getTransactions(userId: number): Promise<Transaction[]> { return await db.transactions.find({ userId }); } async sendGreeting(): Promise<void> { await emailSender.send('Welcome!'); } async sendNotification(text: string): Promise<void> { await emailSender.send(text); } async sendNewsletter(): Promise<void> { // ... } } 

Bueno


 class UserService { constructor(private readonly db: Database) { } async getUser(id: number): Promise<User> { return await this.db.users.findOne({ id }); } async getTransactions(userId: number): Promise<Transaction[]> { return await this.db.transactions.find({ userId }); } } class UserNotifier { constructor(private readonly emailSender: EmailSender) { } async sendGreeting(): Promise<void> { await this.emailSender.send('Welcome!'); } async sendNotification(text: string): Promise<void> { await this.emailSender.send(text); } async sendNewsletter(): Promise<void> { // ... } } 

Prefiere la composición sobre la herencia


Como se dijo en Patrones de diseño de la cuarta banda, debes
Prefiere la composición a la herencia siempre que puedas. Hay muchas buenas razones para usar la herencia y muchas buenas razones para usar la composición. La esencia de este principio es que si su mente instintivamente hereda, intente pensar si la composición puede modelar mejor su problema. En algunos casos, puede.


Entonces puede preguntar: "¿Cuándo debo usar la herencia?" Depende de su problema, pero es una lista decente cuando la herencia tiene más sentido que la composición:


  1. Su herencia es una relación "es-a" y no una relación "tiene-a" (Humano-> Animal vs. Usuario-> Detalles del usuario).
  2. Puede reutilizar el código de las clases base (las personas pueden moverse como todos los animales).
  3. Desea realizar cambios globales en las clases derivadas cambiando la clase base. (Cambio en el gasto calórico en todos los animales al moverlos).

Malo:


 class Employee { constructor( private readonly name: string, private readonly email: string) { } // ... } // ,   Employees ""  . EmployeeTaxData    Employee class EmployeeTaxData extends Employee { constructor( name: string, email: string, private readonly ssn: string, private readonly salary: number) { super(name, email); } // ... } 

Bueno


 class Employee { private taxData: EmployeeTaxData; constructor( private readonly name: string, private readonly email: string) { } setTaxData(ssn: string, salary: number): Employee { this.taxData = new EmployeeTaxData(ssn, salary); return this; } // ... } class EmployeeTaxData { constructor( public readonly ssn: string, public readonly salary: number) { } // ... } 

Usar cadenas de llamadas


Este patrón es muy útil y se usa comúnmente en muchas bibliotecas. Esto permite que su código sea expresivo y menos detallado. Por esta razón, use una cadena de métodos y vea qué tan limpio será su código.


Malo:


 class QueryBuilder { private collection: string; private pageNumber: number = 1; private itemsPerPage: number = 100; private orderByFields: string[] = []; from(collection: string): void { this.collection = collection; } page(number: number, itemsPerPage: number = 100): void { this.pageNumber = number; this.itemsPerPage = itemsPerPage; } orderBy(...fields: string[]): void { this.orderByFields = fields; } build(): Query { // ... } } // ... const queryBuilder = new QueryBuilder(); queryBuilder.from('users'); queryBuilder.page(1, 100); queryBuilder.orderBy('firstName', 'lastName'); const query = queryBuilder.build(); 

Bueno


 class QueryBuilder { private collection: string; private pageNumber: number = 1; private itemsPerPage: number = 100; private orderByFields: string[] = []; from(collection: string): this { this.collection = collection; return this; } page(number: number, itemsPerPage: number = 100): this { this.pageNumber = number; this.itemsPerPage = itemsPerPage; return this; } orderBy(...fields: string[]): this { this.orderByFields = fields; return this; } build(): Query { // ... } } // ... const query = new QueryBuilder() .from('users') .page(1, 100) .orderBy('firstName', 'lastName') .build(); 

Continuará ...

Source: https://habr.com/ru/post/485068/


All Articles