Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
07.01.2025
Міграції в SQLite
🗑️ Стикнувся сьогодні з класичним багом в Ping: видалення тегу звалювалося через порушення FOREIGN KEY. Бо ключ я додати згадав, а ON DELETE CASCADE забув. Почав було виправляти на рівні застосунку (тобто видаляти всі відмітки, а потім сам тег), та й думаю… хіба це не робота для бази даних?
🪨 Але ось що виявилося: в SQLite команда ALTER TABLE здатна тільки на найпростіші зміни схеми: додати / видалити / перейменувати стовпчик, або перейменувати саму таблицю, і все. Що мене, звиклого до PostgreSQL, ввело в ступор: невже я застряг з поганою таблицею?
✂️ Знайшов відповідь на StackOverflow з цікавою порадою: оскільки схема таблиць в SQLite зберігається в спеціальній табличці, то достатньо зняти спеціальний запобігач PRAGMA writable_schema та можна просто замінити схему та додати до неї ON DELETE CASCADE. Ясно, що це виглядає максимально сумнівно.
💣 Сумнівно чи ні, але спробував; виявилося, що на iOS цей метод взагалі не працює, бо там підключення відбувається в особливому defensive mode, де абсолютно заборонені такі “сумнівні дії”. (Якщо в схемі змінити таким чином щось суттєве — типи чи порядок стовпчиків, наприклад — то база гепнеться.)
📖 Тоді почитав трохи документацію, та виявилося, що рекомендований підхід дуже простий: створити другу, “правильну” таблицю, наповнити її даними, а потім підмінити стару таблицю новою. Мінуси цього рішення: не відчуєш себе містером роботом. Плюси: все пройшло успішно, без втрати даних та цілісності.
❓ До речі, причина цього (яка теж є на тій же ж сторінці документації), прагматична. SQLite не зберігає схему в структурній формі, а тільки в текстовій (власне, зберігається команда CREATE TABLE.) Тому будь-які зміни в схемі повинні були б перекладатися назад в текст CREATE TABLE - ще й гарантовано без втрат змісту. Творці SQLite вирішили не ускладнювати цим проєкт та залишили зміни схеми в такому ручному, явному режимі.
06.01.2025
Робота без відволікань з Bunch
Взявся за те, щоб обмежити відволікання навколо робочого місця. Та, якщо на столі можна прибрати, двері зачинити, а телефон — приховати в шухлядку (до речі, всім раджу!), то з відкритими на компʼютері вікнами не так легко. Погодьтеся, зазвичай вікна не хочеться зачиняти, бо потім доведеться відкривати наново, налаштовувати стан… краще нехай всі будуть відкриті! Отримуємо цифровий аналог робочого стола, заваленого папірцями та матеріалами всіх активних проєктів.
Щоб спростити підготовку робочого місця на macOS є застосунок Bunch. Він почався як “ярлик до групи програм” (звідси назва), але з часом виріс в цілий інтерпретатор — заточений під керування програмами, вікнами, режимами ОС, та здатний на будь-які інші задачі.
Я Bunch використав, щоб закривати всі вікна та відкривати мені потрібні. Відповідно на кожну задачу є окремий Bunch-скрипт. Можливостей там безліч. Мій скрипт виглядає приблизно так:
# закриваю все, окрім "системних" застосунків
(quit all except Drafts, Obsidian, Music)
# VSCode в AppleScript не вміє, тому відкриваю воркспейс командою терміналу
$ code ~/projects/ping/ping.code-workspace
# запускаю XCode з файлом проєкту
XCode
- ~/projects/ping/ping.xcodeproj
# ...запускаю проєкт комбінацією Cmd+R
- {@r}
# роблю переднім вікном VSCode
Visual Studio Code^
Це тільки разове налаштування стану, воно не забороняє потім просто відкривати будь-які вікна тощо. Я ставлюся до нього саме так, як до прибирання фізичного столу: підготувався до роботи, а далі роблю те, що зручно.
Закривати всі вікна спочатку страшно, а потім тренує залишати роботу в завершеному стані, зокрема в браузері.
05.01.2025
JSON-бази для простих застосунків
Я якось писав, що використовую JSON для зберігання даних мого застосунку для GTD. Застосунок досі живий та я ним користуюся постійно, тож JSON виріс та займає цілих… 120 кб!
А от на тому тижні натрапив на пост, де згадувалась “база” pickleDB для Python (та ще декілька альтернатив.) Це фактично той самий єдиний JSON, але з накладеною зверху абстракцією “документів”, “списків”, та “словників” - певно, щоб код хоч трохи наближався до такого, що буде працювати зі звичайною базою. (До речі, подивився навколо, та здається, що на Python такі мікробази взагалі популярні — а на Ruby нічого схожого не бачив. Цікаво)
Хотілося додати про досвід своєї “JSON-бази” - зокрема, де я бачу її межі. Головне те, що JSON можна читати та писати тільки цілим документом. Це наче і не проблема, бо є бібліотеки, які здатні читати та писати гігабайти JSON в секунду. Але ж нам також доведеться писати його на диск, а це вже повільніше. Плюс не забути увімкнути атомарний запис, інакше ризикуємо через форс-мажор записати половину файлу та втратити все. Плюс я б не хотів робити синхронізацію доступу через файл, якщо база потрібна більш ніж в одному процесі.
Також доведеться читати та тримати в памʼяті весь зміст разом — це відразу накладає ціну, особливо в мовах, де обʼєкти в пам’яті займатимуть більше місця, ніж сам JSON. Я з таким стикнувся навіть в Go. Але, це стане помітно на сотнях мегабайтів даних.
Гадаю, все залежить від того, як часто вам потрібно читати чи писати в базу. Якщо читання відбувається під час запуску застосунку, а запис — на дії користувача, то JSON вам (як і мені) вистачатиме надовго.
04.01.2025
Моя спроба зробити Undo/Redo
Був в мене проєкт застосунку для самоменеджменту… який не досяг успіху через відсутність бачення. Втім, рушій я для нього нагородив досить потужний. Застосунок був на React Native, а стан зберігався у базі PouchDB, яка синхронізувалася з сервером CouchDB. Для мене, як я вже писав, CouchDB досі залишається потужною, але нішевою технологією без зрозумілого застосування — приблизно як Clojure.
Отже, механізм Undo/Redo, тобто “скасувати/повторити”. Зазвичай його складність полягає в неоднорідності операцій зі станом. Так, в багатьох (чи усіх) застосунках текстові операції є скасованими, але спробуй змінити блоки, що містять той текст — наприклад, задачі — та можливість скасування втрачається.
CouchDB тут має корисну властивість. Документи в ній мають ревізії, та старі ревізії зберігаються поруч з новими. Це потрібно для реплікації. Є схеми, де реплікуються зміни, а ось в CouchDB реплікуються цілі ревізії. Таким чином, досить довго можна прочитати стару версію документа. Навіть видалення відбувається через нову ревізію з маркером _deleted. Тому: всі дії в моєму проєкті проходили через єдину операцію put, а також — я завжди мав доступ до старих ревізій всіх документів.
Але очевидно, для користувача інтерфейс повинен бути вище за скасування “документ за документом, ревізія за ревізією”. Тому всі функції-дії, що потребували скасування, загорталися в функцію withUndo та отримували всередині особливу реалізацію інтерфейсу DBEffects, яку потім передавали усім операціям з базою. А інтерфейс DBEffects складався з єдиної функції put… з Undo вона не тільки писала в базу, а й складала перелік документів та їх ревізій до об’єкта, створеного в функції withUndo. (Звісно, через put були реалізовані звичайні create, update, delete і так далі.)
Потім, щоб скасувати останню дію, я брав крайній обʼєкт з переліку дій, та вертав назад попередній стан порушених документів. Ось так просто. (Ревізії утворювались нові, до речі, бо як і в Git, старі дані не можна змінити.) Та це дійсно працювало з будь-якими змінами, від примітивних до складних.
03.01.2025
Що робить інтерфейси "дорогими"?
✨ Короткий списочок того, що я б хотів бачити у застосунках, над якими працюю — але на це ніколи немає часу. (Найближче була Сінтра, оскільки пан Олександр Зайцев максимально прискіпливо ставиться до деталей.)
-
🍃 Дрібні анімації. Практично кожний елемент інтерфейсу може мати анімації переходу, але робити їх зазвичай важко.
-
⚠️ Змістовні повідомлення про помилки. Не “невірна сума”, а “сума повинна не перебільшувати 66.00”, а ще краще “у вас в гаманці залишилось 66.00 на витрати”.
-
⏳ Видимий стан внутрішньої активності. Коли застосунок робить запити до сервера, або щось обчислює, зробити це помітним та зрозумілим. Як бонус: результат на завершення — теж. Від операцій, що тривають секунди, до багатогодинних.
-
🔙 Можливість скасувати зміни. Навіть найкрутіші застосунки не можуть цього собі дозволити в повному обсязі. Між іншим, я колись намагався це зробити з Redux/CouchDB - та щось навіть виходило. От тільки проєкт не злетів. Треба буде про це написати.
-
🐣 Обробка виключних ситуацій. Найперша з них — порожній стан. Але інтерфейси просто складаються з виключних ситуацій. Наприклад, коли предмет один, або коли їх значно більше, ніж заплановано. Коли назви довгі, а статті короткі. Коли світлина прямокутна, а не квадратна. Далі та далі.
-
🇺🇦 Переклад. Потребує величезних інженерних витрат, а потім ще й власне перекладацьких. А ще й локалізації — правильного відображення кількостей та значень. А потім ще й підтримки. Втім, це найбільш практична “розкіш” з переліченого.
🥳 PS: стохастичний таймтрекер вже доступний для бета-тестування, доєднатися можна за посиланням.
02.01.2025
Мій власний Steam Replay, або в що я грав у 2024 році
Як я обіцяв собі зробити звіт на кшталт Steam Replay, так і зробив. Причому навіть як функціональність застосунку, тобто можна такий саме звіт зібрати з будь-чого. Обираєш тег-“всесвіт” (бо якщо не обмежувати вибірку, то вийде повна каша) - тут це тег gaming. А потім набір тегів-категорій - з тих, що з ним перетинаються. Отримуєш ось такий цікавий графік.
…Насправді зі звітом все складніше. Те, що ви бачите — це я, після першого поганого досвіду, обрав топ-10 ігор. Бо там ще купа дрібних записів на 1-2 години, які вже перетворюють графік на шум. В кольорах розібратися неможливо. (Гадаю, треба відразу автоматично залишати на графіку топ-10.)
Технічно цей функціонал включав купу рефакторингу коду деревної мапи — бо там сидить обчислення “покривної” статистики. А ще форми пінга — бо там є UI вибору тегів. В цілому у Swift це приємно робиться, але: він любить кидатися помилкою про “не можу розібратися в типах”, та далі ми залишаємося на самоті: або розпилюй на менші функції — без допомоги IDE, або шукай помилку — теж наосліп. Вихід примітивний: коментую шматки коду, поки не скомпілюється; тоді поламане місце десь в коментарях.
…А тепер, до результатів. BG3 я так і не пройшов; досяг level cap та стало якось не цікаво. Сталкер здивував; але його поки теж закинув. Evil Within 2 - нудьгуватий, але компетентний. Hades - екшн-Roguelike з безліччю цікавих комбінацій. Echo Night Beyond на PS2 - гра випередила час, вигадлива, але логічна адвенчура від першого лиця. Ashes of the Apocalypse - вже писав, бумер-шутер з мистецьки зробленим світом.
Dread Delusion - не закінчив, але все ще планую, це як King’s Field з відкритим світом. Morrowind - цього року досліджував Tamriel Rebuilt. Rule of Rose - ще одна гра на PS2, SIlent Hill 2-like в цікавому сетінгу. Cyberpunk 2077 - а ви знаєте, що з виходом доповнення вони повністю переробили всю рольову систему та лут? Я таке вперше бачу.
Нагадаю, що вся ця статистика зібрана моїм стохастичним таймтрекером, який ось-ось можна буде завантажити в TestFlight… як тільки відділ підтримки Apple виспиться.
01.01.2025
Застосунки за замовчуванням: 2025
Почнемо рік з встановлення цікавої традиції: визначення застосунків, якими ти користуєшся “за замовчуванням”. Це цілий рух, ось його домашня сторінка, а нижче — мої застосунки. Тицяй у 📝, щоб перейти на відповідний пост.
- 📨 Поштовий клієнт: MailMate
- 📮 Поштовий сервер: Fastmail
- 📝 Нотатки: Obsidian 📝 та Drafts 📝
- ✅ Задачі: мій власний застосунок CoreGTD
- 📷 Фотографування айфоном: звичайна камера, але в складних ситуаціях (як ручний фокус) також Halide.
- 🟦 Керування світлинами: Apple Photos
- 📆 Календар: Apple Calendar та Dato
- 📁 Хмарне сховище файлів: Apple iCloud
- 📖 RSS: Reeder Classic та Miniflux. 📝
- 🙍🏻♂️ Контакти: Apple Contacts
- 🌐 Браузер: Safari :)
- 💬 Спілкування: там, де контакти. Але будь моя воля, певно залишив би тільки Apple Messages.
- 🔖 Закладки: Obsidian. Просто закладки “окремо” я не люблю.
- 📑 Список читання: Reeder Classic 📝
- 📜 Текстовий процесор: Apple Pages, я не вибагливий :)
- 📈 Табличний редактор: Apple Numbers :)
- 📊 Презентації: давно не робив, але Deckset.
- 🛒 Список покупок: Apple Reminders
- 🍴 Планування їжі: рецепти мої у Paprika, а не мої — в NYT Cooking. 📝
- 💰 Особисті фінанси: якби вів, то в нашій Сінтрі. 📝.
- 📰 Новини: знову Reeder та RSS 📝
- 🎵 Музика: Apple Music
- 🎤 Подкасти: Overcast
- 🔐 Паролі: 1Password 📝
- 👨💻 Редактор коду: VSCode
- 🔣 Термінал: Fish, а емулятор - в тому ж VSCode
- 🎨 Графічний редактор: Pixelmator.
31.12.2024
Дев-адвент 31: воно живе!
🎁 Настав час поділитися застосунком. На публічний випуск він ще не готовий (та й маркетингові матеріали теж). Але базовий функціонал весь в нормі — я проробив онбордінг, увімкнення всіляких дозволів і таке інше — тому його вже точно пора запускати в бета-тестування.
⏰ Зупинився на короткій назві: Ping!. Насправді я цю назву бачу вже місяцями як заголовок сповіщення, та нічого кращого не уявляю. Значок застосунку все ще тимчасовий.
Так що сьогодні я ще встиг перейменувати всі ресурси застосунку, а ще змінити назву моделі Sample на Ping. Це майже тривіальна заміна по коду, єдине, що довелося експортувати та наново імпортувати власні дані в новий застосунок (бо перейменування — це ж фактично створення нового застосунку.)
🚚 Багато чого ще залишилось доробити. Я не планую зупинятися, хоча точно доведеться приділити час іншим справам (та іншим темам для каналу, хе-хе.)
🧪 Доєднатися до TestFlight можна за посиланням. На жаль, про що я забув — перед розповсюдженням застосунку на TestFlight він повинен пройти перевірку від Apple. Тому посилання наразі не пускає. Не впевнений, що це відбудеться сьогодні, але як тільки пройде перевірку, так відразу й посилання запрацює. 🧘
❄️ З прийдешнім Новим Роком вас!
30.12.2024
Дев-адвент 30: TipKit
Сьогодні накидав більше порад з TipKit та продовжую бути ним вражений. До речі, ось гарне викладення всіх можливостей, а я поки поділюся власними думками.
Колись на роботі ціла команда робила інтерактивний “підручник” для одного з продуктів. Це був, фактично, ще один застосунок, який показував поверх головного продукту всілякі підказки та поради та відстежував твій шлях навчання. Ось TipKit мені й нагадує цей застосунок, тільки вже готовий. В нього і база своя є, і інтерфейс, і рушій логіки — повний комплект.
Відразу хочеться розставити ці поради просто на кожному кроці. Принаймні для онбордінгу це навіть має сенс — бо замість традиційних слайдів (які мені робити та підтримувати буде важко) я вкраплю інструкції прямо в інтерфейс. Та як окрема перевага - інтерфейс не буде надто порожнім.
Наприклад: замість типового слайду “дозволь нам надсилати сповіщення” показую пораду на кшталт: “тут ти бачиш крайню пробу, але вона вже минула… а щоб побачити наступну вчасно, увімкни сповіщення”. Або “ось 10 порожніх проб за ніч… якщо дозволити доступ до Apple Health, ми їх автоматично протегаємо.” Органічно та зрозуміло.
29.12.2024
Дев-адвент 29: онбордінг та поради
🏁 Десь нещодавно чув — можливо, в документалці до 20-річчя Half-Life 2 (яку я всім раджу подивитись задля цікавих продуктових історій) - що перший рівень гри варто робити останнім, коли всі механіки та підходи вже пропрацьовані. Бо перше враження — головне, тож варто зробити його найсильнішим.
⏪ Із застосунками це теж доцільно. Протилежний підхід — почати розробку з онбордінгу — виглядає логічніше з першого погляду. Але тоді є ризик, що продукт розвиватиметься, а про онбордінг забудуть, та він застаріє. В моїй практиці таке вже бувало. Втім, якщо продукт великий та бюджет необмежений, то колись онбордінг перероблять наново. А от коли таких ресурсів немає, то краще все ж відкласти розробку першого знайомства на останнє.
🧪 Коли я тільки починав робити трекер, то вигадав для онбордінгу “тестову пробу”, в якій можна погратися з інтерфейсом та не дочікуватись першої справжньої проби. Нагадаю, що вони відбуваються за чітким графіком, причому є “стандартний” графік зі скриптів TagTime, та є сенс не відхилятись від нього. А тестова проба — просто створювалась на поточний час зі спеціальною приміткою.
🗃️ Але “тестова проба” була функціональним баластом та тільки створювала ускладнення (наприклад, її треба прибирати з усіх статистичних запитів.) Тому знайшов кращий спосіб: під час першого запуску я буду генерувати до тижня старих проб. З ними можна не тільки погратися, а й зрозуміти частоту проб, випробувати автозаповнення з Apple Health (та запросити дозвіл для нього!) та отримати перші результати.
💬 Також випробовую TipKit. Це цілий фреймворк для підказок. Не просто для того, щоб їх показати (але це теж), а відстежити, які підказки вже були закриті, а також в який момент їх пора показати. Наприклад, підказку про улюблені теги має сенс показувати, коли тегів буде принаймні 5. Цікавий взагалі фреймворк — адже підказки потрібні всім.

