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

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

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

03.04.2024

Дизайн та юзабіліті скрипту для експорту рецептів

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

Я б хотів його вже опублікувати, але для того доведеться ще попрацювати маркетологом.


02.04.2024

Федеративна авторизація з AWS в GCP

Довелося мати справу з проєктом, де є компоненти в AWS та GCP. Наївне рішення в такому випадку — просто поділитися ключами доступу. Проте ключі можна вкрасти, тому всередині хмари ми користуємося ролями, що призначені з оточення. Дізнався, що роль AWS можна використати для авторизації в GCP - називається це Workload Identity Federation.

Яким чином Google може перевірити нашу роль? Трохи криво, але дієво: викликом AWS API GetCallerIdentity. Причому щоб Google міг зробити той виклик, ми формуємо параметри, підписуємо їх власним ключем AWS, та передаємо всі параметри в сервіс STS Google (зазначу, що ключі залишаються в нас - Google отримує тільки підпис). Той власноруч робить цей виклик, отримує у відповідь автентичну сутність AWS та, якщо їй дозволено вхід, видає нам ключ Google.

Окрему складність ставить бізнес-модель авторизації: є workload identity pool, який є “дверми в Google”; пул отримує доступ до конкретного service account, який буде нашим представником по всіх інших справах в Google Cloud. Але за авторизацію відповідає workload identity provider; в одного пула може бути декілька провайдерів. Як на мене, то дуже заплутано.

На щастя, більшість тих нюансів (в тому числі весь процес авторизації!) приховані в SDK. Тобто пул видає нам стандартний для GCP credentials.json, в якому міститься повна інструкція для клієнта, який буде запущений на AWS. (Нічого секретного: тільки інформація про засіб авторизації, та координати цільового service account.) Оскільки в межах AWS клієнт може отримати (тимчасові) ключі самостійно з оточення, то нам нічого більше робити не треба.

Тобто, коли вже розібрався, то все “просто”. До речі, так само Google підтримує авторизацію з OpenID Connect та іншими технологіями. Гарно!


01.04.2024

Декодування ненормального JSON у Go

Кастомизація розбору та генерації JSON у Go - один з підходів “вище середнього”, який робить цю типізовану мову зручнішою або взагалі, відкриває шлях до задач, які здаються неможливими.

Вчора зіткнувся з проблемкою: в JSON-LD в одних рецептах світлини передаються переліком об’єктів (в об’єктах - посилання, розмір, і т.д.), а в інших - просто рядком з посиланням.

В мовах з динамічним типізуванням це навіть не варто уваги. А в Go ми повинні задати конкретний тип JSON, який хочемо отримати. Та написати в ньому “рядок або масив обʼєктів” неможливо, бо в Go немає алгебраїчних типів.

Можна, як я колись писав, встановити тип interface{}. Результат можна розібрати або рефлексією, або перевіркою типів; в будь-якому випадку код буде багатослівний. Особливо якщо дані мають складну структуру, а не просто “рядок або число”.

Однак є й кращий спосіб. Це створити спеціальний тип поля та реалізувати в ньому метод UnmarshalJSON. Метод отримує на вхід []byte, та повинний призначити з них свій зміст. Не потрібно вручну парсити JSON; ми делегуємо це звичайному парсеру, коли зʼясуємо, який конкретний тип перед нами. Наприклад, в моєму випадку, я просто дивлюся, чи є перший символ лапкою ", та викликаю Unmarshal в рядок, а якщо ні — то в структуру.


31.03.2024

Організація рецептів з відкритими форматами даних

🌶️ NYT Cooking залишається моїм найнадійнішим джерелом рецептів. Так, New York Times за часи війни втратили мою довіру, проте розділ з рецептами це не стосується. Особливо подобається те, що кількість оцінок рецептів вимірюється тисячами, та коли рецепт на пʼять зірок — то можна бути впевненим, що вийде гарним. Мати надійні рецепти в одному місці, без прочісування інтернету — незрівнянно.

Але… є проблема. Всі інші рецепти я зберігаю в застосунок Paprika. На macOS та iOS це практично стандарт. Набридло мати (та шукати) одні рецепти в NYT Cooking, а інші — в Paprika. Захотілось перенести.

Крок перший. Як імпортувати рецепти в Paprika? Якби йшлося про один рецепт, то Paprika непогано імпортує з HTML. Але мені потрібний масовий імпорт. Спосіб є; з доступних форматів найпростіше підготувати YML. Тільки з чого?

