September 14, 2019

Конспект идеального кода Роберта Мартина

Данная статья является конспектом книги "Чистый Код" Роберта Мартина и моим пониманием того, каким Чистый Код должен быть. Тут нет разделов о тестировании, TDD, о том какая должна быть архитектура и т.д. Здесь все только о том, каким должен быть Чистый Код.

Да, возможно, тема Чистого Кода уже заезженна, но тем не менее еще не все с ним знакомы и, тем более, я не встретил аналогов контента, который содержится в моей статье.

Общее

Нет истинного пути и решения. Есть тот, который лучше всего подходит для решения конкретной задачи.

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

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

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

Всегда задумывайся о том как можно сделать твой код проще, чище и читабельнее.

  • Какие кейсы могут быть у задачи?
  • Все ли я учел?
  • Что может пойти не так?
  • Что можно объединить?
  • Есть ли похожий функционал?
  • Что тут лишнее?
  • Как сделать проще?
  • Как сделать читабельнее?
  • Как сделать понятнее?

Чистый Код

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

Под сущностью понимается — интерфейс, класс, метод, переменная, объект и т.д.

  • Чистый код простой, выразительный и направлен на конкретную задачу.
  • Чистый код читается легко, как проза. Если это не так, то его стоит рефакторить.
  • Чистый код легко изменять. Он не должен быть жестко завязан на куче сущностей. Любую сущность можно легко изменить.
  • Чистый код намного лучше проходит ревью. Если ревью проходит с огромным количеством комментариев, то он не чистый и его надо рефакторить.
  • Чистый код всегда выглядит так, словно над ним очень долго трудились. Какие бы пути для его улучшения ты не искал, ты все равно придешь к тому, что этот код лучший. Соответственно, чистый код — продуманный до всех мелочей.
  • Правило бойскаута: Оставь место стоянки чище, чем оно было до тебя. Это легко перекладывается и на программирование. Видишь грязный код? Сделай его чище, пока решаешь свою задачу. Не стоит увлекаться этим и если грязный код очень грязный, то стоит выделить отдельную задачу и время для его очистки.
  • Не бойся делать изменений. Если ты хочешь их сделать, то значит у тебя есть на то причины, а значит ты сделаешь код лучше и чище. Тем более тесты покажут нет ли ошибок в твоем коде (при условии, что они вообще есть).
  • Любая сущность должна отвечать за один функционал и только за него. И она должна выполнять его хорошо. Single Responsibility.
  • Если сущность отвечает сразу за два и более действий, то её функционал нужно разделять.
  • Код должен читаться сверху вниз.
  • В хорошей и грамотной архитектуре внесение изменений обходится без значительных затрат и усилий.
  • Удаляй мертвый код. Мертвый код это код, который не будет вызван ни при каких условиях или код, который нигде не используется.

Наименования и разделения

  • Используй понятные и удобнопроизносимые имена для любых сущностей. Они должны описывать почему эта сущность существует, что она делает и как используется.
  • Не бойся тратить время на выбор лучшего и понятного имени. Ты выиграешь в будущем при работе или чтении этого кода.
  • Если название сущности не соответствует еë функционалу или по названию не понятно, что сущность делает, то еë надо переименовать в самое понятное название. Если этого сделать невозможно, то значит с еë функционалом что-то не так и еë надо рефакторить.
  • Сущность, которая имеет в названии "And", "With" — нарушает Single Responsibility. Функционал такой сущности стоит разделять. Но этим правилом стоит иногда пренебрегать.
  • Непонятные тексты, строки стоит выносить в переменные и давать им понятные названия.
  • Названия методов должны содержать глагол, который описывает, что этот метод делает и ключевое слово с которым работает данный метод. Если в названии метода нет глагола, то эта сущность не должна быть методом или ему нужно дать правильное название.
  • Нужно избегать одинаковых наименований для двух разных целей.
  • Если сущность имеет схожее с другой сущностью название, то скорее всего их функционал очень сильно похож и их нужно объединить? Если нет, то их названия нужно менять так, чтобы они не были похожими.
  • Если ты мысленно переименовываешь сущность, когда читаешь код, чтобы тебе было понятнее понимать её функционал, то переименуй её в это мысленное название.
  • Выбери одно слово для одной концепции. Сложно будет понимать функционал, когда у тебя есть fetch, retrieve и get в названиях. Пусть лучше везде будет get.
  • Длинное и понятное имя лучше, чем короткое, но непонятное.

