Тег «табуреточка»

Как ускорить код-ревью

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

Что можно с этим сделать? Одним подходом может быть «установить чёткие дедлайны для ревьюеров и бить их палками, если что-то идёт не так». Это не системный подход, поэтому давайте его сразу пропустим и перейдём к более интересному.

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

Что такое код-ревью; какие у него цели?

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

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

Разработчик Олег пушит свои изменения напрямую в мастер, откуда эти изменения выезжают в продакшен. Если Олег сделал всё правильно и ничего не забыл, то нам повезло и мы ускорили пайплайн в этом месте. Но при этом есть разработчица Александра, которой придётся пересматривать свои ещё не смёрженные изменения с учётом только что вмёрженных изменений Олега. Если вам кажется, что это просто — ну, да, запустить rebase origin/master действительно просто, но что если изменения Олега меняют предположения о системе, из которых исходила Александра? В таком случае автоматический ребейз может либо выдать мёржконфликт (и это будет хорошо! Это может заострить внимание на нарушенных предположениях), либо, что страшнее, молча сделать вид, что всё нормально, хотя изменения Олега и Александры работают по отдельности и не работают вместе.

Здесь важно, что Александра и Олег меняют одну и ту же систему — если их изменения затрагивают независимые системы, или же системы, которые как-то зависят друг от друга, но отгорожены публичными интерфейсами, то всё становится сильно проще.

Ключевой момент здесь: изменения меняют предположения о системе; под словом «предположения» я имею в виду изменения, которые обладают потенциалом «каскада», где потребуется менять какую-то ещё часть кода системы. Такие изменения можно грубо охарактеризовать, разбив их на три уровня:

  • уровень интерфейсов между горизонтальными (равноправными) частями системы. Например, «чтобы показать попап, надо нарисовать в своём компоненте <Popup>{popupContent}</Popup>».
  • уровень фич. Например, «мы хотим показывать попап в такой ситуации».
  • уровень платформы. Например, «наше приложение запускается в контейнере с Node.js такой-то версии».

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

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

В этом контексте часто говорят о time to market определенной фичи, но это не очень подходящая метрика: если мы релизим одну фичу в неделю, но зато её провели через конвейер за полдня, то у нас будет отличный ttm (зарелизили за полдня!), но отвратительный throughput (в неделю только одну фичу!).

Код

На самом деле мы хотим уменьшить latency и увеличить throughput одновременно. Это возможно за счёт снижения времени на код-ревью без снижения качества.

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

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

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

В код-ревью в целом можно выделить четыре стадии:

  1. Время от создания пулреквеста до начала ревью
  2. Время ревью, где ревьюер должен осознать суть изменений, на какие предположения опирается пулреквест, не нарушены ли они. (В плохих случаях здесь также идёт анализ кода на соответствие код-стайлу, в большинстве случаев это можно вынести на шаг 1 через автоматизацию)
  3. Время правок, где автор пулреквеста исправляет замечания с шага 2; после чего мы снова переходим на шаг 1, если только на шаге 2 не было никаких замечаний.
  4. Мёрж изменений, тестирование, релиз, потенциально — отлавливание проблем в продакшене.

Даже если мы не в состоянии изменить структуру, мы можем повлиять на количество итераций в цикле 1-3. Один из главных рисков — во время цикла система изменяется таким образом, что придётся ещё раз вносить изменения относительно предположений, или же отлавливать баги после; и то, и другое — переработка, необходимость которой возникла из-за долгих процессов ревью.

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

Другой способ снизить количество изменчивых предположений — сделать куски системы более независимыми, ограничить их взаимодействие более стабильными интерфейсами и протоколами; тогда нам может помешать только два момента: те изменения, которые ломают внешний API, и те ситуации, где мы зачем-то зависим от видимых проявлений, не являющихся частью формального внешнего API (закон Хайрума гласит, что, при достаточно большом количестве потребителей, на каждое наблюдаемое поведение системы найдётся потребитель, который зависит от этого поведения).

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

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

