### Good Import Organization Example Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Shows correctly organized and alphabetized import statements according to established TypeScript conventions. ```typescript import 'reflect-metadata'; import fs from 'fs'; import { BindingScopeEnum, Container } from 'inversify'; import { AttributeTypes } from '../model/attribute'; import { TypeDefinition } from '../types/typeDefinition'; import type { Customer, Credentials } from '../model/types'; import { ApiCredentials, Adapters } from './common/api/authorization'; import { ConfigPlugin } from './plugins/config/configPlugin'; ``` -------------------------------- ### Bad Import Organization Example Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates disorganized import statements that violate best practices for alphabetization and grouping. ```typescript import { TypeDefinition } from '../types/typeDefinition'; import { AttributeTypes } from '../model/attribute'; import { Customer, Credentials } from '../model/types'; import { ApiCredentials, Adapters } from './common/api/authorization'; import fs from 'fs'; import { ConfigPlugin } from './plugins/config/configPlugin'; import { BindingScopeEnum, Container } from 'inversify'; import 'reflect-metadata'; ``` -------------------------------- ### Small Class Example (Good) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Presents a 'good' example of a refactored class that adheres to the Single Responsibility Principle by having a smaller scope. ```ts class Dashboard { disable(): void { /* ... */ } enable(): void { /* ... */ } getVersion(): string { /* ... */ } } // split the responsibilities by moving the remaining methods to other classes // ... ``` -------------------------------- ### SRP: Good Example - Separating Responsibilities Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This demonstrates separating authentication logic into a dedicated class, adhering to the Single Responsibility Principle. Each class now has a single reason to change. ```typescript class UserAuth { constructor(private readonly user: User) { } verifyCredentials() { // ... } } class UserSettings { private readonly auth: UserAuth; constructor(private readonly user: User) { this.auth = new UserAuth(user); } changeSettings(settings: UserSettings) { if (this.auth.verifyCredentials()) { // ... } } } ``` -------------------------------- ### Duplicate Code Example (Developers) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This snippet shows a function that iterates over developers and renders their data. It contains logic that might be duplicated in other similar functions. ```typescript function showDeveloperList(developers: Developer[]) { developers.forEach((developer) => { const expectedSalary = developer.calculateExpectedSalary(); const experience = developer.getExperience(); const githubLink = developer.getGithubLink(); const data = { expectedSalary, experience, githubLink }; render(data); }); } ``` -------------------------------- ### OCP: Bad Example - Modifying Existing Code for New Functionality Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This example violates the Open/Closed Principle by using instanceof checks within HttpRequester to handle different adapter types. Adding a new adapter requires modifying HttpRequester. ```typescript class AjaxAdapter extends Adapter { constructor() { super(); } // ... } class NodeAdapter extends Adapter { constructor() { super(); } // ... } class HttpRequester { constructor(private readonly adapter: Adapter) { } async fetch(url: string): Promise { if (this.adapter instanceof AjaxAdapter) { const response = await makeAjaxCall(url); // transform response and return } else if (this.adapter instanceof NodeAdapter) { const response = await makeHttpCall(url); // transform response and return } } } function makeAjaxCall(url: string): Promise { // request and return promise } function makeHttpCall(url: string): Promise { // request and return promise } ``` -------------------------------- ### Large Class Example (Bad) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Shows a 'bad' example of a large class with too many responsibilities, violating the Single Responsibility Principle. ```ts 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 { /* ... */ } // ... } ``` -------------------------------- ### Good Implementation of DIP (Loose Coupling with Abstraction) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This example demonstrates the Dependency Inversion Principle by introducing an abstraction (Formatter interface) and injecting the dependency. This allows for different formatter implementations without modifying the ReportReader. ```typescript import { readFile as readFileCb } from 'fs'; import { promisify } from 'util'; const readFile = promisify(readFileCb); type ReportData = { // .. } interface Formatter { parse(content: string): T; } class XmlFormatter implements Formatter { parse(content: string): T { // Converts an XML string to an object T } } class JsonFormatter implements Formatter { parse(content: string): T { // Converts a JSON string to an object T } } class ReportReader { constructor(private readonly formatter: Formatter) { } async read(path: string): Promise { const text = await readFile(path, 'UTF8'); return this.formatter.parse(text); } } // ... const reader = new ReportReader(new XmlFormatter()); const report = await reader.read('report.xml'); // or if we had to read a json report const reader = new ReportReader(new JsonFormatter()); const report = await reader.read('report.json'); ``` -------------------------------- ### Duplicate Code Example (Managers) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This snippet shows a function that iterates over managers and renders their data. It shares similarities with the developer list function, indicating potential for abstraction. ```typescript function showManagerList(managers: Manager[]) { managers.forEach((manager) => { const expectedSalary = manager.calculateExpectedSalary(); const experience = manager.getExperience(); const portfolio = manager.getMBAProjects(); const data = { expectedSalary, experience, portfolio }; render(data); }); } ``` -------------------------------- ### OCP: Good Example - Extending Functionality Without Modification Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This demonstrates the Open/Closed Principle by using an abstract Adapter class and polymorphism. New adapters can be added without modifying the HttpRequester class. ```typescript abstract class Adapter { abstract async request(url: string): Promise; // code shared to subclasses ... } class AjaxAdapter extends Adapter { constructor() { super(); } async request(url: string): Promise{ // request and return promise } // ... } class NodeAdapter extends Adapter { constructor() { super(); } async request(url: string): Promise{ // request and return promise } // ... } class HttpRequester { constructor(private readonly adapter: Adapter) { } async fetch(url: string): Promise { const response = await this.adapter.request(url); // transform response and return } } ``` -------------------------------- ### Bad Capitalization Examples in TypeScript Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Illustrates inconsistent capitalization for constants, variables, and function names. This can lead to confusion and reduced readability. ```typescript const DAYS_IN_WEEK = 7; const daysInMonth = 30; const songs = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; const Artists = ['ACDC', 'Led Zeppelin', 'The Beatles']; function eraseDatabase() {} function restore_database() {} type animal = { /* ... */ } type Container = { /* ... */ } ``` -------------------------------- ### Good: Single Assert per Test Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This example shows how to refactor the previous test into multiple tests, each focusing on a single assertion and concept. This improves readability and maintainability. ```typescript import { assert } from 'chai'; describe('AwesomeDate', () => { it('handles 30-day months', () => { const date = new AwesomeDate('1/1/2015'); assert.equal('1/31/2015', date.addDays(30)); }); it('handles leap year', () => { const date = new AwesomeDate('2/1/2016'); assert.equal('2/29/2016', date.addDays(28)); }); it('handles non-leap year', () => { const date = new AwesomeDate('2/1/2015'); assert.equal('3/1/2015', date.addDays(28)); }); }); ``` -------------------------------- ### Fibonacci Generation with itiriri library Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This example demonstrates using the 'itiriri' library to process a stream of Fibonacci numbers generated by a generator function. It allows chaining operations like 'take' and 'forEach'. ```typescript import itiriri from 'itiriri'; function* fibonacci(): IterableIterator { let [a, b] = [0, 1]; while (true) { yield a; [a, b] = [b, a + b]; } } itiriri(fibonacci()) .take(10) .forEach(fib => console.log(fib)); ``` -------------------------------- ### Bad Implementation of DIP (Tight Coupling) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This example shows a class tightly coupled to a specific formatter implementation, violating the Dependency Inversion Principle. It should depend on an abstraction instead. ```typescript import { readFile as readFileCb } from 'fs'; import { promisify } from 'util'; const readFile = promisify(readFileCb); type ReportData = { // .. } class XmlFormatter { parse(content: string): T { // Converts an XML string to an object T } } class ReportReader { // BAD: We have created a dependency on a specific request implementation. // We should just have ReportReader depend on a parse method: `parse` private readonly formatter = new XmlFormatter(); async read(path: string): Promise { const text = await readFile(path, 'UTF8'); return this.formatter.parse(text); } } // ... const reader = new ReportReader(); const report = await reader.read('report.xml'); ``` -------------------------------- ### Function performing multiple actions Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Functions should adhere to the single responsibility principle. This 'bad' example iterates through clients, checks their status, and emails them, violating this principle. ```typescript function emailActiveClients(clients: Client[]) { clients.forEach((client) => { const clientRecord = database.lookup(client); if (clientRecord.isActive()) { email(client); } }); } ``` -------------------------------- ### Function with a descriptive name Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Use descriptive function names that clearly state the action performed. The 'good' example 'addMonthToDate' explicitly states that a month is being added. ```typescript function addMonthToDate(date: Date, month: number): Date { // ... } const date = new Date(); addMonthToDate(date, 1); ``` -------------------------------- ### Method Chaining: Bad QueryBuilder Usage Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Shows a 'bad' example of using a QueryBuilder class where each method call is on a separate line. This is verbose and less readable compared to method chaining. ```typescript 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(); ``` -------------------------------- ### SRP: Bad Example - Violating Single Responsibility Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This class handles both user authentication and settings changes, violating the Single Responsibility Principle. It has multiple reasons to change. ```typescript class UserSettings { constructor(private readonly user: User) { } changeSettings(settings: UserSettings) { if (this.verifyCredentials()) { // ... } } verifyCredentials() { // ... } } ``` -------------------------------- ### Function with too many arguments Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Avoid functions with a large number of arguments. This example shows a function with four arguments that should ideally be consolidated into an options object. ```typescript function createMenu(title: string, body: string, buttonText: string, cancellable: boolean) { // ... } createMenu('Foo', 'Bar', 'Baz', true); ``` -------------------------------- ### Good: Descriptive Test Names Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This example demonstrates descriptive test names that clearly state the intention of the test. This improves the debugging process when tests fail. ```typescript describe('Calendar', () => { it('should handle leap year', () => { // ... }); it('should throw when format is invalid', () => { // ... }); }); ``` -------------------------------- ### Function with an unclear name Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Function names should clearly indicate their purpose. The 'bad' example 'addToDate' is ambiguous about what is being added to the date. ```typescript function addToDate(date: Date, month: number): Date { // ... } const date = new Date(); // It's hard to tell from the function name what is added addToDate(date, 1); ``` -------------------------------- ### Bad: Vague Test Names Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This example shows test names that do not clearly indicate the intention or the scenario being tested. Vague names make it difficult to understand failures. ```typescript describe('Calendar', () => { it('2/29/2020', () => { // ... }); it('throws', () => { // ... }); }); ``` -------------------------------- ### Good Capitalization Examples in TypeScript Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates consistent capitalization following common conventions: capitalized SNAKE_CASE for constants, PascalCase for types, and camelCase for variables and functions. This improves code clarity. ```typescript const DAYS_IN_WEEK = 7; const DAYS_IN_MONTH = 30; const SONGS = ['Back In Black', 'Stairway to Heaven', 'Hey Jude']; const ARTISTS = ['ACDC', 'Led Zeppelin', 'The Beatles']; const discography = getArtistDiscography('ACDC'); const beatlesSongs = SONGS.filter((song) => isBeatlesSong(song)); function eraseDatabase() {} function restoreDatabase() {} type Animal = { /* ... */ } type Container = { /* ... */ } ``` -------------------------------- ### Function with multiple levels of abstraction Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Functions should operate at a single level of abstraction. This 'bad' example mixes high-level parsing logic with lower-level tokenization and AST generation. ```typescript function parseCode(code: string) { const REGEXES = [ /* ... */ ]; const statements = code.split(' '); const tokens = []; REGEXES.forEach((regex) => { statements.forEach((statement) => { // ... }); }); const ast = []; tokens.forEach((token) => { // lex... }); ast.forEach((node) => { // parse... }); } ``` -------------------------------- ### Function with a single level of abstraction Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Break down complex operations into smaller functions, each operating at a single abstraction level. This 'good' example separates parsing into 'tokenize' and 'parse' functions. ```typescript const REGEXES = [ /* ... */ ]; function parseCode(code: string) { const tokens = tokenize(code); const syntaxTree = parse(tokens); syntaxTree.forEach((node) => { // parse... }); } function tokenize(code: string): Token[] { const statements = code.split(' '); const tokens: Token[] = []; REGEXES.forEach((regex) => { statements.forEach((statement) => { tokens.push( /* ... */ ); }); }); return tokens; } function parse(tokens: Token[]): SyntaxTree { const syntaxTree: SyntaxTree[] = []; tokens.forEach((token) => { syntaxTree.push( /* ... */ ); }); return syntaxTree; } ``` -------------------------------- ### Function performing a single action Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Refactor functions to perform a single, well-defined task. This 'good' example separates the filtering of active clients into a dedicated function, improving modularity. ```typescript function emailActiveClients(clients: Client[]) { clients.filter(isActiveClient).forEach(email); } function isActiveClient(client: Client) { const clientRecord = database.lookup(client); return clientRecord.isActive(); } ``` -------------------------------- ### Bad: Multiple Asserts in One Test Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This example demonstrates a test with multiple assertions, violating the single concept per test rule. It checks date boundary handling with various scenarios in a single test case. ```typescript import { assert } from 'chai'; describe('AwesomeDate', () => { it('handles date boundaries', () => { let date: AwesomeDate; date = new AwesomeDate('1/1/2015'); assert.equal('1/31/2015', date.addDays(30)); date = new AwesomeDate('2/1/2016'); assert.equal('2/29/2016', date.addDays(28)); date = new AwesomeDate('2/1/2015'); assert.equal('3/1/2015', date.addDays(28)); }); }); ``` -------------------------------- ### Async/Await File Download (Cleaner Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Utilizes `async`/`await` syntax to further simplify asynchronous operations, making the code appear more synchronous and easier to follow. Error handling is managed with `try...catch`. ```typescript import { get } from 'request'; import { writeFile } from 'fs'; import { promisify } from 'util'; const write = promisify(writeFile); async function downloadPage(url: string): Promise { const response = await get(url); return response; } // somewhere in an async function try { const content = await downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin'); await write('article.html', content); console.log(content); } catch (error) { console.error(error); } ``` -------------------------------- ### Callback-based File Download (Bad Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates the traditional callback pattern for downloading a page and writing its content to a file. This approach can lead to deeply nested code ('callback hell'). ```typescript import { get } from 'request'; import { writeFile } from 'fs'; function downloadPage(url: string, saveTo: string, callback: (error: Error, content?: string) => void) { get(url, (error, response) => { if (error) { callback(error); } else { writeFile(saveTo, response.body, (error) => { if (error) { callback(error); } else { callback(null, response.body); } }); } }); } downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html', (error, content) => { if (error) { console.error(error); } else { console.log(content); } }); ``` -------------------------------- ### Promise-based File Download (Good Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Refactors the download and write operation using Promises and `util.promisify` for cleaner asynchronous code. This avoids callback hell and improves readability. ```typescript import { get } from 'request'; import { writeFile } from 'fs'; import { promisify } from 'util'; const write = promisify(writeFile); function downloadPage(url: string, saveTo: string): Promise { return get(url) .then(response => write(saveTo, response)); } downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html') .then(content => console.log(content)) .catch(error => console.error(error)); ``` -------------------------------- ### Good Import with TypeScript Alias Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates using a TypeScript alias to create a cleaner, more readable import path. ```typescript import { UserService } from '@services/UserService'; ``` -------------------------------- ### tsconfig.json Configuration for Aliases Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Shows the necessary tsconfig.json compilerOptions to enable path aliases for cleaner imports. ```json // tsconfig.json ... "compilerOptions": { ... "baseUrl": "src", "paths": { "@services": ["services/*"] } ... } ... ``` -------------------------------- ### ISP Adherence: Segregated interfaces for printer functions Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates adherence to ISP by segregating functionalities into distinct interfaces (Printer, Fax, Scanner), allowing classes to implement only the interfaces they need. ```typescript interface Printer { print(); } interface Fax { fax(); } interface Scanner { scan(); } class AllInOnePrinter implements Printer, Fax, Scanner { print() { // ... } fax() { // ... } scan() { // ... } } class EconomicPrinter implements Printer { print() { // ... } } ``` -------------------------------- ### LSP Adherence: Using an abstract Shape class Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Illustrates a correct implementation adhering to LSP by using an abstract Shape class, allowing different geometric forms to be treated polymorphically without issues. ```typescript abstract class Shape { setColor(color: string): this { // ... } render(area: number) { // ... } abstract getArea(): number; } class Rectangle extends Shape { constructor( private readonly width = 0, private readonly height = 0) { super(); } getArea(): number { return this.width * this.height; } } class Square extends Shape { constructor(private readonly length: number) { super(); } getArea(): number { return this.length * this.length; } } function renderLargeShapes(shapes: Shape[]) { shapes.forEach((shape) => { const area = shape.getArea(); shape.render(area); }); } const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)]; renderLargeShapes(shapes); ``` -------------------------------- ### Inheritance vs. Composition: Good Employee and EmployeeTaxData Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Illustrates a 'good' approach using composition, where EmployeeTaxData is a separate class and is held as a property within the Employee class. This adheres to the 'has-a' relationship and promotes better separation of concerns. ```typescript 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) { } // ... } ``` -------------------------------- ### Type vs. Interface Declaration (Bad) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates the 'bad' practice of using both type and interface for similar configurations without clear distinction. ```ts interface EmailConfig { // ... } interface DbConfig { // ... } interface Config { // ... } //... type Shape = { // ... } ``` -------------------------------- ### Type vs. Interface Declaration (Good) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Illustrates the 'good' practice of using types for unions/intersections and interfaces for extensibility, aligning with common TypeScript conventions. ```ts type EmailConfig = { // ... } type DbConfig = { // ... } type Config = EmailConfig | DbConfig; // ... interface Shape { // ... } class Circle implements Shape { // ... } class Square implements Shape { // ... } ``` -------------------------------- ### Method Chaining: Good QueryBuilder Usage Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates a 'good' implementation of the QueryBuilder class using method chaining. Each method returns 'this', allowing for fluent, chained calls that improve code readability and reduce verbosity. ```typescript 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(); ``` -------------------------------- ### High Cohesion and Low Coupling (Good) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates a 'good' refactoring into separate classes (UserService, UserNotifier) to achieve high cohesion and low coupling. ```ts class UserService { constructor(private readonly db: Database) { } async getUser(id: number): Promise { return await this.db.users.findOne({ id }); } async getTransactions(userId: number): Promise { return await this.db.transactions.find({ userId }); } } class UserNotifier { constructor(private readonly emailSender: EmailSender) { } async sendGreeting(): Promise { await this.emailSender.send('Welcome!'); } async sendNotification(text: string): Promise { await this.emailSender.send(text); } async sendNewsletter(): Promise { // ... } } ``` -------------------------------- ### Favor Functional Programming: Calculate Total Lines of Code (Good) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This functional approach uses the `reduce` method to calculate the total lines of code. It is more declarative and often considered more readable. ```typescript const contributions = [ { name: 'Uncle Bobby', linesOfCode: 500 }, { name: 'Suzie Q', linesOfCode: 1500 }, { name: 'Jimmy Gosling', linesOfCode: 150 }, { name: 'Gracie Hopper', linesOfCode: 1000 } ]; const totalOutput = contributions .reduce((totalLines, output) => totalLines + output.linesOfCode, 0); ``` -------------------------------- ### Function with consolidated arguments using an object Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Improve function readability and testability by accepting a single options object. This approach makes the function signature clearer and simulates named parameters. ```typescript function createMenu(options: { title: string, body: string, buttonText: string, cancellable: boolean }) { // ... } createMenu({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }); ``` -------------------------------- ### High Cohesion and Low Coupling (Bad) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Illustrates a 'bad' class design where a UserManager holds multiple responsibilities, leading to low cohesion and high coupling. ```ts class UserManager { // Bad: each private variable is used by one or another group of methods. // It makes clear evidence that the class is holding more than a single responsibility. // If I need only to create the service to get the transactions for a user, // I'm still forced to pass and instance of `emailSender`. constructor( private readonly db: Database, private readonly emailSender: EmailSender) { } async getUser(id: number): Promise { return await db.users.findOne({ id }); } async getTransactions(userId: number): Promise { return await db.transactions.find({ userId }); } async sendGreeting(): Promise { await emailSender.send('Welcome!'); } async sendNotification(text: string): Promise { await emailSender.send(text); } async sendNewsletter(): Promise { // ... } } ``` -------------------------------- ### Handling caught errors with a logger (Good Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md When an error is caught, ensure it is handled appropriately, such as logging it using a dedicated logger. This provides visibility and allows for necessary actions. ```typescript import { logger } from './logging' try { functionThatMightThrow(); } catch (error) { logger.log(error); } ``` -------------------------------- ### Bad Import with Long Relative Path Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Illustrates an import statement using a long, relative path, which can be difficult to manage. ```typescript import { UserService } from '../../../services/UserService'; ``` -------------------------------- ### Inheritance vs. Composition: Bad EmployeeTaxData Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates a 'bad' use of inheritance where a class extends another when it represents a 'has-a' relationship. This can lead to tightly coupled and less maintainable code. ```typescript class Employee { constructor( private readonly name: string, private readonly email: string) { } // ... } // Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee class EmployeeTaxData extends Employee { constructor( name: string, email: string, private readonly ssn: string, private readonly salary: number) { super(name, email); } // ... } ``` -------------------------------- ### Use Default Arguments: Function Parameter Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Employ default function arguments for optional parameters instead of using conditional logic or short-circuiting. This simplifies function definitions. ```typescript function loadPages(count?: number) { const loadCount = count !== undefined ? count : 10; // ... } ``` ```typescript function loadPages(count: number = 10) { // ... } ``` -------------------------------- ### Set Default Object Properties with Object.assign Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Use Object.assign to set default properties for an object configuration. This ensures all properties have a default value if not provided. ```typescript type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean }; function createMenu(config: MenuConfig) { const menuConfig = Object.assign({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }, config); // ... } createMenu({ body: 'Bar' }); ``` -------------------------------- ### Handling rejected promises with a logger (Good Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Handle rejected promises by catching the error and logging it. This ensures that promise rejections are not ignored and can be addressed. ```typescript import { logger } from './logging' getUser() .then((user: User) => { return sendEmail(user.email, 'Welcome!'); }) .catch((error) => { logger.log(error); }); // or using the async/await syntax: try { const user = await getUser(); await sendEmail(user.email, 'Welcome!'); } catch (error) { logger.log(error); } ``` -------------------------------- ### Consistent Vocabulary for Variables: User Functions Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Maintain a consistent naming convention for similar types of variables across your codebase. This avoids confusion. ```typescript function getUserInfo(): User; function getUserDetails(): User; function getUserData(): User; ``` ```typescript function getUser(): User; ``` -------------------------------- ### Use Enum to Document Intent: Genre Configuration Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Utilize enums to clearly document the intended meaning of values, especially when the exact value is less important than its category. ```typescript const GENRE = { ROMANTIC: 'romantic', DRAMA: 'drama', COMEDY: 'comedy', DOCUMENTARY: 'documentary', } projector.configureFilm(GENRE.COMEDY); class Projector { // declaration of Projector configureFilm(genre) { switch (genre) { case GENRE.ROMANTIC: // some logic to be executed } } } ``` ```typescript enum GENRE { ROMANTIC, DRAMA, COMEDY, DOCUMENTARY, } projector.configureFilm(GENRE.COMEDY); class Projector { // declaration of Projector configureFilm(genre) { switch (genre) { case GENRE.ROMANTIC: // some logic to be executed } } } ``` -------------------------------- ### Ignoring rejected promises (Bad Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Similar to `try/catch`, do not ignore errors from rejected promises. Unhandled rejections can lead to unexpected application behavior. ```typescript getUser() .then((user: User) => { return sendEmail(user.email, 'Welcome!'); }) .catch((error) => { console.log(error); }); ``` -------------------------------- ### Avoid Side Effects by Returning Values Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Functions should ideally take input and return output without modifying external state. Centralize side effects like file writing into dedicated services. ```typescript const name = 'Robert C. Martin'; function toBase64(text: string): string { return btoa(text); } const encodedName = toBase64(name); console.log(name); ``` -------------------------------- ### Prefer Self-Explanatory Code Over Comments Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Use descriptive variable names and expressions to make code's intent clear, rather than relying on comments. This improves readability and maintainability. ```typescript const isSubscriptionActive = subscription.endDate > Date.now; if (isSubscriptionActive) { /* ... */ } ``` -------------------------------- ### Favor Functional Programming: Calculate Total Lines of Code (Bad) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This imperative approach uses a traditional for loop to iterate and sum lines of code. It requires manual management of the loop counter and accumulator. ```typescript const contributions = [ { name: 'Uncle Bobby', linesOfCode: 500 }, { name: 'Suzie Q', linesOfCode: 1500 }, { name: 'Jimmy Gosling', linesOfCode: 150 }, { name: 'Gracie Hopper', linesOfCode: 1000 } ]; let totalOutput = 0; for (let i = 0; i < contributions.length; i++) { totalOutput += contributions[i].linesOfCode; } ``` -------------------------------- ### Set Default Object Properties with Destructuring Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Use destructuring with default values to set default properties for function parameters. This is a concise way to handle optional configuration. ```typescript type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean }; function createMenu({ title = 'Foo', body = 'Bar', buttonText = 'Baz', cancellable = true }: MenuConfig) { // ... } createMenu({ body: 'Bar' }); ``` -------------------------------- ### Explanatory Variables: Map Iteration Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Use explanatory variables to make complex expressions or data structures more understandable. Destructuring can help. ```typescript declare const users: Map; for (const keyValue of users) { // iterate through users map } ``` ```typescript declare const users: Map; for (const [id, user] of users) { // iterate through users map } ``` -------------------------------- ### Use TODO Comments for Future Improvements Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Employ TODO comments to mark areas for future enhancements or necessary fixes. Ensure that TODO comments do not serve as an excuse for writing suboptimal code. ```typescript function getActiveSubscriptions(): Promise { // TODO: ensure `dueDate` is indexed. return db.subscriptions.find({ dueDate: { $lte: new Date() } }); } ``` -------------------------------- ### Searchable Names: Timeout Constant Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Use searchable names for variables, especially constants, to easily locate them in the codebase. Define constants with capitalized names. ```typescript // What the heck is 86400000 for? setTimeout(restart, 86400000); ``` ```typescript // Declare them as capitalized named constants. const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; // 86400000 setTimeout(restart, MILLISECONDS_PER_DAY); ``` -------------------------------- ### ISP Violation: Single interface for multiple printer functions Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Shows a violation of ISP where a single 'SmartPrinter' interface forces all implementing classes to provide methods (fax, scan) they might not support, leading to errors. ```typescript interface SmartPrinter { print(); fax(); scan(); } class AllInOnePrinter implements SmartPrinter { print() { // ... } fax() { // ... } scan() { // ... } } class EconomicPrinter implements SmartPrinter { print() { // ... } fax() { throw new Error('Fax not supported.'); } scan() { throw new Error('Scan not supported.'); } } ``` -------------------------------- ### Using Getters and Setters for Encapsulation Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Encapsulate validation logic within a class using getters and setters. This approach centralizes changes, making maintenance easier when specifications evolve. ```typescript type BankAccount = { balance: number; // ... } const value = 100; const account: BankAccount = { balance: 0, // ... }; if (value < 0) { throw new Error('Cannot set negative balance.'); } account.balance = value; ``` ```typescript class BankAccount { private accountBalance: number = 0; get balance(): number { return this.accountBalance; } set balance(value: number) { if (value < 0) { throw new Error('Cannot set negative balance.'); } this.accountBalance = value; } // ... } // Now `BankAccount` encapsulates the validation logic. // If one day the specifications change, and we need extra validation rule, // we would have to alter only the `setter` implementation, // leaving all dependent code unchanged. const account = new BankAccount(); account.balance = 100; ``` -------------------------------- ### Avoid Commented-Out Code Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Do not leave commented-out code in the codebase. Utilize version control systems like Git to track historical code changes. ```typescript type User = { name: string; email: string; } ``` -------------------------------- ### Type-safe error handling with custom objects Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Implement type-safe error handling by returning custom error objects instead of throwing. This approach uses a discriminated union to represent success or failure states. ```typescript type Result = { isError: false, value: R }; type Failure = { isError: true, error: E }; type Failable = Result | Failure; function calculateTotal(items: Item[]): Failable { if (items.length === 0) { return { isError: true, error: 'empty' }; } // ... return { isError: false, value: 42 }; } ``` -------------------------------- ### Employee List with Union Type Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md An alternative abstraction using a union type to represent employees, allowing a single function to process different employee types. ```typescript class Developer { // ... } class Manager { // ... } type Employee = Developer | Manager function showEmployeeList(employee: Employee[]) { // ... }); } ``` -------------------------------- ### Bad Fibonacci Implementation (Array-based) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This function generates an array containing the first n Fibonacci numbers. It is less efficient for large n due to storing all numbers in memory. ```typescript function fibonacci(n: number): number[] { if (n === 1) return [0]; if (n === 2) return [0, 1]; const items: number[] = [0, 1]; while (items.length < n) { items.push(items[items.length - 2] + items[items.length - 1]); } return items; } function print(n: number) { fibonacci(n).forEach(fib => console.log(fib)); } // Print first 10 Fibonacci numbers. print(10); ``` -------------------------------- ### Good Function Proximity in TypeScript Class Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates a class where related methods are kept vertically close, improving readability and making it easier to trace function calls. Callers are placed near their callees. ```typescript class PerformanceReview { constructor(private readonly employee: Employee) { } review() { this.getPeerReviews(); this.getManagerReview(); this.getSelfReview(); // ... } private getPeerReviews() { const peers = this.lookupPeers(); // ... } private lookupPeers() { return db.lookup(this.employee.id, 'peers'); } private getManagerReview() { const manager = this.lookupManager(); } private lookupManager() { return db.lookup(this.employee, 'manager'); } private getSelfReview() { // ... } } const review = new PerformanceReview(employee); review.review(); ``` -------------------------------- ### Throwing Error types (Good Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Always use the `Error` type when throwing or rejecting promises. This ensures errors have a `stack` property for debugging and are compatible with `try/catch`. ```typescript function calculateTotal(items: Item[]): number { throw new Error('Not implemented.'); } function get(): Promise { return Promise.reject(new Error('Not implemented.')); } // or equivalent to: async function get(): Promise { throw new Error('Not implemented.'); } ``` -------------------------------- ### LSP Violation: Square inheriting from Rectangle Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Demonstrates how extending Rectangle with Square violates LSP, leading to incorrect area calculations when setters are used interchangeably. ```typescript class Rectangle { constructor( protected width: number = 0, protected height: number = 0) { } setColor(color: string): this { // ... } render(area: number) { // ... } setWidth(width: number): this { this.width = width; return this; } setHeight(height: number): this { this.height = height; return this; } getArea(): number { return this.width * this.height; } } class Square extends Rectangle { setWidth(width: number): this { this.width = width; this.height = width; return this; } setHeight(height: number): this { this.width = height; this.height = height; return this; } } function renderLargeRectangles(rectangles: Rectangle[]) { rectangles.forEach((rectangle) => { const area = rectangle .setWidth(4) .setHeight(5) .getArea(); // BAD: Returns 25 for Square. Should be 20. rectangle.render(area); }); } const rectangles = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles(rectangles); ``` -------------------------------- ### Ignoring caught errors (Bad Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Never ignore errors caught in a `try/catch` block. Failing to handle errors prevents debugging and reaction to potential issues. ```typescript try { functionThatMightThrow(); } catch (error) { console.log(error); } // or even worse try { functionThatMightThrow(); } catch (error) { // ignore error } ``` -------------------------------- ### Set Default Object Properties with Spread Operator Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Use the spread operator to define new properties and set default values for an object configuration. This is similar to Object.assign but creates new properties. ```typescript type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean }; function createMenu(config: MenuConfig) { const menuConfig = { title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true, ...config, }; // ... } ``` -------------------------------- ### Throwing non-Error types (Bad Practice) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Avoid throwing or rejecting with non-Error types like strings. This can lead to debugging difficulties when errors are caught at higher levels. ```typescript function calculateTotal(items: Item[]): number { throw 'Not implemented.'; } function get(): Promise { return Promise.reject('Not implemented.'); } ``` -------------------------------- ### Pronounceable Variable Names: Data Record Type Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Choose variable names that are easy to pronounce to facilitate discussion and understanding. Avoid cryptic abbreviations. ```typescript type DtaRcrd102 = { genymdhms: Date; modymdhms: Date; pszqint: number; } ``` ```typescript type Customer = { generationTimestamp: Date; modificationTimestamp: Date; recordId: number; } ``` -------------------------------- ### Abstracted Employee List Function Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This refactored function handles both Developers and Managers by abstracting common logic into a shared method and accepting a union type. ```typescript class Developer { // ... getExtraDetails() { return { githubLink: this.githubLink, } } } class Manager { // ... getExtraDetails() { return { portfolio: this.portfolio, } } } function showEmployeeList(employee: (Developer | Manager)[]) { employee.forEach((employee) => { const expectedSalary = employee.calculateExpectedSalary(); const experience = employee.getExperience(); const extra = employee.getExtraDetails(); const data = { expectedSalary, experience, extra, }; render(data); }); } ``` -------------------------------- ### Good Fibonacci Implementation (Generator-based) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This generator function produces an infinite stream of Fibonacci numbers lazily. It avoids storing all numbers in memory, making it more efficient. ```typescript // Generates an infinite stream of Fibonacci numbers. // The generator doesn't keep the array of all numbers. function* fibonacci(): IterableIterator { let [a, b] = [0, 1]; while (true) { yield a; [a, b] = [b, a + b]; } } function print(n: number) { let i = 0; for (const fib of fibonacci()) { if (i++ === n) break; console.log(fib); } } // Print first 10 Fibonacci numbers. print(10); ``` -------------------------------- ### Avoid Side Effects: Add Item to Cart (Good) Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md This function returns a new array with the added item, leaving the original cart unchanged. Prefer this approach to prevent unintended modifications to shared state. ```typescript function addItemToCart(cart: CartItem[], item: Item): CartItem[] { return [...cart, { item, date: Date.now() }]; }; ``` -------------------------------- ### Avoid Negative Conditionals Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Prefer positive conditional statements over negative ones for clarity. This makes the intent of the code more direct and easier to follow. ```typescript function isEmailNotUsed(email: string): boolean { // ... } if (isEmailNotUsed(email)) { // ... } ``` ```typescript function isEmailUsed(email: string): boolean { // ... } if (!isEmailUsed(email)) { // ... } ``` -------------------------------- ### Function with consolidated arguments using a type alias Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Further enhance code clarity by defining a type alias for the options object. This promotes reusability and makes the function signature even more descriptive. ```typescript type MenuOptions = { title: string, body: string, buttonText: string, cancellable: boolean }; function createMenu(options: MenuOptions) { // ... } createMenu({ title: 'Foo', body: 'Bar', buttonText: 'Baz', cancellable: true }); ``` -------------------------------- ### Avoid Journal Comments Source: https://github.com/labs42io/clean-code-typescript/blob/main/README.md Refrain from using journal comments that track code changes or author notes. Version control systems provide a better mechanism for this historical tracking. ```typescript function combine(a: number, b: number): number { return a + b; } ```