Автор: Виктор Свирский, Senior Python Developer / Team Lead, DataArt

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

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

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

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

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


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

  • Код легко модифицировать. При правильном проектировании и архитектуре расширение кода обходится без особых временных и технических затрат. Сущности кода не должны быть тесно связаны между собой, код должен быть отчасти абстрактным и самодостаточным. Каждая сущность, которой мы оперируем при разработке, должна отвечать только за свою часть функциональности.
  • Код должен быть стабильным, предсказуемым, безопасным и надежным. Каким бы простым код не был в чтении, он должен быть покрыт тестами. Хороший код и тесты всегда рядом. Причем важно не только количество тестов, но и их качество. С таким кодом не возникает проблем при запуске и отладке, он не вызывает изменений в окружающей среде.
  • Защищенный код. При написании любого кода нельзя забывать об общей безопасности продукта. Рекомендую ознакомиться с базовыми принципами безопасности и придерживаться их. Если мы говорим о веб-проектах, рекомендую OWASP.
  • Хороший код — код, которого нет. Это не значит, что весь код должен быть написан в одну строку, а вы обязательно гордиться тонкими методами. Это значит, что код не стоит дублировать, а часть общих вещей должна остаться на уровне абстракций. Теоретически, упрощение кода должно привести к уменьшению количества дефектов.
  • Само чтение кода тоже немаловажно. У каждого разработчика есть собственный стиль написания, а уровень чтения зависит от нашего опыта. Все мы хотим писать простой, красивый и лаконичный код.

Чтобы уйти от субъективной оценки качества своего кода, с 1961 года введен термин «запахи кода» или «код с запашком». Это группа правил и рекомендаций, которые четко определяют, пора ли делать рефакторинг. «Запашок» ведет к распаду кода, и разработчики всегда должны стремиться его устранить. Необходимость рефакторинга напрямую следует из наличия запаха кода, и предотвращая причину, мы сможем избежать и следствия. Более подробно о ключевых признаках того, что вам пора заняться рефакторингом можно прочесть в книге Мартина Фаулера «Рефакторинг. Улучшение существующего кода».

Стоит ли писать чистый код?


Однозначно стоит! Но не всегда и не везде стоит уделять чистоте слишком много внимания.

Не стоит забывать о целесообразности и сроке жизни вашего кода. Например, если перед вами стоит задача разработки концепции — PoC (Proof of concept), и вы доказываете, что выбранный стек технологий выполняет поставленную задачу, ваш код станет неактуален уже через неделю или две. Не стоит тратить силы на совершенствование этого функционала.

Бытует мнение, что не нужно следить за качеством кода или части системы, которые в скором времени будут заменены. И это неверно по нескольким причинам. Высокое качество исполнения сделает переход или интеграцию с новыми частями более простыми, бесшовными и быстрыми. Оно наверняка упростит жизнь в тех случаях, когда несколько версий кода придется поддерживать одновременно. Количество регрессионных ошибок с чистым кодом будет в разы меньше. Также не стоит забывать, что нет ничего более постоянного, чем временное. Возможно, задачи по улучшению этой части кода еще несколько месяцев будут лежать в бэклоге.

Что поможет улучшить ваш код?


Большинство программистов мечтают писать код быстро и максимально красиво, причем так, чтобы все идеально работало с первого раза. Но далеко не каждому удается сделать код не просто работающим, но и понятным. Как же добиться успеха в написании чистого кода? Есть два пути — самоорганизация и командная работа.



