7 июня 2022 г.

Как писать неподдерживаемый код?

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Более новая информация по этой теме находится на странице https://learn.javascript.ru/ninja-code.

Познай свой код

Эта статья представляет собой мой вольный перевод How To Write Unmaintainable Code («как писать неподдерживаемый код») с дополнениями, актуальными для JavaScript.

Возможно, в каких-то из этих советов вам даже удастся узнать «этого парня в зеркале».

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

Если вы будете им следовать, то ваш код будет так сложен в поддержке, что у JavaScript’еров, которые придут после вас, даже простейшее изменение займёт годы оплачиваемого труда! А сложные задачи оплачиваются хорошо, так что они, определённо, скажут вам «Спасибо».

Более того, внимательно следуя этим правилам, вы сохраните и своё рабочее место, так как все будут бояться вашего кода и бежать от него…

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

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

Соглашения – по настроению

Рабочий-чистильщик осматривает дом:
«…Вот только жук у вас необычный…
И чтобы с ним справиться, я должен жить как жук, стать жуком, думать как жук.»
(грызёт стол Симпсонов)

Сериал "Симпсоны", серия Helter Shelter

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

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

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

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

Как затруднить задачу? Можно везде нарушать соглашения – это помешает ему, но такое могут заметить, и код будет переписан. Как поступил бы ниндзя на вашем месте?

…Правильно! Следуйте соглашениям «в общем», но иногда – нарушайте их.

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

Пример из jQuery

jQuery / DOM

Этот пример требует знаний jQuery/DOM, если пока их у вас нет – пропустите его, ничего страшного, но обязательно вернитесь к нему позже. Подобное стоит многих часов отладки.

Во фреймворке jQuery есть метод wrap, который обёртывает один элемент вокруг другого:

var img = $('<img/>'); // создали новые элементы (jQuery-синтаксис)
var div = $('<div/>'); // и поместили в переменную

img.wrap(div); // обернуть img в div
div.append('<span/>');

Результат кода после операции wrap – два элемента, один вложен в другой:

<div>
  <img/>
</div>

А что же после append?

Можно предположить, что <span/> добавится в конец div, сразу после img… Но ничего подобного!

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

Как правило, методы jQuery работают с теми элементами, которые им переданы. Но не здесь!

Внутри вызова img.wrap(div) происходит клонирование div и вокруг img оборачивается не сам div, а его клон. При этом исходная переменная div не меняется, в ней как был пустой div, так и остался.

В итоге, после вызова получается два независимых div'а: первый содержит img (этот неявный клон никуда не присвоен), а второй – наш span.

Объяснения не очень понятны? Написано что-то странное? Это просто разум, привыкший, что соглашения уважаются, не допускает мысли, что вызов wrap – неявно клонирует элемент. Ведь другие jQuery-методы, кроме clone этого не делают.

Как говорил Учитель: «В древности люди учились для того, чтобы совершенствовать себя. Нынче учатся для того, чтобы удивить других».

Краткость – сестра таланта!

Пишите «как короче», а не как понятнее. «Меньше букв» – уважительная причина для нарушения любых соглашений.

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

Обратите внимание на оператор вопросительный знак '?', например:

// код из jQuery
i = i ? i < 0 ? Math.max(0, len + i) : i : 0;

Разработчик, встретивший эту строку и попытавшийся понять, чему же всё-таки равно i, скорее всего придёт к вам за разъяснениями. Смело скажите ему, что короче – это всегда лучше. Посвятите и его в пути ниндзя. Не забудьте вручить Дао дэ цзин.

Именование

Существенную часть науки о создании неподдерживаемого кода занимает искусство выбора имён.

Однобуквенные переменные

Называйте переменные коротко: a, b или c.

В этом случае никто не сможет найти её, используя фунцию «Поиск» текстового редактора.

Более того, даже найдя – никто не сможет «расшифровать» её и догадаться, что она означает.