Но мы же не можем релизить половину фичи! — скажете вы, и будете отчасти правы, мы не хотим, чтобы пользователь увидел наполовину готовое. Для этого можно использовать фича-флаги, эксплицируя время разработки внутри кода. Можно автоматизировать удаление ненужного кода через несколько дней после финального пулреквеста.

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

Люди

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

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

Часто говорят о роли код-ревью в ещё трёх процессах: снижение code ownership, распространение знаний о коде, обучение best practices и программированию в целом. Это всё полезные цели, но если мы достигли точки, где код-ревью само по себе стало заметной проблемой, то стоит вынести, хотя бы временно, их из критического пути в асинхронные методы коммуникации (например, рассылки и чейнджлоги).

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

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

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

Большая картина

Теперь немного посмотрим на картину в целом: как определить, что процесс код-ревью выстроен хорошо?

  1. Ошибки в продакшене — редкость, и обычно связаны с непредсказуемыми факторами: код-ревью выполняет свою главную роль повышения стабильности.
  2. Код-ревью редко превращается в пинг-понг: ревьюеры правильно и полноценно коммуницируют замечания.
  3. Время прохождения код-ревью обладает небольшой вариативностью: не бывает гигантских или чрезвычайно сложных пулреквестов.
  4. В системе нет мест, которые страшно менять: у разработчиков есть вера в то, что код-ревью поймает возможные ошибки.
  5. Через ревью проходят как мелкие фичи, так и большие инфраструктурные изменения.

Каждый из пунктов при желании можно измерять и отслеживать — но стоит сравнивать проект только с этим же проектом, для каждой системы целевые значения метрик свои: требования к надёжности в NASA и стартапе разные.

Слишком длинно, давай вкратце

  1. Пулреквесты должны быть маленькими: меньше строк кода — меньше предположений — быстрее ревью,
  2. Пулреквесты могут быть на половинку фичи, используйте фича-флаги: ранняя фиксация предположений в мастере снижает вероятность их нарушения,
  3. Чем меньше ревью требует ручного анализа, тем лучше: разделяйте систему на независимые и отграниченные интерфейсами части, чтобы снизить количество необходимых предположений.

Браузер должен отражать ценности пользователя

Каждый браузер посылает заголовок User-Agent, в котором пытается рассказать, что это за браузер (там, конечно, полный кошмар, об этом был перевод на Фронтире). Это интересное словосочетание: агент пользователя. То есть, тот, кто действует от лица и в интересах пользователя.

Но кажется, что где-то веб свернул не туда, и, в результате понятной динамики, сейчас браузер это в первую очередь агент корпораций, которые создают браузеры: Гугл, Эппл, Мозилла, Майкрософт, Яндекс, Самсунг, и так далее.

Кроме корпораций, это ещё и агент больших сайтов, таких как Медиум или Ютуб, которые могут потратить большое количество ресурсов на разработку своей платформы с одной целью — вовлечённость (это страшное слово engagement). Как правило, это вовлечённость затем монетизируется опосредованно, через рекламу.

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

Реклама раздражает многих, но если бы у подавляющего большинства стояли блокировщики рекламы, то сайтам пришлось бы искать другие способы монетизации — не было бы рекламодателей, готовых платить за ничего. Я здесь не говорю, что блокировать рекламу неэтично — я говорю, что вижу расхождение между интересами «обычных» пользователей и тем, как эти интересы представляют браузеры, номинальные агенты пользователя.

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

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

У больших платформ, таких как Ютуб, Твиттер, Медиум есть ещё одно большое преимущество перед независимыми публикациями — у них получается донести контент до пользователя. Так статьи на Медиуме расшариваются чаще, люди чаще переходят по ссылкам на Медиум, да даже в поисковиках статья на Медиуме скорее всего будет выше, из-за оптимизации и большего количества пользователей. А ведь ещё есть внутримедиумная система рекомендаций, благодаря которой статью может прочитать и тот, кто о вас совершенно не слышал.

