Уровни абстракции в Clean Code: понятное объяснение для разработчиков

    Книга Роберта Мартина «Чистый код» - настольное руководство для программистов, но глава о функциях и уровнях абстракции часто вызывает вопросы. Особенно когда речь заходит о switch-выражениях и TO-абзацах. Давайте разберёмся простыми словами.

    Что такое уровень абстракции функции?

    Уровень абстракции - это степень детализации описания действия. Представьте, что вы объясняете коллеге, как приготовить кофе:

    • Высокий уровень: «Приготовь эспрессо» (бизнес-логика, цель).
    • Средний уровень: «Смели зерна, засыпь в рожок, включи кофемашину» (конкретные шаги).
    • Низкий уровень: «Нажми кнопку включения, подожди 10 секунд, протри поддон» (детали реализации).

    В коде функция должна работать на одном уровне абстракции. Если внутри неё смешаны высокоуровневые вызовы (например, calculatePay()) и низкоуровневые операции (доступ к БД, форматирование строк), читатель путается - он не понимает, что именно делает функция.

    Почему switch в примере - это плохо?

    Вернёмся к примеру из книги:

    public Money calculatePay(Employee e) throws InvalidEmployeeType {
        switch (e.type) {
          case COMMISSIONED:
            return calculateCommissionedPay(e);
          case HOURLY:
            return calculateHourlyPay(e);
          case SALARIED:
            return calculateSalariedPay(e);
          default:
            throw new InvalidEmployeeType(e.type);
        }
      }

    Мартин пишет, что эта функция выполняет более одной операции. Почему? Потому что она объединяет два уровня абстракции:

    • Высокий уровень: «Рассчитать зарплату сотрудника» (сама функция calculatePay).
    • Низкий уровень: «Выбрать тип сотрудника и вызвать соответствующий метод» (логика switch).

    Switch - это деталь реализации, которая относится к низкому уровню. Если функция называется calculatePay, читатель ожидает увидеть внутри только высокоуровневые шаги (например, calculateBaseSalary(), applyBonus()), а не ветвление по типам.

    Как TO-абзацы помогают выделить уровни?

    TO-абзацы (от англ. «To-do paragraphs») - это приём, когда вы пишете функцию как список действий на естественном языке. Например:

    «Чтобы рассчитать зарплату сотрудника, нужно:
    1. Получить базовую ставку.
    2. Применить надбавки.
    3. Вычесть налоги.»

    Каждый пункт - это один уровень абстракции. Если вы замечаете, что один из пунктов требует уточнения («выбрать тип сотрудника»), значит, он относится к более низкому уровню и его нужно вынести в отдельную функцию.

    Как правильно реорганизовать switch?

    Мартин предлагает спрятать switch за полиморфизмом. Вместо calculatePay с ветвлением, создайте абстрактный класс Employee и переопределите метод в наследниках:

    abstract class Employee {
        abstract Money calculatePay();
      }
      class CommissionedEmployee extends Employee {
        Money calculatePay() { /* своя логика */ }
      }
      class HourlyEmployee extends Employee {
        Money calculatePay() { /* своя логика */ }
      }

    Тогда вызов employee.calculatePay() будет на одном высоком уровне абстракции, а switch исчезнет.

    Практические советы по выделению уровней

    • Читайте функцию как предложение: если можно вставить слово «затем» между строками, значит, уровни смешаны.
    • Проверяйте длину: функция должна умещаться на экране (10-20 строк). Если больше - скорее всего, в ней несколько уровней.
    • Используйте принцип единой ответственности (SRP): одна функция - одна задача на одном уровне.

    Часто задаваемые вопросы

    Часто задаваемые вопросы