Написание чистого и поддерживаемого кода является важным аспектом разработки программного обеспечения. Чистый код не только упрощает его понимание и поддержку, но также помогает снизить риск появления ошибок и уязвимостей в системе безопасности. В этой статье мы рассмотрим некоторые из лучших идей из книги «Чистый код» Роберта С. Мартина для написания чистого кода, а также примеры Typescript.

Имена функций

Дайте вашим функциям описательные и осмысленные имена. Имена функций должны описывать, что делает функция, чтобы с первого взгляда было легче понять код.

function calculateTotalPrice(items: Item[]): number { ... }
function sendEmailToUser(user: User, message: string): void { ... }

Функция Длина

Длина функции: делайте функции короткими и лаконичными. Хорошее эмпирическое правило состоит в том, чтобы они не превышали 20 строк кода.

// bad example
function processData(data: any[]) {
    data.sort((a, b) => a.value - b.value);
    const filteredData = data.filter(item => item.value > 10);
    const result = filteredData.map(item => item.name).join(", ");
    return result;
}

// good example
function sortData(data: any[]) {
    return data.sort((a, b) => a.value - b.value);
}

function filterData(data: any[]) {
    return data.filter(item => item.value > 10);
}

function extractNames(data: any[]) {
    return data.map(item => item.name).join(", ");
}

function processData(data: any[]) {
    const sortedData = sortData(data);
    const filteredData = filterData(sortedData);
    const result = extractNames(filteredData);
    return result;
}

Принцип единой ответственности

Каждая функция должна иметь единую четко определенную ответственность. Это упрощает поддержку и обновление вашего кода, а также повторное использование функций в других частях вашего кода.

// bad example
class User {
    private name: string;
    private email: string;
    private password: string;

    constructor(name: string, email: string, password: string) {
        this.name = name;
        this.email = email;
        this.password = password;
    }

    public getName(): string {
        return this.name;
    }

    public getEmail(): string {
        return this.email;
    }

    public hashPassword(): string {
        // hash password logic
        return this.password;
    }
}

// good example
class User {
    private name: string;
    private email: string;
    private password: string;

    constructor(name: string, email: string, password: string) {
        this.name = name;
        this.email = email;
        this.password = password;
    }

    public getName(): string {
        return this.name;
    }

    public getEmail(): string {
        return this.email;
    }
}

class PasswordHasher {
    public static hashPassword(password: string): string {
        // hash password logic
        return password;
    }
}

Читабельность

Сделайте свой код легко читаемым, используя четкие и краткие имена переменных, добавляя комментарии там, где это необходимо, и разбивая сложные функции на более мелкие, более управляемые части.

// Clear and concise variable names
const userName = getUserName(userId);
const formattedDate = formatDate(date);

// Comments to explain complex logic
function calculateDiscount(price: number, quantity: number): number {
  // Apply 10% discount for orders over 100 items
  if (quantity >= 100) {
    return price * 0.9;
  }

  // No discount for orders under 100 items
  return price;
}

// Breaking up complex functions into smaller pieces
function applyDiscount(price: number, discount: number): number {
  return price * (1 - discount);
}

function calculateDiscount(price: number, quantity: number): number {
  if (quantity >= 100) {
    return applyDiscount(price, 0.1);
  }

  return price;
}

Избегайте магических чисел

Избегайте использования в коде «магических чисел» или жестко запрограммированных значений, не имеющих четкого значения. Вместо этого используйте описательные константы или перечисления, чтобы сделать код более читабельным и удобным для сопровождения. Например:

// Avoid magic numbers
function calculateDiscount(price: number, quantity: number): number {
  if (quantity >= 100) {
    return price * 0.9;
  }

  if (quantity >= 50) {
    return price * 0.95;
  }

  return price;
}

// Use descriptive constants
const MIN_QUANTITY_FOR_10_PERCENT_DISCOUNT = 100;
const MIN_QUANTITY_FOR_5_PERCENT_DISCOUNT = 50;

function calculateDiscount(price: number, quantity: number): number {
  if (quantity >= MIN_QUANTITY_FOR_10_PERCENT_DISCOUNT) {
    return price * 0.9;
  }

  if (quantity >= MIN_QUANTITY_FOR_5_PERCENT_DISCOUNT) {
    return price * 0.95;
  }

  return price;
}

СУХОЙ принцип

Не повторяйтесь. Максимально возможное повторное использование кода и реорганизация дублирующегося кода в повторно используемые функции и модули.

// bad example
function validateEmail(email: string): boolean {
    const regex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
    return regex.test(email);
}

function validatePassword(password: string): boolean {
    const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
    return regex.test(password);
}

// good example
const emailRegex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;

function validate(value: string, regex: RegExp): boolean {
    return regex.test(value);
}

function validateEmail(email: string): boolean {
    return validate(email, emailRegex);
}

function validatePassword(password: string): boolean {
    return validate(password, passwordRegex);
}

Обработка ошибок

