Писать код для компьютера и так достаточно сложно: вы берёте что-то большое и неопределённое, какую-нибудь крупную расплывчатую цель бизнеса, которой нужно достичь. Затем рекурсивно разбиваете её на части и продумываете все сценарии, пока не получите чёткие логические конструкции, которым сможет следовать компьютер. Компьютеры очень хорошо справляются с выполнением логических конструкций.
А теперь немного усложним задачу. Будем писать код для людей!
Объясню, что я имею в виду: я говорю о коде, с которым смогут взаимодействовать другие люди. В частности, я имею в виду искусство создания удобных фреймворков, библиотек, API, SDK, DSL, встроенных DSL или даже языков программирования.
Писать такой код гораздо сложнее, потому что ты не только говоришь компьютеру, что делать, но и вступаешь в борьбу с ментальной моделью твоего кода в глазах другого пользователя. Это в равной степени computer science и психология мышления. Как сделать так, чтобы этот человек понял твой код?
Ричард Фейнман как-то сказал: «Представьте, насколько сложнее была бы физика, если бы электроны имели чувства». Он говорил это в другом контексте, но мне кажется, это частично описывает и программирование для людей. У человека, интерпретирующего твой код, есть чувства!
Давайте поговорим о том, как упростить ему задачу.
▍ Освоение продукта — это и есть продукт
Разумеется, нужно обязательно прислушиваться к своим пользователям и принимать во внимание их отзывы. Как оказалось, основную часть отзывов оставляют опытные пользователи, постоянно работающие с вашим продуктом!
Как это повлияет на распределение получаемых отзывов? Не будет ли оно перекошенным? И как с этим связан рисунок самолёта?
Конечно же, здесь идёт речь об ошибке выжившего. Есть пользователи, которые не применяют ваш инструмент, потому что не смогли освоить его. И их отзывы мы обычно никогда не слышим!
У потребительских продуктов уже давно есть хакеры роста, оптимизирующие каждую часть воронки онбординга. Для инструментов разработчика тоже нужен такой подход. Процесс освоения нельзя придумывать уже после того, как вы создали продукт. Освоение — это и есть продукт!
Вплоть до того, что я рекомендую реструктурировать продукт целиком, чтобы обеспечить быстрый онбординг. Избавьтесь от обязательной конфигурации. Сделайте так, чтобы настройка токенов API была до абсурда простой. Устраните все препятствия. Обеспечьте пользователям возможность работать с продуктом на ноутбуке спустя максимум пару минут.
Вы можете заявить что-то типа: «Да кого волнуют ленивые пользователи». Отвечу вам так: сегодня уже есть примерно семь миллиардов инструментов разработчика. У пользователей нет ни лишней энергии, ни терпения, чтобы разбираться, чем ваш пакет NPM кэша LRU лучше остальных. Так что простите!
▍ Люди учатся на примерах, а не на «базовых концепциях»
Люди замечательно справляются с сопоставлением паттернов, в отличие от компьютеров, следующих булевой логике и строгим командам. Часто документация инструментов разработчика структурирована как компьютерная программа. Она начинается с базовой модели данных, взаимосвязей и атомов, с «базовых концепций», с объяснения того, как конфигурировать и выполнять операции.
А люди учатся иначе.
Сразу после того, как я написал предыдущий параграф, в Twitter (X) обнаружился пост, сформулировавший как раз то, что мне хотелось сказать:
Слишком многие книги и туториалы по программированию устроены так: «давайте построим дом с нуля, кирпич за кирпичом». А мне нужно что-то такое: «вот вам готовый дом, давайте изучим его, поменяв что-нибудь и отметив, что поменялось» — Крис Элбон (@chrisalbon)
Вместо того, чтобы писать хронику «базовых концепций» на пять тысяч слов, я советую придумать с десяток примеров. У такого подхода есть несколько преимуществ:
- Люди будут видеть примеры и изучать по ним, как работает ваш инструмент. Именно так и учатся люди!
- Человек, у которого возникла проблема, будет смотреть на начальный пример, достаточно близкий к ней. Чем больше потенциальных начальных примеров, тем выше вероятность, что в них найдётся что-то близкое к нужному ему.
▍ Ловушка успеха
Печальная часть программирования заключается в том, что по умолчанию приходится исправлять те или иные ошибки. То есть пользователи будут больше всего времени тратить на то, чтобы понять, что не работает в вашем инструменте. Именно поэтому помощь им в достижении успеха — неотъемлемая часть продукта.
Вот краткий список:
- Разработчики, быстрее добивающиеся успеха — это довольные разработчики. Им понравится ваш инструмент.
- Разработчики, бьющиеся головами об ошибки — несчастные разработчики. Они будут винить ваш инструмент.
Воспринимайте каждую ошибку как возможность направить пользователя по правильному пути. Помещайте фрагменты кода в исключения. Генерируйте полезные уведомления, когда пользователи делают что-то странное. Делайте всё для того, чтобы пользователь достиг успеха.
▍ Избегайте перегрузки концепциями
Каждый новый концептуальный элемент, который нужно понять перед использованием инструмента — это преграда. Если таких элементов два-три, то это нормально. Но никто не захочет заморачиваться изучением восьми новых концепций.
Это даже не самый вопиющий пример (Kubernetes). С ним можно начать работать, изучив только часть. Существуют примеры и похуже.
Вероятно, к работе можно приступить, не осваивая подавляющее большинство этого. Но когда мне нужно изучать новые концепции, у меня начинает болеть голова. Слишком много концепций!
Есть что-то очень изящное во фреймворке из всего трёх-пяти концепций, которому при этом удаётся быть невероятно мощным. Я помню то чувство, когда впервые попробовал React и спустя один-два часа разобрался со всеми концептуальными преградами. Всего несколько относительно простых кирпичиков, позволяющих построить целый собор. Настоящая магия.
Уточню, что сложность не в том, чтобы снизить количество концепций. Сложность в том, чтобы при этом сохранить количество всего того, что можно создать при помощи продукта. Или, по крайней мере, снизить количество первых сильнее, чем количество вторых. Я говорю об этом, потому что могу представить «спираль смерти тупого упрощения инструментов разработчика»:
- У фреймворка слишком много концепций.
- Добавим слой абстракции, позволяющий реализовать самые популярные возможности, используя меньшее количество концепций.
- Есть много всего, чего нельзя делать в слое абстракции.
- Добавим новые концепции, чтобы это можно было делать.
- Возвращаемся к пункту 1.
Не знаю, происходит ли такое в действительности, но считаю, что существует уровень «плохих» упрощений. В конечном итоге вам нужно изменить баланс между «сложностью» (тем, что нужно знать) и «возможностями» (тем, что можно создать). Самые потрясающие инструменты способны снизить сложность на 90%, сохранив тот же уровень возможностей. Но я бы согласился и на инструмент, снижающий сложность на 90% с уменьшением возможностей на 10%. Вполне нормальный компромисс!
▍ Принцип концептуальной утки
Этот пункт связан с предыдущим. Допустим, вы добавляете в свой фреймворк элемент, получающий какие-то значения и вычисляющий на их основе новые значения. Как его назвать? Вычислительным узлом? Блоком вычислений? Думателем?
Нет! Нужно назвать его функцией!
Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка.
Возможно, у этого элемента есть тонкости, немного отличающие его от функции. Например, значения кэшируются. Но он достаточно похож на функцию!
Если назвать его функцией, то можно воспользоваться уже имеющейся у пользователя ментальной моделью того, что умеет функция. А это сэкономит вам 90% времени на объяснение возможностей элемента.
▍ Программируемость
Люди будут делать с вашей кодовой базой безумные вещи. Они будут брать написанное вами и засовывать в цикл for внутри функции, которая находится внутри чего-то ещё. Люди изобретательны!
Поэтому почти всё в вашем фреймворке должно быть «программируемым».
Это целый класс проблем, связанных друг с другом и решаемых схожими способами.
Позвольте вызывать всё напрямую в коде, а не через CLI. Избегайте конфигураций и превратите их в SDK или API. Упростите параметризацию, чтобы можно было создать n вещей, а не только одну.
У такого подхода есть неожиданное преимущество — иногда пользователи самостоятельно могут обнаруживать новые сценарии использования. Воспользуйтесь стремлением людей к «хакингу» поверх вашего фреймворка. Эти пользователи принесут определённое количество проблем, но не мешайте им! Возможно, они на пороге открытия чего-то неожиданного.
▍ Тщательно продумывайте магию, значения по умолчанию и синтаксический сахар
Допустим, вы создаёте инструмент, исполняющий Jupyter notebook в облаке, и у вас есть функция
run_notebook
, получающая список ячеек (с компьютерным кодом) или что-то подобное.Как пользователь может указать, какой образ контейнера следует использовать? У нас есть несколько вариантов:
- Аргумент
image=...
, который нужно указывать всегда. - Аргумент
image=...
, который по умолчанию имеет какое-то базовое значение, используемое большинством библиотек data science; пользователь может его переопределять. - Вы исследуете код в ячейках и выбираете образ «магическим» образом на основании необходимых зависимостей.
- То же, что и предыдущий способ, но опционально пользователь может указывать конкретный образ.
Какой вариант использовать? Если вы хотите минимизировать объём усилий пользователей, сохранив при этом поддержку самого широкого множества сценариев использования, то я бы выбрал последний. Но у этих вариантов, за исключением первого, есть некоторые проблемы:
- Будем реалистичны — в каком-то проценте ситуаций магия ломается.
- Пользователи, читающие код, который использует значения по умолчанию, не осознают, что их можно настраивать.
Если стандартные значения не подходят в 97%+ случаев, а магия — в 99% случаев, то будьте аккуратны с их использованием. Разумеется, это не строгие значения, но смысл в том, что надо всё продумывать.
Можно подумать, что цель разработчика инструмента — минимизация количества кода, который придётся писать пользователю. Но кодинг — это не гольф!
Я провожу здесь аналогию с разницей между Perl и Python. Разработчики Perl упорно стремились оптимизировать код, максимально сокращая его, из-за чего каждая программа выглядела, как строка из одних специальных символов. Потом появился Python, и его код был на 50% длиннее. Он никогда не пытался стать самым коротким! Но оказалось, что код на Python очень удобен для чтения, а потому гораздо понятнее. А люди читают в десять раз больше кода, чем пишут.
То же самое относится и к синтаксическому сахару. Есть искушение добавить специальный синтаксис для наиболее распространённых сценариев использования инструмента. Но это часто мешает целостности и снижает очевидность способов кастомизации кода. Поэтому если синтаксический сахар не покрывает 99%+ случаев, то, вероятно, добавлять его не стоит.
▍ Писать код для людей сложно
Мы добрались до конца статьи, но мне бы хотелось продолжать обсуждать множество других тем:
- Большинство элементов (если не все) должны обладать иммутабельностью.
- Избегайте добавления scaffolding (генерации кода).
- Делайте циклы обратной связи невероятно быстрыми.
- Депрекация должна быть максимально простой для пользователей.
- Используйте автоматизированное тестирование блоков кода в документации и примерах.
И, наверно, многое другое. Возможно, я напишу об этом ещё один пост!
В том числе и о том, что я считаю самым удивительным: почему крупные компании обычно неспособны выпускать высококачественные инструменты разработчика.
Я иногда думаю, что сложность проектирования инструмента для никогда не работавшего с ним пользователя похожа на сложность написания популярной песни. Продюсер прослушает песню тысячу раз. Но даже слушая её в 999-й раз, он должен представлять, как она будет звучать для человека, слышащего её впервые, что, как мне кажется, крайне сложно.
Скорее всего, именно поэтому я пишу инструменты разработчика, а не сочиняю попсу.
Telegram-канал со скидками, розыгрышами призов и новостями IT 💻