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

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

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

17.07.2025

Мови програмування та їхні узбіччя

Обговорення попереднього поста про jsdate.wtf нагадало мені чомусь старі гоночні ігри. Є серед них такі, де як тільки зʼїжджаєш з дороги, починаєш їхати повільніше та повільніше, аж поки не “завʼязнеш” десь в багнюці. А є NFS: Underground, де межа дороги є стінкою, по якій навіть можна досить успішно ковзати.

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

Але чим далі від дороги ти відхиляєшся, тим важче просуватися далі, через накопичення нечіткої логіки. Та тим довше потім вертатися назад — чистити програму. Причому — і це головне — в JavaScript ця відповідальність лягає на плечі програмістів, а не мови. Поганий парсер дат? А що ж ти в нього сміття тулиш, треба було перевірити рядок заздалегідь. І таке інше. В цій моделі JavaScript - це такий Mario Kart мов програмування.

Те, що мова динамічна, ще не робить її маріокартом. Наприклад, в Ruby немає неявного перетворення типів, та й парсер дат суворіший. Хоча, певно, всі динамічні мови схильні “увʼязати”. А статичні теж є різні: схильність Swift викинути помилку “цей код надто складний, щоб я міг його перевірити” ще і як дозволяє завʼязнути.

Як протилежний приклад (з мені знайомих): в Go незалучена змінна вважається помилкою. Хочеш ти або не хочеш, але зайвої змінної мати не будеш. Мова за тебе про це попіклується. Навіть коли ти не дуже гарно знаєш, куди рухатись, “стінки” мови тебе спрямують.

Хоч звучить що стінки — це завжди гарно, але є й третій варіант, коли замість стінок ти падаєш з траси. Таку асоціацію навіюють помилки C++ чи Clojure. Багато коли краще вже “проскочити по узбіччю”, ніж ретельно уникати всіх помилок.


16.07.2025

Реєстр сервісів AWS Cloud Map

Трапляється що треба дізнатися повний перелік запущених копій сервісу. Зазвичай щоб встановити між ними синхронізацію чи кворум. На AWS в такому випадку можна не вигадувати педальників, а скористатися готовим сервісом AWS Cloud Map.

Воно звучить страшно, але насправді технічно досить простий сервіс — це лише приватний (або навіть публічний) сервер DNS, з автоматичною реєстрацією копій сервісу. От в цій автоматичній реєстрації вся суть.

Тобто можна у сервісі ECS вказати область імен AWS Cloud Map, та він буде там реєструвати кожну нову задачу. А взагалі AWS Cloud Map підтримує практично всі різновиди сервісів в AWS - від EC2 до Lambda.

На виході отримуємо локальне імʼя вузла (myservice.local), яке можна через локальний DNS розвʼязати у список активних копій. Ну є ще API DiscoverInstances, але мені ним не доводилося користатися.

Бо виявлення копій через DNS це доволі типовий сценарій, тоді достатньо вказати в конфігурації то локальне імʼя та решту сервіс робить самостійно. І не треба ніяких редісів, куди ми пишемо під час запуску, а потім ще й переклик робимо, щоб не було зомбі.

Все це трохи нагадує домашній mDNS, але не дуже. Бо хоч Cloud Map так само надає можливість перелічити сервіси, та відбувається це через центральний сервіс. Інакше в хмарі складно було б уявити.


15.07.2025

Віддалений робочий стіл з Mac на Mac

Можна сказати, що в той день, як я завів окремий Mac Mini для роботи, було передбачено навчитися працювати з ним віддалено. Бо звичайно для Mac Mini потрібний монітор, що привʼязує до робочого місця — а я час від часу маю потребу бути в розʼїздах. Кинути в валізу Mac Mini легко, та й Macbook завжди з собою… тільки як їх поєднати?

З наївних рішень: використовувати Macbook буквально як монітор не вийде. А було б гарно.

Тому перша задача: поєднати в мережу. Якщо є локальна мережа чи знайомий Wi-Fi, то достатньо один раз налаштувати на Mac Mini. Але в сценарії готелю так не вийде. Якщо є кабель Thunderbolt (виглядає як USB-C, а коштує від 40 доларів), то можна ним зʼєднати, тоді маки створять мінімережу. А якщо натомість є більш корисний адаптер Ethernet (за ті ж гроші!), то можна зʼєднати макбук з макміні звичайним мережевим кабелем. Так в них теж зʼявиться локальна мережа. (А адаптер Ethernet для макбука взагалі річ незамінна.)

Тепер найпростіше — роздати з ноутбука інтернет (бо ноутбук буде на готельному Wi-Fi, чи може на телефонному, а на макміні все це налаштовувати це зайве.) Для того в налаштуваннях макбуку General -> Sharing -> Internet Sharing, та дозволяємо роздавати на наш адаптер Ethernet.

Залишається підʼєднатися до робочого столу Mac Mini. Для того є вбудований Screen Sharing, який заздалегідь потрібно увімкнути на Mac Mini (General -> Sharing -> Screen Sharing.) Тут ніяких додаткових дозволів не потрібно; тепер з макбука заходимо в Finder -> Network, обираємо наш макміні та натискаємо кнопку “Screen Share”.

Вводимо логін/пароль облікового запису. Бачимо два варіанти роздачі — обираємо “швидкісний”, бо “простий” буде огидно розмитий. До того ж у швидкісному режимі можна натиснути нагорі вікна “динамічну роздільну здатність”, та вона зрівняється з ноутбучною (ну й розкриваємо вікно на весь екран, звісно). Після цього сеанс не відрізняється від локального, всі комбінації клавіш працюють, а перехід відбувається як між віртуальними робочими столами. Супер!

Але після перезавантаження стикаємось з величезною проблемою. Мак із шифруванням диска потребує вводу пароля, щоб завантажитись! Причому це дозволено тільки локально, бо на цей момент запущений тільки мінімальний завантажувач. Звісно, вимикати шифрування я не збираюся. Тому поки придумав возити з собою ще й клавіатуру. Ввести пароль наосліп зовсім нескладно, якщо бути до цього готовим. Причому що гарно, навіть Bluetooth-клавіатура спрацює.

Ось так, наче цілком робоча конфігурація. І це в мене ще старий макміні, а сучасні взагалі кишенькового формату, хоч в кавʼярню носи.


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”. В ньому перелічені чи не всі приклади з jsdate.wtf, хоча, на жаль, нема пояснень, звідки саме взялася спадщина.

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

Оцю всю кухню доводиться підтримувати досі, бо браузери хочуть мати найбільшу сумісність, а ніяких версій в 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% місця вільним, щоб диск не перенапружувався. Та й дійсно, за моїм досвідом, якщо місце закінчується, починаються гальма. Так що доводиться повертатися до прибирання раз на пару місяців.