Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
19.11.2023
Оптимізація зображень для вебу
Сьогодні в черговий раз прочищав блог від тих проблем, які встигли накопичитись. Для цього в мене безплатний акаунт Ahrefs, який без $99 на місяць небагато всього робить, проте надає корисний аудит проблем з сайтом.
Одною з проблем виявилось те, що в мене забагато великих світлин. Дивно, що Ahrefs не каже прямо, що таке “завелика” світлина, але за розміром найменшою такої зрозуміло, що поріг дорівнює одному мегабайту. Причому, не мебібайту, а саме одному мільйону байтів, що буде важливо нижче.
Звісно, виправляти це вручну — робота не для мене, я краще відшукаю утиліту та буду писати скрипт. Знайшов jpegoptim. Головне тут — це можливість вказати бажаний розмір результату… щоправда, в кібібайтах. Емпірично визначив, що якщо вказувати розмір у 920 кб
, то світлини будуть виходити точно менше за 1000000 байтів.
Залишилось знайти всі великі файли JPEG та застосувати до них jpegoptim
. Для того підійде стандартна команда find
. Єдине, що виявилось, що пару файлів я не хочу оптимізувати (вони стосуються глобуса). Тому додав також файл ігнорування, а щоб застосувати його, довелося дізнатися про команду comm - вона робить, фактично, операції над двома множинами рядків.
Нарешті, само собою, я хочу запобігти повторенню появі нових завеликих світлин. Тому до своїх лінтерів для блогу додав також такий, який просто знаходить файли та попереджає про їх наявність.
Остаточний скрипт, якщо він потрібний чи цікавий, можна забрати тут.
18.11.2023
Піраміда складності проєкту
Спостереження при доповненні додатка на Firebase + Redux Toolkit + React. Але воно, мабуть, справджується для інших архітектур теж.
Додаток мій поділений на три шари. Це інтеграційний шар для Firebase, шар логіки Redux Toolkit, та презентаційний шар React. Відповідна кількість коду зростає; інтеграція з Firebase це 1000 рядків, логіки десь в пʼять разів більше, а презентації ще в чотири рази більше ніж логіки.
Але ось, що я помітив: складність змін зростає у зворотному напрямку. Нововведення до моделі можна тижнями обмірковувати, а маленька зміна призводить до декількох годин роботи. Навпаки, коректувати компоненти React, поки загальна логіка залишається незмінною можна легко та швидко.
Думаю, таке розуміння важливо під час оцінки часу: зміни до різних частин проєкту, при однаковому на вигляд змісту, можуть розбігатися в оцінках на порядки. Власне, я до цього дійшов, коли після тижня швидкої та легкої роботи по React застряг на півдня з малесенькою поправкою до структури даних у Firestore.
Стосовно Firestore, складність обумовлена тим, що база фактично програмується мовою правил безпеки, та шукати в них помилки через використання додатка виходить дуже неефективно. Краще написати тести. Точніше, не так: інвестиції в тести окупляться саме в найскладніших частинах проєкту. Нехай вже компоненти React залишаються без тестів, головне, щоб модель була ретельно ними покрита.
17.11.2023
Struct, Data та OpenStruct в Ruby
Типу в продовження колишнього поста про структури в Ruby… Наразі в Ruby є три класи, які пропонують абстракцію “структури даних”. Чому так, в чому різниця, та який треба використовувати?
Насамперед про OpenStruct. Його офіційно рекомендують не використовувати (та видають попередження.) Чому так? Бо це буквально обгортка для звичайного хеша, написана на чистому Ruby. Та було б ще не так погано, якби це був просто хеш — але до нього додається ще метапрограмування, що й утворює не дуже швидкий клас — особливо для низькорівневого використання.
Тепер, Struct та Data. Це класи-брати. Вони мають спільний код та написані на C. Тут для збереження полів використовується масив, а методи утворюються без метапрограмування. Така реалізація і працює швидше, і займає менше памʼяті. Обидва класи утворюють звичайні класи-нащадки, з якими можна робити що завгодно, в тому числі й додавати нові атрибути та методи.
Struct та Data майже у два рази швидше за хеш, тому їх використання має практичний сенс, коли треба зберігати багато однотипних обʼєктів.
Що ж таке новий тип Data? Це обрізана за можливостями Struct, де відсутні методи-сеттери та деякі інші. Технічно, іншої різниці ви там не знайдете. Семантично, це реалізація патерну “value object”, тобто простого незмінного набору даних. Ось детальніше на трекері Ruby. Звісно, незмінність в Ruby відносна та примарна, але все одно має сенс брати Data для коду у функціональному стилі.
До речі, запропонував та реалізував Data харків’янин Віктор Шепелєв.
16.11.2023
Автоматизація macOS: витягання поточної вкладки з Safari
Я останнім часом багато зберігаю закладок в Obsidian. Зазвичай просто копіюю посилання з браузера та зберігаю у належний документ. Все б добре, але хотілося б також автоматично отримувати й назву сторінки. А ще краще відразу ціле посилання у синтаксисі Markdown, щоб не робити його вручну.
Напевно, є безліч способів це робити, але більшість з них починається зі встановлення чогось новенького, яке отримує доступ до всіх моїх вкладок. Хотілося обійтись наявними засобами. З таких найбільш очевидними є засоби автоматизації macOS: Shortcuts та AppleScript.
Shortcuts - то нове графічне оточення для побудови скриптів з готових блоків. AppleScript - стародавня мова та оточення для автоматизації. Задачу можна розвʼязати кожним з них, як видно з ілюстрації. Також кожний зі скриптів можна привʼязати до комбінації клавіш Hyper+C
за допомогою BetterTouchTool (може й інакше, але я так звик.) Працює бездоганно, я дуже задоволений. 🥳
Є один важливий аспект. У Shortcuts кожна дія має налаштування приватності, тому під час запуску в тебе спитають дозволу на перегляд сторінки — причому дозвіл привʼязаний до домену, тому доведеться бачити його часто. У AppleScript немає таких детальних дозволів, тому тебе просто один раз спитають, чи дозволяєш BetterTouchTool (де-факто власнику скрипту) взаємодіяти з Safari. Тому я зупинився саме на рішенні на AppleScript.
…Ну й бібліотека засобів у AppleScript та Shortcuts абсолютно різна. На щастя, принаймні можна викликати один скрипт з іншого. Та й скрипти оболонки теж. Мак взагалі надзвичайно дружня для автоматизації система, якщо мати бажання.
(Минулої осені я як раз писав скрипт, щоб зберігати вкладки з Safari до Reeder. От, дійшла черга ці вкладки опрацьовувати та каталогізувати.)
15.11.2023
Вкладені підказки у VSCode; власні налаштування кольорів
💡 Десь близько року у VS Code є така функція, як вкладені підказки (inlay hints.) Вони додають “фантомний” допоміжний текст до коду — наприклад, назви параметрів функцій або їхні типи. В тих мовах, де це підтримується (а для мене це TypeScript та Go), це дуже зручно. Раджу відкрити налаштування, пошукати inlay
та подивитися на численні можливі підказки.
До речі, офіційної української локалізації VS Code немає, отже доводиться вигадувати переклади функцій власноруч.
Є один неприємний мені аспект цих підказок — колір, яким вони відображаються за замовчуванням. Він надто близький до звичайного тексту, тож важко стає відрізнити. А відрізняти важливо, бо ті ж типи деколи хочеться написати вручну, та незрозуміло, які з типів є “фантомними”.
🎨 Тому розібрався та знайшов налаштування workbench.colorCustomizations
. Ним можна перепризначити будь-який з контекстних кольорів. До речі, для підказок кольорів цілих три — перераховувати їх не буду, бо легко знайти в редакторі налаштувань знов-таки за ключем inlay
. (Не знаю, чи всі знають, що окрім графічного діалогу налаштувань у VSCode можна редагувати їх у вигляді JSON, для чого є команда Open User Settings (JSON)
.)
🌑 Отже, зробив у себе підказки блідішими. Нарешті, залишився останній нюанс — блідий текст у світлій темі стає яскравим у темній. А я один з прихильників автоматичного перемикання тем. Тому довелося знайти ще, що у colorCustomizations
можна вкласти ключ з назвою теми та вказати інші кольори для темної теми. Які й можна побачити на ілюстрації.
14.11.2023
Неочевидні блокери
Добре, коли роботу не виходить почати через зрозумілу причину. Бракує спільних рішень, не готова залежність, просто не вмієш це робити — тут легко вказати, чому не вдається почати.
Але є підступніші блокери — такі, які не повʼязані напряму з предметом роботи.
-
🕸️ Технічний борг. Код, над яким треба працювати, має застарілі норми. Або тести випадково падають на кожний пʼятий запуск.
-
🧰 Погані інструменти. Код доводиться вручну форматувати? Поки працюють тести, можна сходити за кавою? Симулятор завжди займає 100% процесору?
-
🚦 Пріоритизація. Є більш важлива задача, яка все ж не робиться зараз, бо ми вирішили робити те що робимо. Але думками все одно повертаємось до тієї іншої задачі. А поточна робота гальмує.
В таких та інших випадках можна на дні чи навіть тижні застрягти в одному місці, з повільним або відсутнім прогресом. Щоб розблокувати себе, без чуйки на проблеми не обійтись.
13.11.2023
Виправлення випадкових помилок в інтеграційних тестах на Go
Випадкові помилки в тестах мучать усіх нас. Причому коли практики виправлення інтеграційних тестів для вебу добре відомі, то в доморощеному та спеціалізованому пакеті доведеться шукати причини власноруч.
⏳ Очікування. Коли має справу з одночасним процесом, найбанальніша причина помилки — це те, що він не встиг доробити до очікуваного стану. Причому, як всі знають, в 99% випадків він, може, встигає, а на сотий раз — гальмує та валить CI. Щоб чекати, взяли модуль backoff - він трохи складніше ніж потрібно, але вже був в проєкті для інших потреб. Код попросту повторює перевірку, поки вона не справдиться.
🔍 Розуміння логіки. З одним з очікувань була інша проблема — ми чекали, поки не залишиться записів в проміжному стані. Інколи таке очікування не призводило до результату. Уважне вивчення документації виявило, що є інші проміжні стани, які ми не рахували (якщо просто, то перевіряли стан “активний”, але не стан “очікування”.)
🤹♂️ Перегони даних. Є в тестах такий сервер, який приймає посилання та складає в масив. Все б добре, але запис у масив не є безпечною одночасною операцією. Тож в рідкісному випадку, коли два посилання надходило одночасно, масив втрачав одне з них. Додаємо мьютекс та проблема зникає.
🐳 Залежності Docker. Нарешті, траплялось ще таке, що контейнер з сервером запускався раніше за Redis, намагався підʼєднатись та тут же ж виходив з помилкою. Звісно, траплялось рідко та зазвичай на CI. Не вистачало параметра depends_on.
12.11.2023
Альтернативи для QR-кодів
Чи можна ділитися посиланням якось окрім QR-коду?
Графічних кодів є багато різних, але вони всі мають один недолік: потрібний спеціальний додаток або інший читач. Я знайшов навіть таке дивне рішення під назвою SnapTag: сфотографувати код та надіслати світлину по СМС. Зрозуміло, така технологія не прижилась (а ще, зі Snapchat вони не мають ніякого звʼязку). Бо з нестандартних кодів, мабуть, найбільш відомий — це як раз коди Снапчату Snapcode. Але для поширення звичайного посилання вони не підійдуть.
А от справжня альтернатива - NFC-мітки. Це наліпка розміром десь від 20 мм з мікросхемкою всередині. Всі їх бачили на дорогих товарах в крамницях. Але в таку само мітку можна закодувати URL - місткість більш ніж достатня (100 символів та більше). Читаються NFC-мітки всіма сучасними смартфонами. Причому для читання достатньо піднести телефон на 10 см до мітки — не потрібно шукати вірний кут чи гарне освітлення. Якщо в мітці закодоване посилання, телефон його відкриє.
Щоправда, так люди робити ще не звикли. Можна спонукати їх інструкцією (або навіть наліпити зверху муляж QR-коду.) Та інша проблема — мітки треба програмувати. Програматор наразі коштує близько 1000 грн, а одна мітка — від 10 грн. Оскільки сама мітка малесенька, можна поєднувати її зі звичайною графічною наліпкою… або замовити друк наліпок з мітками, якщо знайти де.
Та інша сучасна альтернатива, до якої теж ще не всі звикли: просто надрукувати коротке посилання текстом. Сучасні смартфони здатні розпізнати текст камерою та відкрити посилання так само як і з QR-коду. (За досвідом, айфон хоч написаний від руки номер телефону зрозуміє.) Таке рішення мені подобається понад усе.
11.11.2023
QR коди: про зміст
Щоб вже закрити тему про QR коди: чи можемо ми впливати та, як зміст коду перетворюється на пікселі, тобто згенерувати інші пікселі?
Взагалі кажучи, ні. Алгоритм побудови коду надзвичайно складний та заданий жорстко. Наприклад, якщо дані не заповнюють всю місткість коду (бо місткість визначається розміром коду), то решта має бути заповнена конкретною послідовністю бітів.
В алгоритмі немає ніякого “зерна”, яке можна було б варіювати. Максимум, на що можна вплинути — це змінити маску, яка накладається на код, щоб розбити небажані послідовності. Але маска здатна тільки інвертувати пікселі за визначеним рисунком — так багато не зробиш.
Якщо зміст — це посилання, то є цікавий трюк: до посилання можна дописати як якір будь-яку послідовність, та таким чином наповнити код бажаними пікселями. Так працює цей генератор. То, напевно, найкрутіше, що можна зробити абсолютно без втрати якості коду. Ну, як — без втрати… насправді великі площі одного кольору теж погіршують читання коду, на то й зробили маску, щоб їх розбивати.
Є цікава наукова робота про QR коди з напівтонами. Виглядають вони ефектно. Проте тут напівтони утворюються розбиттям клітин коду на пікселі — та для кожної клітини обирається найкращий рисунок. Такий підхід це майже те саме, що перетворити вхідне зображення на напівтон та додати до нього “центральні крапки”, про які я писав вчора. Я не впевнений, що складний алгоритм зі статті — який закінчується розвʼязком Марковського випадкового поля — щось додає.
Ще є коди-рамки, про які пише Вікіпедія. Це правильне, стандартне розв’язання задачі поєднання коду з графікою. Але їх не підтримують типові читачі. Хоча Frame QR існує вже девʼять років. Ну, сам QR код існує двадцять девʼять, так що колись підтримка зʼявиться.
10.11.2023
QR коди з зображенням на тлі
Сьогодні малював собі наліпку з QR кодом. (див. вище) Попередньо, ідея була приховати QR код в малюнку. Зараз можна побачити багато підходів, в тому числі розмальовку кодів нейромережею, але при цьому є фундаментальні обмеження, від яких не втечеш.
-
QR код починається з маркерів у кутах. Маркери обовʼязково мають бути присутні та заповнені одним кольором, бо саме за маркерами сканер знаходить код у світлині. Але не обовʼязково квадратними — головне утворити лінійну послідовність в 1-1-3-1-1 діапазонів темного та світлого. До речі, QR код може бути й світлим на темному. А також не обовʼязково чорно-білим, бо в справжньому світі немає нічого чорного або білого — хоча чим вище контрастність, тим краще код буде читатись.
-
А окрім маркерів, весь інший зміст кожної клітини, як я це розумію, читається за центральним “пікселем” (світлини з камери) — він має бути темним або світлим. Емпірично, решта пікселів не має значення, тому на тлі QR-коду може бути будь-яке зображення. Навіть якщо тло чорне, а в центрі біла крапочка, то відповідна клітина QR-коду читається як біла. А якщо тло в цьому місці світле, то взагалі ніякої крапочки не потрібно.
-
Є ще популярний метод поставити посередині QR-коду логотип. Він дуже просто працює - QR-код використовує надлишкове кодування інформації, тож до 30% коду можна закрити будь-чим. Тільки нюанс: маркери та деякі інші функціональні зони коду в ці 30% не входять — тільки клітини з даними. Тож насправді логотип обмежений ще більше.
-
До речі, в кодах з більшою “місткістю” маркери займають меншу частину площі. Навіть з коротким змістом можна обрати більшу роздільну здатність, та отримати більш “акуратний” код (якщо скористуватися бібліотекою, яка дозволяє власноруч обирати місткість, а правильніше, версію, коду). Тільки треба розуміти, що доведеться його друкувати більшим форматом, щоб камера могла роздивитись клітини.
Чомусь по генерації QR класні бібліотеки написані на Python. Є qrcode, а є amazing-qr. Є ще CuteR, який краще поєднує код з зображення.