Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

14.07.2025

Чому в JavaScript такий хаотичний парсер дат?

Поділилися на роботі тестом “на знання парсера дат JavaScript” jsdate.wtf. Так, там багато дичини, так, я навіть на 50% не вгадав, але це всі й так знають. Краще з’ясуймо, чому?

Спочатку можна зайти в специфікацію ECMAScript. Тут все цілком притомно: є один-єдиний формат YYYY-MM-DDTHH:mm:ss.sssZ з невеличкими варіаціями. Ну й хіба те, що аргумент обовʼязково перетворюється в рядок. Все решта… на розсуд реалізації.

Тепер до реалізації. Тест написаний для Node.js. Втім, варто знати, що Node.js побудований на рушії V8, який також використовується в Chrome (та інших місцях.) Тож шукати подробиці будемо в коді V8, який, до речі, написаний на C++. Знаходимо там метод ParseDateTimeString, а з нього - DateParser::Parse. Тут з першого ж погляду бачимо коментар, який пояснює наявність “legacy dates” nf

Можливо, ще цікавіше подивитися на юніт-тест парсера, бо в ньому перелічено багато прикладів. З прикладів можна побачити, чому дивно інтерпретуються короткі значення років та чому має сенс пропускати рядки на початку (як-от день тижня.) Єдине, що мені досі незрозуміле, це пропуск змісту (дужок).

Оцю всю кухню доводиться підтримувати досі, бо браузери хочуть мати найбільшу сумісність, а ніяких версій в JavaScript немає. Отак і живемо, і далі жити будемо, аж поки JavaScript не переродиться чи не помре. (Як гадаєте, побачимо таке?)

У власних проєктах я б радив уникати таких вбудованих методів та використовувати бібліотеку, як-от dayjs, де ви самі контролюєте версію та формат. Та точно не використовувати Date.parse() для валідації! А від хиб на кшталт Date.parse(-[]) захистить, як завжди, TypeScript.

Так що, як бачите, річ не у тім, що парсер погано написаний, та не в тому, що специфікація погана. Спадщина — ось найбільший тягар JavaScript.


13.07.2025

Ретроемуляція

Є в мене маленька мрія купити маленький телевізор з CRT, та підʼєднати до нього маленький сучасний компʼютер, заряджений емуляторами консолей епохи 90-х. Є така думка, що на CRT старі консолі дають кращу картинку. Приблизно як оригінал картини маслом проти її світлини. Отже, можна було б зробити таку ретромодернізовану консоль та насолоджуватися. Вийшло все не так просто.

Спочатку, про програмну частину. Мене цікавлять консолі десь з NES до PS2. Емуляція консолей до покоління Sony Playstation включаючи — розвʼязана задача та була такою ще 15 років тому. На неї здатні зараз навіть недорогі кишенькові пристрої, як-от в мене Anbernic RG353M. А ось на покоління PS2 здатні тільки повноцінні компʼютери (мій Macbook Air M2 чудово впорався), а не ніякі Raspberry Pi тощо. Як мінімум можна знайти NUC з пасивним охолодженням.

(Якщо цікаво зануритися в емуляцію, то раджу подивитися на RetroArch та цілий дистрибутив Linux Batocera.)

Але підʼєднати сучасний компʼютер до CRT значно складніше. Я спочатку надіявся на композитний вихід в Raspberry Pi, тобто наче все сходилося. Але як виявилося, по-перше, в RPi 5 його вже немає, а по-друге, це ніяк не “plug and play”.

Той композитний вихід підтримував деякі екрани в деяких режимах, в першу чергу - як я зрозумів - CRT монітори, а не телевізори. Бо монітори краще пристосовані до компʼютерного сигналу.

Якщо коротко, то CRT технологічно повністю відрізняються від сучасних матричних екранів. Картинка будується струменем електронів, які накидаються рядками на екран. Тут немає, немає ніяких пікселів та сіток. Все, що робить компʼютер, це модулює цей струмінь. Це аналоговий сигнал — так само як і звук, тільки вимоги до синхронізації вище. Отже, для перекладу з цифрового виходу як-от HDMI у щось, що зрозуміє CRT, потрібний цілий цифро-аналоговий перетворювач (ЦАП). І нормальні такі ЦАПи не дешеві, бо вони повинні покривати різні режими входу та виходу. Рекомендований HDFury X4 коштує $289. Та це практично ще один компʼютер!

