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

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

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

03.11.2024

./configure; make; make install

📖 Розгорнута відповідь на питання, для чого я використаю make. Make - це найстаріша система для збірки, тобто така що запускає на машині розробника специфічні для проєкту інструкції. Компіляцію, запуск тестів, налаштування середовища… власне, всі команди, які властиві проєкту, можна зібрати в набір Make-файлів та спростити команді життя.

🎅 Make - система, стара як Unix, їй майже 50 років, тому вона має деякі анахронічні особливості. В серці Make лежить можливість запускати команди для кожного файлу окремо та пропускати команду, якщо вихідний файл новіший за вхідний. Бачите, в C збірка починається з компіляції кожного файлу з кодом у проміжний файл .obj, а потім вони складаються (лінкуються) в виконуваний файл. Тож в Make вбудований не тільки кеш збірки, а й домовленості, які з сучасними проєктами рідко справджуються.

🍃 Втім, я постійно беру Make для власних та робочих проєктів (з одним"але" 👇.) ЇЇ головна перевага: вона вже встановлена в будь-якому середовищі розробки та не залежить від мов програмування. Тож для роботи з Make не потрібно спочатку підготувати залежності Node, Ruby, Python тощо. Особливо на CI та в Docker це дуже зручно. Також синтаксис Make-файлів стислий та складається тільки з шелскриптів та мінімальної розмітки. Якщо вже комусь доведеться його вивчити, то вчити там небагато.

🚷 Власне, найбільший недолік Make для мене це як раз розбіжності з тими ж шелскриптами. Змінні оточення та вкладені виклики $() тут доступні тільки через власний синтаксис — це не складно, але копіпастити з оболонки не вийде. Але це дрібниці.

🍝 Звісно, у Make є безліч альтернатив. Найменш люблю скрипти в package.json. Коментарів — дзуськи! Лапки — маскуй! Залежності — тільки через зовнішній виклик. Жах.

🍣 А моя улюблена альтернатива — це Rake в Ruby. Її я беру там, де скрипти можна спростити метапрограмуванням або параметризацією. Бо в Rake можна писати на Ruby - наприклад, робити запити до серверів чи перетворювати дані. А для виклику оболонки в Ruby є синтаксис зі зворотними лапками `echo "Hello World!"`, в якому не потрібно маскувати звичайні лапки. Та який-не-який Ruby теж встановлений на більшості систем.

🤙 Наприклад, в одному з проєктів в мене Rakefile, який генерує комбінації команд на кшталт rake start:foo:test та rake stop:bar:dev для запуску оточення Docker. Та на тому ж проєкті Makefile, який збирає виконувані файли Go з вкладених тек (make -C cmd/baz) в паралелі (і таке можливо! make -j 8).

🪶 Що мені цікаво спробувати, це сучасний підхід “документація як система збірки” на кшталт xc. Але, в 9 випадках з 10, Make повністю вистачає. (А в десятому допоможе Rake.)


02.11.2024

Коли я робив проєкт з картами

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

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

У звіті про хакатон вже розказано про першу технічну реалізацію, яка, на перший погляд, була успішною, але далі почалися… вибоїни.

Вибоїна перша: алгоритм обчислення якості дороги з акселерометра зовсім не очевидний. Бо в показниках безліч змінних. Окрім ям, це параметри автомобіля (наприклад, підвіска), швидкість та прискорення пересування, щільність дорожнього руху тощо. Таке слово як data science я тоді ще й не чув, певно.

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

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

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

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


01.11.2024

TSSpec - Перевірка збіжності фронтенду та бекенду

Цього тижня довелося виправляти доволі типову ситуацію: фронтенд в разі помилки показував не детальний текст з бекенду, а натомість загальну “щось пішло не так”. По-перше, дякувати тестувальнику, що це помітив. По-друге, інтеграційні тести покривали тільки успішний випадок.

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

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

Засмучує те, що фронтенд та бекенд живуть окремим життям, а перевірка взаємодії обмежується інтеграційними тестами. От якби вони обидва були на TypeScript, тоді можна вживати однакові типи для клієнта та сервера API. Так ця категорія помилок зникає, навіть якщо тестів не писати — ось вам ще одна величезна перевага TS.