Крок другий. Як отримати список збережених рецептів з NYT Cooking? Швидко помітив, що вебчастина це SPA. Гарна особливість SPA - можна знайти API, який його живить.Так і тут: є API recipe_box_search. От тільки як авторизуватися, щоб викликати його? Тут є хитрий спосіб: написати скрипт та виконати його в консолі браузера. В цьому та іншому випадках це простий шлях отримати дані з-під авторизації.

Крок третій. Як забрати дані рецепту? Авторизація для того не потрібна. Знайшов https://github.com/ianbrault/nyt_recipe, який конвертує рецепти в простий HTML. Проте він розбирає сторінку з рецептом — цей метод поламається як тільки зміниться дизайн сторінки. Чи є надійніші джерела? Спроби завантажити сторінку з розширенням .json та іншими не допомогли. Тоді помітив, що в сторінці є блок <script type="application/ld+json>. Такий блок містить дані сторінки в форматі JSON-LD для машин — пошукових рушіїв та іншого. У випадку NYT Cooking, він містить всі дані рецепту! Залишилось тільки конвертувати їх у формат YML, який очікує Paprika.

Який тут висновок: при вебскрейпінгу варто звернути перевірити, чи є альтернатива у машинному форматі - RSS, JSON, і так далі. Тоді не доведеться робити ненадійний розбір сторінок.


30.03.2024

Відстежування витрат електроенергії з Home Assistant

💡 Трохи актуального. Задався задачею стежити за власним електроспоживанням. Це виявилося цілком реально.

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

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

Пристрій Shelly EM має розміри пачки сірників та під’єднується по Wi-Fi. На радість, цей Wi-Fi в мене витримав зʼєднання через металевий щиток та броньовані вхідні двері, але то був найбільш несподіваний та ризикований момент всього проєкту.

У пристрою є вебінтерфейс та навіть якась власна хмара, але мені таке не цікаво, я хочу у свій HomeAssistant. Так можна, причому без жодних хмар. У Shelly хмара опціональна; спочатку налаштовується підключення до Wi-Fi та локальний вебінтерфейс, а потім вже за бажанням обліковий запис; в HA пристрій зʼявляється автоматично, як буде підключений до спільного вайфаю.

Поки, щоправда, нічого екстраординарного з даними не зробив — тільки дивлюся та зберігаю історію. Як зроблю — напишу.


29.03.2024

Golang у VSCode: точимо сокиру

🪓 Історія перша. Автори asdf-golang придумали друкувати попередження, якщо в тебе не встановлена змінна оточення (типу, щоб попередити про її наявність.) Все б було чудово, але інтеграція Go у VSCode почала лаятися тим попередження просто без кінця.

Звісно, в оболонці в мене та змінна встановлена. Але то не допомагало. Чому? Бо VSCode, як графічний застосунок, не отримує оточення оболонки. Бо графічні застосунки в macOS запускає процес launchd, а не термінал — на відміну від Linux, де весь графічний інтерфейс запускається з термінала. Раніше мене це цілком влаштовувало, але цього разу знайшов, як додати змінну оточення до launchd:

launchctl setenv ASDF_GOLANG_MOD_VERSION_ENABLED true

Простішою альтернативою було б зробити скрипт-обгортку, яка б встановлювала потрібну змінну.

Історія друга. В мене чомусь давно golangci-lint у VSCode скаржився на відсутність типів з того самого модуля. Кумедно, коли тип підкреслений червоним, але є доступним для навігації — бо навігацію робить інша підсистема, а саме gopls. Ну й звісно помилка golangci-lint була хибною принаймні тому, що код компілювався та працював без жодних проблем.

Виявилося, що в лінтера golangci-lint є три режими перевірки під час збереження: перевірка файлу, модуля, або всього проєкту. Та якщо перевіряти один файл, то типів лінтер не бачить (хоча навіщо взагалі тоді робити цю перевірку — не знаю). Режим перевірки модуля все виправив.

Нарешті, бонус: помітив, що у кодування у JSON є дві приховані особливості. json.Marshal автоматично замінить символи HTML на еквівалентні послідовності (& на \u0026, наприклад.) Це щоб уникнути інʼєкцій — причому цю заміну неможливо вимкнути. А json.Encoder.Encode автоматично додає після обʼєкту символ нового рядка - тобто фактично прямо генерує формат jsonlines.


28.03.2024

Як вийти з розумового затору?

Ось дві стратегії:

🙋 Запитати колег або інтернет. Але є нюанс: запитання має бути “правильним”. Необхідно викласти всі деталі: що не працює, що вже пробував, які обставини відомі, може навіть зробити мінімальний приклад, і так далі. Мені завжди допомагає спитати на StackOverflow, бо там планка якості дуже висока та я намагаюсь вичерпно описати проблему.

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

