Объекты и структуры данных играют решающую роль в разработке программного обеспечения, и, следуя принципам чистого кода, мы можем улучшить читабельность, удобство сопровождения и гибкость нашего кода. Мы углубимся в инкапсуляцию, модификаторы доступа, методы получения и установки, композицию вместо наследования, минимизацию изменяемого состояния и принцип единой ответственности (SRP). Есть причина, по которой мы держим наши переменные в секрете. Мы не хотим, чтобы кто-то еще зависел от них. Мы хотим сохранить свободу изменять их тип или реализацию по прихоти или импульсу.

1. Инкапсуляция данных

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

class Car {
  private brand: string;
  private model: string;
  
  constructor(brand: string, model: string) {
    this.brand = brand;
    this.model = model;
  }
  
  public getBrand(): string {
    return this.brand;
  }
  
  public getModel(): string {
    return this.model;
  }
}

В этом примере мы инкапсулируем свойства brand и model в класс Car. Мы используем модификаторы закрытого доступа, чтобы ограничить прямой доступ к этим свойствам. Вместо этого мы предоставляем общедоступные методы получения (getBrand() и getModel()) для доступа к значениям. Эта инкапсуляция гарантирует, что внутренние детали класса Car скрыты, а доступ к данным осуществляется через контролируемый интерфейс.

2. Избегайте методов получения и установки

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

class Rectangle {
  private width: number;
  private height: number;
  
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
  
  public get area(): number {
    return this.width * this.height;
  }
  
  public setDimensions(width: number, height: number): void {
    this.width = width;
    this.height = height;
  }
}

В этом примере мы используем свойство area для вычисления площади прямоугольника. Вместо того, чтобы вызывать отдельный метод, такой как calculateArea(), мы можем обращаться к области напрямую, как если бы это было обычное свойство. Кроме того, мы предоставляем метод setDimensions() для обновления размеров прямоугольника, что позволяет избежать необходимости в отдельных методах получения и установки.

3. Предпочтение композиции наследованию

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

class Engine {
  public start(): void {
    console.log('Engine started');
  }
}

class Car {
  private engine: Engine;
  
  constructor() {
    this.engine = new Engine();
  }
  
  public startEngine(): void {
    this.engine.start();
  }
}

В этом примере мы предпочитаем композицию наследованию. Вместо создания класса Car, расширяющего класс Engine, мы создаем отдельный класс Engine и включаем его как свойство в класс Car. Этот подход к композиции позволяет нам легко менять местами различные реализации движка, не затрагивая сам класс Car.

4. Минимизация изменяемого состояния

Изменяемое состояние относится к объектам или данным, которые можно изменить после создания. Хотя изменяемость иногда необходима, сведение к минимуму изменяемого состояния может снизить сложность и повысить удобство сопровождения кода. Предпочтение неизменности упрощает анализ кода и помогает предотвратить непреднамеренные побочные эффекты.

function calculateTotalPrice(prices) {
  let totalPrice = 0;

  for (const price of prices) {
    totalPrice += price;
  }

  return totalPrice;
}

const prices = [10, 20, 30, 40];
const total = calculateTotalPrice(prices);

В этом примере JavaScript мы вычисляем общую цену, суммируя массив цен. Переменная totalPrice объявляется с использованием ключевого слова let, чтобы разрешить изменение в цикле. Однако массив prices остается неизменным. Минимизируя изменяемое состояние и избегая ненужных мутаций переменных, мы создаем более предсказуемый и удобный в сопровождении код.

5. Следование принципу единой ответственности (SRP)

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

class Logger {
  public logError(message: string): void {
    // Log the error message to a file or console
  }
  
  public logMetric(metric: string, value: number): void {
    // Log the metric and its value to a monitoring system
  }
}

class PaymentProcessor {
  private logger: Logger;
  
  constructor(logger: Logger) {
    this.logger = logger;
  }
  
  public processPayment(amount: number): void {
    try {
      // Process the payment logic
    } catch (error) {
      this.logger.logError('Payment processing failed');
    }
  }
}

В этом примере мы демонстрируем SRP, разделяя проблемы на два класса: Logger и PaymentProcessor. Класс Logger отвечает за регистрацию ошибок и метрик, а класс PaymentProcessor занимается исключительно обработкой платежей. Разделяя обязанности, каждый класс становится более сплоченным и понятным.

Заключение

Объекты и структуры данных являются фундаментальными строительными блоками разработки программного обеспечения. Применяя принципы чистого кода к объектам и структурам данных, мы можем улучшить читабельность, удобство сопровождения и гибкость нашего кода. На протяжении всей этой главы мы исследовали инкапсуляцию данных, избегая методов получения и установки, предпочитая композицию наследованию, минимизируя изменяемое состояние и придерживаясь принципа единой ответственности (SRP). Следуя этим принципам и адаптируя их к конкретным требованиям наших проектов, мы можем писать более чистый и надежный код.