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

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

Тем не менее, не смотря на эти сложности и противоречия, как правило, вещи бывают хорошие и плохие. Можно даже попытаться как-то формально определить, чем они отличаются – вроде того, что “Хорошие вещи максимизируют эффект/пользу важных для определенной категории пользователей факторов так, что противоречащие им факторы при этом не делают вещь бесполезной.” Но, вообще, формальные определения таких общих понятий редко бывают полезными. Известный мыслитель и прагматик современности, Линус Торвальдс, говорит на этот счёт, что у человека просто должен быть “вкус”, чтобы отделять хорошее от плохого. Понятно, что это определение менее информативно, но, наверное, одинаково бесполезно с практической точки зрения.

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

В своё время я написал довольно простую утилитку для генерации содержания для текстовых файлов, написанных с использованием разметки Org – toc-org. Неожиданно для меня самого, она оказалась довольно популярной и в момент написания статьи насчитывает уже более двухсот тысяч скачиваний (попадая при этом в 3% самых популярных пакетов на melpa.org). В наибольшей степени этот пакет, наверное, обязан успехом сборке spacemacs. После медленного вскарабкивания до (примерно) 30-50 тысяч скачиваний, количество стремительно удвоилось именно после того, как toc-org был включён в spacemacs. И с тех же пор оно продолжает уверенными темпами расти.

Конечно, вклад spacemacs’а переоценить нельзя, но я льщу себе и тем, что пакет-таки обладает некоторыми хорошими качествами, которые позволили ему заработать народную любовь. Для большей наглядности, я хочу сравнить свой toc-org и пакет org-make-toc с точки зрения подхода к разработке. org-make-toc написал нормальный увлечённый паренёк, который какое-то время присылал мне предложения по toc-org и с которым мы много на эту тему спорили в комментариях. Спустя какое-то время, как я понимаю, ему надоело получать отказы на большинство своих предложений (которые я считал неправильными) и он решил написать свой пакет – так, как кажется правильным ему. Своим взглядом на разницу в наших подходах я и хотел бы поделиться.

Минимальная интеллектуальная нагрузка

В современном мире человек перегружен потоком информации. Где-то я видел цифру, что через современного обладателя смартфона за день проходит столько же информации, сколько через крестьянина/пролетария начала 20го века проходило то ли за месяц, то ли и того больше.

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

Первое, где нас встречают различия в пакетах – документация по установке. В toc-org первое же, что встречает пользователя после короткого описания – три строчки, которые нужно просто скопировать в конфигурационный файл, для того, чтобы “всё заработало”. Кроме того, это практически единственные строчки на elisp’e во всём файле – заблудиться просто негде. В org-make-toc сначала идёт секция про установку, содержащая бесполезную информацию для тех, кто и так знает, как устанавливать пакеты, и, в то же время, недостаточную для тех, кто не знает. Т.е. информационный шум.

После этого там идёт словесное описание того, как сгенерировать содержание один раз. Тут у меня два вопроса - почему описание словесное? А, во-вторых, и это самое главное, зачем нужно описывать, как сгенерировать содержание один раз? Ведь практически всегда пользователи захотят поддерживать его в актуальном состоянии. И описывать в первую очередь нужно именно этот случай. Он тоже, конечно, описан, но существенно ниже, после описания неких “продвинутых” возможностей. Причём просто скопировать код оттуда недостаточно – его непременно надо приклеить к коду из предыдущей секции по установке.

Второе, где мы видим различия – это необходимая разметка в файле для того, чтобы авто-генерация заработала. В случае с toc-org, пользователю нужно просто добавить тэг :TOC: к заголовку (5 символов на той же строчке, что и заголовок, стандартная разметка для формата org). В случае c org-make-toc, казалось бы, нужно просто добавить свойство (а не тэг). Но “просто добавить свойство” – это уже 3 специальные строчки, одну из которых нельзя будет скрыть:

  :PROPERTIES:
  :TOC:      this
  :END:

Свойства в формате org – более гибкий способ добавлять мета-информацию к заголовкам. Но если вам достаточно иметь переменные, которые принимают всего два значения (true или false) – тогда вполне можно обойтись тэгами (занимающими минимум места). Если же вы хотите приклеивать числа или строки, тогда org предлагает использовать свойства (существенно более громоздкая конструкция).