На фоне этого инди-сайт совершенно теряется, и становится меньше причин поддерживать свой независимый сайт («я получу больше лайков в Твиттере!»). Часть авторов нашла интересный компромисс: они публикуют оригинал у себя, и загружают на Медиум копию вместе с ссылкой на оригинал. Таким образом они получают и распространение Медиума, и сохраняют контроль над текстом.

Что может хотя бы чуть-чуть противостоять таким системным эффектам, как фидбек луп распространения Медиума? Агент пользователя. 11 декабря я написал твит с идеей расширения, которое автомагически переходило бы по ссылке на самостоятельно размещенную версию статьи с Медиума. Это пример того, как браузер вместе с расширениями могут точнее отражать мои намерения, могут помочь проявить мою agency, то есть, способность действовать самостоятельно.

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

Но я сейчас использую Firefox — что тоже кажется важным в свете смерти движка внутри Edge. Потому что разнообразие браузеров — тоже одна из моих ценностей.

Точно таким же агентом пользователя выступает браузер, когда предлагает такие фичи, как «режим чтения», увеличивание текста, игнорирование запрета автозаполнения паролей. Любая возможность, которая даёт пользователю больше контроля над происходящим, неизбежно забирая часть контроля у авторов сайтов, увеличивает agency пользователя — и потому хороша.

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

Сейчас маятник контроля сайт/пользователь сильно на стороне сайта, и сам по себе он не качнётся в обратную сторону. Существуют Greasemonkey и Stylish, расширения, которые позволяют вмешиваться в происходящее на странице, но их аудитория во многом ограничена теми, кто уже владеет JS и CSS; существуют адблокеры разной степени честности. Нужно больше, нужно больше новых идей и расширений, меняющих опыт просмотра сети, и ещё больше расширений, которые изменят опыт авторства в сети. Нужно, чтобы идеи и расширения были настолько хорошими, чтобы у них получилось преодолеть барьер «а что такое расширения» (браузеры могут здесь, несомненно, помочь пиаром — но вряд ли будут это делать).

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

Если у вас есть такие идеи, можете поделиться в Твиттере (я осознаю иронию): @marinintim, или по почте: [email protected].

Пустое пространство в жизни

Скорее всего, вы слышали от дизайнеров о важности пустого пространства. Умелое использование такого пространства концентрирует наше внимание на том, что есть на странице, и позволяет элементам «дышать».

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

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

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

Из этого вырисовывается образ «идеального программиста»: её расписание забито под завязку, и те минуты, что она не тратит на рабочий проект, она изучает что-то, ну, в крайнем случае, пробует новый инструмент.

Информационное давление усугубляет эффект. Стоит открыть твиттер, как сразу сыпется огромное количество новостей, которые следует прочитать и учесть. Рассылки «Лучшие репозитории на Гитхабе за неделю», «Главные статьи по фронтенду за месяц».

Я не говорю, что всё это не нужно или не важно. Я говорю, что важно и другое.

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

Пространство, которое целенаправленно не занято чем-то.

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

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

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

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

Например, что пустое пространство в дизайне и в жизни решают схожие задачи.

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

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

И если подышать этим свежим скучным воздухом, то станет понятно, что большая часть этого «потока» не нужна. Освободите ментальное пространство для скуки, сна, отдыха — и тогда то, что останется, станет более значимым.

1

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

Поэты, генералы и учёные

Trigger warning: стереотипы, diversity, фронтенд.

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

Поэты, генералы и учёные как бы говорят на одном и том же языке, но даже этот язык они используют по-разному. Так и с фронтендом: технологии внутри используются настолько по-разному, что сгребать в одну кучу сравнимо по смыслу с попыткой анализировать в твиттере тексты Молдбага (но мы попробуем).

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