До всього того ще потрібно знайти телевізор, який не тільки показуватиме гарну картинку в принципі, а ще й буде сумісним з ЦАПом, бо як виявляється, в аналоговому світі значно більше шансів зіпсувати картинку, ніж в цифровому — розʼєми, вирівнювання, синхронізація, шуми тощо. А мета ж отримати не якусь, а гарну картинку, та надійну, щоб не боротися за неї щоразу як хочеш пограти.

Отже, який тут я зробив висновок. Якщо в тебе є консоль аналогового покоління, то можна до неї знайти відповідний телевізор та мати принаймні автентичну систему. Але якщо ти вже емулюєш, то може кращого успіху отримаєш від гарної емуляції CRT шейдерами на OLED 4K. А зі справжніми консолями я звʼязуватися не буду, оці всі “подуй на картридж” та “потри диск на щастя” згадувати не хочеться.


12.07.2025

Спочатку копіювати, а потім рефакторити

Час від часу доводиться переносити великі шматки коду в нове місце. Від копіювання цілого підпроєкту в новий проєкт до перенесення класу в більш доцільне місце.

Неодмінно в такій ситуації бачиш технічний борг — який теж доведеться переносити. Та приходить ідея — навіщо переносити борг, якщо саме зараз можна його на корені переписати так, як краще? Оце ніколи так не треба робити!

Чому? Бо перенесення створює багато змін. На їхньому тлі не буде зрозуміло, що рефакторили, а що просто перенесли. Та коли в цьому коді виникнуть баги, історія версій мало допоможе в пошуку.

Найкраща історія — це кожен коміт містить маленькі, повʼязані між собою зміни. Тоді відкрив блейм з того рядка, в якому баг — та бачиш, що ще міняли, та що взагалі автор хотів цим сказати. А коли блейм вказує на зміни в 1000+ рядків, де більшість коду просто скопійована — штибу з нього не буде. (Не раз мені доводилося потім відшукувати попереднє розташування коду та вручну порівнювати.)

Отже, мій підхід такий: спочатку пересуваємо код з мінімальними змінами. Навіть можна, щоб він не працював відразу. Наступними комітами приводимо у притомність. А потім можна й рефакторити. Причому рефакторити краще взагалі після того, як пересування прийняте та перевірене, щоб знов-таки знати, чи відносяться баги до перенесення, чи до рефакторингу.

Таким чином в нас буде один коміт з пересуванням. Він буде величезний, але головне знати, що в ньому нема змін за змістом. А далі вже — маленькі, змістовні коміти. Чудова історія для майбутнього пошуку!

Те ж стосується й пересування в новий репозиторій — навіть важливіше — бо будемо знати, звідки взялася початкова версія коду.


11.07.2025

Звідки беруться мої пости

Натрапив на статтю Why I Blog and How I Automate it та відразу сповнився надією — невже можна щось автоматизувати? Виявилося, що автор просто має на увазі публікацію зі статичних файлів… що, прямо скажу, не найскладніша частина процесу.

Але, як все в мене. Як і справжні стендапери (ті, що коміки), я постійно знаходжуся в пошуках матеріалу. Тільки в мене це зазвичай робочі моменти, де застосовуєш якийсь підхід чи правило, які вибиваються з рутини. Наприклад, сказав десь “краще спочатку скопіювати, а потім рефакторити”. А чому я так сказав? Корисно навіть самому собі пояснити, звідки переконання беруться.

Ну й друге джерело — це цікаві знахідки в роботі. Які, на жаль, не кожен день трапляються. Інколи вдається створити прецедент штучно — спробувати новий інструмент чи бібліотеку. Це теж корисно саме по собі.

А далі — залишається написати. В мене раніше була звичка полірувати та розписувати статтю місяцями (зрозуміло, не безперестанно.) Публікувати щодня — це вірний від того порятунок. Знаю, є різні підходи, що спрощують написання — як-от написати план, а потім його послідовно розкривати. Але в мене підхід прямий — писати від початку до кінця. Потім перевірити граматику — та в друк.

