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

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

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

20.02.2025

Фічафлаги та рефакторинг

Чистий код та DRY - це гарні принципи, але з них я роблю один виняток: коли працюю з фічафлагом. (Чи це “всім відоме” поняття? Фічафлаг — це перемикач, яким можна наживо обрати між старою та новою поведінкою частини коду. Зазвичай не для всіх, а для конкретного користувача. Таким чином можна не показувати нові фічі всім відразу, але мати можливість їх випробувати.)

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

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

Окремий випадок — то тести. В тестах рідко виходить красиво параметризувати поведінку коду під фічафлагом. Тому замість того, щоб намагатись вплести фічафлаг у тести, краще скопіювати тест чи цілий файл, та впровадити зміни такими, якими вони стануть остаточно. Якийсь час у вас буде зайвий тест, а потім старий видалите і все знову буде красиво.

Та й не тільки в тестах, а й будь-де в коді краще копіпастити весь той код, де гілки фічафлагу не є простими. А лінтер — сміливо вимикати; почекає.


19.02.2025

Мистецтво назв пакеті в Go

Ще одна унікальна особливість Go - підхід до організації пакетів. Тут пакетом є кожна директорія. Без вкладеності, вкладені — то вже окремі пакети. Я таке бачив ще хіба що в Terraform… а на чому написаний Terraform? На Go, то-то.

Саме пакет визначає межі приватності ідентифікаторів. (Чи знаєте ви, що в Go ідентифікатор з маленької літери є приватним, а з великої — публічним? Причому що типи, що змінні, що константи можуть бути як з маленької, так із великої. Ще одна абсолютно інопланетна особливість.)

За межами пакета імена включають його імʼя: mypkg.Type. (Є ще “імпорт всього”, але він майже не використовується.) Причому хоч назвою пакета є фактично його URL, та кожний імпорт згадує його повну форму: import "github.com/me/mypkg/subpkg", та в коді шлях не згадується. (Можна ще імпортувати з перейменуванням, бо без нього швидко опинишся із конфліктами.)

Це все довгий вступ до того, що в Go до йменування пакетів потрібно ставитися з великою відповідальністю. Зокрема, вони повинні бути стислими, бо постійно згадуються в коді. А тепер — ще стислішими. Два слова? Пиши разом, скорочуй. Звісно, це накладає особливий стиль. httputil, textproto, strconv, slog - це тільки приклади зі стандартної бібліотеки.

Втім, з власними пакетами складніше, бо рідко зустрінеш стале скорочення. Тоді в допомогу стає розділення імені. Наприклад, ніхто не забороняє зробити вкладені директорії: service/queries, api/v1. Це часто буває просто зручно: наприклад, коли пакети service1/config та service2/config будуть використані всередині відповідного сервісу, то з контексту й так зрозуміло, що то за config, а у винятку зробити import service1config "service1/config".

Також є домовленість виносити назву типу в назву пакета. Наприклад, http.Server або service1.Request. Хоча не завжди це виходить очевидно. Краще, коли пакети маленькі та тісно звʼязані (це взагалі краще.)

Взагалі. Успіх проєкту на Go дуже залежить від вірної організації пакетів. Про це, може, іншим разом.


18.02.2025

Три речі, яким я приділяю увагу під час перегляду ПРів

🧩 “Цікавий” код. Більшість коду є нудною та передбачуваною. Я довіряю колегам в тому, що він робить те, що потрібно. Але… інколи дивишся на єдиний змінений рядок (або навіть переставлений місцями з іншим) та підіймаєш брови… що це?

В таких випадках обовʼязково зʼясую, чи зміна навмисна (бо інколи залишається якесь сміття від розробки, або масова заміна не спрацьовує, або ще щось.) Або, може, навмисна, але неправильна. Чи я чогось не розумію. Що веде до наступного пункту…

💬 Коментарі до кожної незрозумілої ділянки. Я прихильник того, що код повинен бути зрозумілим. Проте завжди будуть місця, де наміри не вгадати тільки з коду. Їх, на мою думку, потрібно розʼяснити не тільки рецензентам ПРу, але й кожному майбутньому читачу. Тому там, де не виходить зробити код очевидним, допоможуть коментарі.

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


17.02.2025

Забиратися на гору

Останнім часом трішечки захопився видатною карʼєрою Кендріка Ламара та таким чином натрапив на відео, де згадувалося інше відео про ментальну модель покращення власних навичок. Хоча там йдеться про розвʼязок кубика Рубіка, цю модель можна застосувати й для інженерів теж.

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