А що коли бекенд на Ruby (on Rails)? Три роки тому я задав собі це питання та винайшов гем tscheck. Він поєднує типи TypeScript з тестами RSpec. В (нормальних) проєктах на Rails є юніт-тести контролерів, де ми перевіряємо варіанти їхніх відповідей. TSCheck дозволяє також перевірити, що відповідь контролера вірно реалізує деякий тип TypeScript. Це обмежена, але гарантія, що фронтенд та бекенд розуміють один одного.

Як воно працює? Простіше, ніж думаєте. Я будую файл TypeScript, що містить імпорт типу та константу зі змістом відповіді. Та передаю його у мовний сервіс tsserver - це той, що здійснює перевірку для VSCode тощо. У відповідь отримую перелік помилок: якщо вони є, тип не сходиться.

Є й інші розвʼязки цієї задачі — зокрема перевірка за схемою OpenAPI - але вони потребують додаткової роботи. А в мене все спирається на типи TypeScript, які у вас вже повинні бути. (Повинні бути, правда?)


31.10.2024

Чому вам потрібний маркетолог?

Кілька років тому ми випустили Сінтру — застосунок для обліку витрат за принципом щоденного бюджету. Він, хоча й має своїх користувачів, але все ж великого успіху не досяг. Сама Сінтра чудово зроблена в міру команди з двох людей — мене та Саші Зайцева, який уособлює для мене все, чого можна чекати від дизайнера. Де ж успіх?

Я, здається, несподівано натрапив на відповідь: жоден з нас не старався дуже її продавати. Чи це звучить очевидно? А якби мова йшла про твій сайд-проєкт?

Як інженери, так і дизайнери бачать продукт як предмет власної праці. Вона ніколи не завершена; завжди щось в продукті можна зробити краще. Хоч це й правда, але така перспектива призводить до надто критичного ставлення. Якщо продукт можна доробляти, чи готовий він до користувача? Інженер воліє сказати “ні”; тільки обставини змушують сказати “так”.

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

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


30.10.2024

Ніколи не довіряй клієнту

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

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

☝️ Сервер ніколи не може бути впевнений, що відповідає на запити справжнього клієнта. Це аксіома інтернету. Звідси висновок: запити клієнта та відповіді сервера мусять завжди бути авторизовані щодо поточного автентифікованого користувача. На сервері!

Коли тримаєш це в голові, не так важко зробити правильний архітектурний вибір. Звісно, сучасні тенденції його ускладнюють. Зокрема GraphQL суттєво заплутує обставини доступу до сутностей, як викладено в цій статті. Проте принаймні я не зустрічався з проєктами, де не зрозуміло, де межа сервера та клієнта — тобто де потрібно застосувати цю аксіому про недовіру.

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

Окремо стоять “рідні” застосунки, бо вони виглядають цільнішими та, на перший погляд, їм можна довіряти. Це самообман, рідні застосунки наділені такими ж вразливостями. Критерій “клієнта” лише один — чи виконується код в оточенні, яке ми не контролюємо? Все, що там, в будь-який момент перетвориться на агента з Матриці. 🕴️


29.10.2024

Ігри в хмарі з GeForce Now

Я вже більш як рік граю віддалено через Parsec - інколи з сусідньої кімнати, інколи з іншого міста. Та це працює дійсно чудово — але коли є до чого підключатися. Коли ігровий компʼютер став недоступним, то я лишився з тим, що можна запустити на macOS… та з вірою в хмарний геймінг. Нещодавно, нарешті, наважився спробувати й Geforce Now - справжнє хмарне рішення від NVidia. Ось те, про що чомусь мало розповідають.


28.10.2024

Обхід блокування Reddit API

В мене окрім гарної стрічки RSS для Hacker News також є стрічка для Reddit - вона наразі не така цікава, але я збирався розширити асортимент та, можливо, зібрати альтернативу для HN з Реддіту.

Ну точніше, вона б мала бути, але на жаль вона давно порожня, та нарешті вирішив зʼясувати, чому. Виявилось, що Reddit блокує запити до власного API з адрес AWS. Причому я спочатку й не думав про блокування, бо використовую Lambda, яка отримує нову адресу щоразу, але коли почав шукати причину, що з дому стрічка працює, а з лямбди — ні, то натрапив на пост, який вказав на це пояснення.

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