З усього цього найскладніше — це помітити матеріал. От якби це автоматизувати…


10.07.2025

Спроби закинути зміст з macOS на iOS

Є в мене чудовий однокористувацький застосунок CoreGTD, в якому я веду справи. Він всім гарний, але: дані зберігає на ноутбуці. Тож і переглянути їх можна тільки на ноутбуці.

Загалом мене це влаштовує, та керувати задачами з айфона потреби не виникає. Але бачити їх хотілося б, щоб не залазити кожен раз в ноутбук. На цьому й збудував задачу: скидати список задач в текстовому вигляді якось на айфон. Вийшло не так просто, як гадалося.

Я точно не хочу використовувати Apple Reminders. Хоч це й виглядає логічним. Вже стикався з втратою даних там та не хочу спричинити ще одну — нехай Reminders будуть для мінімального набору рідко змінюваних нагадувань.

Швидкий пошук знайшов програму Any Text. Вона вміє те, що я хочу: текст входить на ноутбуці, виходить на телефоні. Якщо вставляти текст вручну. Але як це робити з коду? На жаль, можливості немає — бо єдиний спосіб автоматизації тут це Shortcuts, а вони не доступні програмно. Було б краще, якби був AppleScript, але то зараз не модно впроваджувати.

Так само й Drafts все вміють, окрім отримати текст. Drafts мають дуже багато функцій для вивантаження тексту — аж навіть скрипти на TypeScript можна писати — але не дають програмно оновити зміст нотатки. Тобто це “віяло назовні”, а не “віяло всередину”.

Є ще Apple Notes… тут можна оновити зміст нотатки через AppleScript! (До речі, для того у Swift є фреймворк NSAppleScript.) Майже перемога! Наступний квест: стикнувся, що мій застосунок не може просто керувати іншим застосунком — на то потрібний дозвіл. Дозвіл буде запитано автоматично… проте тільки якщо вірно заповнити Info.plist та entitlements. Оце коли всі ці секрети доробив, то нарешті отримав оновлену нотатку. Успіх!

Звісно, оновлюється воно не прямо миттєво, але це мені тут і не треба. Тепер залишилося ще генерувати зміст нотатки та можна буде перевірити у ділі.


09.07.2025

GitHub API з GitHub Actions

Є у нас ситуація, де на GA треба обчислити diff між двома комітами. Причому вони можуть бути на абсолютно різних гілках. Як це було зробити ефективно?

Зазвичай на GitHub Actions всі використовують actions/checkout, щоб витягнути зміст репозиторію. Втім, нормально ця дія витягує тільки останній коміт (тобто глибину 1.) Бо це рятує дорогоцінні секунди. Втім, з такою глибиною цей diff ніяк не зробиш.

Можна витягнути й весь зміст — якщо передати в actions/checkout параметр fetch-depth: 0. Це задачу розвʼязує, але ціною… наразі 15 секунд очікувань. Чи можна зробити якось швидше?

Звісно. можна! Згадаймо, що в GitHub є API. А в ньому, зокрема, є й порівняння комітів. Ми користуємось цією можливістю кожен раз, як створюємо PR - але так само можна зробити й зі скрипту на CI.

Щодо доступу: в задачі GitHub Actions завжди є токен для API - він сидить в secrets.GITHUB_TOKEN. Ось тут більше про його права, але на перегляд репозиторію права точно будуть.

Отже, замість операцій з Git роблю виклик GitHub API, та отримую на виході JSON, в якому зокрема є ключ .files, де перелічені всі файли зі змінами. Та ще на відміну від Git, не треба парсити ніякі цікаві формати. Береш jq та готуєш такий результат, який потрібний.

В результаті — замість 15 секунд ну може 1-2. Дрібниця, але важлива.


08.07.2025

Очищення жорсткого диска

Знаєте як, скільки б не був диск, завжди забивається. Особливо коли на ньому є XCode, бо там не встигнеш озирнутися як в тебе вже 5 версій симулятора по 10 Гб кожна.

