Идеальный код

Как сейчас помню тот день, когда написал идеальный код. Он был прекрасен в своей простоте и лаконичности. Слёзы счастья катились из глаз, хотелось петь и танцевать. Я был счастлив. А через два дня я понял, что код - говно.

Какой же ты, идеальный код?

Спустя годы разработки я начал понимать, что код можно поделить на идеальный и рабочий. Идеальный код можно легко найти в книгах, мудрых статьях про программирование, и даже, как утвердажют некоторые, на древних скрижалях из времён, когда и трава была зеленее и солнце ярче. Рабочий же код можно найти повсюду, где что-то работает, ну или делает вид, что работает. Может показаться, что потому я и называл этот код рабочим, но это не так. Рабочий код, это код, с которым приходится работать каждый день, год за годом.

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

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

Нам надо!

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

Возьмём к примеру житие простого класса Пользователь. Кстати, вот он, красавец:

class User
{
    public $id;
    public $name;
}

Жил себе Пользователь в проекте, никого не трогал. И тут как гром среди ясного неба: "Нам надо добавить стопиццот данных в пользователя!" Ну пускай, не стопиццот, а всего два - телефон и адрес электронной почты. Но это надо сделать вчера! Почему вчера? А вот потому что, это вам не книжка, а жизнь. Если покумекать, то решение видится очень простое - добавить пару свойств в класс $phone и $email. Это решение быстрое и делает именно то, что хочет заказчик.

class User
{
    public $id;
    public $name;
    public $phone;
    public $email;
}

Живёт себе Пользователь дальше, не тужит, а во всём проекте уже посажены ростки амброзии. Как например в этом шаблоне письма:

Здравствуйте, <?=$user->name?> (<?=$user->email?>)

Мы выслали ссылку на скачивание нашего продукта на Ваш контакный телефон: <?=$user->phone?>
--
С уважением, Мы

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

  1. Из-за ограничений по времени принимется решение сохранить обратную совместимость. То есть оставить свойство $phone.
  2. Поскольку адресов может быть много, то по умолчанию заполять $phone первым телефоном из списка.
  3. Добавить в класс Пользователь методы для работы со списком телефонов.

Теперь Пользователь уже выглядит так:

class User
{
    // Старый функционал
    public $id;
    public $name;
    public $phone;
    public $email;
    
    // Новые плюшки
    public function __construct() {
        $this->phone = $this->getPhoneList()[0] ?? null;
    }
    
    public function addPhone($phone) {...}
    public function removePhone($phone) {...}
    public function getPhoneList() {...}
}

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

class User
{
    public $id;
    public $name;
    public $phone;
    public $email;
    
    public function __construct() {
        $this->phone = $this->getPhoneList()[0] ?? null;
        $this->email = $this->getEmailList()[0] ?? null;
    }
    
    public function addPhone($phone) {...}
    public function removePhone($phone) {...}
    public function getPhoneList() {...}
    
    // Новые плюшки
    public function addEmail($email) {...}
    public function removeEmail($email) {...}
    public function getEmailList() {...}
}

Ещё через некоторое время вдруг оказывается, что жизнь уже не мила, если у пользователя нет прикреплённых файлов, паспортов, кредитных карточек, заказов, билетов, балетов, бассейнов и загородных вилл. И каждый кусок этой информации отдаётся программистам на протяжении месяцев и даже лет. И всегда с пометкой "срочно! вчера!".

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

class User
{
    const STATUS_OKAY = 1;
    const STATUS_NE_OKAY = 2;
    const STATUS_JUNK = 5;

    var $id, $name, $phone, $email;
    private $status = 2;
    
    public function __construct() {
        if (MOON_1) {
            // Не удалять!!! Отвалится оплата!!!
            $this->do_abrakadabra_today();
        }
        $this->phone = $this->getPhoneList()[0] ?? null;
        $this->email = $this->getEmailList()[0] ?? null;
    }
    
    function do_abrakadabra_today($project, User $sosed) {...}
    function add_someThing($item) {...}
    function drop_someThing($item) {...}
    function addPhone($phone) {...}
    function removePhone($phone) {...}
    function getPhoneList() {...}
    function addEmail($email) {...}
    function removeEmail($email) {...}
    function getEmailList() {...}
    function files() {...}
    function attachFile(File $file) {...}
    function detachFile(File $file) {...}
    function sendsms($text) {...}
    function confirmOrderById($orderId) {...}
    function confirmOrderByEmail($email = null) {...}
    function confirmOrderByKakaytaFignya($data) {...}
    function sendNews() {...}
    function sendNews_o_zakazah() {...}
}

Естественно, что на трезвую голову сложно понять некоторые особенности архитектуры данного класса. Может потребоваться время, чтобы смириться, что этот ёжик живёт тут. Но задачи не любят ждать, и как всегда вчера надо было добавить новый функционал. Но что же делает новый программист? Убирает все корявые функции и свойства, делает код чище, разносит логику по спец классам? Нет. На это требуется время, которого никогда нет. Может быть он идёт к начальнику и сообщает, что на устранение технического мусора нужно 4 часа 47 минут и 28 секунд? Может быть, но ответ будет отрицательный, ведь задачу сделать надо было ещё вчера. Так что же он делает? Всё просто - добавляет очередную функцию:

class User
{
    /*
      Тут всякая старая непонятная фигня от предыдущих программистов
      на 600 строк
    */
    
    // А это новая хорошая функция!
    function do_theHalzanTolgoytoy() {...}
}

Почему функцию назвали do_theHalzanTolgoytoy? Ну... Это один из тех вопросов, на которые могут ответить только седые старожилы проекта. На всё когда-то была своя причина, просто не всегда она лежит на поверхности.

Я знаю, как надо!

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

Возьми любую книгу про программирование, там всё просто и понятно. Все примеры изложены доступно и разжёванно. Читаешь и воображение рисует радужные замки, стада розовых пони и зарплату в сотни тысяч рублей, которая валится прямо с неба в руки. Идиллия. Но после этого ты приходишь на работу и видишь класс Пользователь. Розовые пони в ужасе разбегаются, а от замков отваливаются метровые куски радуги. Да, это реальность. Да, суровая.

И вот новый программист кричит на весь отдел: "Ваш код гавно! Я знаю, как надо!". И не просто кричит, а ещё и тыкает пальцем прямо в амброзию. А потом этим же пальцем начинает учить, как жить правильно: "Вот тут надо было фабрику, вот тут композицию, а вот тут вообще стратегия нужна, а тут нарушен PSR-7. Как можно не знать таких простых вещей?!"

Думаете, что так не бывает? Я лишь привёл пример из своего опыта работы ведущим программистом. Каждый второй учил нас "правильно" писать код, "правильно" делать проекты, "правильно" жить. Может быть, в других компаниях этого и нет, но откуда-то они ведь приходят.

Однако, всезнайство и какнадство очень легко лечится решением задач. Почувствовали в себе великое знание "как надо правильно"? Возьмите ручку, листочек и нарисуйте решение на бумаге. Не знаете, как нарисовать? А как вы будете писать код без представления полной рабочей схемы в голове? Пока код не заработает на бумаге, каша будет и в проекте. Думаете, что это пустая трата времени и тру-программистам не нужно уметь рисовать? Нужно. Блок-схемы, это часть разработки и нужно уметь их рисовать и понимать.

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

Написали идеальный код? Заработал? Чудненько. Можете повесить его в рамочку и будете показывать внукам. Ну или в книжку вставите, когда её напишете.

Рабочий код

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

Разгадка оказалась простой. В старых проектах логика почти всегда была на поверхности, её не прятали за сотнями разных классов. И было примерно понятно, какой кусок кода за что отвечает. Современные фреймворки прячут логику настолько глубоко, что самостоятельно в ней не разобраться. Нужно читать документацию, тратя драгоценное время.

Конечно, за простоту старого кода приходится платить большим количеством ошибок и не очень приятными исходниками. Но если код работает, приносит прибыль и его легче править, то так ли он плох? Эта мысль долгое время не давала мне покоя. Что лучше - потратить неделю на продумывание идеального кода и не сделать ничего, или за два часа написать черновой рабочий вариант? Для заказчика рабочий код, пусть даже и черновой, гораздо выгодней, чем идеальный. Лучше начать работать сегодня, чем ждать неделю идеального кода и получить ничего. Почему ничего? Потому что нет объективных критериев идеальности. На идеальность можно потратить и день, и неделю, и год, а в итоге понять, что всё тлен.

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

Но самое страшное в идеальном коде (а мне иногда казалось, что именно такой код я написал), это его изменение. Вот написал ты идеальный код, пустил слезу от счастья, что наконец-то есть спасение человечеству. И тут опять эти голоса с небес, которые твердят, что курс изменился на 180 градусов, вертай как было. Что прикажете делать? А ничего. И сидишь ты код удаляешь, а слёзы в два ручья так и льются. И код жалко, и себя жалко, и человечество - ведь, нет ему больше спасения.

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

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

Для себя я давно уже решил, что функции в умеренных количествах, это удобно. Простое ООП, это классно. Фреймворки, как правило, зло. Для простых задач нужны простые решения. Сложные задачи надо разбивать на простые. А идеального кода нет.

Итого

Если вы вдруг дочитали до этого места, то скорее всего вас уже ничего не остановит. Ладно, записывайте суперрецепт суперингредиентного супа:

  1. Увидели говнокод? Прежде чем ругаться, напишите его лучше. Не можете лучше? Делайте умный вид молча.
  2. Написали код лучше? Замените говнокод на новый код. Не можете? Ваше мнение никому не интересно.
  3. Заменили говнокод на новый код? Молодец! Теперь вы суперпрограммист.
  4. Теперь исходите из того, что вы написали говнокод.

Автор: Антон Прибора. 11 декабря 2017 года.

Хорошая статья, мне понравилась. Оставлю отзыв!