Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
24.11.2024
Ремастеринг книжки
Сьогодні цікавий день, бо я зголосився допомогти з сімейною книжкою. Книжка на 160 сторінок, стара, надрукована на аркушах A4 розворотом та зшита скріпками. Задача була просто скопіювати її, але наявний екземпляр сам був вицвілою ксерокопією, та мені свербіло її освіжити, або, як то тепер кажуть, зробити ремастер. Зацифрувати, зверстати, віддати в нормальну типографію.
Книжку мені дали в вигляді сканів тих самих розібраних аркушів. По-перше, потрібно було скласти з них сторінки в правильному порядку (бо на одному аркуші друкувалися сторінки 3 та 157, 4 та 156 тощо.) На то я зробив скрипт на Ruby із застосуванням ImageMagick:
left_index = index
right_index = 163 - index
angle = index.odd? ? 90 : -90
`magick '#{page}' -crop 100%x50% +repage output_%d.jpg`
`magick output_0.jpg -rotate #{angle} output_0.jpg`
`magick output_1.jpg -rotate #{angle} output_1.jpg`
FileUtils.move('output_0.jpg', "#{'%03d' % left_index}.jpg")
FileUtils.move('output_1.jpg', "#{'%03d' % right_index}.jpg")
Це дало мені впорядкований набір сторінок. Далі направив їх в ABBYY FineReader, яку памʼятаю ще зі шкільних часів. Якість розпізнавання відтоді стала незрівнянно краще, на виході отримуємо документ майже без помилок, а головне — з виправленими перенесеннями слів. Хоча вичитка все одно була потрібна, та виявилася складнішою, ніж я думав, бо перевірка граматики знаходила безліч хибних помилок у всіх назвах, іменах та місцевих словах. Так що робота була майже ручна.
Ще було питання, в якому редакторі робити чистку. Я почав у Google Docs, бо думав що це зараз найкращий вибір, але ні — зокрема там не вистачило банальної можливості вказати різні поля для лівої та правої сторінки. Тому більшість роботи виконав в Apple Pages. Не знаю, хто їм окрім мене користується, але насправді це повноцінний та потужний текстовий редактор. (Так, я знаю, що, мабуть, ідеальним вибором був би MS Word, але його в мене немає. Чи може Ворд зараз теж є онлайн?)
Нарешті, захотілося від себе додати локальну мапу. Але ж не просто знімок з Google Maps, а щось гідне книги. Трохи пошукав продукт, який вміє робити карти за потребою (наприклад, такий, де можна виділити потрібні мені селища.) А потім зрозумів — мені потрібно взяти знімок з карти та обмалювати його в графічному редакторі. Тоді можна наповнити карту будь-яким змістом, та при тому зберегти масштаб та пропорції. Так я й зробив в Pixelmator - стилі в мене трохи примітивні, думаю, якщо запастися гарними географічними стилями для ліній та багатокутників, вийшло б ще краще.
23.11.2024
Місце для Offline First
Чомусь у світі мало застосунків, які й повноцінно доступні офлайн, і мають автоматичну синхронізацію з сервером. Навіть великі серйозні продукти потребують доступу в інтернет, принаймні для запису. Синхронізація локальних копій з серверною — досі складна задача без загальних рішень. Я вже довго на це дивлюсь, та набуваю думки, що краще не думати, що в тебе воно вийде.
Розробка вебзастосунків псує людину: ти забуваєш, що можна робити застосунки, які взагалі не мають серверної частини. Але застосунків без серверної частини повно, та ми всі користуємося ними щодня. Навіть веброзробники з вебтехнологіями вільні взяти рішення на кшталт Electron та робити собі SPA без серверної частини. Також рішення з локальною базою буде простіше, ніж всяке задумане під синхронізацію, тож навіть Offline First варто брати тільки якщо вам дійсно потрібно щось синхронізувати з хмарою. Тому якщо хочете зробити щось однокористувацьке, вистачить SQLite, а то й JSON. Ваша продуктивність вам віддячить.
Головна проблема такого рішення — як і його головна сила — що дані залишаються на одному пристрої. Для веброзробника це теж ненормальний стан речей, але це може виявитися зовсім не проблемою — дивлячись на застосунок. Он, Apple Health досі не має ніякої синхронізації. Також, обмін файлами на цей час — розвʼязана задача, тому може нам ніякий не Offline First потрібний, а файли в спільній теці чи хмарному диску.
Коли ми працюємо з чужими чи спільними даними, потреба в доступі до інтернету більш виправдана, тож тут доцільніше мати класичний вебзастосунок з тонким клієнтом та зберіганням на сервері. Особливо тому, що тоді не доведеться розвʼязувати конфлікти між змінами різних користувачів.
А ось коли ми хочемо, щоб дані зберігалися в першу чергу локально, але зміни з одного пристрою зʼявлялися миттєво на іншому — тут вже доведеться шукати рішення “Offline First” - Firebase, Realm, CloudKit - та готуватися витрачати на нього зусилля: навіть коли ніби синхронізація є вбудованою, безплатною вона не буде.
22.11.2024
Теги в каналі — тепер й на сайті
Додав до сайту підтримку тегів. Нарешті можу скинути посилання на серію про основи безпеки в Інтернеті — проміж інших, одна з моїх улюблених. Також можна насолоджуватись хмарою тегів, хоча я тільки почав її наповнювати.
Трохи нюансів. Я хотів теги для каналу окремо від інших розділів сайту. Знайшов, як це роблять, на форумі. Якщо без деталей, то такі теги треба робити окремою таксономією, назвати її stendap/tags
- і тільки так, бо ця назва стає шляхом в URL. Але також тепер в метаданих поста теж треба писати "stendap/tags"
, що вже дуже дивно виглядає. Зате результат досягнений.
Хмару тегів в наш час легко зробити з CSS, як ось тут пояснюють. Тільки там кожному тегу призначається клас розміру, а я його обчислюю на ходу з кількості постів. Ще залишається нормалізувати її відносно максимальної, що звучить просто, але в шаблонах Go арифметики немає, а є тільки виклики функцій, та ще й в Польській нотації: font-size:{{add 1 (mul $pageCount $multiplier)}}em
.
Ну і ще придумав на сторінці тегу показувати повні пости, але щоб легше було зорієнтуватись, додати також табличку зі змістом. А табличка вже була готова, бо в статтях на сайті вона зʼявляється, коли достатньо заголовків. Тож залишалось тільки адаптувати.
21.11.2024
Розширена інформація після невдалого тесту в Go
Захотілося, щоб після невдалого інтеграційного тесту я бачив журнал сервісів з Docker Compose. Бо в мене на CI вже й так журнал друкується наприкінці збірки, але за тим загальним журналом складно знайти потрібне місце.
Перше питання стало — як взагалі в Go викликати код після тесту? Рішення в стандартній бібліотеці не знайшов, але ті тести виконуються пакетом testify/suite
, а в нього є метод TearDownTest(), проміж інших. Тож тут проблем немає.
…Далі, як дізнатися, що тест був невдалий? Я очікував якогось аргументу в колбеці, але ні. Насправді той обʼєкт *testing.T
, навколо якого обертаються тести, має метод t.Failed(). (Взагалі раджу його вивчити детальніше, бо там багато чого цікавого, наприклад, можливість отримати тимчасову директорію для одного тесту - t.TempDir()
.) А в testify/suite
, до речі, через s.T()
отримуємо той самий обʼєкт.
(PS: а ще в testify/suite
знайшов suite.HandleStats - якщо його оголосити, то цей метод буде викликаний наприкінці всього пакету зі статистикою про час виконання та результати тестів.)
Окей, тепер, як отримати логи? Спробував через програмний клієнт Docker… надто низькорівневий, бо він повертає для логи конкретного контейнера, та ще й потоком. А мені потрібні зведені логи всіх контейнерів в проєкті. Тому просто викликаю консольну команду docker compose
. Для того є зручний високорівневий тип exec.Cmd. Йому можна прямо сказати “спрямуй вивід команди в мій вивід” і все, готово.
20.11.2024
Як я робив 3D-рушій
🎈🐴🧑🚀 В далекому 2004 році я писав той самий “другий проєкт”, про який тут згадували в коментарях: а саме, ідеальний рушій для 3D-ігор у вакуумі. На той час я вже трохи наробив ігор на Паскалі, але треба було кудись рости… Тому новий рушій був на С++. Тоді мене в C++ закохала можливість перевантаження операторів та, порівняно з C, можливість ООП (яка й в Паскалі вже була знайома.)
💯 Почав з математики. (Та, як можна уявити, я все хотів писати самостійно.) Лінійна алгебра вона наче не дуже складна, але в реалізації легко заплутатись, бо всі ті формули з підручників потрібно було переписати. (А про юніт-тести я тоді ще й не знав.) Вектори, матриці то ще добре, а от від кватерніонів досі жахаюсь. Кватерніонами робляться оберти в 3D-просторі - про ті оберти цілий айсберг нюансів.
🎥 На базовій математиці будується модель простору. Бо щоб щось показати на екрані, для початку треба визначитись, де камера, та як відносно неї розташовані предмети. Це все купа матриць; матрицями задаються перетворення систем координат. За основу я взяв OpenGL - низькорівневий графічний API. Так от йому подавай матрицю камери та світові координати точок… суцільна математика.
🚛 А далі — що показувати? Обʼєкти та їхні текстури потрібно завантажувати з файлів. Тому я, звісно, сам написав завантажувач з BMP (найпростіший формат графічних файлів, до речі!) та моделей Milkshape 3D (оце вебдизайн!)
🏗️ Зверху того рушію потрібна модель виконання (тобто петля з таймером, кадри, таке інше), інтерфейс (вікна, HUD, шрифти), механізми керування. І це ще ми не дійшли до головного — до гри!
🍰 От і я не дійшов. Попри такий докладний підхід, нічого окрім простих демок я так і не зробив. Та якби зараз собі радив, то почати з “вертикального зрізу”, тобто з простої, але завершеної гри, а потім вже узагальнювати.
🏆 Але я не думаю, що проєкт був повною дурнею. Для програміста писати рушій це найчистіша форма самовиявлення. Плюс, з цим рушієм в резюме я потім знайшов першу серйозну роботу (хоч і зовсім не про ігри, а про веб та PHP.)
19.11.2024
Користуйтеся посиланнями замість буквальних імен
З Terraform метрику AWS CloudWatch видалив, а тривога за метрикою залишилась. Та ще й негайно стривожилася. Та підняла інцидент в PagerDuty. (Гарно хоч, що не посеред ночі.) Цього можна було уникнути, якби ресурс тривоги посилався на ресурс метрики:
resource "aws_cloudwatch_metric_alarm" "foo" {
# ✅
alarm_name = aws_cloudwatch_log_metric_filter.foo.name
# ❌
alarm_name = "foo"
}
Звісно, схожа проблема зустрічається у всіх мовах та постійно. Рядки повинні бути в константах або ідентифікаторах. В одному місці. В решті місць — посилання на ті константи. Тоді в коді легко відстежити використання термінів.
Статична типізація трохи допомагає, бо в багатьох місцях мова вимагає чіткості. Мені подобаються вирази-шляхи у Swift. Але там теж є ключі в словниках та всілякі “магічні значення” (які фактично є неоголошеними константами). Добре коли в мові є тип enum
.
В динамічних мовах гірше, бо там менше різниці між рядками та ідентифікаторами. Он, в Ruby багато всього працює через :символи
. Все одно, коли символ використовується всюди в проєкті, краще покласти його в якусь константу чи метод. (Всім знайомий приклад: Rails.env.development?
замість Rails.env == "development"
)
А ще в Ruby є погана тенденція конструювати ті імена на ходу, як-от "#{attribute}_exists?"
. Та й в Terraform такого багато. Тоді взагалі ходи шукай ті назви.
18.11.2024
Варто починати дослідження з написання тесту
Коли треба щось перевірити — баг чи просто незрозумілу поведінку — я починаю з тесту. Зазвичай інтеграційного, але не обовʼязково.
Для мене в цьому більше сенсу, ніж в “test-driven development”, оскільки я не люблю писати тести на порожньо. Ще я не люблю вручну відтворювати обставини — особливо на неповноцінній локальній версії — а тест дозволяє це автоматизувати.
Наприклад, показали дивну поведінку в продакшні — що легше буде — шукати причину з журналів та обмеженого доступу до даних, чи спробувати відтворити в тесті та мати для дослідження вичерпний стан? Або згадали на нараді неочевидну ситуацію — достатньо написати тест, щоб побачити її на власні очі.
Тому в мене час від часу зʼявляються ПРи, в яких є тільки пара тестів, та ще може коментарі по коду. Написав, перевірив, код виявився вірним, а тест — навіщо його викидати?
Звісно, для того, щоб так робити, потрібна структура запуску тестів. Та це непоганий аргумент її мати, навіть коли ви не плануєте серйозно покривати проєкт тестами. В мене був проєкт, де тестів спочатку не було, а почалися вони саме з ось таких “дослідницьких” тестів.
Скажу по секрету, коли мені потрібно надати знімок екрана з якогось функціоналу, я часто роблю його з інтеграційного тесту, бо так легше підготувати оточення, а якщо потрібно буде щось поміняти — то і відтворити.
17.11.2024
Переїзд сайту з Vercel на Cloudflare Pages
Давно вже обмірковую цей переїзд. Зараз мій сайт і так доступний через Cloudflare, але розміщений на Vercel. Хочеться консолідувати майно; та можливо навіть почати платити за Cloudflare гроші — бо там багато є гарного по аналітиці та по захисту. (Але базовий статичний хостинг що там, що там безплатний.) До Vercel в мене найбільше нарікання — що час від часу Ahrefs скаржиться на повільність сторінок, що для статичного сайту дуже підозріло.
Сьогодні спробував перенести. Власне, сам зміст майже тривіально переноситься, і це сучасне досягнення. Обидва сервіси забирають сайт з GitHub, та обидва здатні викликати Hugo для збірки. Тобто майже без налаштувань зʼявляється нова копія. Єдине, що вилізло — це на CF Pages встановлений Yarn 3 та він чомусь не погоджувався з моїм Yarn 1. Але, оновити собі Yarn справа недовга.
(До речі, також всі схожі сервіси використовують спочатку тестові домени, тому підготувати переїзд можна спокійно на тестовому, а потім перемкнути DNS, коли вже буде впевненість.)
З несподіваного: у CF Pages є обмеження на розмір файлу — до 25 Мб. В мене є пара архівів, що його перебільшують. Але їх можна перенести кудись в S3 чи на його аналог - Cloudflare R2.
А ось де справжня проблема, так це з перенаправленнями. В мене після змін архітектури залишився список з близько 1000 застарілих шляхів. В Cloudflare є зручний файл _redirects, але вони надто примітивні для моїх потреб. Наприклад, у Vercel можна написати /page(/?)
, а у CF тільки двома правилами /page
та /page/
. Так я починаю упиратися в обмеження на кількість перенаправлень (2000 + 100 динамічних.)
В мене очевидно надлишковий список, можливо, доведеться його обрізати до реально потрібних. Або взагалі почати з порожнього та спостерігати за 404-ми. (Можна було б, за всі ці роки вже зібрати “живий” список старих посилань, але кому він був потрібний?)
Є ще варіант замінити 404-ту на функцію та обробляти перенаправлення там. Безплатно доступні 100 тисяч викликів функцій, вистачить з головою. Функції в Cloudflare потужні, там і база даних є, тож цікаво випробувати.
16.11.2024
Виконання задач за порядком Final Version
Чесно, поки писав пост про порядок виконання задач, тільки під кінець згадав про існування “системи” Final Version (Perfected). А я їй колись користувався в блокноті, з непоганими результатами. Цього разу вирішив адаптувати систему для власного домашнього застосунку для ведення справ. Далі більше про систему FVP та деталі реалізації.
Нащо воно потрібно? Бо чим довше список задач, тим важче обрати, що з нього робити наступним. Та найстрашніше те, що природна реакція на те — взагалі нічого не робити. Тому люди вигадують всякі системи, як я писав в попередньому пості, а також розставляють пріоритети, плани на сьогодні та таке інше.
FVP пропонує елегантне та просте рішення, яке все ж включає постійний перегляд всього списку — тому важливі задачі будуть виконані якнайшвидше. Це, якщо хочете, розвʼязок задачі динамічним програмуванням.
Для FVP тобі потрібний список, де можна відмічати задачі. (На папері — ідеально.) Відмічати будемо задачу, яку робити наступною.
-
Спочатку — відмічаємо першу задачу (як початковий стан).
-
Далі шукаємо в списку задачу, яку треба зробити до відміченої.
-
Знайшли — відмічаємо її, та продовжуємо порівнювати решту задач вже з нею.
-
Дійшли до кінця списку — тепер остання відмічена задача є найпріоритетнішою. Робимо її.
-
Повертаємося до попередньої відміченої задачі, та продовжуємо перебір вниз по списку.
Сила цього метода в тому, що доводиться тільки порівнювати по дві задачі, та лише N раз. Ніби нічого революційного, але дуже дієво. (Залишається ще чистити список від того, що там застрягає, але то окрема історія.)
15.11.2024
Чому вам потрібний відділ підтримки?
Продовжимо розмову про професії, необхідні для програмного продукту, які не є інженерами. Я схиляюся перед терпінням, емпатією та розумінням працівників підтримки, це святі люди.
В інженерних колах існують деякі зневажливі стереотипи щодо підтримки: що там ніхто нічого не розуміє, що підтримка працює машинально, що її аутсорсять низькооплачуваним, незацікавленим найманцям. Звісно, все це можна було б застосувати й до самих інженерів, тож нічого особливого для підтримки тут немає. З єдиною різницею: з підтримкою ми знайомі на власному досвіді.
Бо, якщо маркетинг створює перші враження від продукту, то підтримка відповідає за весь шлях користувача. Та гарна, людяна підтримка не менш важлива для успіху, ніж якісне програмування.
Я навіть більше скажу: підтримка перекриває недоліки інженерів. Бо хоч я й працюю в компанії, де майстерність інженерів на найвищому рівні, але все одно наші продукти отримують десятки звернень до служби підтримки щодня.
Інженер скаже: PEBKAC, це все тупі юзери. Саме тому між інженерами та користувачами потрібний відділ підтримки. Проблеми користувачів — завжди проблема продукту, та щоб користувачі залишились, їм потрібно допомогти. Та якщо інженер ставиться до продукту, як тато, а маркетолог — як мати, то підтримка — це для продукту друг: знає підхід до нього, які в нього кращі сторони та тригери, та допоможе вам познайомитись ближче.
До того ж мені здається, що емпатія підтримки розповсюджується й на самих інженерів, бо з ними теж буває важко.Гарний інженер прислухається до підтримки, намагається побачити, де плани були хибними, та що можна зробити краще. Ну, а якщо продукт індивідуальний, то треба вчитись вдягати “капелюха” підтримки, на додачу до всіх інших.