CleanMyMac мені серйозно не подобається за агресивний маркетинг (підписатися на розсилку? пане, я й так вже його завантажив через ваш же ж Setapp.) Але справу видалення зайвих файлів він виконує дійсно вправно. Зокрема, вичистити всі непотрібні версії XCode можна одним натиском кнопки, а вручну це ціла історія. Також і файли журналів, і різні кеші, які в розробників завжди накопичуються — не кеш браузера, а кеш Yarn, наприклад.

Але якщо без нього… AppCleaner - маленька утиліта яка видаляє разом із застосунком всі допоміжні файли. Яких, повірте, є чимало.

DaisyDisk - вірний помічник в пошуку витрат. Зазвичай нагадує, що в мене сидять нестиснуті відеозаписи, які давно пора обробити. (Але я ось-ось доберуся, тому переписувати на зовнішній диск невчасно.) Жарти жартами, але насправді часто місце займають файли, які не ти туди поклав. Наприклад, Хром чомусь залишає кілька версій оновлень.

Docker любить поїсти місце, особливо якщо збирати багато образів. Тут тимчасово допомагає docker system prune та зменшення розміру його диску.

Купа старих версій мов, встановлених з asdf - класика. Бо кожна версія ще ж зберігає всі бібліотеки наново.

Величезні файли нейронних мереж — проблема сучасності, якщо ти запускаєш щось таке локально.

А окрім того, нагадую, що на SSD рекомендовано тримати 10% місця вільним, щоб диск не перенапружувався. Та й дійсно, за моїм досвідом, якщо місце закінчується, починаються гальма. Так що доводиться повертатися до прибирання раз на пару місяців.


07.07.2025

Вашій системі Мабуть/Колись потрібний огляд

Оце рівно рік тому писав, що вам потрібна система “мабуть/колись” - місце, куди можна спокійно відвантажувати всі справи, які зараз немає можливості або сенсу робити.

Проте на ділі таке спокійне відвантаження можливе тільки за обіцянкою, що в деякий момент ти повернешся до тих нотаток на майбутнє. Бо інакше це не “мабуть/колись”, а смітник. Оце сьогодні можу з гордістю сказати, що моя більше не смітник!

Система огляду дуже проста. Мої мабуть/колись це купа файлів-нотаток, десь близько 50. Я зробив список для огляду, де кожен файл треба помітити галочкою. Оце й все.

Під час тижневого огляду вдалося досить швидко (може, за годину) передивитися всі ці нотатки. Я спочатку не вірив, що це можливо. Але виявилося, що перебирати прямо кожний пункт не потрібно. Достатньо широким оком оглянути черговий файл (“машина”, “ідеї проєктів” тощо) та прийняти рішення, що по цій області мені зараз нічого нового брати не треба.

(Та так, в мене “Мабуть/колись” умовно поділяється за тематикою. Важливо уникати надто великих скупчень, бо в них потім загубишся. Як бачу, що таке назріває, ділю нотатку далі.)

А в кількох файлах знайшлося й те, що на часі! Та пішло в проєкти. Нарешті замкнулося велике коло, та я можу впевнено перекладати поточні проєкти в “мабуть/колись”, бо дійсно матиму шанс побачити їх в майбутньому.

Є й досвід повного перегляду деяких нотаток. Наприклад, я збираю покращення для житла в файли покімнатно. От був момент, коли я відкрив умовно “кухня”, перебрав все, що там є (може з 40 пунктів), розставив пріоритети та дещо поставив в роботу. Тут багато залежить від теми та завантаженості.

Так що все ще раджу завести такий каталог майбутніх справ, це й на роботі й вдома велика допомога.


06.07.2025

Ітеративна розробка свого проєкту з ШІ

Сьогодні чимало встиг зробити для свого застосунку для GTD. (А саме, впровадити області фокуса, а також керування файлами з Obsidian. Але специфіка не так цікава.) Практично все робив через агент в Cursor. Хочу сказати, що це робота, яка без агента не відбулася б, в мене просто не було б цих надбань, бо на ручне виконання пішли б дні, яких в мене немає та не буде.

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

Мій рівень комфорту з агентами — це короткі інструкції, результат яких я можу легко перевірити. приблизно на такому ж рівні, якби сидів в парі з людиною:

