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 = [];
Gut
const array: ReadonlyArray<number> = [ 1, 3, 5 ]; array = [];
Schreibgeschützte Argumente deklarieren TypeScript 3.4 ist etwas einfacher .
function hoge(args: readonly string[]) { args.push(1);
Bevorzugte const-Zusicherungen für Literalwerte.
Schlecht:
const config = { hello: 'world' }; config.hello = 'world';
Gut
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 {
Gut
type EmailConfig = {
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 {
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:
- Ihre Vererbung ist eine "Ist-Eine" -Beziehung und keine "Hat-Eine" -Beziehung (Mensch -> Tier -> Benutzer -> Benutzerdetails).
- Sie können Code aus Basisklassen wiederverwenden (Menschen können sich wie alle Tiere bewegen).
- 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) { }
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; }
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 {
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 {
Fortsetzung folgt...