Скрипт, як і з Лямбди, завантажує кеш з S3 та вивантажує результат роботи теж в S3. (Нагадаю, що він генерує щоденний дайджест, а не живу стрічку.) Таким чином, в споживанні стрічки нічого не змінилося. Щоб надати доступ, створив користувача з такими самими правами. А більше нічого й міняти не треба.

Для запуску скриптів щодня в macOS є сервіс launchd - аналог systemd в Linux. (Не тільки щодня, а й за купою різноманітних сценаріїв.) А для зручної роботи з ним є застосунок LaunchControl. Це прямо цілий IDE, в ньому все видно та дуже зручно. Особливо тому, що вивчати параметри, наприклад, для запуску за розкладом, не хочеться — а в LaunchControl за ними є довідка та графічний інтерфейс. Так само і для перегляду журналу все готово. Рекомендую.

З несподіваного: Ruby в launchd вмикав кодування ASCII, а не UTF8. Бо він виконується поза оточення оболонки, де в мене виставлена локаль. Це можна рішити аргументом -E utf-8:utf-8. А ще, оскільки сам Ruby в мене сидить в asdf, та тягнути його в сервіс не хотілося, можна дізнатися прямий шлях до виконуваного файлу командою asdf which ruby.


27.10.2024

Проєкти у GTD

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

На мою думку, ідея проєкту спотворена спробами їх формалізувати. Вкладені проєкти, послідовні проєкти, паралельні проєкти, проєкти на паузі… Будь-яка сучасна програма для керування задачами заганяє проєкти в надто вузькі рамки.

Та, на відміну від наступних дій, проєкт в GTD має широке значення. Та жодного натяку на структуру. Проєкт — це будь-яка мета, до якої ми зараз йдемо. Оскільки майже нічого не здобувається за одну наступну дію, нам потрібно памʼятати, чого ж ми плануємо досягти. Сенс списку проєктів — щоб не тримати всі мети в голові. (Бо весь сенс GTD - це не тримати нічого в голові.)

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

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

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


26.10.2024

Середовище розробки для Hugo

В мене блог на Hugo та в цілому для мене як для інженера, та ще й з досвідом Go, не маю кращих варіантів. Hugo вміє все, включаючи самостійно збирати JS/CSS та обробляти зображення. Попри всю цю потужність, в мене гальмувала робота над оновленням сайту. Поміркував та зрозумів, що це через брак зручного середовища розробки.

Hugo використовує шаблони Go - звичайні html/template. Акуратний HTML і так не легко підтримувати, а коли в нього домішують теги шаблонізатора, то взагалі. (Або це я зіпсований роками роботи з JSX, який форматується з математичною точністю.)

Втім, все вирішується гарними інструментами. Дізнався, що тепер в Prettier є плагін prettier-plugin-go-template, який зокрема вміє форматувати й шаблони Hugo. Цікаво, що для того шаблон повинен мати “збалансовану” вкладеність, тобто, наприклад не можна робити {{if .x}}</div><div>{{end}}. А Prettier в мене вже був, для TypeScript та для Markdown - тепер розширив його обовʼязки.

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

Ще є скрипти, що виконуються на сервері. Звісно, це вже не Hugo, бо він тільки робить статичний сайт — а Vercel, Netlify, Cloudflare Workers чи що там у вас. Але локально сервер розробки вміє інтегрувати їх в сайт десь так само, як і хостинг. Для того є конфігурація redirects; звісно, сам сервер зі скриптами доведеться запускати окремо, може, утилітою goreman.


25.10.2024

Kafka: неочевидне з досвіду

Неочевидна сильна сторона Kafka: кожний потік можна обробляти будь скільки разів. Тобто я звик думати, що черга це: один зайшов — один вийшов. А з Кафкою не так. В Кафці записи живуть визначений час — наприклад, декілька днів — та протягом цього часу доступні для читання скільки завгодно разів. Причому якщо під час читання оголосити ідентифікатор — тобто створити групу споживачів (consumer group) - то Кафка відстежуватиме її позицію в потоці записів.

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

Неочевидна слабка сторона Kafka: відсутня можливість відкласти запис на майбутнє. Для черги це типова функція, але ж Кафка це не черга (а журнал!) Одна з головних характеристик Кафки — записи читаються в тому самому порядку, як їх записали. Так само й статус прочитання призначається послідовно. А щоб відкласти на майбутнє, нам потрібно порушити цей порядок.

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

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