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

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

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

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 цілком влаштовує, тому не поспішаю.


24.03.2024

Paletro - Cmd+Shift+P в будь-якому застосунку

…Все почалося з того, що я заходів редагувати файл з XCode у VSCode. Так, XCode непоганий редактор, але масове редагування як у VSCode він не вміє. (Або я не вмію. Але перевчатись на XCode навряд чи буду, бо не підходить мені редактор, який вміє тільки одне.)

Це досить стандартна задача для Shortcuts або AppleScript: “візьми поточний документ в XCode” … “відкрий його у VSCode”; проте обидва інструменти залежать від реалізації конкретним застосунком. Виявилося, що XCode взагалі не підтримує Shortcuts, та має дуже обмежену підтримку AppleScript. Навіть для отримання поточного документа найкраще рішення — це “отримати назву вікна, відокремити з неї імʼя поточного файлу, а потім знайти його в проєкті”. Ганебно для офіційного застосунку.

Тоді… роздивився меню XCode та знайшов в ньому команду “Open with external editor”. Вона робить буквально те, що мені потрібно! І не треба було нічого дописувати.

Тут чудова нагода розповісти про утиліту Paletro. Вона створює для будь-якого застосунку “палітру команд” - таку, яка вже є в VSCode, Obsidian, та багатьох інших програмах. А команди беруться зі смуги меню. Тієї самої смуги, яка через вкладеність буває заплутаною, а під час — повністю ігнорованою. Вікно палітри команд, обʼєктивно, кращий інтерфейс для пошуку та запуску нечастих команд.

До того ж я звик натискати Cmd+Shift+P настільки, що знаю, що в Safari - це команда друку сторінки. Але тепер навіть в Safari в мене є зручна палітра команд.

На додаток Paletro можна розширювати власними командами за допомогою скриптів. Хороша альтернатива комбінаціям клавіш для рідких дій.


23.03.2024

Як лягати спати раніше?

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

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

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

В інші періоди життя я залишався не спати, щоб відібрати у дня час на себе, коли обовʼязки закінчились. Для такої поведінки вже є окрема назва - reverse bedtime procrastination. Тут можна звернути увагу, що час вранці так само належить собі, тільки вранці ти вже виспаний. (Якби ж ще воно допомагало сбе переконати.)

Я ніякого рішення запропонувати не можу, проте пропоную запитати себе: чи тобі не подобається прокидатись рано, або не висинатись?


22.03.2024

Клас Fiber в Ruby - кооперативна рівночасність

🧶 Готую тут матеріал про засоби рівночасності в Ruby, та відкрив для себе клас Fiber. Він дуже схожий на Thread, але насправді корисний зовсім в інших умовах. (Якщо ці назви класів перекладати, то все стає ще заплутаніше, тому я не буду.)

Fiber, на відміну від Thread, не є рівночасними в типовому розумінні. Натомість виконання передається від Fiber до Fiber явно, командами resume / yield. До виклику такої команди буде виконуватись тільки поточний Fiber - навіть у разі очікування на IO. Тобто можна сказати, що Fiber гарантовано не будуть паралельними.

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

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

Звісно, такий код буде складніше, ніж просто рекурсія. Тому я відразу побачив перевагу Fiber - з ними можна запустити дві рекурсії паралельно та взагалі не перейматись про внутрішню форму алгоритму. Це чудовий підхід для роз’єднання деталей виконання, які зазвичай тісно поєднані. Більше — в майбутній статті.


21.03.2024

Генерація CSV: насправді це дуже просто

Коли попереднього разу робив експорт в CSV (на Swift), то згадав, що зробив генерацію самотужки; тоді я просто обʼєднував поля в рядок комами.

Інтуїтивно здається, що то надто ненадійно та треба брати спеціальну бібліотеку. Це не так: щоб зробити вірний CSV, достатньо дотримуватись єдиного правила:

Якщо поле містить лапки, кому, або новий рядок (так, навіть це дозволено), то його потрібно взяти в лапки. При цьому лапки всередині поля замінити на подвійні лапки ("").

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

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


20.03.2024

1BRC на Ruby: YJIT, потоки, знову Apple Silicon

В одному з обговорень згадали, що ще в Ruby є YJIT, а я згадав, що попередні вимірювання робив без нього. (YJIT оптимізує код через фіксовану адресацію там, де вона можлива.)

Отже, перевірив, що робить YJIT з найшвидшим з моїх рішень. Прискорення у 20% - з одного боку, до кратних прискорень паралелізації далеко, але з іншого, для зміни в один рядок коду (RubyVM::YJIT.enable з Ruby 3.3.0) - дуже приємно. Для досягнення такого результату вручну потрібна ретельна оптимізація коду, яка часто ще й ускладнює його читання.

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

Нарешті, так і не вийшло виправити segmentation fault в моєму розвʼязку з пулом ракторів (Linux, Ruby 3.2.2 та 3.3.0.) Причому як з StringScanner, так і з StringIO помилка зʼявлялася, хоча в різних місцях. Це на fly.io; локально ж на Apple M2 як рактори, так і процеси дають прискорення у 2.5 разів при пулі з 8 потоків на 8 ядрах. Треба буде колись детально роздивитись, що ж не так з Apple Silicon; ну добре, повільні ядра — повільні, але хіба не має бути прискорення принаймні в 4+ рази?