Не используйте i для цикла

В тех местах, где однобуквенные переменные общеприняты, например, в счётчике цикла – ни в коем случае не используйте стандартные названия i, j, k. Где угодно, только не здесь!

Остановите свой взыскательный взгляд на чём-нибудь более экзотическом. Например, x или y.

Эффективность этого подхода особенно заметна, если тело цикла занимает одну-две страницы (чем длиннее – тем лучше).

В этом случае заметить, что переменная – счётчик цикла, без пролистывания вверх, невозможно.

Русские слова и сокращения

Если вам приходится использовать длинные, понятные имена переменных – что поделать… Но и здесь есть простор для творчества!

Назовите переменные «калькой» с русского языка или как-то «улучшите» английское слово.

В одном месте напишите var ssilka, в другом var ssylka, в третьем var link, в четвёртом – var lnk… Это действительно великолепно работает и очень креативно!

Количество ошибок при поддержке такого кода увеличивается во много раз.

Будьте абстрактны при выборе имени

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

Лао-цзы

При выборе имени старайтесь применить максимально абстрактное слово, например obj, data, value, item, elem и т.п.

  • Идеальное имя для переменной: data. Используйте это имя везде, где можно. В конце концов, каждая переменная содержит данные, не правда ли?

    Но что делать, если имя data уже занято? Попробуйте value, оно не менее универсально. Ведь каждая переменная содержит значение.

    Занято и это? Есть и другой вариант.

  • Называйте переменную по типу данных, которые она хранит: obj, num, arr

    Насколько это усложнит разработку? Как ни странно, намного!

    Казалось бы, название переменной содержит информацию, говорит о том, что в переменной – число, объект или массив… С другой стороны, когда непосвящённый будет разбирать этот код – он с удивлением обнаружит, что информации нет!

    Ведь как раз тип легко понять, запустив отладчик и посмотрев, что внутри. Но в чём смысл этой переменной? Что за массив/объект/число в ней хранится? Без долгой медитации над кодом тут не обойтись!

  • Что делать, если и эти имена кончились? Просто добавьте цифру: item1, item2, elem5, data1

Похожие имена

Только истинно внимательный программист достоин понять ваш код. Но как проверить, достоин ли читающий?

Один из способов – использовать похожие имена переменных, например data и date. Бегло прочитать такой код почти невозможно. А уж заметить опечатку и поправить её… Ммммм… Мы здесь надолго, время попить чайку.

А.К.Р.О.Н.И.М

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

Например ie (Inner Element), mc (Money Counter) и другие. Если вы обнаружите, что путаетесь в них сами – героически страдайте, но не переписывайте код. Вы знали, на что шли.

Хитрые синонимы

Очень трудно найти чёрную кошку в тёмной комнате, особенно когда её там нет.

Конфуций

Чтобы было не скучно – используйте похожие названия для обозначения одинаковых действий.

Например, если метод показывает что-то на экране – начните его название с display.. (скажем, displayElement), а в другом месте объявите аналогичный метод как show.. (showFrame).

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

По возможности, договоритесь с членами своей команды. Если Вася в своих классах использует display.., то Валера – обязательно render.., а Петя – paint...

…И напротив, если есть две функции с важными отличиями – используйте одно и то же слово для их описания! Например, с print... можно начать метод печати на принтере printPage, а также – метод добавления текста на страницу printText.

А теперь, пусть читающий код думает: «Куда же выводит сообщение printMessage?». Особый шик – добавить элемент неожиданности. Пусть printMessage выводит не туда, куда все, а в новое окно!

Словарь терминов – это еда!

Ни в коем случае не поддавайтесь требованиям написать словарь терминов для проекта. Если же он уже есть – не следуйте ему, а лучше проглотите и скажите, что так и былО!

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

Для обозначения посетителя в одном месте используйте user, а в другом visitor, в третьем – просто u. Выбирайте одно имя или другое, в зависимости от функции и настроения.