Изящно обрабатывайте ошибки, используя четкие и описательные сообщения об ошибках, регистрируя ошибки при необходимости и используя соответствующие методы обработки ошибок, чтобы предотвратить сбой кода. Например:

// Use try-catch for synchronous errors
try {
  const user = getUser(userId);
  console.log(`User: ${user.name}`);
} catch (error) {
  console.error(`Error: ${error.message}`);
}

// Use promises and async-await for asynchronous errors
async function getOrders(userId: number): Promise<Order[]> {
  try {
    const orders = await fetchOrders(userId);
    return orders;
  } catch (error) {
    console.error(`Error: ${error.message}`);
    throw error;
  }
}

Кроме того, рекомендуется использовать исключения только в исключительных ситуациях. Используйте коды возврата или объекты ошибок вместо исключений, когда ожидается частое возникновение ошибки. Например:

// Use exceptions for exceptional situations
function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Cannot divide by zero");
  }
  return a / b;
}

// Use return codes or error objects for expected errors
interface Result {
  result: number;
  error: string | null;
}

function divideWithReturnCode(a: number, b: number): Result {
  if (b === 0) {
    return { result: 0, error: "Cannot divide by zero" };
  }
  return { result: a / b, error: null };
}

Такой подход помогает повысить производительность и читабельность кода, поскольку исключения снижают производительность и их сложнее отследить, чем коды возврата или объекты ошибок. Кроме того, это также облегчает понимание цели кода, поскольку логика обработки ошибок четко отделена от основной логики.

Простой код

Держите код простым и понятным. Избегайте использования сложных алгоритмов, запутанных структур потока управления или ненужных абстракций. Ниже приведен пример плохой реализации:

// Bad Example: Complex for loop to sum an array of numbers
function sumArray(numbers: number[]): number {
  let sum = 0;
  for (let i = 0; i < numbers.length; i++) {
    for (let j = 0; j < numbers[i]; j++) {
      sum++;
    }
  }
  return sum;
}

И лучшая реализация:

// Simple for loop to sum an array of numbers
function sumArray(numbers: number[]): number {
  let sum = 0;
  for (const number of numbers) {
    sum += number;
  }
  return sum;
}

Избегайте глобальных переменных

Избегайте использования глобальных переменных, так как они могут затруднить сопровождение и отладку вашего кода. Вместо этого используйте модули и функции для инкапсуляции состояния и поведения.

// bad example
let name = "John Doe";

function getName() {
    return name;
}

function setName(newName: string) {
    name = newName;
}

// good example
function createNameModule() {
    let name = "John Doe";
    
    return {
        getName: () => name,
        setName: (newName: string) => { name = newName; }
    }
}

const nameModule = createNameModule();
const currentName = nameModule.getName();
nameModule.setName("Jane Doe");

Разработка через тестирование

Сначала напишите тесты, а затем напишите код, чтобы тесты проходили. Это помогает гарантировать, что ваш код работает так, как задумано, и упрощает его обслуживание и обновление.

// good example
function add(a: number, b: number): number {
    return a + b;
}

describe("add", () => {
    it("should add two numbers", () => {
        const result = add(1, 2);
        expect(result).toBe(3);
    });
});

Держите самый важный код наверху

Поместите самый важный код вверху файла, а затем менее важный код, чтобы читатели могли быстро найти и понять основные части кода. Это облегчает другим понимание и поддержку вашего кода.

// createAnimal.ts

// Good Example: Most important code at the top
function createAnimal(name: string, food: string, hours: number) {
  console.log(`Creating animal ${name}...`);
  eat(food);
  sleep(hours);
  console.log(`${name} has been created.`);
}

// Other functions and code
function eat(food: string) {
  console.log(`Eating ${food}...`);
}

function sleep(hours: number) {
  console.log(`Sleeping for ${hours} hours...`);
}

Организация кода

Разделите код на отдельные разделы, чтобы его было легче читать и поддерживать. Сгруппируйте похожие функции и классы вместе и разделите их четкими и содержательными комментариями.

// Utility functions section
function getUserName(userId: number): string { ... }
function formatDate(date: Date): string { ... }

// Database access section
class UserRepository { ... }
class OrderRepository { ... }

Форматирование кода

Придерживайтесь единого стиля форматирования кода, например, правильно используйте пробелы и отступы. Правильное форматирование кода облегчает его чтение, понимание и поддержку. Хороший стиль форматирования кода должен быть последовательным, ясным и лаконичным. Например:

// Good Example: Code Formatting
function calculateSum(numbers: number[]): number {
  let sum = 0;
  for (const number of numbers) {
    sum += number;
  }
  return sum;
}

// Bad Example: Code Formatting
function calculateSum(numbers:number[])
{let sum=0
for(const number of numbers)
{sum+=number}
return sum}

Это всего лишь несколько примеров лучших практик написания чистого кода с помощью TypeScript. Следуя этим рекомендациям, вы сможете писать код, который легко понять, поддерживать и масштабировать.

Кроме того, важно отметить, что эти методы не являются специфическими для TypeScript, они обычно рекомендуются для написания чистого и поддерживаемого кода на любом языке программирования.