Но такое ощущение, что у всех в голове стереотип парня лет двадцати четырёх, который пилит в стартапе переворачивающее игру прогрессивное веб-приложение, в основном состоящее из сборки. Не помогает этому и то, что в сообществе считается «нормой», и здесь неприятности идут от бесконечного потока нового, что ты должен был узнать ещё вчера до откровенной незрелости (см., ситуацию про видео-туториалы про реакт и реакцию на оклик Макеева; ссылок давать не хочется, но). Технологии нейтральны. В стартап (tm) к нам приходил дядечка за пятьдесят и спокойно пушил код; про то, что есть женщины, даже говорить странно, но — приходится. Ладно, back to the topic.

Есть экспериментаторы. Люди, которые раздвигают границы того, что нам кажется возможным. Они делают демки, выступают с докладами на конференциях, и повторяют, что всем нужно экспериментировать. Эти эксперименты бывают красивыми и иногда становятся полезными: так, например. появились треугольники на CSS, когда использовать картинки было кхм дорого. Но время на эксперименты есть не у всех, как и далеко не у всех, кто пишет фронтенд, есть желание этим заниматься. Это нормально.

Есть преподаватели. Люди, которые рассказывают. Они записывают курсы, выступают с докладами, пишут объясняющие статьи, они — агрегаторы свежего, списки awesome-things на гитхабе — их рук дело. Не у всех есть способность объяснять, да и не у всех есть желание или время. Это нормально.

Есть программисты-в-стартапах. Люди, которые пишут модное. Они делают продукты (tm), ходят на митапы, где выступают с докладами, пьют крафтовое пиво и пробуют новый инструмент для сборки, создавая ишьи на гитхабе. Не у всех работодатели позволяют экспериментировать с продакшеном, как не у всех есть возможность объяснять неделями в ишьях, что же сломалось, да и время и желание тоже есть не у всех. Это нормально.

Есть программисты-в-студиях. Люди, которые фигачат. Они создают сайты, собирают промо-страницы из того, что попало в руку, переключаются между тридцатью тремя проектами быстрее своего редактора или IDE. Не у всех хватает нервов на такую работу, да ещё и не всем повезло со стэком в продакшене, уж точно не у каждого получается объяснить заказчику, кто же прав, да и не у всех есть желание или время. Это нормально.

Есть программисты-в-энтерпрайзе. Люди, которые пишут что-то. Они занимаются старыми системами, которыми пользуются люди по работе, получают зарплату и иногда почитывают статьи про новые фичи ES20xx, чтобы спрашивать на собеседованиях. Я, кстати, не пытаюсь описать круги ада и не считаю, что программисты-в-энтерпрайзе лучше/хуже остальных в списке, здесь нет критерия сортировки, и в этом как раз и суть! Это нормально.

Так вот, это уже на четыре стереотипа больше, чем было, но можно написать ещё двадцать, и не описать всего (например, мир людей, занимающихся разработкой для нонпрофитов; людей, делающие инфографику и твиттер-ботов; мир профессоров, публикующих lecture notes, и так далее, и так далее). Веб в этом смысле действительно как бумага — стерпит всё.

Что с того? Мне кажется ценным подумать об этой сложной слабосвязанной системе людей, технологий, компаний и мнений. У меня она вызывает восхищение — и как техническое, и как произведение искусства.


О своём опыте с фронтендом, особенно если он не укладывается в те четыре «корзины», можно и стоит написать на почту [email protected] или в телеграм @marinintim.

Пагинация

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


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

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

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

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

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

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

Это можно рассматривать как частное применение принципа Тима Бернерса-Ли Клёвые урлы не меняются. У каждого элемента в отдельности скорее всего есть свой пермалинк — ссылка именно на него, которая вероятно не изменится. 4 Но есть ценность и в поддержании контекста, как пост смотрелся в окружении других постов. Так, например, если результаты поиска ведут на какую-то страницу в 2009 году, хочется быть уверенным, что перейдя по ссылке вы увидите нужный фрагмент на самой странице, а не полистав дополнительно вокруг.

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

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