Это воплотит сразу два ключевых принципа ниндзя-дизайна – сокрытие информации и подмена понятий!

Повторно используйте имена

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

Добавляйте новое имя только если это абсолютно необходимо.

В функции старайтесь обойтись только теми переменными, которые были переданы как параметры.

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

Цель – максимально усложнить отладку и заставить читающего код программиста построчно анализировать код и конспектировать изменения переменных для каждой ветки исполнения.

Продвинутый вариант этого подхода – незаметно (!) подменить переменную на нечто похожее, например:

function ninjaFunction(elem) {
  // 20 строк кода, работающего с elem

  elem = elem.cloneNode(true);

  // ещё 20 строк кода, работающего с elem
}

Программист, пожелавший добавить действия с elem во вторую часть функции, будет удивлён. Лишь во время отладки, посмотрев весь код, он с удивлением обнаружит, что оказывается имел дело с клоном!

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

Добавляйте подчёркивания

Добавляйте подчёркивания _ и __ к именам переменных. Желательно, чтобы их смысл был известен только вам, а лучше – вообще без явной причины.

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

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

Покажите вашу любовь к разработке

Пусть все видят, какими замечательными сущностями вы оперируете! Имена superElement, megaFrame и niceItem при благоприятном положении звёзд могут привести к просветлению читающего.

Действительно, с одной стороны, кое-что написано: super.., mega.., nice.. С другой – это не несёт никакой конкретики. Читающий может решить поискать в этом глубинный смысл и замедитировать на часок-другой оплаченного рабочего времени.

Перекрывайте внешние переменные

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

Гуань Инь-цзы

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

var user = authenticateUser();

function render() {
  var user = anotherValue();
  ...
  ...многобукв...
  ...
  ... // <-- программист захочет внести исправления сюда, и..
  ...
}

Зашедший в середину метода render программист, скорее всего, не заметит, что переменная user локально перекрыта и попытается работать с ней, полагая, что это результат authenticateUser()… Ловушка захлопнулась! Здравствуй, отладчик.

Мощные функции!

Не ограничивайте действия функции тем, что написано в её названии. Будьте шире.

Например, функция validateEmail(email) может, кроме проверки e-mail на правильность, выводить сообщение об ошибке и просить заново ввести e-mail.

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

Главное – они должны быть неочевидны из названия функции. Истинный ниндзя-девелопер сделает так, что они будут неочевидны и из кода тоже.

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

Представьте, что другому разработчику нужно только проверить адрес, а сообщение – не выводить. Ваша функция validateEmail(email), которая делает и то и другое, ему не подойдёт. Работодатель будет вынужден оплатить создание новой.

Внимание… Сюр-при-из!

Есть функции, название которых говорит о том, что они ничего не меняют. Например, isReady, checkPermission, findTags… Предполагается, что при вызове они произведут некие вычисления, или найдут и возвратят полезные данные, но при этом их не изменят. В трактатах это называется «отсутствие сторонних эффектов».

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

Удивление и ошеломление, которое возникнет у вашего коллеги, когда он увидит, что функция с названием на is.., check.. или find... что-то меняет – несомненно, расширит его границы разумного!

Ещё одна вариация такого подхода – возвращать нестандартное значение.

Ведь общеизвестно, что is.. и check.. обычно возвращают true/false. Продемонстрируйте оригинальное мышление. Пусть вызов checkPermission возвращает не результат true/false, а объект с результатами проверки! А чего, полезно.

Те же разработчики, кто попытается написать проверку if (checkPermission(..)), будут весьма удивлены результатом. Ответьте им: «надо читать документацию!». И перешлите эту статью.

Заключение

Все советы выше пришли из реального кода… И в том числе от разработчиков с большим опытом.

Возможно, даже больше вашего, так что не судите опрометчиво ;)

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

Комментарии

перед тем как писать…
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.