«Чистый код» Роберта С. Мартина (он же дядя Боб) должен прочитать каждый разработчик программного обеспечения. Он содержит многолетний опыт, накопленный Мартином и его командой в производстве качественного программного обеспечения на протяжении всей их карьеры. Хотя существует множество определений чистого кода, я хотел бы описать его как элегантно написанный код. Книга содержит рекомендации, которым вы можете следовать, чтобы писать элегантный код. В двух словах их можно свести к наборам того, что можно и чего нельзя делать. Не обязательно постоянно следовать им всем, но знание поможет, если вы решили пропустить некоторые из них и позже столкнулись с неожиданной ситуацией.
Эта статья представляет собой краткую версию книги, но в ней не рассматриваются темы глубже, как в книге. Вы всегда можете обратиться к книге за более подробными объяснениями.
- Код читается больше, чем пишется (очевидно). Так что сосредоточьтесь на удобочитаемости при написании кода.
- Чистый код не может быть создан с первой попытки. Возможно, вам придется рефакторить код несколько раз. Так что приложите некоторые усилия, чтобы это выглядело хорошо.
- Код будет (и должен) постоянно развиваться.
- Наличие надежного набора тестовых случаев побудит вас уверенно проводить рефакторинг.
- Оставьте код чище, чем вы его нашли. Облегчить жизнь другим.
Именование переменных
- Имена должны быть доступными для поиска (читаемыми), описательными и раскрывающими намерения.
- По возможности отдавайте предпочтение доменным именам решений (например, концепции CS, шаблоны проектирования и т. д.), а не проблемным доменным именам (должны иметь знание предметной области, чтобы иметь смысл). Разработчики больше знакомы с доменными именами решений.
- Выберите по одному слову для каждой концепции. (Не используйте одновременно «получить», «выбрать», «извлечь» в одном проекте).
- Избегайте кодирования (например, не используйте List‹String› userList. Используйте List‹String› пользователей).
- Использование однобуквенного именования переменных допустимо, если область действия невелика. (например, использование i, j, k в цикле for.)
Функции
- Должно быть меньше. (Уровень отступа не должен быть больше 1 или 2.)
- Должен делать только одну вещь. (Не должен делать скрытые вещи (побочные эффекты).)
- Один уровень абстракции для каждой функции. (Поэтому мы можем легко читать функции сверху вниз. Читая их, мы переходим от более высоких к более низким абстракциям.)
- Оператор Switch — должен появиться один раз и должен создавать полиморфные объекты. Он должен быть скрыт за отношениями наследования. (Инструкция Switch может легко нарушить SRP (принцип единой ответственности) и OCP (принцип открытия-закрытия), если не соблюдать осторожность.)
- Аргументы функции - 0 идеально. 1 или 2 нормально. 3 следует избегать, если это возможно. более 3 неприемлемо (скорее всего, некоторые из них могут быть заключены в отдельный класс).
- Если функция имеет один логический параметр (аргумент флага), то эта функция нарушает SRP и может быть разбита на 2 отдельные функции.
- Избегайте выходных аргументов (например, предпочтите report.appendFooter() для appendFooter(report).)
- Разделение запросов команд. (Функция должна либо изменять состояние объекта, либо возвращать некоторую информацию, но не то и другое) (например, предпочитать исключения для возвращаемых кодов, иначе это нарушит разделение запросов команд.)
- Извлеките блоки try/catch. (Напишите обработку ошибок как отдельную функцию, т.е. такая функция должна начинаться с try в качестве первой строки и заканчиваться с помощью catch/finally.) Это сохранит SRP и улучшит читаемость кода.
Комментарии
- Хороший код не нуждается в комментариях, поскольку он объясняет сам себя (но могут быть исключения).
- Объясните намерение (например, почему было принято такое дизайнерское решение? Почему этот тест игнорируется? Почему этот фрагмент кода закомментирован?)
- TODO — не повод оставлять плохой код в кодовой базе.
- Чтобы усилить/подчеркнуть важность определенного кода.
- Не оставляйте закомментированный код без причины. Это будет препятствовать другим удалить его.
Форматирование
- Вертикальное форматирование — не более 500 строк на исходный файл, используйте новые строки для разделения понятий, относительные понятия следует хранить в одном исходном файле и располагать вертикально близко друг к другу, а их код должен быть вертикально плотным, отражая их близость. (Улучшает читаемость.)
- Горизонтальное форматирование — максимальная длина строки до 120.
- Объявление переменной. Локальная переменная должна быть объявлена в начале каждого метода, переменная экземпляра/члена должна быть объявлена в начале класса.
Классы
- Класс должен начинаться со списка переменных, сначала общедоступных статических констант, затем частных статических переменных, за которыми следуют частные переменные экземпляра. Частная служебная функция, вызываемая публичной функцией, должна идти сразу после вызывающей функции.
- Классы должны быть небольшими и нести единую ответственность. Это можно передать, назвав класс и используя меньшее количество методов внутри.
- Когда методы класса манипулируют одной или несколькими переменными экземпляра, это означает, что эти методы связаны с классом. Меньшие связанные методы (методы, которые манипулируют меньшим количеством переменных экземпляра) могут быть извлечены в новый класс. Наличие большего количества переменных экземпляра в классе может привести к менее связным методам.
- При именовании интерфейса и класса всегда кодируйте класс (например, ShapeFactory и ShapeFactoryImpl.)
Объекты и структуры данных
Объекты — раскрывайте поведение и скрывайте данные.
Структуры данных — раскрывают данные и не имеют существенного поведения.
- Для объектов не раскрывайте их данные напрямую (общедоступные переменные) или с помощью методов доступа (геттеры/сеттеры), поскольку это раскрывает их внутреннюю структуру. Вместо этого выставляйте методы, которые манипулируют данными и возвращают что-то значимое (внутренняя структура скрыта).
- Метод F класса C должен вызывать только методы C, методы объекта, хранящиеся в переменной экземпляра C, методы объекта, передаваемого в качестве аргумента F, и методы объекта, созданного F.
- Не объединяйте методы вызова в цепочку, т. е. класс не должен перемещаться по методам, которых он не должен знать. Вместо этого напишите метод, который делает то, что ожидает вызывающая сторона, без необходимости перемещаться по дополнительным методам.
- Рассматривайте DTO/активные записи (например, классы DB ORM) как структуры данных, не включая в них бизнес-логику.
Обработка ошибок
- Предпочитайте исключение кодам возврата.
- Разделите бизнес-логику с логикой обработки ошибок. (В идеале логика обработки ошибок должна начинаться с блока try.)
- Предпочитать непроверенное исключение проверенному исключению. (Проверенное исключение является нарушением OCP.)
- По возможности обрабатывайте особый случай бизнес-логики, используя специальный объект, а не исключение.
- Не возвращайте значение null из метода. (Вызовите исключение или используйте объект особого случая.) Если вы вызываете метод, возвращающий значение null, оберните этот метод методом, который выдает исключение или возвращает объект особого случая.
- Не передавайте null в методы.
Модульное тестирование
- В TDD тестовый и рабочий код пишутся вместе.
- Тесты развиваются вместе с производственным кодом.
- Наличие тестов дает вам уверенность в рефакторинге производственного кода.
- Одна концепция на тест.
- Количество утверждений в тесте должно быть минимальным.
- Тесты должны быть быстрыми.
- Тесты не должны зависеть друг от друга (должны выполняться в любом порядке).
- Должен быть воспроизводим в любой среде.
- Должен выполнять самопроверку, выдавая логический вывод, говорящий о прохождении или сбое.
- Тест должен быть написан своевременно, как описано ниже в законах TDD.
Три закона TDD
1. Сначала напишите неудачный модульный тест, прежде чем писать какой-либо производственный код.
2. Не пишите больше модульного теста, чем достаточно для провала.
3. Не пишите больше производственного кода, чем достаточно для прохождения неудачного теста.
Использование сторонних API/библиотек
- При использовании сторонних API создайте промежуточный интерфейс для их использования. Таким образом, у нас будет больше контроля над открытыми API-интерфейсами, и нам будет легче вносить изменения позже или создавать макеты для тестирования. Также это будет полезно при интеграции нашей системы с «еще не реализованными API», поскольку мы можем продолжить разработку, используя макет, и подключить реальный API, когда он будет готов.
- Написание тестов для сторонних API улучшает наше понимание этого. Эти тесты называются «обучающими тестами». Обучающие тесты будут полезны позже, если мы обновим API до более новой версии или полностью заменим их другим набором API. Кроме того, эти тесты будут действовать как документация, чтобы показать другому разработчику, как использовать эти API.
Окружающая среда
- Проект должен быть в состоянии построить из одного шага.
- Тесты должны выполняться с одного шага.
Параллелизм
- Держите код, связанный с параллелизмом, отдельно от другого кода.
- Ограничьте доступ к общим ресурсам.
- Избегайте использования более одного метода на общем ресурсе.
- Большинство параллельных проблем относятся к проблемам производитель-потребитель, читатели-писатели или проблемы обедающих философов.
- Старайтесь, чтобы синхронизированные разделы (критические разделы) были как можно меньше.
- Протестируйте код, в котором представлены различные операции с потоками (например, ожидание, сон, выход, приоритет и т. д.).
- Запускайте код, связанный с параллельным выполнением, в нескольких конфигурациях и на всех целевых платформах как можно раньше и чаще.
- Не игнорируйте системные сбои (или сбои тестов) как единичные случаи, потому что может быть сложно воссоздать этот конкретный сбой.
Общие
- Избегайте дублирования (это может выглядеть как идентичный код или похожий алгоритм).
- Предпочитайте нестатические функции статическим функциям.
- Предпочитайте полиморфизм if/else или switch/case.
- Инкапсулируйте условные операторы.
- Избегайте отрицательных условий.
- Обеспечьте временную связь, когда важен порядок вызовов.
- Используйте подстановочные знаки, чтобы избежать длинных списков импорта.
- Предпочитайте перечисления константам. (Перечисления могут иметь поля и методы.)
На этом статья заканчивается. Пожалуйста, дай мне знать, что ты думаешь. Спасибо за прочтение! :)