Функции

  • Функции должны быть короткими и компактными.
  • Функции должны быть очень короткими и очень компактными.
  • Приблизительный максимум 20 строк и 150 символов в одной строке, если не влезает, то нужно разделять.

  • Функция должна выполнять только одну операцию.

    Она должна выполнять её хорошо и ничего другого она делать не должна.
    Если функция выполняет только те действия, которые находятся на одном уровне абстракции, то функция выполняет одну операцию.
    Чтобы определить выполняет ли функция более одной операции, попробуй извлечь из нее другую функцию, которая не будет являться простой переформулировкой реализации.

  • Любые условные операторы с длинными выборами через switch-case, if-else должны разделяться или объединяться без дублирования, возможно на классы с реализациями, а выбор реализации передать базовому классу, фабрике или еще кому-то.
  • If, else, while и т.д. должны содержать вызов одной функции. Так будет читабельнее, понятнее и проще.
  • Идеальное количество входных аргументов для функции = 0. Если входных аргументов больше трех, то стоит задуматься каким образом лучше от них избавиться, например, создать класс для этих аргументов.
  • Чем больше входных аргументов, тем тяжелее понимается функция.
  • Функция в которую передается аргумент-флаг, от которого зависит работа функции говорит о том, что функция выполняет более одной операции. Такие функции следует разбить на две и вызывать их уровнем выше.
  • Функция, которая изменяет входной аргумент, должна отдавать ссылку на измененный объект, а не просто изменять без возврата. String transform(String text)
  • Если функция, должна изменять входной аргумент, то пуст�� она изменяет состояние своего объекта-владельца.
  • Если входной аргумент функции не должен меняться (и используется дальше в коде), то следует скопировать значение аргумента и внутри функции работать с копией.
  • Вместо return null лучше использовать пустой объект — Collection.empty() или null-объект -EmptyObject().
  • Всегда старайся использовать нестатические функции. Если это невозможно, то используй статические.
  • Если есть код, который должен следовать один за другим, то передавай результаты первой функции во вторую, чтобы кто-нибудь не изменил последовательность вызовов.
  • Используй полиморфизм вместо if/else или switch/case или when.
  • Избегай отрицательных условий.

Комментарии

  • Не используй комментарии, если ты можешь использовать функцию или переменную вместо этого.
  • Не комментируй плохой код — перепиши его. Не стоит объяснять, что происходит в плохом коде, лучше сделать его явным и понятным.
  • Комментарии можно использовать для передачи какой-то информации, предупреждения о последствиях, но не для объяснения того, как работает код.
  • Используй TODO и FIXME в тех случаях, когда нужно пометить, что код нуждается в доработке, но сейчас нет ресурсов на это.
  • Используй //region REGIONNAME //endregion REGIONNAME, а если используешь, то подумай можно ли разделить region на сущности.
  • Документируй код, который является сложным, но чистым.
  • Не оставляй старый закомментированный код. Ты можешь найти его в истории коммитов, если необходимо.
  • Комментарии должны быть краткими и понятными. В комментариях с информацией не должно быть много информации. Все должно быть кратко и по делу.