Самая главная мета-информация в нашем случае – это какую секцию считать содержанием. Оправдано ли здесь применений свойств (читай, добавление трёх строчек)? Никак нет.

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

Можно резонно предположить, что таким образом я, кроме прочего, сужаю себе манёвр для последующего расширения функциональности. Но дело в том, что пакет в его текущей (и практически, изначальной) форме решает именно ту задачу, которую должен решать в полном объёме. Принципиально новая функциональность уже будет уделом другого пакета, с другими целевыми группами пользователей. К слову, org-make-toc может стать таким пакетом – нацеленным на хранение сложных архивов каких-либо документов, в которых нужно хитрым образом настраивать навигацию. Очевидно, пользователей у такого подхода будет на несколько порядков меньше, чем “рядовых” пользователей toc-org (на текущий момент, на 3 порядка).

Подводя некоторым образом итог, org-make-toc, на мой взгляд, безответственно распоряжается вниманием пользователя – для настройки ему нужно до некоторой степени разобраться в устройстве пакета, понять, что большинство функций на витрине ему практически никогда не понадобятся, плюс, в каждом файле ему нужно занять непропорционально много места для разметки мета-информации. Например, readme пакета содержит 24 “вспомогательные” строчки разметки из 135-ти, т.е. почти пятая (!) часть всего файла не несёт содержательной информации.

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

Максимальная полезность и стабильность

Насколько я понимаю, последней каплей, послужившей поводом для создания org-make-toc был мой отказ включать довольно странную функциональность, предусматривающую большее количество способов контролировать включаемые заголовки. toc-org позволяет игнорировать отдельные заголовки и группы подзаголовков (оставляя “родительский” заголовок) в довольно лаконичной форме.

Вот что предлагает toc-org:

  • если заголовок надо исключить, к нему нужно добавить тэг :noexport:,
  • если заголовок надо оставить, но исключить его подзаголовки, нужно добавить тэг :noexport_1:, :noexport_2:, :noexport_3: и т.д. в зависимости от желаемой глубины исключаемых подзаголовков

Очевидно, что при этом нельзя, например, исключить подзаголовки первого уровня, оставив подзаголовки второго уровня. Вот какой дизайн для этого предлагает автор org-make-toc:


  A document may contain multiple tables of contents. Tables of contents
  can be customized by setting the TOC property of headings to these values:

  - all: Include all headings in the file, except ignored headings.
  - children: Include only child headings of this ToC.
  - siblings: Include only sibling headings of this ToC.
  - ignore: Omit a heading from the TOC.
  - ignore-children or 0: Omit a heading’s child headings from the TOC.
  - a number N: Include child headings up to N levels deep in the TOC.

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

Так или иначе, вот (насколько мне известно) полный список достоинств, которыми обладает org-make-toc и которых нет у toc-org:

  1. Уже озвученные возможности создавать несколько (?) содержаний и более детально контролировать, что должно туда попасть, а что нет.
  2. Правильное отображение ссылок, содержащих внутреннюю разметку (например, “жирные” или курсивные слова)
  3. “Качественный” код, использующий современные emacs’овские библиотеки

Первый пункт мне нечем крыть, кроме как тем, что я ещё не встречал ни одного человека, кроме автора org-make-toc, кому эта функциональность была бы интересна. А по двум остальным мне есть, что сказать.

Когда я начинал писать toc-org, я не очень хорошо был знаком с emacs-lisp и уж тем более, разными модными библиотеками (я и по-прежнему не очень-то хорошо с ними знаком). Понятное дело, можно ожидать от пакета org, что там есть какая-то функциональность, позволяющая получить список заголовков. Но нет ничего проще, чем в текстовом файле удалить всё, кроме строчек, начинающихся со звёздочки. Зачем для этого нужен org, славящийся своей страстью к частой смене API? В общем-то, ни за чем. А значит, основную часть пакета можно написать просто используя поиск по паре регулярных выражений. С одной стороны – это изобретение велосипеда, но с другой – это сокращение зависимостей.

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

Более того – то, что я мог контролировать весь процесс, играло мне на руку в том разрезе, что библиотека org-ruby, которая используется GitHub'ом для отрисовки разметки org, тоже является самописной. И я мог подстраивать формирование содержания согласно тому, как это делает org-ruby, который, очевидно, реализует не все возможности org (используй я org, мне бы пришлось это как-то обходить).