🚶 Сходити на прогулянку. Більше нічого робити не треба. (Можна ще поспати.) Це взагалі робоче чаклунство. Як ще пояснити, коли дві години бʼєшся над задачею, нічого не виходить, а потім йдеш у справах, повертаєшся, та розвʼязуєш з першої ж спроби?! Не знаю, що за психологія тут залучена, але воно дійсно працює. Думати про роботу на прогулянці не потрібно — ну, може трішечки. Головне — це спорожнити контекст.

Спочатку, звісно, потрібно зрозуміти, що ти в заторі. Та це завжди складно (на то він і затор.)


27.03.2024

Програміст проти інженера

Хоч я зазвичай використовую “програміст” та “інженер” як синоніми для простоти, проте насправді це окремі ролі. Приблизно так само, як робітник та інженер на заводі.

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

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

На практиці, кожен програмний інженер також буде виконувати роль програміста. Тоді навіщо взагалі розрізняти такі поняття та думати про це? Бо коли розумієш, в якій ролі ти зараз, легше бачити пріоритети та приймати рішення. Роль інженера зазвичай первісна: ми шукаємо засоби для розвʼязку задачі. А потім вдягаємо костюм програміста та дописуємо те, чого не вистачає. Програмування потребує багато зусиль, тож краще коли вони витрачаються на щось нове та особливе. Тому важливо не закопуватись в роль програміста, а перетворюватись назад в інженера.


26.03.2024

OpenSearch як рушій потоку подій

OpenSearch насправді вміє робити цікаві речі як сховище подій та рушій для архітектури Event sourcing.

Як я, певно, вже писав, OpenSearch дуже погано робить JOIN. Зате досить добре вміє GROUP BY в межах Transform job (раніше). Це вже основа того, щоб збирати повʼязані події та утворювати з них нову сутність.

Тепер, серед агрегацій, доступних для Transform job, доступна так звана Scripted metric aggregation (раніше). Це цілий map/reduce: за допомогою Scripted metric можна комбінувати дані у складні структури та повертати такі документи, як нам потрібно. Причому працює десь так само швидко, як і інші агрегації. (До речі, це не очевидно, але на вхід метрики потрапляють документи кожної групи окремо.)

І нарешті, Transform job можна робити й над індексами, отриманими з іншої Transform job. В мене був випадок, що одні й ті самі дані потрібно було групувати двома різними способами, тож однією Transform job такого не зробиш; але можна зробити дві, а потім — третю, що їх поєднає.

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


25.03.2024

React Native чи Swift, чи ще щось: 2024

📱 Оскільки я трохи попрацював зі SwiftUI, хочу переосмислити свої бачення та рекомендації з приводу вибору мобільної технології для iOS.

Як RN, так і SwiftUI приблизно однаково зрілі. Вчити доведеться як те, так і інше; у RN багато відмінностей від вебу. Це далеко не просто “інший набір компонент”; рушій викладки та стилів абсолютно інший та працює за іншими правилами. Та й тулчейн інший. Можна взагалі сказати, що з RN у вас збережеться знайома мова та стано-логічний кінець застосунку.

А далі все залежить від того, скільки того стану та логіки. Якщо застосунок тільки показує дані з сервера, то це і на Swift зробити нескладно. Проте, якщо на фронтенді є складна бізнес-логіка, то переписувати її буде ризиковано — тоді RN дійсно дає велику перевагу.

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

Нарешті, хоч застосунки на React Native можна застилити без жодних обмежень та досягти будь-яких вимог дизайнера, в них залишається деякий ефект моторошної долини: невловні деталі поводяться не так, як ми від них очікуємо. Певно, через нерідний рушій викладки. Звісно, це не заважає багатьом популярним застосункам використовувати RN та не обовʼязково буде перешкодою для вас.

Одним словом, складно сказати, в яких обставинах RN має перевагу. На мою думку, варто вже взяти той Swift та робити нормально. Може, коли є велика команда, яка вся знає JavaScript, але раптом отримала задачу робити невеличкий мобільний застосунок-компаньйон до сайту - ось тоді RN має сенс. Якось так.

Додаток 1: Flutter: я ніколи не пробував і бажання мало, бо це і додаткова мова, і все одно не “рідне” середовище…

Додаток 2: NativeScript - дуже цікава мені платформа; вона обіцяє “рідні” оболонки для будь-якого фреймворку JavaScript (а конкретно, я б обрав Svelte.) Проте поки мене й Swift цілком влаштовує, тому не поспішаю.