Форматирование и правила

  • Соблюдай codestyle, принятый на проекте.
  • Соблюдай правила, принятые в команде.
  • При соблюдении форматирования и codestyle код будет читаться проще и лучше. Ведь не зря книгу отдают на редакцию, перед тем, как её издавать.
  • Нужно иметь автоматические средства, которые будут форматировать код за тебя.
  • Файл с исходным кодом должен быть как газетная статья. Есть заголовок, краткое описание в виде параметров и содержание в виде функций. Если это не так, то стоит изменить форматирование.
  • Сущности, связанные друг с другом, должны находиться рядом, например, в одном package, чтобы было проще навигировать по коду.
  • Переменные(поля) класса должны находиться вверху класса.
  • Переменные методов должны находиться ближе к своему месту использования.
  • Функции должны находиться в порядке вызова. Если одна вызывает другую, то вызывающая функция должна находиться над вызываемой. C другой стороны, приватные функции более низкого уровня могут находиться внизу файла и не мешать пониманию кода высокого уровня. Но я предпочитаю первый способ.

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

  • Ты должен работать с абстракциями, чтобы реализацию можно было легко изменить.
  • Ты должен работать с абстракциями, потому что клиент, использующий функционал, не должен знать о деталях реализации, он должен знать какую реализацию в каком случае использовать.
  • Ты должен предоставлять API, с которым стоит работать и скрывать детали реализации, структуру. Так будет проще работать с такими сущностями и добавлять новые виды поведений, функционала и реализаций.
  • DTO — Data Transfer Object. Класс, который содержит только данные и никакого функционала. Нужен для того, чтобы передавать какие-то данные. Объект такого класса должен быть неизменяемым.

Классы

  • Классы должны быть компактными.
  • Классы должны быть еще компактнее.
  • Имя класса должно описывать его ответственности. Отсюда можно и вычислить размер класса.
  • Функционал класса должен четко соответствовать и вписываться в название класса.
  • Разделяй связанность на маленькие классы. Жесткой и обильной связанности не должно быть — это усложняет поддержку и развитие проекта.
  • Помни о Single Responsibility. Сущность должна иметь одну и только одну причину для изменения.
  • Соблюдай инкапсуляцию. Ослабление инкапсуляции всегда должно быть последней мерой.
  • Обычно мы объявляем переменные и вспомогательные функции приватными, но иногда их нужно объявлять protected и иметь возможность обратиться к ней из теста.
  • Если группа функций относится к определенному функционалу, то эту группу функций можно и нужно выделить в отдельный класс и использовать его экземпляр.

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

  • Используй Exceptions вместо возвращения кодов ошибок.
  • Обработка ошибок — это одна операция. Если в функции есть ключевое слово try, то после блоков catch/finally ничего другого в функции быть не должно.
  • Если у тебя есть enum, который перечисляет ошибки, то от него лучше избавиться и вместо него использовать исключения.
  • Используй unchecked exceptions, чтобы явно указать на место в котором есть проблемы. Такие ошибки не нужно отлавливать, вместо этого нужно написать код так, чтобы этой ошибки никогда не было.
  • Передавай достаточное количество информации вместе с выбросом исключения, чтобы потом пользователи твоего кода могли понять, что же действительно произошло.
  • Вместо условных операторов с обработкой ошибок лучше выбрасывать исключения и обрабатывать их.
  • Не передавай null куда-либо. Старайся этого максимально избежать.
  • Обработка ошибок — это отдельная задача и не относится к основной логике программы.

Границы

  • Мы всегда используем какие-либо библиотеки, которые чаще всего дают нам слишком широкий, слишком маленький функционал или конфликтуют с ожидаемым функционалом, что делает код грязнее в его конечном использовании. Избежать этого можно просто применив паттерны типа Decorator, Adapter, Facade или другие.
  • Бывают ситуации, когда тебе нужно работать с функционалом, который находится в разработке или пока что не адаптирован для использования в продакшен коде. В этом случае стоит представить чего ты ждешь от библиотеки/этого функционала и написать свой интерфейс или создать сущность с которыми ты будешь работать в своем проекте так, как тебе нужно. Когда библиотека доделается и станет стабильной, ты адаптируешь её под свои готовые структуры и использовать уже готовый функционал.

Послесловие

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

Источник: https://habr.com/ru/post/424051/