Самоорганизация


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

  1. Инструменты
    Уделяйте внимание используемым инструментам, особенно главному из них — среде разработке. Удобный инструмент — это половина успеха. IDE (интегрированная среда разработки) не только дополняет код на лету, но и подсказывает места с запахом, которые стоит привести в порядок. Расширив IDE необходимым плагинами, вы получаете швейцарский армейский нож. Большую часть работы вы проводите именно в этом инструменте — сделайте его максимально удобным и гибким.

    Не упустите возможности приобрести свой любимый продукт. Борьба с лицензиями сократит вам часть времени, а официальная поддержка никогда не будет лишней.
  2. Участие в open source проектах
    В командной работе важна возможность поработать с чужим кодом. Понимать, читать и придерживаться стиля не всегда просто. Работая с чужим кодом, нередко удается узнать о новых подходах к решению нетривиальных задач. Читайте и изучайте код!
  3. Строгие рамки
    Начните разработку небольшого проекта, который решает конкретную проблему. Самостоятельно разработайте архитектуру и реализуйте ее. При этом вы можете установить себе технические ограничения. Например, разработка только с использованием ООП, цикломатическая сложность методов не больше 10, соблюдение всех рекомендаций по разработке в данном языке, осознанное использование шаблонов проектирования и т. д.

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

    Решайте программистские головоломки. Это отличный способ улучшить навыки программирования и узнать тонкости выбранного вами языка.
  5. Анализ решений

    Не спешите решать задачи в лоб. Задавайте вопросы старшим разработчикам и самому себе. Всегда важно понимать причинно-следственную связь тех или иных решений. Хорошо понимая проблему, вы сможете эффективно ее решить.

    Хороший разработчик — это не ремесленник, который пишет код, а инженер, который совмещает в своей работе прикладные исследования, планирование и проектирование.
  6. Чтение документации

    Уметь читать документацию не менее важно, чем читать код. Следующий шаг — научиться писать документацию.
  7. Практика!

    Любой опыт лучше, чем его отсутствие.

Командная работа


Большинство задач решается в команде. Очень важно разделять ответственность за качество между ее участниками. Чем больше команда, тем сложнее поддерживать продукт в хорошем состоянии. Рассмотрим несколько подходов удержания кода в вышеуказанных условиях.

  1. Код ревью
    Это простой принцип для понимания и исполнения. По крайней мере два человека, включая автора кода, проверяют код.

    Во время проверки кода необходимо учитывать несколько вещей:

    • Один из них — проверка, не нарушает ли код правила соглашения о коде. Это процесс, который можно и нужно автоматизировать с помощью статических анализаторов в CI (непрерывной интеграции).
    • Другие — это maintainability (легкость в поддержке) кода и обработка ошибок, которые нельзя проверить автоматически.
    • Наконец, что не менее важно, код нужно проверить на полноту. Содержит ли этот фрагмент кода весь объем функции, как было задумано?

  2. Непрерывная интеграция (Continuous integration)

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

    Непрерывная интеграция работает, когда вы следуете двум простым правилам:

    • Сборка продукта происходит быстро. Не допускайте медленных сборок. Непрерывная интеграция улучшает качество кода, поскольку обеспечивает быструю обратную связь. Если тесты не пройдены, сборка не удастся, вы мгновенно получаете уведомление.
    • Вы добавляете в скрипт сборки статические анализаторы, которые проверяют соглашения о кодировании, повышают качество кода и проверяют безопасность.
  3. Соглашения о кодировании (Coding сonventions)

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

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

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

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

    Чем меньше ошибок в коде, тем выше его качество. Тщательное тестирование отфильтровывает критические ошибки и гарантирует, что код работает так, как задумано.

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

    Наибольшее количество тестов в программном проекте должны быть юнит-тестами. Они дешевые и быстрые. Существует множество различных инструментов, которые могут помочь вам в создании модульных тестов и отчетов о покрытии кода. Запуск набора тестов и создание отчета о покрытии кода могут выполняться автоматически посредством непрерывной интеграции. Можно даже сделать сборку неудачной, если покрытие кода не соответствует требуемому проценту.
  5. Анализ ошибок

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

    Когда возникает ошибка, проанализируйте ее с помощью нескольких вопросов:

    • Это ошибка с низким или высоким приоритетом? Если да, она должна быть немедленно исправлена. Если ошибка незначительная и позволяет продукту выполнять задачу без особых проблем, такая ошибка может быть исправлена в следующих итерациях.
    • Что пошло не так?
    • Почему мы не проверили это (правильно)?
    • В каких еще местах это происходит?
    • И самое главное — как мы можем предотвратить подобное в будущем?

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

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

    • Потенциальные ошибки

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

      Каждая часть знания должна иметь единственное, непротиворечивое и авторитетное представление в рамках системы — принцип DRY (Don’t repeat yourself).
    • Метрики сложности

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

      Всего нескольких правильно расставленных строк с комментариями, комментария к модулю, классу или методу будет достаточно, чтобы код стал намного понятнее.
    • Степень покрытия кода тестами

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

***


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

Хорошо, если удастся не превращать чистоту в фетиш, учитывая срок жизни нашего кода и оценивая целесообразность дальнейших улучшений. Главное помнить о людях: пользователях, которых может подвести внезапный отказ даже небольшой части разработанной нами системы, и инженерах, которым предстоит эту систему поддерживать.