Ця модель “забирання на гору сходами” дає раду: розрізняти практику заради можливостей (знань) та практику заради навичок. Потрібні обидві.

Щоб отримати знання, потрібно вийти з зони комфорту, зробити щось нове (або новим чином.) Це не буде ефективна робота, бо навичок ще немає. Мета — набути нову можливість.

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

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

А от коли б для власних проєктів обирати такі технології чи підходи, які можна запровадити у роботу — тоді можна вибудовувати в себе нові рівні майстерності. (Або ж робити це на axe friday, певно, ще краще.)


16.02.2025

LLM, Obsidian та Windows - щось таке

Вчора спало на думку / згадали в коментарях, що можна було б використовувати базу знань з Obsidian як вхідні дані для чат-моделі, та таким чином отримати пошук з кращим розумінням понять, ніж в прямого порівняння рядків.

Про цю ідею взагалі багато всього зроблено, бо на базовому рівні тут наче типова для LLM задача. З серйозних проєктів знайшов Khoj - в нього є плагін для Obsidian, можливість запускати сервер локально та використовувати локальні моделі. Але сьогодні так і не вдалося запустити його для Obsidian; тільки в режимі дослідження вебу.

Багато часу відняло поєднання Khoj на макбуці з Ollama на Windows; через такі базові речі, як вибір у Windows режиму “приватна мережа” та відкриття фаєрволу для Ollama - бо без того він не був видимий назовні. Також потрібно вказати OLLAMA_HOST=0.0.0.0; для мене найпростіший спосіб це зробити — це з NSSM зробити з Ollama сервіс, а заодно й змінну оточення вказати.

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

Тепер застряг на тому, що документи з Obsidian ніяк не зʼявляються в Khoj.

А ще, найлегший спосіб погратися з локальними LLM - це LM Studio. Бо це кросплатформенний графічний застосунок, готовий до роботи без всяких налаштувань. Тут можна: спробувати різні моделі, а також їхні параметри; побачити та порівняти швидкодію різних машин; та й просто користуватися чатом.


15.02.2025

Як я користуюся Obsidian

Як подивишся на приклади баз Obsidian - наприклад, в топі /r/obsidianmd - то будеш бачити складні, витончені, застилізовані системи. Тут, я гадаю, доцільна аналогія з автомобілями: ті, хто ними діляться — зазвичай ентузіасти, а у більшості власників машини звичайні нудні, втім — незамінні та люблені.

В мене вже бозна скільки років є особиста база знань, що складається з текстових файлів. Для роботи такої бази потрібна програма зі зручним пошуком. Колись це був Bear, а до того - nvALT, а ще раніше — здається, Vimwiki. Ключовою ідеєю для мене завжди була можливість знайти нотатку через пошук. (Що звучить тривіально, але, наприклад, у VSCode такого пошуку немає: наприклад, коли потрібно знайти два слова, що не стоять поруч.)

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

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

Останній рік також багато користуюся канвою (Obsidian Canvas) для планування та зберігання ідей. Як я вже не раз писав, мені легше сприймати таку інформацію, коли вона викладена на канву. До речі, також в Obsidian можна додавати й зображення. Та й взагалі будь-що — бо нотатки це файли в директорії. Але я обмежуюсь ілюстраціями.

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

З інструментів раджу встановити Omnisearch - це як раз для ще кращого пошуку. Та друге, спробувати глобальну комбінацію для пошуку в нотатках — база повинна бути завжди близько.


14.02.2025

Obsidian Canvas та Excalidraw

Obsidian Canvas - найбільш успішний з інструментів, що я спробував у 2024. Але в цього доповнення є суттєві недоліки. Головний з них — навіть не брак функціональності (хоча в порівнянні з іншими програмами тут сміховинні можливості стилізації.) А те, що Obsidian Canvas не має відкритих точок розширення. Дарма що це офіційний плагін, документовані тільки його CSS-стилі.

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

Отже, нещодавно я подивився на доповнення Excalidraw. Взагалі Excalidraw це векторний редактор для вебу, а це доповнення інтегрує його в Obsidian (де редактор працює вже з файлами з Obsidian, без жодної привʼязки до якогось сервера). Документи Excalidraw виглядають (примруживши очі) так само як і Canvas. Зате в Excalidraw багатші образотворчі можливості: не тільки вбудована стилізація, а й бібліотека фігур тощо.