Где я не мог обойтись без вызовов org, так это при добавлении возможности навигации по построенному содержанию. Если пользователь открывает файл в Emacs, он может нажать C-c C-o, стоя на имени заголовка в содержании, и курсор прыгнет к самому заголовку. Я потратил порядком времени, чтобы разобраться во внутренностях org’a, но всё-таки добавил этот важнейший функционал. К слову, для этого пришлось добавлять код, который работает по-разному для разных версий org (из-за упомянутой любви этого пакета к изменениям).

Что касается качества кода – вряд ли кто-то будет спорить, что оператор --> из библиотеки dash – это более “чистая” альтернатива последовательным вызовам. Для сравнения:

  (target (--> title
               (downcase it)
               (replace-regexp-in-string " " "-" it)
               (replace-regexp-in-string "[^[:alnum:]_-]" "" it)))
  (let* ((spc-fix (replace-regexp-in-string " " "-" str))
         (upcase-fix (downcase spc-fix))
         (special-chars-fix (replace-regexp-in-string toc-org-special-chars-regexp "" upcase-fix t))))

Но НАСТОЛЬКО ли разница сильна? Чтобы уйти от субъективных споров, приведу одну объективную метрику.

На момент написания заметки, org-make-toc насчитывает 407 строк кода, а toc-org – 431. Казалось бы, чуть в пользу org-make-toc. Однако, что пользователь получает за “лишние” 20 строк кода в toc-org?

  • Возможность навигации по ссылкам со всеми актуальными версиями Org (killer feature!)
  • Отсутствие баги, когда при закрытом содержании его текст всё равно появляется при сохранении (очень назойливое поведение, которое нужно специально лечить из-за того, как устроен org)
  • Правильная генерация ссылок для “дублирующихся” заголовков
  • Обработка пользовательских статусов (отличных от TODO и DONE)
  • Обработка глобальных опций разметки – например, сохранять ли статусы заголовков (#+OPTIONS: todo:t)
  • Примитивное облагораживание внешнего вида содержания (блоки QUOTE)

На текущий момент org-make-toc лишён этих важнейших фич и, честно говоря, сомневаюсь, что их удастся добавить, уложившись в 20 строк. Т.е. для того, чтобы повысить “качество” кода, org-make-toc вносит три внешние зависимости, которые, на самом деле, не сокращают, а увеличивают объём исходников. Кроме того, вместо выровненного поведения с Github’ом, код использует отвлечённые функции из org (например, для проверки, каким цветом подсвечено некоторое слово), фактически, используя “невидимую” в тексте файла мета-информацию. Не знаю, конечно, но в моём словаре эти признаки не имеют ничего общего с качеством кода.

Ну и хотелось бы упомянуть среди прочего ещё и стабильность пакета. toc-org имеет порядка 10-ти явных тестов на каждую поддерживаемую фичу, эти тесты крутятся на Travis’e и отрабатывают после каждого изменения. Т.е. сломать в нём что-то довольно затруднительно. org-make-toc имеет лишь номинальное количество тестов, которые не проверяются автоматически.

Подводя второй итог, можно заключить, что org-make-toc ставит во главу угла довольно странные ориентиры – использование модных библиотек и языковых конструкций, а также нишевый функционал по контролю за содержанием содержания (ненавязчивый каламбур!), не уделяя при этом должного внимания действительно полезным аспектам. Не смотря на свою простоту и “велосипедообразность”, toc-org, на мой взгляд обладает всеми признаками ладной программы:

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

В конце хотелось бы добавить ложку дёгтя в эту бочку самолюбования. Если посчитать, сколько времени в общей сложности было потрачено на эти 400 строк и посчитать стоимость этого проекта, исходя из рыночных зарплат программиста моей квалификации – думаю, что эти строки окажутся золотыми. Вряд ли, как кодовые базы NASA, где каждая строчка стоит около двух тысяч долларов, но всё же. Т.е. коммерческий успех этого проекта совершенно точно является крахом. С другой стороны, если я помог ста тысячам пользователей сэкономить хотя бы по 10 минут каждому – то это почти 2 года круглосуточной работы. Так что, с точки зрения общего блага – может быть всё не напрасно.