.pagination
    - for (let $pageNumber = 1; $pageNumber <= $totalPages; $pageNumber++)
        a href=("/page/" + $pageNumber)
            = $pageNumber

Нельзя просто заменить $pageNumber на $totalPages - $pageNumber, потому что тогда при добавлении трёх статей все статьи сдвинутся на три позиции.

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

«Классический» способ заключается в том, что из базы делается выборка, например SELECT * FROM posts ORDER BY published DESC, а разбиение на страницы прикручивается костылём LIMIT..OFFSET:

SELECT * FROM posts ORDER BY published DESC LIMIT 10 -- первая страница
SELECT * FROM posts ORDER BY published DESC LIMIT 10 OFFSET 10 -- вторая страница

У этого метода есть беда с производительностью: OFFSET обычно делается как «выберем всё равно всё, и отбросим ненужное уже в оперативной памяти», и когда мы пытаемся делать большой offset, то это становится заметно: страница 90 грузится дольше, чем страница 10.

Из преимуществ же, помимо простоты-для-разработчика, — мы можем «прыгнуть» на произвольную страницу: для этого достаточно посчитать нужный offset: умножить номер страницы на количество элементов на странице. Больше преимуществ у него нет.

Другой вариант, который предпочитает сочувствующая базам данных публика, называется keyset pagination, и заключается в выборке элементов со значением ключей больше/меньше чем у элемента с предыдущей страницы. В случае с постами таким ключом может выступать дата: «десять постов с датой меньше, чем 2018-01-10», например. Это гораздо быстрее и не становится сильно медленнее даже на «дальних» страницах, но теперь нельзя перескочить на произвольную страницу, не зная нужный ключ.

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

Тут есть альтернативный вариант пагинации: что, если вместо разбиения «по десять» мы начнём разбивать «по дням»? Тогда keyset pagination сработает ещё лучше — можно прыгнуть на дату. На ней может ничего не оказаться, правда.

Но продолжим с пагинацией «по десять». Здесь полезно остановиться и посмотреть, а что мы, собственно, разбиваем по страницам? Сколько этих элементов, сто? Десять тысяч? Миллионы? Как часто добавляются новые элементы?

Если элементы добавляются редко, то есть вариант тупой как пробка, и потому работающий 5: добавить каждому элементу свойство «номер страницы». Тогда наш запрос превращается в тривиальный SELECT * FROM posts WHERE page_number = 5. 6 Тогда остаётся только один вопрос, что же делать на новой странице, когда сайт ещё не набрал нужное количество элементов? Не хочется показывать «обрубок» из трёх постов, где обычно должно быть двадцать. Самое просто решение — позволить на самой новой странице разместиться 2*n - 1 элементам. Это добавляет немного нестабильности для самых новых элементов, но для большинства — тех, что на следующих страницах — соответствующий им номер страницы меняться не будет.

Если же нам нужно обрабатывать миллионы элементы, к которым постоянно добавляются десятки тысяч новых, то такой метод не сработает, как минимум, в простом варианте, но возникает сомнение в том, насколько здесь полезна пагинация, привязанная к количеству элементов, как средство ссылок в целом. Не имеет смысла говорить про «десятую страницу твиттера», но возможно есть смысл говорить о твитах с 2018-04-09T14:00:00 по 2018-04-09T14:30:00, и это решается методом keyset pagination.

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

Даже в случае фильтра качества, человек может захотеть поделиться ссылкой: представим условный кубинг.ком, где можно бронировать номер в гостиницах Кубы. На каждой странице по десять вариантов, я посмотрел тридцать, из которых мне понравилось два на третьей странице, и я хочу отправить друзьям на согласование. (Дурацкая метафора, но оставайтесь со мной). Если друзья перейдут по этой ссылке и не увидят те два номера, то это грустно, но вряд ли они перейдут по ссылке через два месяца, а за две минуты выдача вряд ли изменится. Практически любая пагинация становится условно стабильной, если мы говорим про малый промежуток времени.

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

