Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
20.01.2023
Як я колись шаблонізував документи Word
Згадали на роботі мову шаблонів Handlebars. Я її дуже люблю, як просту мову для шаблонів, що редагуються користувачами. Хоч ця мова за мірками інтернету стародавня — ще з 2010 року — вона залишається актуальною. Але спогади про Handlebars нагадали мені іншу цікаву історію, як колись довелось генерувати шаблоном документи Microsoft Word.
Задача була доволі проста за змістом. Треба було скласти звіт за зразком, але заповнити даними користувача. Таке зазвичай роблять з HTML, а потім, можливо, перетворюють на PDF, але тут вимога була отримати звіт у форматі DOCX. Було це років десять дому, тож може вже зʼявились рішення простіше. А на той час гарного способу зробити DOCX з HTML або PDF я не знайшов. Часто-густо якщо такий конвертор і знаходиться, то він псує форматування та підходить тільки для внутрішнього використання.
Але ж корисно знати, що формат документів DOCX являє собою XML-документ, запакований в архів ZIP. Це вже обіцяє хоч якусь можливість автоматичного перетворення. Та дійсно, перша спроба розвʼязку була така: взяли зразок та спробували зробити з нього шаблон за допомогою ERB або ще чогось. Але документ DOCX містить набагато більше, ніж ми бачимо на екрані, та прочитати та зрозуміти його вихідний код — це зовсім не те що редагувати звичні шаблони вебсайтів. Тож “простий” шаблон видався незграбним та складним у редагуванні — бо кожна зміна форматування зразка практично тягнула за собою створення шаблону наново.
Тоді прийшла інша ідея шаблонування. Якщо зразок документа відкрити у Ворді, та залишити в ньому текстовий маркер, наприклад: {{NAME}}, а потім цей документ відкрити вже у Ruby, то знайти цей маркер за допомогою Nokogiri буде просто. Та не тільки знайти, а й замінити на інший текст. При цьому заміна отримає форматування, що було прикладене до маркера. Тож наш зразок став шаблоном — зберігаючи можливість його відредагувати. Чудово!
На заміні маркерів робота не закінчилась. Вдалося навіть впровадити такі неодмінні функції шаблонування, як умовні блоки та блоки-цикли. Для цього спрацювали ті ж самі маркери. Але тепер ми знаходили пару маркерів, забирали XML-фрагмент, що знаходиться між ними, та опрацьовували. Єдиний нюанс, що маркери мали знаходитись на одному рівні у дереві XML, але це нескладно було влаштувати вручну. Головне, що при виявленні проблем було достатньо видалити маркери з документа та спробувати знову.
В тестуванні це працювало як потрібно, а у продакшн хитре рішення так і не потрапило.
19.01.2023
Проблеми робочих дзвінків — мабуть, такі, як в усіх
Після відпустки помітив, що з усіх аспектів роботи найменш приємним є дзвінки. Хоч вони в нас по ділу та не скажеш, що зайві. Тому вирішив розібратись в собі.
По-перше, кого як, а мене робочі дзвінки дуже виснажують. Навіть якщо не доводиться брати активну участь, все одно, дві години дзвінків — і вже треба психічно відпочивати. До того ж під час дзвінку рідко можливо займатись чимось іншим — ментального ресурсу не вистачає. Хоча деколи дзвінок випадає на момент глибокого занурення в потік програмування, і тоді навпаки, важко відірватись та звернути увагу. А що приємно робити під час дзвінку, то це гуляти. Якщо якість звʼязку дозволяє, та не треба дивитись на екран, то прогулянка та дзвінок — ідеальна комбінація. (А ще залишився спогад з однієї з перших робот, коли ми пішли планувати великий проєкт у зеленому сквері.)
По-друге, все ж таки є питання, обговорення яких на дзвінку є марнуванням часу. В ідеалі, питання мають бути пропорційними кількості присутніх людей (та відповідно, вартості хвилини дзвінку!) Втім, деколи ми натрапляємо на дрібні подробиці, які ж таки є частиною великого цілого, тому без вирішення всіх деталей розмова здається незавершеною. Тоді дзвінок скочується до так званого bike shedding, що ще більше виснажує. Мені здається, що такий момент треба вміти помітити та не боятись переносити кінець обговорення у текстовий чат.
І нарешті, буває й таке, що відчуваю себе зайвим на дзвінку. Або ж буває що присутній тільки на всяк випадок, якщо виникне необхідність. На жаль, відмова від участі у дзвінку виглядає хоча б трохи, але неввічливо. Хоча насправді навпаки, якщо є привід, то уникнути або скоротити дзвінок — це знак поваги до спільного часу. Але робочий етикет в цьому питанні якось недороблений.
Буде що розказати на наступній ретроспективі.
18.01.2023
Рамки, в які нас заганяє Ruby on Rails
Я дуже люблю Ruby on Rails - так, що хоч вже десять років намагаюсь їх здихатись, та ніяк не виходить. Картинка зверху мене трохи зачепила, бо, на мою думку, впевнений вибір — це добре, але досвідчений розробник має знати свої обмеження, та як вони впливають на архітектуру. Тож, не скасовуючи цей мем, все ж таки розгляну питання — в які архітектурні рамки ставить нас Ruby on Rails?
Ну, якщо вже заговорили, то Rails вимагає реляційної бази. Бо ActiveRecord, хоч і чудова технологія, але завʼязана саме на RDBMS - і звісно, саме PostgreSQL буде першим вибором такої. Адаптери для інших баз є другосортними та втрачають найбільш приємні аспекти Rails. Але при цьому Рельси використовують саму базу вкрай обмежено, та купа можливостей — таких як вбудовані функції, складні перевірки цілісності та інше — залишаються проігнорованими. Помилки цілісності — звичне явище для розробника на Ruby on Rails, бо замість перевірок в базі вони робляться на рівні додатка. Виходить технологія в парадигмі “тупо, але дієво” - справжній нащадок PHP.
Далі, Rails була, є та буде дорогою технологією в плані хостингу. Навіть Heroku припинили надавати безплатний хостинг для Ruby on Rails. Хоча головною статтею витрат буде не додаток а… база. Бо хостинг для RDBMS теж не може бути простим та дешевим. Тож як для молодих проєктів, що робляться “з кишені”, так і для гігантів індустрії, вибір Ruby on Rails - обовʼязково також і фінансовий.
Крім того, цей стек відразу анулює можливість мати локальну копію даних. Бо навряд чи у клієнтів на телефоні буде Postgres. А з іншого боку — можливість скористатись безмежним масштабуванням хмарних баз, таких, як Firebase або DynamoDB. Масштабування можна залишити на майбутнє, а ось синхронізація та робота офлайн здатні піднести додаток на абсолютно новий рівень. Хоча, звісно, не кожен додаток такого потребує.
Ніша, де Rails, а з ними й Postgres - це найкращий вибір — нікуди не зникне. Тож, як і розробники на COBOL, знавці цих технологій завжди знайдуть собі роботу. В цьому плані я згоден з вище приведеним мемом.
17.01.2023
Краща оптимізація запиту до бази — це його не робити
Наступна оптимізація — уникнення запитів для нових акаунтів. Справа така. Для акаунта треба знати дату початку роботи, спираючись на журнал. Копатися в журналі — довго — декілька секунд. Тому я зробив кешування: оскільки дата початку роботи — історично сталий факт, то обчислювати її потрібно тільки раз. До речі, у Rails є приємний режим Rails.cache.fetch(key, skip_nil: true), при якому в кеш не потрапляють порожні значення. Тобто, як тільки в журналі є запис, в кеш потрапляє його дата. Чудово.
Але ж нюанс — дата початку роботи використовується, щоб визначити, чи показати клієнтові “порожній аркуш”. А для нових акаунтів — які мають бачити “порожній аркуш” - наш кеш не працює. Тож перші враження кожного нового клієнта проходять через повільне завантаження сторінки — аж допоки вони не почнуть активно користуватись сервісом. Це буквально протилежне від того досвіду, який ми хочемо надати.
Щоб це виправити, просто перевіряємо за іншими прикметами, чи пройшов акаунт початкове налаштування. Якщо ні — то ніяких записів в журналі не може бути — можна навіть не робити запит. Залишається набагато менший проміжок, коли налаштування зроблено, а журнал ще порожній — з ним поки нічого не зробили. Але онбордінг включає й випробування запису в журнал, тож у нормальному випадку клієнт взагалі уникне того гіршого випадку, коли кешу немає, а запит довго копається в базі перед тим, як сказати, що там теж пусто.
Тепер здається, що це все очевидні речі, але ж найбільш прямолінійним рішенням було просто робити необхідний запит, без додаткових ускладнень як-то вище зазначена перевірка прикмет. Зате прибрати зайвий запит до бази — завжди потім приємно.
16.01.2023
Віконні функції PostgreSQL, або ж COUNT+LIMIT одним запитом
Сьогодні повернувся до роботи, а робота в мене зараз — це боротьба з Redshift. Тому маленька, але дуже цінна порада. Вона стосується тієї ситуації, коли потрібно для деякого запиту одночасно й обмежити кількість рядків, і дізнатись, скільки їх є взагалі. Тобто класичної пейджинації.
Я все життя просто робив окремий запит. Тільки що перевірив, і виходить, що не тільки я — найпопулярніша бібліотека для пейджинації у Rails - Kaminari - теж робить окремий запит. У нормальних умовах це і не погано, бо ніхто і не помітить уповільнення. Але якщо у вас пейджинується складний запит, виконання якого займає 3 секунди, то робити його другий раз вже не хочеться. Проте моніторинг, який я передбачливо налаштував перед відпусткою, саме таке і показав — що виклик API робить 2 майже однакових запити — один на COUNT, один на LIMIT - та замість трьох секунд триває шість.
Мені не хотілось вірити, що це найкраще, на що здатний Redshift, тож озброєний цією вірою, пішов шукати розвʼязок. Та знайшов, у так званих віконних функціях. Якщо просто, то віконна функція оперує не тільки поточним рядком, але й іншими рядками. Головне, вона виконується до операції LIMIT. В моєму випадку потрібна найпростіша функція: COUNT(*) OVER (). Вона значить: підрахувати рядки, використовуючи вікно без жодних обмежень, тобто всі рядки. Але з віконними функціями можна робити й цікавіші речі — наприклад, повертати в кожному рядку статистику по рядках зі схожими атрибутами. Віконні функції зустрічаються в секції SELECT, тобто результат буде повернений як стовпчик:
SELECT *, COUNT(*) OVER () AS total_count FROM big_view WHERE ...
В цьому випадку значення для всіх рядків буде однакове, тож залишається взяти total_count з найпершого рядка, і задача розвʼязана.
15.01.2023
Як рахувати кількість підписників RSS з Vercel та Plausible
Сьогодні склав цікавий пазл та зробив для блогу збір аналітики по RSS. Колись для цього використовував різні проксі, наприклад, Feedburner. Та вже декілька років я віддаю перевагу RSS стрічці на власному домені, разом з самим блогом. А блог в мене давно розміщений на різних статичних хостингах, останнім часом — на Vercel. Все чудово, але статистику по відвідуваннях стрічок він не надасть. Не дасть її й безплатний план Cloudflare. Так що нарешті додав аналітику власними руками. Ну, точніше, збирає аналітику Plausible, а мені залишається тільки відправляти туди відповідні події.
Щоб відстежити запити до RSS стрічок, я підміняю їх на запити до хмарної функції. Для цього у Vercel є правила перезапису адрес. Функції можна писати прямо на TypeScript. У іншому це звичайні обробники за стандартом ExpressJS. Вони мають доступ до всіх файлів зі збірки проєкту. Звідти функція забирає RSS, згенерований Hugo, та віддає споживачу. Ну й ще робить запит до API Plausible. Класно, що весь проєкт разом можна запускати локально, командою vercel dev.
Нюанс: правило перезапису не може мати таку саму адресу, як і файл зі збірки; точніше, файли мають пріоритет. Тому, щоб перезаписати RSS стрічки, потрібно було перенести їх в інше місце; я приписав до них суфікс-розширення. Ще нюанс: функція не може перевищувати 50 МБ, а за замовчуванням у пакет потрапляють абсолютно всі файли. Проте опцією excludeFiles можна прибрати зайві; я просто прибрав світлини.
Далі, щоб зареєструвати подію, потрібно викликати API Plausible. Раніше цей API був напівприватним, прихованим, а тепер доступний офіційно. Ми його вже використовуємо для аналітики додатка iOS. У випадку з RSS, достатньо одного типу події, з відповідними властивостями. Це, звісно, агент браузера та IP користувача, а також URL стрічки (бо в мене їх декілька).
Тут теж нюанс: заголовок X-Forwarded-For, що містить IP користувача, не передається з CloudFlare через Vercel. Тобто Vercel його затиратиме. Але немає проблем, бо CloudFlare дублює заголовок своїм нестандартним CF-Connecting-IP, а він передається нормально.
Нарешті, агент браузера я також передаю як власну властивість події, тому що мені здається, що великі агрегатори RSS на кшталт Feedly можуть передавати в рядку агента внутрішню кількість підписників. Але це ще подивимось.
Отак. Здається, все просто, але це не враховуючи нюансів.
14.01.2023
Сни
Сьогодні на Старий Новий рік буде трохи дивний пост. Про сни.
Сон — це унікальна можливість побачити, як працює наша підсвідомість. Так, сни як правило складаються з нісенітниці. Але ця нісенітниця проінформована нашими думками та почуттями. Часто ці думки є пригніченими, тому наяву вони ще більше здаються безглуздими. І тому ще важливіше приділити снам увагу.
Наприклад, якщо у сні людина, з якою в тебе чудові відношення, раптом свариться та каже неприємні речі, то ці речі походять з твоєї уяви, або тривоги, про ставлення цієї людини. Або уяви про себе в очах інших. Раніше я б відкинув такий сон як неприємну мару, але тепер намагаюсь виділити головне і спитати себе, звідки воно береться. Звісно, про саму людину воно нічого не каже, а ось про те, що ми думаємо — каже таке, що інакше можна дізнатись тільки у психолога чи у глибокій медитації.
Так само зі снів можна дізнатись про свої приховані фрустрації, мрії, думки. Наступного разу, замість “присниться ж таке”, використай це як нагоду узнати себе краще. А ще, я помітив, що снів немає, коли я не висипаюсь. А висипатися треба. На добраніч!
13.01.2023
Огляд монітора Sony inZone M9
Набридло чекати на появу кращого 4K монітору, тому замовив 27-дюймовий Sony inZone M9, який вийшов минулого літа, та він нарешті приїхав з США. Обирав його довго, бо 4K монітори всі бракують тієї чи іншої функції, та й взагалі “маленьких” 4K моніторів на ринку мало. (До речі, в цьому році на виставках почали зʼявляться OLED монітори, тож, мабуть, тепер варто ще почекати.)
Про хороше: 27 дюймів та 4K, звісно. HDR! Локальне затемнення! Матова поверхня. Працює по USB-C через док (Caldigit TS3) з підтримкою HDR та VBR. Краще прокидається від сну, ніж LG Ultrafine. Немає засвітлення, як у LG Ultrafine. Багато входів. Вбудований KVM switch (це можна поруч поставити ноутбук та ігровий ПК та перемикати клавіатуру + мишу автоматично через монітор).
Про погане: 1) якась потворна ніжка — але я планував ставити його тільки на штатив VESA; (до речі, люблю ось цю модель штатива від Розетки.) 2) Прошивка оновлюється тільки з Windows. Та оновлення варто було зробити, тому що виправили нюанси пробудження монітора зі сну, а не дрібниці якісь. Що ж, відтягнув монітор до медіацентру, поклав на крісло, під’єднав до ПК, оновив. 3) Мак не вміє регулювати ані яскравість, ані гучність монітора. Гарно що є чудова утиліта Lunar, яка вміє. 4) До речі, динамік у ньому суто символічний, навіть в айфоні краще. 5) Локальне затемнення при роботі з текстом на темному фоні, або взагалі з однотонним темним фоном, створює плями підсвітки навколо яскравих елементів — навіть курсору. Але його можна відключати.
Якщо підсумувати, то, мабуть, не раджу купувати його для макбуків, бо монітор потребує “особливого набору навичок”. Шкода, бо апаратна частина вражає.
12.01.2023
Мій настінний календар
Сьогодні нарешті опублікував на GitHub свій генератор настінних календарів з багатою історією.
У 2015 році автор коміксу Wondermark поділився своїм настінним календарем. Ідея календаря проста: він складається з нескінченного потоку аркушів, кожний з яких повністю заповнений сіткою з днів. Ідея мене зачепила моментально. Дуже зручно бачити на стіні цілий рік. В мене він висить над кухонним столом. На ньому можна малювати, закреслювати дні, планувати — причому всією сімʼєю. Набагато наочніше, ніж в електронному календарі.
Після того, як оригінальні аркуші закінчились, я вирішив написати генератор, щоб надрукувати більше. Генератор збирає PDF-файл бібліотекою jsPDF. Формат PDF - це, по суті, векторний графічний формат: в ньому кожний рядок тексту та кожна фігура задаються та позиціюються окремо. У розверстці календаря є трошки ручних налаштувань, щоб все влізло, але окрім цього згенерувати сітку з днями зовсім нескладно. Також я вирішив додати до календаря свята та інші сімейні дати, відомі заздалегідь.
З таким календарем на стіні ми живемо вже років пʼять. Весь цей час (а точніше, кожний новорічний сезон) я хотів зробити з генератора календарів продукт. Минулого року нарешті спробував. Але виявилось, що генерація PDF - досить складний для монетизації процес. Тож генератор залишався неопублікованим.
От, нарешті, викладаю його у відкритому вигляді, для тих, хто готовий запускати скрипт локально. Або завантажити вже згенерований календар на найближчий рік з українськими святами.
11.01.2023
Антіпаттерн Reselect: один селектор залежить від даних іншого
const category = useSelector((s) => selectCategory(s, expense.categoryID));
const goal = useSelector(
(s) => category?.goalID && selectGoal(category.goalID)
);
Антіпаттерн Reselect - коли один селектор залежить від даних іншого. Поганого тут те, що селектори створюються у момент виклику компоненти. А значить, щоб повністю отримати або оновити дані, доведеться неодмінно викликати її двічі — для отримання результату першого селектору, а потім другого. (От вам і несподіване уповільнення.)
Така ситуація складається через бажання робити селектори надто абстрактними та універсальними. (Особливо іронічно буде, якщо подібний код повторюється кожного разу, коли потрібна категорія.)
Щоб спрямити антіпаттерн, треба сконструювати селектор, який відразу повертає всі необхідні значення. Якщо він повертатиме складений тип — обʼєкт чи масив — не забути зробити селектор стабільним. Тоді не тільки можна буде уникнути подвійного виклику компоненти, але й, можливо, взагалі її не викликати, якщо результат селектора не змінюється.
Як правило, компоненти React мають містити тільки логіку відображення даних. Логіка комбінації даних належить до шару Reselect.

