После нескольких докладов о чистом коде (Clean Code) я решил обобщить в статье самое важное по этой теме. Поскольку в Интернете и так много постов и информации об этом, то, я думаю, еще одна статья, просто рассказывающая о принципах чистого кода, не будет интересной.
Поэтому я попытаюсь показать вам практический подход к чистому коду. Не вдаваясь в теорию, покажу, как я пишу Чистый Код.
Что такое "чистый код" и почему вас это должно беспокоить?
История возникновения и определение "чистого кода"
Обязательно стоит упомянуть одноименную книгу, написанную Робертом К. Мартином в 2008 году (Прим. переводчика: издание на русском "Чистый код. Создание, анализ и рефакторинг."). Но и до выхода этой книги было много других книг и опытных разработчиков, говоривших о подобных концепциях.
Я вывел своего рода определение чистого кода, объединив мнения нескольких авторов и источников:
Чистый код важен. По крайней мере, так же важен, как и другие характеристики, такие как производительность, функциональность, не допускать багов.…
Легко читается любым разработчиком.
Легко модифицируется любым разработчиком.
Автор кода заботится о нем.
Код делает то, что от него ожидается. Код вас не обманет, никаких сюрпризов.
Почему вы должны писать чистый код
Я действительно считаю, написание чистого кода важно, потому что это первый шаг к достижению главной цели любой архитектуры: минимизировать человеческие усилия, необходимые для создания и сопровождения системы.
Программисты тратят больше времени (гораздо больше) на чтение кода, чем на его написание. Мы читаем легаси-код, код библиотек, код коллег по команде, код, написанный вами несколько месяцев назад (которого вы не помните), код, написанный кем-то, кто ушел из компании, код на Stack Overflow… Роберт Мартин резюмирует:
"На самом деле соотношение времени чтения и написания кода превышает 10:1" — Роберт С. Мартин, "Чистый код".
Учитывая это, не стоит ли приложить немного дополнительных усилий при написании кода? Вы окупите это время в дальнейшем несколько раз. Подумайте об этом в следующий раз, когда будете коммитить кусок кода.
Последняя мысль, почему мы должны заботиться о своем коде, заключается в том, что мы, разработчики, пишем код не только для компилятора. Мы пишем код для чтения людьми. Представьте себя в роли журналиста, который пишет статью для газеты или в роли писателя, пишущего роман.
Некоторые принципы
Есть много принципов и идей о том, какой код является чистым, а какой нет. Вы можете найти их в книге, а также в сети. Но поскольку мне нужны некоторые основные принципы, с которыми можно было бы поиграться, я приведу некоторые из них. Если вы уже знакомы с ними, то можете перейти к следующему разделу, если нет, я думаю, они позволят вам получить представление о стиле остальной части теории и, возможно, мотивирует вас на дальнейшее изучение.
Итак, давайте рассмотрим некоторые принципы работы с кодом.
Имена
При написании кода мы повсюду придумываем имена: переменные, функции, классы, пакеты, файлы… Серьезное отношение к именам — первый шаг к чистому коду.
Несколько советов для чистых имен:
Используйте имена, передающие намерения программиста:
Выбирайте произносимые имена:
Используйте имена, доступные для поиска:
Избегайте префиксов, суффиксов и аббревиатур
По именованию есть множество других рекомендаций. Вероятно, использование имен, передающих намерения, является наиболее важным, но еще более важно — серьезное отношение к именованию.
Не торопитесь при выборе имен. И не бойтесь переименовывать, если считаете, что после изменения код станет более читабельным.
Функции
Среди всех идей о функциях я выделю три:
Первое правило простое и легко запоминающееся: функции должны делать что-то одно, делать это хорошо и делать только это. Избегайте побочных эффектов, разделяйте функции, если заметите, что они делают несколько вещей одновременно.
Функции должны быть небольшими. Хорошо, но насколько короткими? Как это измерить? Давайте договоримся, что в функциях будет не более двух уровней отступов. Если для вас это сложно, можно начать с более высокого предела (например, с трех уровней), но, пожалуйста, установите ограничение на уровни отступов, которые вы разрешаете.
По поводу аргументов… Чем меньше аргументов у функции, тем она чище. Почему? Аргументы требуют знания контекста. При каждом вызове читатель должен обладать контекстом, чтобы понять все аргументы. Больше аргументов — больше контекста, который вам нужно понять. Также возникают сложности с точки зрения тестирования. Больше аргументов — больше тестов для покрытия всех комбинаций аргументов.
Комментарии
Когда я учился программировать в 90-х, мои учителя обычно просили меня писать комментарии везде. Довольно часто приходилось слышать что-то вроде: "Если вы не будете комментировать код, я не приму ваш экзамен…". Целью этих комментариев было облегчить чтение нашего кода. Но мы преследуем эту же цель при написании чистого кода, и комментарии — не лучший способ ее достичь.
"Комментарии должны компенсировать нашу неудачу в выражении своих мыслей в коде. Комментарии — всегда признак неудачи." — Роберт С. Мартин, "Чистый код"
В этом я согласен с Мартином. Он также говорит, что комментарии врут, и я уверен, что вы встречали в коде устаревшие неактуальные комментарии. Потому что мы вынуждены поддерживать код, но нет ничего, что заставляло бы нас поддерживать комментарии в актуальном состоянии. Есть ли что-нибудь хуже комментария, который лжет?
Правда только в коде. Когда хотите написать комментарий, всегда подумайте, нет ли лучшего способа выразить это с помощью кода.
Основная мысль — стараться избегать комментариев для пояснения кода. Например:
Избегайте комментариев для переменной. Вместо этого выберите хорошее имя для этой переменной, и вам не понадобится комментарий.
Избегайте комментариев для функций. Вместо этого напишите функцию так, чтобы она делала только одну вещь, у нее было небольшое количество аргументов и понятные имена самой функции и аргументов, и вам не понадобятся комментарии.
Давайте рассмотрим пример:
1. У нас есть нетривиальный участок кода. Мы чувствуем, что в будущем будет трудно его понять:
2. Давайте добавим комментарий, чтобы стало понятнее ("Проверяем year - високосный год или нет"):
3. Попробуем другой вариант: извлечем этот кусок кода в метод с говорящим названием:
Подумайте, что будущему читателю кода будет интересно, когда он встретит этот if? Ему нужно понять, что этот if проверяет, является ли год високосным (leap year). Но, вероятнее всего, его не будет волновать, как выполняется эта проверка. Если все-таки это будет интересно, то он может перейти к реализации этого метода. Убрав комментарий, мы невольно также разделили разные уровни абстракции в коде.
В общем, избегайте комментариев для пояснения кода. А, так как вы используете систему управления версиями кода вроде GIT, то избегайте и закомментированного кода (удалите его!), информации об авторах частей кода и т. п.
Комментарии не запрещены, есть ситуации, когда комментарии вполне уместны:
Информация об авторских правах.
TODO-комментарии.
Пояснения важности чего-либо или объяснение конкретного решения в коде.
Комментарии публичных API обязательны (JavaDocs, …), но избегайте их в непубличном коде. Не заставляйте команду комментировать все функции всех ваших классов!
Дополнительные принципы
Как говорил ранее, я хотел привести примеры лишь нескольких принципов, чтобы вы получили основы теории "чистого кода". Итак, это всего лишь некоторые из базовых идей о чистом коде. Если они показались вам интересными и вам захотелось узнать больше, поищите дополнительные ресурсы в Интернете или прочитайте книгу Мартина.
Как сделать код чистым? Рефакторинг!
Понятные имена, маленькие функции, отсутствие комментариев для пояснения кода… все ясно. Но как это сделать? Как написать код, следуя этим концепциям?
Наша работа трудна сама по себе. Написать работающий код уже может быть достаточно сложной задачей. И это не беспокоясь о том, чтобы он был чистый.
Таким образом, ключевым здесь может стать Рефакторинг. Хороший подход — первоначальное написание кода без беспокойства о его чистоте, с последующей очисткой уже работающего кода с помощью рефакторинга.
Рефакторинг
Рефакторинг кода — это процесс реструктуризации существующего кода без изменения его внешнего поведения. Это означает, что код до и после рефакторинга должен работать одинаково.
Примеры того, что не является рефакторингом:
Изменение алгоритма.
Замена одного типа цикла другим.
Повышение производительности фрагмента кода.
Примеры рефакторинга:
Извлечение фрагмента кода в функцию.
Переименование.
Извлечение нескольких функций в новый класс.
Создание константы для хранения жестко заданного значения в коде.
…
Безопасный рефакторинг
Возможно, вы думаете: "Я не хочу ломать код, он работает нормально!". Да, конечно. Написать работающий код довольно сложно, мы не хотим ломать его при рефакторинге. И это частая причина, из-за которой люди спорят, чтобы не вносить изменения в плохой код. Но не волнуйтесь, есть безопасный способ.
При рефакторинге нас спасут две вещи:
Тестирование. У вас должны быть хорошие автоматизированные тесты по многим причинам. И очевидно, что они помогут провести рефакторинг, ничего не сломав. После каждого рефакторинга вы можете проверить, все ли тесты по-прежнему зеленые. В этом посте я не буду писать о тестировании, возможно, в следующем. Если вы не знакомы с тестированием, вам стоит изучить этот вопрос. В сети есть много информации.
Инструменты рефакторинга. В современных IDE есть инструменты, которые автоматически выполняют некоторые из наиболее распространённых рефакторингов. При их использовании снижается вероятность того, что мы что-то сломаем при внесении изменений в код. Я расскажу о некоторых из них далее в этой статье.
Когда следует проводить рефакторинг кода?
Постоянно. Я имею в виду, что у вас должен быть следующий цикл разработки:
Написание кода.
Написание тестов.
Рефакторинг.
Хотя не обязательно именно в таком порядке. При разработке вы можете использовать TDD, и в этом случае вы будете писать тесты перед кодом. Но в любом случае, вы должны проводить рефакторинг каждый раз, когда напишете кусок рабочего кода. Другими словами, вы должны проводить рефакторинг в конце каждого цикла. И эти циклы должны быть небольшими.
При коротких итерациях проводить рефакторинг гораздо легче, чтобы убедиться, что все чисто, и ничего не ломается. Если вы тратите на написание кода несколько дней и в очередной релиз входит много измененных строк в разных файлах, то, вероятно, это не самая хорошая привычка.
Приложение. Инструменты рефакторинга
Для демонстрации инструментов рефакторинга я выбрал JetBrains IntelliJ. Но вы найдете подобные инструменты и в других IDE. А если не найдете, возможно, вам следует попробовать другую IDE.
Переименование (rename)
Самым простым рефакторингом является переименование (Rename). Например, есть сущность с именем, которое вам не нравится, и вы хотите его изменить. Конечно, можно отредактировать его вручную... но это будет непросто, так как эта сущность может использоваться во многих местах.
Например, я хочу переименовать класс Input
в WordFrequency
.
Поскольку это класс, есть много потенциальных мест, где я должен изменить его имя Input
. Вручную я должен найти их все. Но у нас есть инструмент рефакторинга:
Этот инструмент переименует класс, а также всё остальное, что нам нужно: разные варианты использования, имя файла, тесты ... даже имена переменных, которые могут быть связаны с нашим классом:
Извлечение метода (Extract method)
Этот рефакторинг я использую чаще всего. Давайте рассмотрим пример:
У меня есть фрагмент кода, являющийся частью очень большой функции, которую я хочу отрефакторить. Я думаю, что часть этих строк делает какую-то законченную операцию и поэтому ее можно вынести в приватную функцию.
2. Я использую рефакторинг Extract Method. У этого инструмента также есть ряд параметров:
3. Теперь код перемещен в метод, а вместо него идет вызов этого метода. Инструмент позаботился о создании метода, перемещении туда кода и изменении всех мест, где был такой же код, на этот один вызов метода (он ищет дубликаты кода).
Другие инструменты
Существует множество инструментов рефакторинга для популярных действий, которые выполняются при реорганизации кода. Говоря об IntelliJ Idea, вы можете взглянуть на ее документацию по рефакторингу, или поискать в документации вашей любимой IDE. Я рекомендую вам изучить все доступные инструменты рефакторинга вашей IDE и поэкспериментировать с ними, чтобы понять, как они работают и насколько они будут вам полезны.
На самом деле, в моей IDE я могу выделить кусок кода и в меню "Refactor This" увидеть все доступные рефакторинги, которые можно применить к этому конкретному случаю:
Финальный аргумент
Есть финальный аргумент в пользу поддержания чистоты вашего кода, если я до сих пор вас не убедил:
Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте — Джон Ф. Вудс (John F. Woods).
Приглашаем всех на открытое занятие «Элементы формальной логики. Базовые структуры данных в языке Java». На нем познакомимся с основами алгоритмов и булевой алгебры. В процессе мы изучим базовые структуры данных языка Java: массивы, списки и словари. Регистрация по ссылке.