“Так, что здесь, черт побери, происходит?!?”

Сейчас 1:30 ночи, и я смотрю на фрагмент кода, который написал около месяца назад. В то время он казался мне произведением искусства. Все здесь имело смысл. Он был элегантен, прост и замечателен. Но больше нет. У меня завтра дедлайн, а я обнаружил баг всего несколько часов назад. То, что казалось простым и логичным в то время, сейчас просто не поддается моему пониманию. Конечно, если я написал этот код, мне ведь должно хватить мозгов, чтобы понять его?

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

Проблема №1 – Слишком сложные ментальные модели

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

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

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

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

Проблема №2 – Плохой перевод семантических моделей в код

Как только вы сформировали наилучшую семантическую модель, пришло время превратить ее в код. Давайте назовем это синтаксической моделью. На этом этапе вы пытаетесь перевести суть вашей семантической модели в синтаксис понятный компьютеру.

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

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

Итак, как вы можете это сделать?

Именование и структура классов

Если вы используете объектно-ориентированный язык, постарайтесь, чтобы структура и имена классов были как можно ближе к вашей семантической модели. Предметно-ориентированное проектирование (DDD) — это подход, который уделяет этой практике особое внимание. Даже если вы не согласны с подходом объектно-ориентированного проектирования во всей его полноте, вам все-равно следует очень тщательно продумывать структуру и имена классов. Каждый класс — это подсказка, которую вы оставляете как всем остальным, так и самому себе, и которая поможет вам восстановить свою ментальную модель, когда вы вернетесь позже.

Именование переменных, параметров и методов

Старайтесь избегать общих имен для переменных и методов. Не называйте метод “Process”, если больше смысла будет иметь “PaySalesCommision”. Не называйте переменную “x”, когда она может быть названа “currentContract”. Лучше не стоит называть параметр “input”, когда ему больше подходит “outstandingInvoices”.

Принцип единственной ответственности

Принцип единственной ответственности (SRP) является одним из основных принципов объектно-ориентированного проектирования (OOD) и связан с надлежащим именованием классов и переменных. Он гласит, что любой класс или метод должен служить одной и только одной задаче. Если вы хотите дать классам и методам осмысленные имена, они должны иметь одну четко определенную цель. Если один класс считывает и вносит записи в вашу базу данных, рассчитывает налог с продаж, уведомляет клиентов о продаже и формирует счет, вам вряд ли повезет в поиске хорошего имени для него. Я часто прибегаю к рефакторингу класса, которому не могу дать достаточно короткое имя, описывающее все, что он делает. Более подробное обсуждение принципа единственной ответственности и других принципов объектно-ориентированного программирования вы можете найти в моем посте про объектно-ориентированное проектирование.

Адекватные комментарии

Если вам нужно сделать что-то по причине, которая не ясно прослеживается в вашем коде, пожалейте себя в будущем и оставьте заметку, описывающую, почему вы должны были сделать именно так. Комментарии, как правило, быстро устаревают, поэтому я предпочитаю, чтобы код был как можно более самоописательным, а комментарии лишь объясняли, почему вам нужно было что-то сделать, а не то, как это было сделано.

Проблема № 3 – Недостаток группирования

Группирование (chunking) в психологии представляет ментальный мнемонический процесс, который включает разбивку массива на известные и неизвестные для человека фрагменты, и последующее объединение элементов каждого неизвестного фрагмента в единый комплекс, который для памяти становится одним целостным объектом. Итак, как это применимо к программированию? По мере того, как вы приобретаете опыт разработки, вы начинаете замечать повторяющиеся паттерны, которые снова и снова появляются в ваших решениях. Очень авторитетная книга “Паттерны объектно-ориентированного проектирования” (Design Patterns: Elements of Reusable Object-Oriented Software) стала первой книгой, в которой перечислены и объяснены некоторые из этих паттернов. Однако группирование применимо не только к паттернам проектирования и объектно-ориентированному программированию. В функциональном программировании (FP) есть ряд хорошо известных стандартных функций, которые служат той же цели. Алгоритмы — это еще одна форма группирования (подробнее об этом позже).

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

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

Проблема №4 – Не вполне ясное использование

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

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

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

Проблема №5 – Путь от модели к модели не очень хорошо прослеживается

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

Проблема №6 – Изобретение велосипедов

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

Заключение

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

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

Особая благодарность Нику Янгу и Ульви Гулиеву за их вклад в эту статью. Первоначально опубликовано на syoung.org 3 ноября 2014 г.


В завершение хочу пригласить вас на бесплатный урок, в рамках которого мы поговорим о JHipster, а точнее о том, почему это стало так "модно и молодёжно", затронем Rapid Application Development и рассмотрим некоторые примеры использования.

Комментарии (7)


  1. Travisw
    00.00.0000 00:00

    Наработанная кодовая база давит на не желание что-то с ней делать, что в этом случае делать?


    1. KongEnGe
      00.00.0000 00:00
      +4

      Рефакторить до умопомрачения :)


      1. Travisw
        00.00.0000 00:00
        +1

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


  1. fransua
    00.00.0000 00:00
    +3

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


    1. 0x131315
      00.00.0000 00:00

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


  1. zaiats_2k
    00.00.0000 00:00
    +1

    Вспомнилась классика:

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

    Не коммитим сразу, перечитываем наутро!


  1. Gena856
    00.00.0000 00:00

    Эта статья маст хэв для новичков.

    Я разделяю проект на модули когда кода или сущностей становится много. Новую задачу начинаю с комментария с текстом задачи и шагами решения. Дальше под каждый шаг пишу метод. Если метод парсит или строит какой-то текст то в комментарии пишу пример. Сам метод начинаю с написания todo-шек. Имена методов и переменных всегда говорят о содержании.

    Это пришло с опытом. Будучи студентом страдал отсутствием комментов и всё пихал в один файл :)