AI Прочитал «Чистый код», чтобы вам не пришлось

AI

Редактор
Регистрация
23 Август 2023
Сообщения
2 815
Лучшие ответы
0
Реакции
0
Баллы
51
Offline
#1
Качество кода - Критический фактор успеха любого программного проекта. Некорректные наименования сущностей, избыточная сложность методов, противоречивые комментарии и нарушение структурных принципов ведут к существенным проблемам: снижению скорости разработки, росту количества ошибок и сложности поддержки. Роберт Мартин (известный как "Дядя Боб"), один из авторов Agile-манифеста, в своей фундаментальной работе "Чистый код" систематизировал принципы написания эффективного, поддерживаемого кода.

Книга "Чистый код" это детально проработанная методология, основанная на десятилетиях опыта разработки коммерческих систем. Она отвечает на ключевые вопросы:


  • Как создать код, который понятен коллегам через месяцы после написания?


  • Какие паттерны предотвращают накопление технического долга?


  • Как проектировать компоненты для лёгкого тестирования и модификации?

Все примеры в этой статье будут на языке программирования C#. Каждый раздел сопровождается примерами рефакторинга "плохого" кода в "чистый" согласно стандартам Мартина.

Что такое чистый код?


Мартин начинает с фундамента: чистый код - это код, написанный с заботой о читателе (часто будущем вас или коллеге). Этот код:


  • Прямолинеен: Делает ровно то, что ожидается.


  • Лаконичен: Ничего лишнего. Одна операция - одна функция, одна ответственность - один класс.


  • Выразителен: Имена и структура и без комментариев объясняет, что делается и почему.


  • Поддерживаем: Легко изменять и расширять без страха сломать что-то ещё.


  • Проверен: Покрыт осмысленными юнит-тестами.
Основа всего - имена


Правильные названия - 90% чистого кода. Вот основные правила:


  1. Выразительность: Имя должно отвечать на все главные вопросы: Что это? Зачем нужно? Как используется? Избегайте общих слов (data, info, manager), если они не полностью говорят о смысле.

    • Плохо: int d; // elapsed time in days


    • Хорошо: int elapsedTimeInDays;


    • Плохо: List list1;


    • Хорошо: List activeCustomers;

  2. Избегайте дезинформации:
    Не используйте accountList для переменной типа Account[] (это не список). Лучше accounts или accountGroup.
    Не используйте похожие имена: XYZControllerForEfficientHandlingOfStrings и XYZControllerForEfficientStorageOfStrings.


  3. Делайте осмысленные различия:

    • Плохо: Product, ProductInfo, ProductData (что отличает их?).


    • Плохо: a1, a2, ... aN.


    • Хорошо: sourceCustomer, targetCustomer; originalMessage, encryptedMessage.

  4. Используйте произносимые имена: genymdhms (generate date, year, month, day, hour, minute, second) - кошмар. Лучше generationTimestamp.


  5. Используйте поисковые имена: Однобуквенные имена (i, j, k в коротких циклах - исключение) и числовые константы (15) сложно найти в коде.

    • Плохо: if (employee.Flags & 15) ...


    • Хорошо: const int IsFullTimeFlag = 0x01; const int IsOnProbationFlag = 0x02;

  6. Классы / Типы: Имена существительных или существительных с уточнением (Customer, AddressParser, AccountService). Избегайте Manager, Processor, Data, Info.


  7. Методы: Имена глаголов или глагольных фраз (Save(), Delete(), ParseConfiguration(), CalculateTotal()). Геттеры/сеттеры - GetName(), SetName().


  8. Булевы переменные / методы: Используйте префиксы is, has, can, should для ясности: isActive, hasLicense, canExecute, shouldValidate.
Методы и функции


