Как создавать и поддерживать хороший код?

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

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

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

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

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

Начнем с не очень чистого примера

При создании методов нам нравится делать их короткими и лаконичными, ориентированными на достижение одной цели.

В качестве примера возьмем следующую функцию:

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

Даже если есть какие-то полезные комментарии, в этом примере прокомментированы не все строки, а некоторые комментарии, такие как Добавить необратимые травмы и Потерять 5 монет, не были обновлены с учетом последних изменений. Это заставляет нас отличать комментарии, которые действительно объясняют фрагмент, от тех, которые приводят к путанице. По этой причине мы могли бы сократить количество строк и сосредоточиться на том, чтобы каждый из наших методов следовал принципу Единого уровня абстракции. Другими словами, каждая функция должна иметь дело с понятиями, относящимися только к одному уровню абстракции. Мы можем добиться этого, извлекая новые методы, которые скрывают детали или реализации, не относящиеся к контексту, который мы сейчас анализируем. Например, если бы мы выполнили этот процесс для кода, упомянутого выше, мы могли бы получить следующее:

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

Мы не ограничиваем этот вид практики функциями; мы также применяем их к классам в целом. В этом случае мы следуем важной концепции: S.O.L.I.D. принципы. Мы хотели бы подчеркнуть, что буква S означает Single Responsibility. Согласно этому принципу, мы должны убедиться, что у классов есть только одна обязанность и что все его методы связаны с ней. По этой причине, если мы создаем класс и сталкиваемся с функциями, которые не соответствуют назначению указанного класса, мы извлечем их в новые классы. Таким образом, мы избежим длинных сценариев, в которых сложно найти нужные нам фрагменты, и у нас будет больше уверенности при выборе классов в тех случаях, когда создание нового класса неприменимо.

Имена методов и комментарии

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

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

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

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

Волшебные числа

Давайте подробнее рассмотрим одну из этих функций, чтобы продолжить оценку возможных улучшений:

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

Запах кода: как распознать плохой код?

Вообще индикаторов грязного кода много, например:

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

Сопровождение кода

Мы должны пояснить, что недостаточно пытаться писать хороший код; мы должны поддерживать его качество. Это объясняет важность правила бойскаута, которое гласит, что мы всегда должны оставлять код лучше, чем мы его нашли. Это правило побуждает нас вносить небольшие изменения — подобные тем, которые мы упоминали в этой статье, — чтобы работа над определенными классами постоянно становилась проще не только для нас, но и для наших коллег, которым в будущем могут понадобиться изменения.

Заключение

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