Спасибо за внимание.

1

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

2

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

3

Это, кстати, хороший принцип для проектирования систем в целом — как правило, все решения неидеальны, поэтому стоит выбирать то, что наименее неидеально для самых частых сценариев.

5

Если вам хочется термин, то я только что придумал такой: ahead-of-time pagination.

6

Стоит посыпать индексом, ну этой sql-шамано-магией.

21 идея для разработчика

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

  1. Задача программиста не «писать код», задача программиста — решать бизнес-задачи, и «поиграться с новым фреймворком» зачастую не решает бизнес-задачу.
  2. Мы работаем с людьми, и только иногда пишем код, поэтому отношения между людьми — важная часть работы.
  3. Разработчики тоже люди, и подвержены всем тем же когнитивным ошибкам, что и все остальные. Стоит почитать про когнитивные ошибки, FAE, и книгу Канемана в отдельности.
  4. Постоянно сменяющиеся фреймворки появляются потому, что у нас нет идеального решения проблем, которые стоят перед фронтенд-разработчиками. Каждый следующий успешный фреймворк — шаг в интересном направлении в сторону идеала, стоит относиться к ним с точки зрения «что интересного этот фреймворк/библиотека привносит в мою работу».
  5. Разработчики не просто «пишут код», они участвуют в бизнес-процессах. Если в компании принят Аджайл, то нужно относиться к этому если не серьёзно, то как минимум с уважением.
  6. Код-ревью — важная часть процесса разработки. Нельзя быть хорошим разработчиком, но относиться к код-ревью халатно.
  7. Как программисты, мы несём ответственность за то, что деплоим. В том числе, и моральную. Не стоит делать неэтичные вещи.
  8. Пользователи — живые люди. Наши продукты и ошибки могут напрямую влиять на их жизнь, стоит осознавать последствия наших действий.
  9. Люди отличаются друг от друга, люди думают по-разному: что нам кажется сложным, бизнесу может казаться тривиальным — это создаёт конфликт, который приходится разрешать.
  10. Нужно нести ответственность за свои дедлайны, и если не укладываешься — идти передоговариваться.
  11. У задач есть два вида сложности: внутренняя и внешняя сложность. От первого вида сложности не избавиться, он есть в задаче изначально; второй привносим мы, городя неуместные архитектуры и изобретая велосипеды. Надо следить за тем, чтобы внешняя сложность не была больше внутренней.
  12. Когда при написании кода или проектировании архитектуры разработчик выбирает простое решение вместо правильного, он создаёт технический долг. По долгам придётся платить.
  13. Код других людей почти всегда кажется непонятным или криво написанным, не всегда причина в том, что код действительно криво написан. Иногда эти другие люди — это мы полгода назад.
  14. Иногда задача решается без изменения кода вовсе.
  15. Смелость менять то, что нужно менять, безразличие к тому, что изменить невозможно, и мудрость отличить одно от другого.
  16. Случается так, что тривиальное для разработчика бизнесу чрезвычайно полезно и ценно — это хорошая ситуация, не стоит от неё бежать.
  17. Редкая компания заинтересована в твоём росте: если бы её не устраивал твой текущий уровень, тебя бы не взяли.
  18. Конференции, митапы и прочее полезны в первую очередь людьми, которые пришли, и во вторую — докладами.
  19. Собеседование — это игра на двоих, не только компания смотрит на тебя, но и ты — на компанию.
  20. Мы приходим в профессию, потому что нам это интересно, но платят нам за пользу, которую мы приносим. Стоит почитать про cost center и profit center, и понимать, где ты находишься сейчас.
  21. Когда работаешь фрилансером, заказчик нанимает тебя ради скиллов, которых у него нет — он не может указать тебе на плохой код, а ошибки, которые ему видны, описывает своим языком.

Написать мысли насчёт, кхм, списка мыслей можно на [email protected] или в твиттере: я там @marinintim.