Дневник разработки · День 4

Копировать собственный прототип было неправильной целью

Вчера я закрыл ноутбук с чек-листом на утро.

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

Чек-лист назывался «на утро». Открыл я его в половине второго ночи.

Cmd+B.

Не собралось.

До третьего пункта дело не дошло.

Вчера я предположил, что мне не понравится.

Не угадал. Ровно этого я и ждал.

🎨 Сначала пятна

Разочарования во всём этом ноль.

Я так и планировал.

Объясню аналогией. И сразу скажу: я не художник.

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

И только поверх — детали. Объём. Текстура.

Пятно на этом этапе не ошибка. Пятно — это метод.

Мне нужно было увидеть картину, чтобы её потом нарисовать.

Возможно, настоящий художник прочитает это и скажет, что так не пишут. Что это неэффективно и вообще не с того конца.

Ну и пусть.

Сделав ноль попыток, ты точно не узнаешь, как надо было пробовать.

И вот это уже не про живопись.

Те, кто запускал первые ракеты, отлично понимали, что они ляпнутся. Первые три Falcon 1 не долетели. Четвёртый вышел на орбиту.

Никто не рассчитывал, что первая попытка будет удачной. Рассчитывали, что она будет.

(Я, понятное дело, не сравниваю приложение для сниппетов с ракетой. Я про то, что первая попытка нужна не для результата.)

Так что «работает через задницу» стояло в плане с самого начала. Интересовало меня другое, и это четыре вопроса.

Насколько поломанным оно окажется.

Выйдет ли вообще его запустить.

Когда запустится — чего будет стоить это поправить.

И вывезет ли это ИИ.

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

🧭 Никто не знает, как правильно

Я когда-то не дочитал книжку Брэнсона — «К чёрту всё! Берись и делай!». Но главное из неё помню до сих пор.

Никто не знает, как правильно.

Не «мало кто». Никто. Того, чего ещё нет, никто ещё не делал, и спросить не у кого.

И когда ты это понимаешь, тебя становится сложнее остановить. Ты больше не ждёшь, пока у тебя появятся компетенции. Ты просто пробуешь. Потом ещё раз. Потом снова.

Хотя работаю я ровно наоборот.

Я работаю с теми, кто умнее и начитаннее меня. Кто делает то, что умеет, и не лезет в то, чего не умеет. Это правильно, это эффективно, и так я работаю много лет.

А этот проект устроен наизнанку. Я делаю то, чего не умею. Пока не умею. И сознательно не даю своему незнанию права голоса.

Потому что от ординарных действий не бывает экстраординарных результатов.

А я хочу большего.

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

Но если я хочу чего-то большего, оно делается только шагами в неизвестность.

Вот один из них.

Он сейчас не собирается.

🔨 Три причины

И ни одна из них не про Swift.

Первая — та, над которой я вчера пожал плечами и пошёл спать.

Коммит назывался «Start iOs development», и я решил, что это оговорка. Пальцы помнят десять лет фронтенда, Apple — значит iOS, бывает.

Это была не оговорка.

Проект и правда был настроен как iOS'ный. Целевая платформа — iphoneos. Семейство устройств — телефон и планшет. Идентификатор приложения — koskei.com.jpaste, то есть обратный домен, написанный наоборот.

А код внутри — AppKit. Тот самый NSPasteboard, ради двух строчек которого всё и затевалось.

AppKit на iOS не существует.

Мы всю ночь строили приложение для Mac внутри проекта для айфона. И не заметили этого оба: я — потому что не знаю, где у Xcode ручка, он — потому что его об этом не спросили.

Вторая причина: файлы тестов лежат в мишени приложения, и import Testing там просто не находится.

Третья: вьюхи и вьюмодели лежат в мишени тестов, поэтому главное окно их не видит.

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

Вчера я смотрел на слипшиеся имена и видел неопрятность.

Это была не неопрятность. Это была схема сборки.

Плюс пара файлов, существовавших в двух экземплярах сразу.

Итог: тридцать семь файлов, две с половиной тысячи строк, и ни одной новой возможности.

Похоже на уборку. На выравнивание стопки бумаги перед тем, как начать писать.

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

Но вот что я заметил, пока выравнивал.

🪞 Неправильная цель

Сегодня утром я попросил две вещи, и попросил именно в таком порядке.

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

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

Ответ вернулся переписанной спецификацией.

Первый нативный проход, запущенный. Ни прототипа, ни дизайна — безликая системная утилита.
Первый нативный проход, запущенный. Ни прототипа, ни дизайна — безликая системная утилита.

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

Эта фраза — про то, что приложение делает.

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

Потому что прототип жил в браузере.

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

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

А в нативном приложении этих ограничений нет.

И получалось, что я переношу в него не решение, а компромисс. Причём компромисс с условием, которого больше не существует.

Прототип отвечал на вопрос, что должно происходить. Он никогда не отвечал на вопрос, как это должно выглядеть.

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

Полностью. Совсем.

🧊 Стекло, которое можно выключить

Вместо плоского фона — родные материалы системы.

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

И сразу два решения, которые пришлось принять сегодня утром.

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

Второе мне нравится больше.

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

Потому что стекло — это вкус. Кому-то оно мешает читать. Кому-то просто не нравится.

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

А лежит он в настройках достаточно далеко, чтобы никому не мешать. Дать переключатель дешевле, чем быть правым.

🔌 Engineer honesty moment

Теперь две вещи, которые выяснились за сегодня. Это и есть ответ на первый из моих вопросов — насколько поломанным оно окажется.

Первая.

Я сидел и смотрел на холст с одним блоком. Не искал ошибку. Просто смотрел.

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

В приложении не было поля ввода.

Вчера я обещал себе сырое, глючное, неработоспособное — но существующее. Вот насколько сырое, и это не по краю.

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

Приложение для сниппетов, в которое нельзя положить сниппет.

«New snippet» — это заглушка, а не набранный текст. Блок рисуется, и на этом всё.
«New snippet» — это заглушка, а не набранный текст. Блок рисуется, и на этом всё.

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

Вторая хуже. И нашёл её не я.

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

Я посмотрел на него ровно один раз. Подумал: а зачем он мне сейчас, если в блок всё равно нечего положить. Потестирую потом.

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

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

Их просто не звал тот, кто сохраняет блок.

А в функции, которая выводит ключ из пароля, в комментарии написано: PBKDF2, сто тысяч итераций. В теле — HKDF. У HKDF итераций нет вообще, и число, которое туда передавали, было некуда девать.

Замок висел на двери, которая не закрывалась. И на замке стояла гравировка с классом стойкости.

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

И вот это — единственное за сегодня, чего в плане не было. Поломанное я ждал.

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

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

💬 Вопрос

Когда вы в последний раз копировали собственное старое решение, не спросив, почему оно было таким?

У меня ответ — сегодня. И самое обидное, что ограничение, под которое я его когда-то придумал, давно снято, а решение я тащил дальше как ценность.

Расскажите, как у вас 👇

Дальше всё зелёное: сборка, тесты, четырнадцать задач.

Вот это меня и беспокоит 😉