Clean Code für TypeScript - Teil 2

Der erste Teil hat nach den Kommentaren eine gemischte Meinung hervorgerufen, insbesondere im Hinblick auf den Aufzählungsteil. Irgendwo kann ich auch nicht zustimmen, sowohl mit dem Autor des Originals als auch mit einigen Kommentaren. Aber wie in der anfänglichen Beschreibung des ersten Teils angegeben, ist sauberer Code kein zu befolgendes Dogma, sondern lediglich Empfehlungen, deren Einhaltung jeder nach seinen Bedürfnissen und Ansichten auswählt.



Objekte und Datenstrukturen


Nutzen Sie die Immunität


Mit dem Typsystem in TypeScript können Sie einzelne Eigenschaften einer Schnittstelle / Klasse als schreibgeschützte Felder kennzeichnen (schreibgeschützt) . Auf diese Weise können Sie funktional arbeiten (unerwartete Mutation ist schlecht). Für komplexere Szenarien gibt es einen integrierten Readonly Typ, der einen T Typ annimmt und alle schreibgeschützten Eigenschaften mit zugeordneten Typen kennzeichnet (siehe zugeordnete Typen ).


Schlecht:


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

Gut


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

Im Fall eines Arrays können Sie mit ReadonlyArray<T> ein schreibgeschütztes Array ReadonlyArray<T> . Das erlaubt keine Änderungen mit push() und fill() , aber Sie können concat() und slice() sie ändern die Werte nicht.


Schlecht:


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

Gut


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

Schreibgeschützte Argumente deklarieren TypeScript 3.4 ist etwas einfacher .


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

Bevorzugte const-Zusicherungen für Literalwerte.


Schlecht:


 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; //   

Gut


 //     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; //  

Typen vs. Schnittstellen


Verwenden Sie Typen, wenn Sie Vereinigung oder Kreuzung benötigen. Verwenden Sie die Schnittstelle, wenn Sie Erweiterungen oder implements möchten. Es gibt jedoch keine strenge Regel, verwenden Sie, was für Sie funktioniert. Eine ausführlichere Erläuterung finden Sie in diesen Antworten zu den Unterschieden zwischen type und interface in TypeScript.


Schlecht:


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

Gut


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

Klassen


Klassen sollten klein sein


Die Größe einer Klasse wird an ihrer Verantwortung gemessen. Nach dem Prinzip der alleinigen Verantwortung sollte die Klasse klein sein.


Schlecht:


 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 { /* ... */ } // ... } 

Gut


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

Hohe Kohäsion, niedrige Bindung


Kohäsion bestimmt, inwieweit die Mitglieder einer Klasse miteinander verwandt sind. Idealerweise sollten alle Felder in einer Klasse von jeder Methode verwendet werden. Wir sagen, dass die Klasse so verbunden wie möglich ist . In der Praxis ist dies jedoch nicht immer möglich und sogar unerwünscht. Sie müssen jedoch sicherstellen, dass der Zusammenhalt hoch ist.


Konnektivität bezieht sich auch darauf, wie zwei Klassen miteinander verwandt oder voneinander abhängig sind. Klassen gelten als lose gekoppelt, wenn sich Änderungen an einer von ihnen nicht auf die andere auswirken.


Schlecht:


 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> { // ... } } 

Gut


 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> { // ... } } 

Bevorzugen Sie die Komposition gegenüber der Vererbung


Wie in Design Patterns aus der vierten Gang gesagt, müssen Sie
Ziehen Sie die Komposition der Vererbung vor, wo immer Sie können. Es gibt viele gute Gründe für die Verwendung der Vererbung und viele gute Gründe für die Verwendung der Komposition. Das Wesentliche an diesem Prinzip ist, dass Sie versuchen, zu überlegen, ob die Komposition Ihr Problem besser modellieren kann, wenn Ihr Verstand instinktiv erbt. In einigen Fällen kann es.


Dann können Sie fragen: "Wann sollte ich Vererbung verwenden?" Es hängt von Ihrem Problem ab, aber es ist eine anständige Liste, wenn Vererbung sinnvoller ist als Komposition:


  1. Ihre Vererbung ist eine "Ist-Eine" -Beziehung und keine "Hat-Eine" -Beziehung (Mensch -> Tier -> Benutzer -> Benutzerdetails).
  2. Sie können Code aus Basisklassen wiederverwenden (Menschen können sich wie alle Tiere bewegen).
  3. Sie möchten globale Änderungen an abgeleiteten Klassen vornehmen, indem Sie die Basisklasse ändern. (Veränderung des Kalorienverbrauchs bei allen Tieren beim Umsetzen).

Schlecht:


 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); } // ... } 

Gut


 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) { } // ... } 

Verwenden Sie Aufrufketten


Dieses Muster ist sehr nützlich und wird häufig in vielen Bibliotheken verwendet. Dadurch kann Ihr Code ausdrucksstark und weniger ausführlich sein. Verwenden Sie aus diesem Grund eine Reihe von Methoden, um festzustellen, wie sauber Ihr Code sein wird.


Schlecht:


 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(); 

Gut


 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(); 

Fortsetzung folgt...

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


All Articles