Когда мы говорим о написании хорошего кода, первые слова, которые приходят на ум, - это «структуры данных и алгоритмы». Затем следует прочная основа объектно-ориентированного программирования, как это преподается в любом вводном курсе программной инженерии.

Но что на самом деле представляет собой хороший код? Как вы можете его идентифицировать, количественно оценить и сравнить с другим фрагментом кода, который решает ту же проблему? Имея несколько одинаково эффективных решений проблемы, как выбрать лучший?

Это вопрос, который застрял у меня в голове, когда я читал книгу Сэнди Мец и Катрины Оуэн, рекомендованную мне 99 бутылок ООП: Практическое руководство по объектно-ориентированному дизайну. по моему руководству в Shopify.

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

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

заново открывая простоту

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

Изучив основы объектно-ориентированного программирования, молодой разработчик может легко перейти к абстрагированию кода везде, где это возможно. Иногда… конкретный код на самом деле лучше!

примечание: абстракции здесь обычно относятся к извлечению частей кода в новый метод или переменную.

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

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

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

Ограничьте количество зависимостей. Знание, которое один объект имеет о другом, создает зависимость. Это может быть, например, передача параметра в метод или условное форматирование вывода функции. Чем больше зависимостей, тем больше один объект знает о том, как работает другой. Функция должна раскрывать только свое предназначение!

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

притормози и сначала протестируй

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

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

  1. Напишите один тест, который проверяет простейшую вещь, которая покажет, что ваш код что-то делает правильно.
  2. Напишите ровно столько кода, чтобы пройти этот тест - ради стратегии это может означать жесткое кодирование вывода.
  3. Напишите второй тест, который проверяет простейшее, но самое полезное, чтобы показать, что ваш существующий код неверен.
  4. Напишите еще код и повторите…

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

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

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

количественная оценка хорошего кода

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

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

Метрика назначений, ветвей, условий (ABC) - подсчитывает количество переменных назначения (A), ветвей потока управления (B) и путей условной логики (C). Это можно использовать как индикатор сложности кода, где чем выше оценка, тем сложнее. Решения с большим количеством строк кода, как правило, имеют более высокие оценки.

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

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

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

Я настоятельно рекомендую прочитать 99 бутылок ООП: Практическое руководство по объектно-ориентированному дизайну Сэнди Мец и Катрины Оуэн для более конкретных примеров того, как применять эти концепции!