Фёдор Борщёв

Новее

Day One → Obsidian

Несколько месяцев назад я решил отказаться от Day One, которым пользовался 5 лет для ведения личного дневника. Причин было несколько:

  • Невозможные тормоза. Дело тут даже не в электроне, или на чём он там написан: кажется Day One — это просто плохой софт. Удивляюсь, откуда у разработчиков берётся энергия, чтобы писать столько кода, чтобы тормозило даже на новом макбуке, где давно уже даже ноушен летает.
  • Фокус на метаданных. Day One с самого начала обвешивает привычку писать дневник всякими ненужными вещами, которые не представляют интереса уже через день — предлагает логать погоду, место и даже музыку, которую я слушаю. Чтобы ненужных данных было ещё больше, Day One ломает паттерн, привычный людям уже несколько сотен лет — вместо обычных ежедневных заметок, вас заставляют писать по одному посту на каждое событие. Добавил фотку — пост, записал мысль — ещё пост.
  • Переусложнённый формат хранения. Возможно следствие предыдущего пункта, но внутри у Day One довольно сложный формат хранения данных. Вроде бы и маркдаун, но он зарыт внутри довольно сложного JSON, который непонятно как парсить. То есть я как бы владею собственными данными, но вот считать их без Day One я скорее всего не смогу. А если и смогу сегодня — никто не гарантирует, что я смогу повторить это через 10 лет, когда формат переживёт ещё 3 инкарнации, а контента в дневнике станет в 4 раза больше.

Для замены Day One я выбрал Obsidian — он показался мне самым надёжным из всех инструментов для ведения заметок. Самое главное преимущество Obsidian перед специализированным софтом — все данные хранятся в виде MD-файлов на диске. То есть я владею своими данными, бекаплю их как хочу, и они никогда и никуда от меня не денутся.

От встроенной синхронизации решил отказаться, потому что iCloud работает быстрее, а на экосистеме Apple я сижу давно и плотно. Так же пришлось почистить слегка перегруженный интерфейс — помогло, что Obsidian проектировали как инструмент для программистов. Довольно легко мне удалось убрать всё лишнее — отключить штатные плагины вроде Canvas или записи аудио, попрятать ненужные детали интерфейса при помощи Hider.

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

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

Почему я не люблю GraphQL

Будучи разработчиком, я не любил GraphQL с самого его появления — из-за ноды, привязки к Apollo, отсутствия APM (если не покупать подписку у Apollo) и сложного, вложенного языка запросов. В принципе до сих пор ничего изменилось, но в этом посте я хочу отдельно поговорить о своей любимой теме – тестировании.

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

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

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

Конечно, есть бизнесы, которые выигрывают от умения отдавать по 20 сущностей на один запрос, и делают это своей core-ценностью — к примеру облачные BaaS или высокоуровневые headless CMS вроде Sanity. Но если вам надо просто отдавать на фронт десяток известных сущностей, даже если вы планируете добавить в будущем ещё пару десятков — скорее всего GraphQL обойдётся вам намного дороже чем обычный REST, потому что с самого старта вам придётся тратить намного больше сил на тесты.

Обхожусь без нетворкинга

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

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

Да, без нетворкинга не работает биздев — если вы продаёте b2b товары или услуги, и не придумали как взломать систему, то вкладывать кучу времени в знакомства с почти нулевым результатом — это ваша святая обязанность.

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

LMS для Школы Сильных Программистов

У Школы Сильных Программистов была небольшая LMS. Делать её решили потому, что принятый в индустрии Getсourse превращает редактирование уроков в мучение (в школе привыкли писать и редактировать в Notion), а пафосные опенсорсные LMS вроде moodle годились для чего угодно, кроме обучения взрослых людей. К тому же почти во всех курсах внедрена сложна фича, которую не найти в готовых продуктах — p2p-проверка домашки, когда одни студенты смотрят и ревьюят работу других: это экономит время экспертов и здорово повышает доходимость.

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

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

  • Нормальный код покрытый тестами
  • Хороший дизайн и UI-kit
  • Удобная мобильная версия для чтения на ходу

Старт

Для старта работ у нас было:

  1. Бекенд на Django, покрытый тестами
  2. Код текущего фронтенда LMS
  3. Большой пост с описанием функциональности

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

  1. Писать хорошую дизайн-систему с нуля проще, чем рефакторить старую
  2. Cтарая LMS была написана на Vue 2, а в то время уже вышел Vue 3, в котором появилась поддержка Typescript, а ещё крутой туллинг в виде Vite и Pinia

С решением не ошиблись, получилось быстро и круто.

Редизайн LMS мы решили делать прямо в коде. Используя старую LMS как вайрфреймы, мы собрали все страницы внутри Storybook на мокнутых данных из фейкера.

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

Материалы

Материалы для LMS пишутся в ноушене и отдаются на фронтенд через хитрое API. Бекенд нужен, чтобы контролировать доступ к материалам, кешировать их (чтобы грузилось быстрее, чем в самом ноушене) и немножечеко его менять, чтобы было просто рендерить на фронте.

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

Забегая вперёд: такое решение — единственная сложная часть LMS. Реализации рендеринга на Vue печальные: одна очень плохо написана, а другая редко обновляется. Сейчас мы форкнули удачную реализацию и обновляем её сами (к счастью, ноушен ничего не меняет, поэтому это не много работы).

Почему просто не использовать маркдаун? Маркдаун нужно уметь писать, а курсы в школе пишут не только программисты. Кроме того, наладить простое (git — это не простое) совместное редактировние текстов в маркдауне — тяжёлая задача.

Домашки

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

Ответы на домашку ученики пишут в удобном WYSIWYG редакторе. Редактор собран на основе tiptap. Чтобы оживить общение между студентами, мы добавили возможность оставлять реакции.

Профиль студента

По окончании каждого курса школа выдаёт кайфовый диплом: можно положить в LinkedIn, добавить в резюме или просто повесить на стенку. Чтобы в диплом было вписано корректное имя пользователя, мы сделали раздел «профиль», где студенты заполняют свои данные На странице настроек ученик управлет своими данными, а также указывает информацию, которая появится на дипломе.

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

На бекенде уже была сделана хорошая валидация с понятными текстами ошибки. Чтобы забыть про валидацию на фронтенде раз и навсегда ответы с бекенда в выводятся в тосты. Перехват ошибок делается на уровне axios и все ошибки ловятся автоматически, без использования try … catch.

Релиз и что дальше

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

Релиз прошел плавно. В том, что так будет, мы были уверены — код был хорошо покрыт тестами.

Дополнительно убедиться в том, что новые фичи не развалили все прямо перед релизом, помогали тесты на визуальный регресс. Они сравнивают скриншоты актуального интерфейса с тем, как он выглядит в мастере, и показывают различия если что-то поменялось. Чтобы написать такой тест, нужно добавить историю в Storybook и написать тест который ходит туда через Playwright, делает скриншот и сравнивает его. Через такой такой тест не пройдут ошибки, затрагивающие рендер, в том числе ошибки на уровне CSS.

Код проекта открытый. Посмотреть можно на гитхабе.

Технологии: vue3, pinia, vitest, playwright.

Разработчик и автор статьи: Тимур Брачков.

Старее