# перший запит, на модель
let's add an "Areas of Focus" class to the model

an  Area of Focus has id, name, notes, creation date
a Project  should belong to an area of focus (non required for now)

# наступний - вже на доповнення UI

the project row view should have a select box to choose its area of focus, below notes
it should only be visible if project row is focused (similar to notes field)

# та третій - інше місце в UI

in the projects list, projects should be displayed by section; section = area name
in the end, all projects with no area under "Unfocused" section

…Тільки все відбувається на порядок швидше, ніж з парним програмуванням. Після кожного кроку перевіряю, інколи підчищаю, та роблю коміт. Таким чином я залишаю за собою контроль якості, та коли ШІ робить щось негарне — то прошу виправити. Наприклад, коли помітив, що в новому коді повторюється схожа логіка:

the remainingSomedayMaybeForReviewCount method seems to duplicate somedayMaybeFilesForReview and scanSomedayMaybeFolder

let's have only one method to list files from the directory, and normalize their filenames

Та на виході отримую підчищену версію. Ось так, короткими кроками, можна за півдня встигнути додати не одну, а цілих дві серйозних можливості.


05.07.2025

Bose Ultra Open Earbuds - можливо, мої ідеальні навушники

В мене такі вуха (чи звички?), що навушники, які у них вставляються, ніяк не влаштовують. Тому вже деякий час знаходжуся в пошуках. Іншою вимогою є прозорість — тобто можливість чути та спілкуватися, не знімаючи навушників. Та ще й щоб легко було носити з собою (що практично виключає накладні.)

Та нарешті, після кількох невдалих спроб (Sony LinkBuds - бррр, жахливо), нарешті знайшов те, що треба - Bose Ultra Open. Такі навушники потребували певного кроку в невідомість, бо це очевидно щось поза категорією: замість того, щоб вставлятися у вуха, ці вдягаються на вушну раковину. (Хоча насправді ні, зараз навушників-кафів не так мало, ось з іменитих є ще Huawei Freeclip.)

Отже, Bose Ultra Open мають форму кафа, половина його тверда, а половина мʼяка силіконова. Тверда частина йде вперед, в ній динамік. Мʼяка закінчується циліндриком з батареєю, він йде за вухо. Вдягається ця штука не складніше та не довше, ніж звичайні затички. Сидить в нижній частині вуха. Вони дуже легкі та практично не помітні, що мені особливо подобається. Бо на відміну від затичок чи вкладишів такий навушник абсолютно не заважає слуху. Хоч цілий день його носи.

Щодо звуку — він на диво гарний. Навіть баси присутні. Треба розуміти, що з таким розташуванням динаміку їм ніколи не зрівнятися з затичками чи моніторами. Головним чином, це проявляється в чистоті. Але я б порівняв це звучання з парою хороших колонок. Також на диво мало звуку просочується назовні — як я розумію, це тому, що динаміки тут спрямовані.

По програмній частині: є multipoint, тобто між айфоном та маком перемикаються самі собою. Навіть є можливість, якої немає в AirPods - можна натисканням кнопки навушників перемикатися на інший пристрій. (Так, і, до речі, тут є справжня механічна кнопка!) Зате немає автопаузи. Що мене абсолютно не турбує, бо на відміну від AirPods, ці навушники не потрібно знімати. Натиснув на кнопку, поставив на паузу, поговорив та увімкнув далі.

З мінусів: мікрофони тут чиста формальність. Ну й ніякої ізоляції чи тим паче шумодаву не буде, очевидно. На гучній вулиці чи під перфоратор погано чутно. Футляр більш-менш зручний, але петля слабенька. До речі, як і самі навушники — не знаю, на скільки вистачить цього силіконового місточка. Втім, всі сучасні навушники живуть стільки, скільки батарея, тож може це й не проблема.

(Ще розглядав кісткові навушники, як-от Shokz. Але ті заважатимуть окулярам. Також звук кісткових навушників - це цікаво, але на мій досвід гірше за звичайні.)

Якщо підсумувати: це ті навушники, що можна носити всюди та завжди, вони не заважають слуху, цілком гарно співають та ще й по підключеннях проблем немає. Мрії збуваються!