Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
11.09.2024
Дизайн даних для інженерів
Мені як інженеру багато разів доводилося виводити дані… так би мовити, без дизайну. Зазвичай для внутрішніх потреб: нечасто дизайн покриває всілякі адміністративні панелі. Тому, гадаю, перша дисципліна дизайну для інженерів — то подання даних.
Щоб просто показати табличку, чи перелік, чи форму, багато думати не потрібно. Проте тут є аксіома/порада: будь-які дані можна подати в різному вигляді та завжди можна знайти щось краще, ніж вигляд за замовчуванням.
-
📰 Форматування. Взяти, наприклад, відбиток часу. Показати його в ISO 8601? Чи може тільки дату? Чи взагалі як відносний час? Число — з якою точністю? Рядок — як є чи скоротити частину, що повторюється у всіх записів?
-
🍱 Групування. Всякі дані мають внутрішню структуру. Можна зробити їх легшими до споживання, якщо розбити на логічні групи. Для того потрібно зрозуміти, на що буде дивитися читач. До того ж група дозволяє написати повторюване значення тільки один раз.
-
📈 Похідні. Часто важливіше бачити обчислені дані: відсоток, середнє, тривалість — а не сирі дані: кількість, початок та кінець проміжку. Також інколи можна обчислити корисний статус чи категорію (якщо такі цікаві читачу.)
-
💬 Назви замість ідентифікаторів. (Або навіть посилання, якщо є куди.) Звісно, в сирих даних частіше будуть ідентифікатори, проте для читача вони рідко наділені змістом.
-
🎨 Оформлення. Крапля кольору або емодзі багато роблять для сприйняття інформації. Так само як і корекція відступів, розміру шрифту і таке інше, але то вже заходить на територію Великого Дизайну.
-
✂️ Приховування неважливого. Менше = легше. Окрім того, щоб прибрати зовсім, можна сховати дані за підказкою або стиснути у вигляді (емодзі замість текстового статусу) або поєднати два поля в одне (назву та посилання).
Практикуватися в дизайні можна на адмінпанелях, звітах… навіть на записах в журналі та вирізках для слаку! Та від того повсякдення стане трохи зручніше та ефективніше.
10.09.2024
Бібліотека samber/lo: функціональні ідіоми для Golang
Випробував таку нову (для мене) бібліотеку - samber/lo. Знаєте пакет Lodash для JavaScript? Там, де купа зручних функцій для програмування у функціональному стилю? От це таке саме тільки для Go.
Бібліотека вийшла у 2022, після того, як в Go 1.18 зʼявилися дженерики. Та, завдяки дженерикам, вона є швидкою та безпечною.
Та вона дійсно дозволяє писати функціональний код на Go. Наприклад, ось обчислення першого року публікації кожного автора:
lo.MapValues(
lo.GroupBy(books, func(b Book) string { return book.Author }),
func(bb []Book) int {
return lo.Earliest(lo.Map(bb, func(b Book) int { return book.year }))
},
)
Написав приклад та подумав, що імперативне рішення може було б і ясніше. Не впевнений, що варто робити такі багаторівневі конструкції. З іншого боку, виклики в один рядок на кшталт Flatten
чи GroupBy
, чи Filter
здатні замінити багатомовні цикли та зробити код ясніше. А ще тут є функція Must
, якої теж постійно не вистачає, щоб висловити “якщо тут буде помилка, то я зовсім не знаю, що з нею робити”.
Також зазначу, що хоч дженерики здатні виводити типи — тому їх писати не потрібно — але функції потребують явного задання типів. Тому компактне в JS book => book.year
перетворюється на func(book Book) int { return book.year
. Та це помітно псує код.
Щодо ціни всіх цих абстракцій… Роб Пайк дозволив про це не думати. Я раджу всім спробувати у своїх проєктах цю бібліотеку та думаю, вона знайде свою нішу в вашому коді.
09.09.2024
Робочий час та календарний час
Робочий час — той, що ми витратимо безпосередньо на виконання роботи. Це те, про що зазвичай думають при плануванні часу. Але окрім нього є ще…
Календарний час — той, що мусить минути для завершення роботи. Та від нас не залежить. Наприклад: від створення пул-реквесту до його появи на продакшні може минути тиждень. Залежно від процесів, звісно.
Або інший приклад: якщо для побудови моделі нам потрібні три місяці даних, то доведеться чекати. Або поки наступить день бронювання. Або поки поштою щось приїде.
Таке розрізнення стане корисним під час планування. Хоча “календарні” витрати часто займають набагато довше — зате проходять без нашої участі. Особливо по адмініструванню зустрічаю багато задач, де роботи на пів години, потім чекати декілька днів, потім ще пів години.
Чим раніше почнемо “календарну” задачу — тим раніше вона закінчиться. Це теж варто враховувати у плануванні. Трапляється що робота починається з налаштування збору метрик чи додаткових журналів. Поки доробляємо решту — вже буде на що подивитись. А якщо не подумали та почали збір пізніше — доведеться чекати.
А ще “календарні” задачі можна робити паралельно. Теж якщо вірно розпланувати та помітити наявність затримок. Як частий приклад, PR-потяг (це коли наступний PR роблять до попереднього, а не чекають коли той приймуть.)
08.09.2024
SwiftUI та найпростіша модель даних для нього
Сьогодні зненацька зробив 50% мікрозастосунку для GTD (ой, лишенько.) Здебільшого отримував задоволення, хоча розробка зі SwiftUI це американські гірки від “як тут легко зробити Х!” до “ну чому Y абсолютно не піддається логіці?” До речі, писав у SweetPad - це доповнення для VSCode від друга каналу Євгена майже заміняє XCode.
Оскільки хотілося якнайшвидше перейти до бізнес-логіки, спробував таку модель даних: ніякої бази, просто класи, які я зберігаю у JSON.
Завдяки фреймворку Observation, Ui впевнено реагує на зміни даних. Є ще модифікатор @Bindable
, яким можна перетворити ті ж класи на привʼязки для текстових полів та інших місць, які потребують редагування. Тобто фактично одним класом AppState
можна покрити всі потреби застосунку в стані. Зрозуміло, що коли даних буде багато, така “база даних” стане повільною, але для керування власними задачами точно вистачить.
Також поки свідомо відмовився від синхронізації. Взагалі з рішеннями для синхронізації поки більше питань, ніж відповідей. Для мене головним є стабільна робота застосунку локально на десктопі. Поки всі рішення, що я бачив, цю стабільність порушують — зокрема, в мене поламався пошук у Reminders.app - тобто й Apple не може на власній платформі зробити надійну синхронізацію.
07.09.2024
Розгортування в хмару, чому ти таке повільне?
Величезним мінусом всілякої хмари, порівняно з розгортуванням на конкретні машини, було те, як воно до біса повільно розгортується. Як з Heroku почалося -надцять років тому, так і зараз тільки гірше стало.
Повільно все тому, що ми не володіємо в хмарі ресурсами. Це як якби щоразу перед тим як їхати кудись на машині, потрібно було її взяти в оренду. І не зручним Гетьманкаром, а через заповнення документів та пошук машини на стоянці.
Відповідно, хмарний сервіс повинен знайти для нас потужності, потім завантажити наш код (або величезний образ Docker), запустити, пройти перевірки, зупинити старі контейнери. Heroku взагалі чудову абстракцію побудували, коли знаєш, що всередині.
А до того ще CI теж повільний. Теж збирає залежності. А якщо є кеш, то і його буде звідкілясь завантажувати. Підготовка може тривати половину часу, що робить паралельний запуск мало корисним (бо звісно ж, кожний паралельний потік буде все те ж саме робити окремо, на власній машині чи контейнері.)
Де це особливо обурює, так це на стейджингу. В мене є окремий чекліст для перевірки коду на стейджингу, бо поки моя гілка розгорнеться, я точно про неї забуду. Бо не буду я 10% робочого дня дивитися, як CI стягує кеш, чи CD перевіряє хелс-чеки.
06.09.2024
Список для читання через два роки
📖 Списком для читання я користуюся практично щодня. Через те мені не потрібна сотня вкладок.
⏲️ Список для читання гарно працює, коли нічого в ньому не має терміновості. Раніше я користувався ним як буфером для всього, що не зможу закінчити зараз — статті, відео, товари, документація. Але, коли список розростається (а так і буде), то все орієнтоване на задачі в ньому губитиметься.
📝 Тому тепер, коли зустрічаю сторінку, яка не просто цікава, а саме зараз, то записую її в Drafts, де побачу (та оброблю) протягом тижня. (До речі, що в Reeder, що в Drafts, та багато куди ще легко зберігати через функцію “Поділитися” на macOS та iOS.)
🪨 А якщо вона стосується поточного проєкту, то йде в Obsidian, у відповідне місце. Тут дуже задоволений, що таке місце зʼявилося, та ще й можна поруч ілюстрації зберігати. (В Obsidian у мене є як сторінки “інформаційні”, тобто позачасові, так і “проєктні”.)
📺 Також нещодавно почав “список чого подивитись” - просто в Reminders - та так само як список для читання, він допомагає в зручний момент дивитись щось цікаве, а не просто випадкову “піну” з YouTube.
🔎 Ще натрапив на таку зручну функцію, як попередній перегляд посилань в браузері Orion. Вона працює поза браузером та відкриває посилання в тимчасовому вікні — яке за бажанням можна “розгорнути” у звичайну вкладку. Це дуже спрощує перегляд посилань з Obsidian чи з Slack - браузер не захаращується випадковими вкладками.
05.09.2024
Sentry та обмеження обсягу
Почалося все просто з того, що я перевіряв, як працює інтеграція slog з Sentry з вчора. Нічого тут складного немає. Береш ключ з проєкту. Запускаєш з ним локальний скрипт. Скрипт надсилає помилку в Sentry. От тільки… ні. Чомусь не надсилає.
Звісно, спочатку намагався знайти, що роблю не так. Фільтри, налаштування… Ніби все працює. Навіть sentry.CaptureMessage
повертає якийсь ідентифікатор. Але на сайті порожньо.
Точніше ні. На сайті були свіжі випадки якоїсь іншої помилки. Тож Sentry живий. Такого я раніше не бачив. Від розпачу вирішив виправити ту іншу помилку, бо обсяг був немаленький.
Поки шукав, звідки вона взялася (а справа була на стейджингу), помітив, що помилка належить до релізу за травень. Такого старого коду просто не могло бути — але десь він був запущений. Та сипав помилками.
Урок: додавайте до помилок достатньо метаданих, щоб відстежити її до сервісу. Мені довелося шукати в декількох кластерах ECS, бо відома був тільки IP адреса зі спільної для них мережі. Така задача простіше з AWS CLI та jq
.
Все одно не відразу знайшов, бо та IP адреса належала вже зупиненій задачі. Яка з травня висіла в очікування зупинки. Поки не знаю, чому, але моя ставка на багах ECS. Задачу вдалося зупинити через примусову зупинку машини EC2. Взагалі я трохи в шоку, бо в теорії така “зомбі” могла б накоїти гірших наслідків. Доведеться придумати, як за цим спостерігати.
Помилка зникла. Можна було повернутись до перевірки інтеграції та… о диво, вона запрацювала! Виявилось, що стара помилка вичерпувала rate limit
проєкту, тому нова й не могла проскочити. Урок 2: треба дуже уважно ставитись до обмежень обсягу в Sentry, бо якщо вони вичерпуються постійно — навіть чимсь неважливим — то є реальний шанс не побачити нову помилку, навіть критичну. Плюс один показник, за яким потрібно стежити.
04.09.2024
Пригоди з логуванням в Golang
Вже десь рік в Go є вбудований модуль для структурного логування - log/slog. Нарешті наважився на нього переїхати. (Структурне логування — це коли в лог пишуться не просто рядки, а структури. Найчастіше - JSON. Тоді з нього легко дістати будь-яке значення.)
Коли б тільки зі старого стандартного логування, то це більш-менш нескладно. Ну хіба потрібно буде шаблонні рядки перетворити на структурний синтаксис. Та де-не-де замінити log.Fatal
на щось власне, бо в slog
такого немає. Що може й непогано, бо log.Fatal
виходить з програми миттєво та пропускає всі блоки defer
- в тому числі можливо, й ті, що фіналізують журнал.
Бо логи не обовʼязково йдуть тільки у стандартний вивід. Помилки також направляються в Sentry. Є різні способи це зробити — мені подобається само надсилати логи. Sentry - чудовий сервіс, але все одно всі помилки варто бачити в журналі. Зі структурним логуванням в Sentry та в журнал йдуть практично однакові дані. Та й синтаксис slog
краще.
Для структурних логів у нас вже був logrus. Тож його теж потрібно було замінити. (Разом з його інтеграцією в Sentry.) Можливо, легше було б зробити для logrus
та log
адаптери до slog
, а потім вже поступово переписувати. Хоча мені миліше зробити все за один раз, та ще й прикрити лінтером forbidigo, щоб заборонити все, окрім slog
.
Приємно зрештою зробити все акуратно та на майбутнє залишити чіткий підхід, щоб два рази не думати — як його щось залогувати.
03.09.2024
В стрімі про пригодницьку гру 11th Hour побачив цікаву головоломку (До речі, цей канал ведуть головний актор старої пригодницької гри Phantasmagoria 2 разом з чемпіоном по спідрану тієї ж гри. Рідке поєднання!)
Головоломка механічно проста, проте швидко виявляється, що коням немає де розгорнутись. Знаходиться “центральна” точка, до якої зручно дістатись, але рухатись далі заважають інші коні. Поки дивився, як ведучі страждають, згадав, що воно мені нагадує. Це ж граф!
Якщо побудувати з можливих пересувань фігур граф, то розвʼязок стає не те що легким — а очевидним. Мораль тут в тому, що побудова правильної моделі задачі це половина розвʼязку.
А другий висновок такий, що навіть коли є модель, що відразу зрозуміла — як шахові коні — то не завжди вона буде оптимальною. Коли розвʼязок не сходиться, варто озирнутись в пошуках кращої інтерпретації обставин.
А поки писав пост, знайшов ще таку статтю про те, як ця головоломка привела якогось математика до премії… щоправда, за роботу над хроматичними многочленами. Під цією незнайомою мені назвою ховається число розфарбувань графа.
02.09.2024
З Obsidian Canvas у список задач
Не так давно писав про генерацію Obsidian Canvas зі списку. Сьогодні переді мною стала зворотна задача. В канві накопичилася купка задач та я хотів перекинути їх у список. (Бо саму канву як список задач складно використовувати.) Отже, проста людина перекопіювала б ті задачки за декілька хвилин. Але ж мені потрібно автоматизоване рішення.
Дописав до утиліти pbcopy-chromium
з попереднього поста режим вставки: тобто тепер вона може вивести зміст буфера обміну Chromium - наприклад, вузли канви.
Бо конвертувати цілий файл то гарно, але на практиці сьогодні пені потрібні були лише декілька вузлів.
Далі в мене є нехитрий скрипт на Ruby, який обходить спрямований граф вузлів та будує з них список в Markdown. Це така задача, яку в повному обсязі буде досить складно розвʼязати (бо є цикли, орієнтація вузлів в просторі, різновиди стрілочок-ребер), але якщо обмежитись обумовленими, “нормальними” графами, то це традиційний обхід в глибину.
На Markdown історія не закінчується, бо ті списки в мене сидять в Reminders.app. А туди Markdown не вставиш. (Та й взагалі можливості вводу-виводу мінімальні.) Знайшов поки тільки ручне рішення — скопіювати список у Notes.app, там перетворити його на “список задач”, та ось цей “список задач” вже можна скопіювати у Reminders. Поки не знайшов, як то автоматизувати, бо формат буфера обміну тут знову закритий, com.apple.notes.richtext
, та бозна-як з ним працювати.