Але можливо, головне це те, що в доповнення Excalidraw є відкритий API. Причому, як я розумію, зроблений конкретно для Obsidian.

Також документи Excalidraw зберігаються так, що їхній зміст видно в пошуку Obsidian (а документи Canvas - ні або неповноцінно.) Структура канви й там, і там знаходиться у JSON, з тою різницею, що в Excalidraw він містить більше атрибутів стилю, тому є більшим за розміром.

Я поки не вирішив, чи переїду на Excalidraw. З недоліків: тут немає форматування Markdown прямо на канві та не так зручно робити стрілочки (доводиться кожну створювати окремо, тоді як в Canvas можна одним рухом миші створити вузол, привʼязаний до поточного.)


13.02.2025

Історія однієї пакетної оптимізації

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

Після цього в коді залишилися ще тисячі звернень до функцій, які стали тепер зовнішніми. Функції стосувалися обчислення метрик та раніше були суворо закешовані. Але нові вимоги додавали більше фільтрів та закешувати всі комбінації стало неможливим. Переписування на Go, те ще й зі станом в памʼяті робили обчислення швидкими, втім API накладав вагому ціну.

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

Друге — як не крути, тисяча запитів у 10 мс це вже 10 секунд. Звісно, з боку Go я міг робити їх паралельно, тому впровадив пакетні запити та переробив декілька місць. Це досить легко, поки запити відбуваються в циклі — наприклад, будується якийсь графік. Але ж як я казав, застосунок писався із вільним доступом до тих функцій…

Особливо важко було позбавитись серійних запитів у звітах. Там були величезні багатосторінкові шаблони, які зверталися до метрик як у себе вдома. Відокремити доступ до даних від коду звіту було непіднімною задачею.

Тоді я зробив хитрість. Спочатку запускав звіт із фальшивим джерелом даних, яке повертало нульові результати та запамʼятовувало кожний виклик. Потім — викликав справжній сервіс із пакетом викликів, які назбирав. Та нарешті — другий раз запускав той самий звіт, але тепер із підготованим набором даних. Звіт став швидким, а головне — я не перетворив розробку звітів у складну інженерну задачу. It just works!


12.02.2025

Оптимізація: треба йти глибше!

Далі, коли шукаєш причини уповільнення, мусиш ставити під сумнів кожний свій висновок та знаходити для нього обʼєктивні обґрунтування.

Вчора хотів написати пост “чому OpenSearch повільний, коли у вас надто багато шардів?” (Шард — це неподільний блок даних, як партиція; індекс — тобто колекція — складається з фіксованого набору шардів.) Всі радять робити шарди розміром у 10-50 Гб… але чи значить це, що робити їх меншими обовʼязково погано?

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

Але це я знаю після того, як перевірив. А міг би залишитись із такою гадкою, витратити час на переформування індексів, та… нічого не отримати на заміну. Та це не перша та не остання ланка в розслідуванні! Перевіряти потрібно кожну.

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

Ото було колись — шукав, чому API повільний. Локально наче все було чудово, але більшість часу API проводив у серіалізації, тому я довго обирав оптимальний формат. А потім виявилося, що в продакшні є ще рукостискання TLS, та вся проблема зовсім не в серіалізації, а у встановленні підключення. Так що завжди перевіряти!


11.02.2025

Причина уповільнення вебзастосунка майже завжди в базі

Можу згадати тільки один випадок, де повільні запити до вебзастосунка були спричинені процесорним часом самого застосунку. Це коли я оптимізував css_parser. А в типових ситуаціях завжди потрібно зрозуміти, що робиться з базою.

Або це якийсь один жирний запит — тоді доведеться робити EXPLAIN, шукати вірний індекс, може, навіть переосмислити схему даних. Але спочатку обовʼязково зрозуміти, який саме запит повільний: без конкретних цифр краще не продовжувати.

Або це багато запитів в циклі — це вже хиба логіки застосунку, та потрібно шукати, як ті запити обʼєднати. Робити запити у циклі майже завжди погана ідея — але зазвичай це не так очевидно, бо процес прихований в шарах логіки. Тут, як і в першому випадку, допоможе логування запитів. (А ще — інструмент на кшталт New Relic, Sentry, Scout тощо, який автоматично збиратиме статистику по запитах.)

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

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