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

  • Код должен быть гибким (т.е легко поддаваться расширению).
  • Код должен быть идиоматичным (т.е быть написан в соответствии с лучшими практиками, принятыми в сообществе языка программирования).
  • Код должен быть простым (KISS).
  • Код должен быть достаточно общим и не иметь дублирований (DRY).
  • Код должен быть написан без изобретения своих велосипедов.
  • Код должен быть читаемым (с хорошими именами функций, в достаточной мере откомментирован, без признаков "индусского подхода" и прочее).
  • Код должен быть разбит на небольшие фрагменты (классы, модули, функции и так далее).
  • Код должен быть написан с использованием устоявшихся шаблонов проектирования.
  • Код должен быть лишен "магических чисел" (их принято выносить в именованные константы).

Продолжать можно еще очень долго. Но все это правда лишь отчасти. На самом деле у этого списка есть как минимум две беды. Первая (наименьшая) - все эти принципы довольно субъективны.

  • Кому-то код покажется гибким, а на самом деле бизнес-требования изменятся таким образом, что всё все-равно придется переписывать и перепроектировать.
  • Кому-то код покажется идиоматичным, а другой первый раз видит Perl и уже час утирает кровавые слезы.
  • Кому-то код покажется простым, а кто-то не сможет его прочитать и понять.
  • Кто-то избавится от дублирования, но в дальнейшем будет все возвращать назад из-за дальнейших изменений требований.
  • Для кого-то это велосипед, а для кого-то принципиально новая реализация существующей идеи.
  • Для кого-то код читаем, а для кого-то многословен и избыточен. Или слишком лаконичен и емок.
  • Для кого-то это хорошо структурированный код, а кому-то было бы удобнее все запихать в один файл (да, я правда встречал таких людей).
  • Для кого-то шаблоны - это привычный язык выражения идей, а для кого-то - кровавый этерпрайз.
  • Для кого-то 14400 - это магическое числа, а для кого-то "это же 4 часа в секундах, очевидно же!".

Ладно, про последний пункт я вру: магические числа - это всегда плохо. Но в остальных примерах существует тонкая грань, при переходе через которую простой код становится сложным, идиоматичный - неидиоматичным, а велосипеды - ключевой технологией.

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

Итак, хороший код - это код, который решает свою задачу.

Тестирование

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

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

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

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

Логирование

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

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

Что же такое хороший код

Итак, какими же необходимыми условиями должен обладать код, чтобы его можно было назвать хорошим?

  • Код должен решать поставленную перед ним задачу.
  • Код должен быть покрыт тестами.
  • Код должен писать логи в полном объеме.

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


Comments

comments powered by Disqus