Маленькие, хорошо организованные методы это главное чистого кода!


  1. МАЛЕНЬКИЕ! И ещё раз МАЛЕНЬКИЕ!

    • Идеал: Не длиннее 20 строк. Часто 3-4 строки.


    • Правило экрана: Функция должна полностью помещаться на одном экране без прокрутки.

  2. Делайте одно дело (The Single Responsibility Principle - SRP для функций):

    • Функция должна делать ТОЛЬКО то, что явно следует из ее имени. Если вы можете выделить другую операцию с осмысленным именем - вынесите ее в отдельную функцию.


    • Уровни абстракции: Операции внутри функции должны быть на ОДНОМ уровне абстракции. Не смешивайте высокоуровневую логику (ProcessOrder()) с низкоуровневыми деталями (ParseOrderLineItemString()).

  3. Структура кода (Сверху-Вниз - Stepdown Rule):

    • Код должен читаться как повествование, сверху вниз.


    • На верхнем уровне высокоуровневые функции (шаги алгоритма).


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

  4. Аргументы Функций (Параметры):

    • Идеал: 0 (niladic) > 1 (monadic) > 2 (dyadic). 3 (triadic) - Избегать! > 3 - Требует исключительного обоснования.


    • Флаги как аргументы - ЗЛО! (Process(bool isAdmin)) - Это явный признак, что функция делает два разных дела! Разбейте на две: ProcessAdmin(), ProcessUser().


    • Output-параметры (out, ref) - Еще большее зло! Они запутывают и нарушают поток чтения. Возвращайте кортежи или маленькие объекты-результаты.


    • Объекты-аргументы: Если нужно много параметров, логически связанных, объедините их в класс/структуру.

      • Плохо: public void CreateReservation(DateTime start, DateTime end, int roomId, string customerName, bool hasPremium)


      • Хорошо:
        public class ReservationRequest
        {
        public DateTime Start { get; set; }
        public DateTime End { get; set; }
        public int RoomId { get; set; }
        public CustomerInfo Customer { get; set; } // CustomerInfo содержит Name и HasPremium
        }
        public void CreateReservation(ReservationRequest request)

  5. Глаголы и ключевые слова:

    • Один аргумент: Имя функции и аргумента должны образовывать естественную пару глагол/существительное: WriteField(name), AssertExpectedEqualsActual(expected, actual).


    • Два аргумента: Порядок должен следовать общепринятому: Point p = new Point(x, y);.

  6. Исключения вместо кодов ошибок:

    • Плохо: Возврат кодов ошибок (int result = Save(); if (result == ERROR) ...) ведет к загромождению кода проверками сразу после вызова.


    • Хорошо: Бросайте исключения (try { Save(); } catch (SaveException ex) ...). Обработка ошибок отделена от основной логики.


    • Try[Операция]Паттерн: Для ситуаций, где ошибка это часть ожидаемого потока (например, проверка пароля), используйте паттерн TryParse:
      if (int.TryParse(input, out int value)) { ... } // Успех
      else { ... } // Ошибка, но не исключение

  7. DRY (Don't Repeat Yourself): Безжалостно устраняйте дублирование кода, выделяя общую функциональность в методы.

Пример рефакторинга функции (C#):
Грязный метод:



Чистые методы:


Комментарии


Лучший комментарий - отсутствующий комментарий. Хороший код объясняет себя сам через имена и структуру. Комментарии часто лгут (код меняется, а комментарии нет) и загромождают. Но есть исключения:


  1. Законные комментарии:

    • Правовые: Лицензии, авторские права.


    • Поясняющие намерение (WHY): Почему код сделан так, а не иначе? Особенно если причина неочевидна (баг в библиотеке, специфичное требование).


    • Предупреждения о последствиях: // Внимание: Этот метод запускает долгую операцию (до 5 мин). Не вызывать из UI-потока!


    • TODO: Краткие заметки о том, что нужно доделать / поправить позже. Обязательно указывайте контекст / причину.


    • Усиление (Amplification): Подчеркнуть важность чего-то неочевидного. // Не изменять порядок инициализации: компонент Y зависит от X!


    • Javadoc в Public API: Для публичных методов/классов библиотек - объяснение, что делает, параметры, возвращаемое значение, исключения.

  2. Недопустимые комментарии:

    • Закомментированный код: Удаляйте его! Системы контроля версий хранят историю.


    • Избыточные:


    • История изменений:


    • Скобочные / позиционные: // Конец метода CalculateTotal ------------------------


    • Комментарии-извинения: // Извините за этот баг...


    • Неясные ссылки: // См. замечание в мануале - Какой мануал? Какое замечание?
Форматирование


Единый стиль форматирования критически важен для читаемости.


  1. Вертикальное форматирование (Длина файла, пустые строки, группировка):

    • Длина файла: Стремитесь к маленьким файлам (200-500 строк). Большие классы — признак нарушения SRP.


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


    • Вертикальная Близость: Связанные концепции должны быть рядом. Переменная объявляется как можно ближе к месту использования. Вызываемые методы - ниже вызывающих (Правило Stepdown).

  2. Горизонтальное форматирование (Длина строки, отступы, пробелы):

    • Длина строки: 120 символов - разумный максимум. Избегайте горизонтальной прокрутки.


    • Отступы (Indentation): Обязательны! Используйте отступы (обычно 4 пробела) для вложенных блоков (внутри классов, методов, циклов, условий).


    • Пробелы: Используйте для улучшения читаемости:

      • Вокруг операторов (=, +, -, *, /, %, ==, !=, >, <, >=, <=, &&, ||): int sum = a + b;


      • После запятых в списках аргументов/параметров: void Print(string name, int age)


      • После ключевых слов (if, for, while, switch, catch): if (condition)


      • Между скобками и содержимым (кроме пустых): list.Add( item ); -> list.Add(item);


      • Не ставьте пробелы между именем методы и открывающейся скобкой: Save() (не Save ()). А также внутри скобок индексатора: array[0] (не array[ 0 ])

    • Выравнивание: Избегайте выравнивания переменных по типу или значению. Оно создаёт ложный акцент и сложно поддерживается.

      • Плохо:


      • Хорошо:

Объекты и структуры данных


  • Абстракция и сокрытие данных: Классы должны скрывать свои данные и предоставлять абстрактные интерфейсы для работы с ними. Не создавайте просто структуру данных с публичными полями (в C# для этого есть struct, но и их поля лучше делать приватными).

    • Плохо (публичные поля):


    • Хорошо (инкапсуляция):

  • Закон Деметера (Principle of Least Knowledge): Метод объекта M объекта O должен вызывать только методы:

    1. Самого объекта O (this).


    2. Объектов, переданных в M в качестве параметров.


    3. Объектов, созданных внутри M.


    4. Компонентов объекта O.

      • Не objectA.GetObjectB().DoSomething(); - Это "поездка по цепочке". Нарушает инкапсуляцию, делает код хрупким.


      • Решение: Делегирование. Пусть objectA предоставит метод, выполняющий нужное действие, скрыв свою зависимость от ObjectB:
        Плохо:




        Хорошо:

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


Ошибки - часть жизни ПО. Обрабатывайте их чисто:


  1. Используйте исключения, а не коды ошибок.


  2. Сначала пишите Try-Catch-Finally: Обрабатывайте код, который может выбросить исключение, блоком try. Обрабатывайте конкретные исключения в catch. Освобождайте ресурсы в finally.


  3. Не возвращайте null! Это источник NullReferenceException (для C#, аналогично в других языках). Возвращайте пустые коллекции (Enumerable.Empty(), new List()), используйте Nullable<T> для значимых типов, применяйте паттерн Null Object.


  4. Не передавайте null! Это смещает ответственность за проверку на вызывающего. Бросайте ArgumentNullException в начале метода, если null недопустим.
Использование стороннего кода


Работа с библиотеками/API требует аккуратности:


  1. Изолируйте границы: Не позволяйте стороннему коду "расползаться" по всей вашей кодовой базе. Оберните его в адаптер (Adapter или Facade паттерн).


  2. Изучайте через тесты: Напишите небольшие юнит-тесты для сторонней библиотеки перед интеграцией. Это поможет понять её поведение и защитит от неожиданных изменений при обновлении.
Юнит-Тесты. TDD и чистота тестов


Без тестов нет чистого кода. Тесты обеспечивают безопасность рефакторинга.


  1. TDD (Test-Driven Development) цикл:

    1. Красный: Напишите маленький тест для новой функциональности. Он должен упасть (так как функциональности ещё нет).


    2. Зелёный: Напишите минимальный код, чтобы тест прошёл. Можно схитрить.


    3. Рефакторинг (Синий): Улучшите структуру кода (как продакшн, так и тест), удалите дублирование, примените принципы чистого кода. Тесты должны оставаться зелёными.

  2. Правила чистых тестов (F.I.R.S.T.):

    • F (Fast): Тесты должны выполняться мгновенно (секунды, а не минуты). Медленные тесты не запускают часто.


    • I (Independent): Тесты не должны зависеть друг от друга. Порядок выполнения не важен. Сброс состояния перед каждым тестом.


    • R (Repeatable): Результат теста одинаков в любой среде (dev, CI, продакшн) и при любом количестве запусков. Нет зависимостей от сети, времени, случайных чисел.


    • S (Self-Validating): Тест должен выдать бинарный результат: УСПЕХ или ПРОВАЛ. Нет ручной проверки логов.


    • T (Timely): Пишите тесты либо до написания кода (в подходе TDD), либо немедленно после написания рабочего кода. Никогда не откладывайте написание тестов на потом!

  3. Качество тест-кода: Так же важно, как и продакшн код. Применяйте все правила чистого кода: выразительные имена, маленькие функции, минимальные утверждения (Asserts) на тест, отсутствие дублирования (используйте SetUp / TearDown или фабричные методы аккуратно), ясность (Build-Operate-Check паттерн).

Классы


  1. Принцип единственной ответственности (Single Responsibility Principle - SRP): У класса должна быть только одна причина для изменения. Если класс делает слишком много, его нужно разбить.
    Признаки нарушения SRP: Много несвязанных методов/полей, частые изменения в разных местах класса по разным причинам, большой размер.


  2. Инкапсуляция: Скрывайте данные и детали реализации! Делайте поля приватными. Предоставляйте доступ через методы / свойства. Ослабляйте уровень доступа только при явной необходимости.


  3. Композиция прежде наследования (Composition over Inheritance): Наследование создаёт сильную связь между классами. Часто композиция (включение одного класса в другой как поле) + интерфейсы дают больше гибкости.

    • Плохо:


    • Хорошо:

Системы


Чистота на уровне архитектуры.


  1. Разделение забот (Separation of concerns): Система должна быть разделена на слабо связанные модули (например, по слоям: UI, бизнес-логика, доступ к данным). Каждый модуль решает свою задачу.


  2. Чистая архитектура (Clean Architecture/Onion/Hexagonal): Основная идея - зависимости направлены внутрь, к ядру. Бизнес-правила (самые стабильные) в центре. Инфраструктура (БД, UI, фреймворки - часто меняющиеся) на периферии. Ядро не зависит от деталей реализации периферии. Достигается через интерфейсы и DI.


  3. Внедрение зависимостей (Dependency Injection - DI, дополнительно Zenject): Классы не создают свои зависимости, а получают их извне (через конструктор, свойства, методы). Это ослабляет связность, упрощает тестирование и делает код гибче.


  4. Масштабирование: Система должна начинаться с простой, чистой архитектуры. Не добавляйте сложности "на вырост"! Рефакторите и расширяйте архитектуру по мере реальной необходимости.
Пример рефакторинга


Мартин детально разбирает эволюцию небольшой программы (генератор простых чисел) от грязной первой версии до чистого кода. Этапы:


  1. Начало: Рабочая, но ужасно написанная версия.


  2. Написание тестов: Покрытие функциональности тестами для безопасности изменений.


  3. Рефакторинг волнами:

    • Разбиение огромного метода на маленькие.


    • Улучшение имён переменных и функций.


    • Устранение дублирования.


    • Упрощение логики.


    • Улучшение алгоритма.

  4. Результаты: Код становится в разы короче, понятнее, эффективнее и легче для изменения.
Словарик терминов


  1. Чистый код (Clean code) - Код, который легко читать, понимать и изменять. Обладает свойствами: прямолинейность, лаконичность, выразительность, поддерживаемость, проверенность.


  2. Выразительность (Expressiveness) - Свойство кода, при котором имена переменных / методов / классов и структура однозначно передают их назначение и логику без комментариев.


  3. DRY (Don't Repeat Yourself) - Принцип разработки "Не повторяйся". Любое знание или логика должны иметь единственное представление в системе.


  4. SRP (Single Responsibility Principle) - Для функций/классов: Объект должен иметь только одну причину для изменения (выполнять одну обязанность).


  5. Инкапсуляция (Encapsulation) - Сокрытие внутреннего состояния объекта и деталей реализации. Доступ к данным только через публичные методы / свойства.


  6. Закон Деметры (Law of Demetr / Principle of Least Knowledge) - Метод объекта A должен взаимодействовать только с:

    • Самим A


    • Параметрами, переданными в метод


    • Объектами, созданными внутри метода


    • Непосредственными компонентами A. Запрещает цепочки вызовов: objA.GetB().DoC()

  7. Исключение (Exception) - Механизм обработки ошибок, прерывающий нормальный поток выполнения и передающий управление обработчику (catch). Альтернатива кодам ошибок.


  8. Null-объект (Null Object Pattern) - Паттерн, подразумевающий возврат специального объекта с нейтральным поведением вместо null (например, пустая коллекция).


  9. Адаптер (Adapter Pattern) - Паттерн для преобразования интерфейса класса в другой интерфейс, ожидаемый клиентом. Используется для изоляции стороннего кода.


  10. Фасад (Facade Pattern) - Паттерн, предоставляющий простой интерфейс к сложной подсистеме. Скрывает её детали реализации.


  11. Юнит-тест (Unit Test) - Автоматизированный тест, проверяющий корректность работы небольшой изолированной части кода (метода, класса).


  12. TDD (Test-Driven Development) - Методология разработки:
    🔴 Red → Написать падающий тест
    🟢 Green → Написать минимальный код для прохождения теста
    🔵 Refactor → Улучшить код, сохраняя зелёный статус.


  13. F.I.R.S.T. - Принципы чистых тестов:

    • Fast - Быстрые


    • Independent - Независимые


    • Repeatable - Повторяемые


    • Self-Validating - Самопроверяющиеся


    • Timely - Своевременные.

  14. Arrange-Act-Assert (AAA) - Паттерн структуры юнит-теста:

    • Arrange: Подготовка данных и зависимостей


    • Act: Вызов тестируемого метода


    • Assert: Проверка результата.

  15. Внедрение зависимостей (DI, Dependency Injection) - Передача зависимостей объекта извне (через конструктор / свойства) вместо их создания внутри. Уменьшает связанность.


  16. Чистая архитектура (Clean Architecture) - Архитектурный подход, где:

    • Бизнес-логика (ядро) не зависит от деталей (БД, UI, фреймворков)


    • Зависимости направлены к центру системы

  17. Разделение забот (SoC, Separation of Concerns) - Принцип разделения программы на независимые модули, каждый из которых решает отдельную задачу (например: UI, бизнес-логика, БД).


  18. Композиция (Composition) - Построение функциональности за счёт включения одних объектов в другие (предпочтительнее наследования).


  19. Рефакторинг (Refactoring) - Изменение структуры кода без изменения его поведения. Цель - улучшение читаемости и упрощение поддержки.


  20. Непрерывный рефакторинг (Continuous Refactoring) - Практика постоянного улучшения кода в процессе разработки.


  21. Дурной запах кода (Code Smell) - Симптом проблемы в коде (например: длинный метод, дублирование, "божественный класс").


  22. Уровень абстракции (Level of Abstraction) - Степень детализации операций. Код внутри функции должен оперировать на одном уровне (например, только бизнес-логика или только технические детали).


  23. Правило пошагового спуска (Stepdown Rule) - Код должен читаться сверху вниз: высокоуровневая логика -> детали реализации. Каждый уровень раскрывает подробности предыдущего.


  24. Искажение (Misinformation) - Использование имён, вводящих в заблуждение (например: accountList для массива).


  25. Обучающие тесты (Learning Tests) - Тесты, написанные для изучения поведения сторонней библиотеки перед её использованием в проекте.


  26. Границы (Boundaries) - Места интеграции с внешними системами (библиотеки, API, устаревший код). Требуют изоляции.


  27. Модуль (Module) - Логически связанная группа функций / классов (например: компонент, слой, библиотека).


  28. Связанность (Coupling) - Степень зависимости между модулями. Низкая связанность - признак хорошего дизайна.


  29. Зацепление (Cohesion) - Мера связанности обязанностей внутри модуля. Высокое зацепление - все элементы модуля работают на единую цель.


  30. Искажение абстракции (Abstraction Leakage) - Ситуация, когда детали реализации просачиваются через абстракцию, нарушая инкапсуляцию.

Если понравилась статья - рекомендую подписаться на телеграм‑канал NetIntel. Там вы сможете найти множество полезных материалов по IT и разработке!
 
Автор темы Похожие темы Форум Ответов Дата
AI Overview AI 0
Сверху Снизу