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

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

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

20.01.2025

Продукт та маркетинг - це різні речі

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

Коли продукт поганий, а маркетинг гарний — це доволі типова ситуація. Типова тому, що маркетинг — це наше сприйняття, тож більшість того, про що ми взагалі знаємо, ми знаємо через гарний маркетинг. Втім, гарний маркетинг ще нічого не каже про якість продукту… знаєте, інколи маркетинг робиться взагалі до продукту, щоб оцінити попит. Є ще один знайомий випадок: продукт чудовий, а маркетингу немає. Це те, що називається “приховані скарби”.

Проте, нарешті, трапляється, що при гарному продукті маркетинг зроблений жахливо. Від цього особливо прикро, бо поганий маркетинг працює в мінус. От, наприклад, розробили люди цікавий, якісний гаджет — а потім йому зробили крамницю з усіма чорними практиками накрутки — всі ці “залишилось 09:59 до кінця акція”, “тільки що забрали передостанній товар” тощо. Або коли хтось продає дійсно гарну книгу чи курс оцією шаблонною сторінкою з відгуками та “тільки зараз отримайте бонусні матеріали безплатно”.

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


19.01.2025

Потоковий запис JSON

Я в попередньому пості писав про читання JSON поступово з вивільненням памʼяті, але ж хтось той JSON повинен був записати? Ситуація з записом значно простіша, та не потребує ніяких особливих інструментів.

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

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

Але — інколи в нас немає всіх даних відразу — наприклад, вони не влазять в памʼять, або ми отримуємо їх пакетами з Kafka чи ще звідкілясь. Навіть в такому випадку нескладно утворити на виході єдиний JSON без залучення великого буфера.

Наприклад. Пишемо у вихід [ - початок масиву. Далі пишемо по черзі стільки даних, скільки потрібно — читаючи з бази або з генератора або де там ще в нас дані беруться. Наприкінці закриваємо масив: ]. От і все!

Інколи все, що потрібно — це трохи текстових операцій. Я колись так робив, щоб зливати з Kafka зміст для резервного копіювання.


18.01.2025

Потокова обробка JSON

Поки писав вчора про JSONLines, встиг дізнатися про альтернативу. Звичайний JSON теж можна обробляти потоком. Я маю на увазі, зчитувати частину, обробляти та вивільняти — звісно, коли йдеться про якийсь масив незалежних даних.

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

Я все це не вигадав, а прочитав у прикладі до методу json.Decoder.Decode() з Go. Бо в Go декодер JSON відкриває достатньо інтерфейсу для поміркованого читання; можна читати цілий документ, а можна значення за значенням. В цілому, так можна зробити з будь-яким парсером, що читає потік — але ж пропускати коми та пробіли доведеться вручну.

Цікаво, що в JavaScript взагалі не бачу рідного модуля для читання JSON з потоку, а тільки з рядка. Знайшов ось stream-json, яка щось таке вміє. (А от JSONLines можна легко читати з потоку через модуль readline та обробляти рядок за рядком.)

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


17.01.2025

Формат JSONLines

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

Перед тим, як обирати з форматів для Big Data, краще подивитися на JSON Lines. Це формат масиву даних, де в кожному рядку міститься повний документ JSON. (Звісно, при цьому розриви рядка в середині документа заборонені.)

Перевага JSONLines: кожен рядок можна читати та обробляти окремо (та вивільняти памʼять). Також можна, навіть без розбору змісту JSON, орієнтуватися в масиві — достатньо стежити за закінченнями рядка, щоб визначити місце, полічити записи, розділити масив (чи склеїти два!) тощо.

Недолік JSONLines: він все ж не такий загально прийнятний, як JSON. Також людям його читати важче. Ну й те, що JSONLines - обовʼязково масив. Тому сфера застосування — скоріше, машинні документи, чи запити до API. Я бачив JSONLines в пакетних запитах ElasticSearch, експортах AWS Redshift, AWS Kinesis Firehose… вебхуках Mailtrap. :) Раджу мати на озброєнні.

Ще дотепний факт: насправді є цілих два стандарти для JSON Lines - другий називається NDJson та функціонально ідентичний, за винятком медіатипу.


16.01.2025

Скільки інженерних практик є прихованими механізмами подолання РДУГ?

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

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


15.01.2025

Чому в Rails крута модель

Вчорашній пост змусив мене знову подумати, чому ж в Ruby on Rails з моделлю працювати зручніше, ніж будь-де. ActiveRecord залишається неперевершеним в простоті використання, навіть для побудови складних та потужних бізнес-процесів.

Отже. В Ruby немає різниці між методами та атрибутами, а точніше, весь публічний інтерфейс обʼєктів є виключно методами. Це приховує силу-силенну складності — аспекти реалізації не обтяжують споживача. Наприклад, User.all.map(&:name) елегантно та непомітно залізе в базу. (Але краще User.pluck(:name), що відразу витягне масив імен.) Фактично в Rails база виглядає, як “внутрішня”, а не “зовнішня” структура даних. Інколи це підштовхує писати неоптимальний код — але, як каже Роб Пайк, не варто оптимізувати код наперед.

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

Можна подумати, що це робить код Rails неможливим для перевірки. Але, в Ruby весь код живе у спільному просторі імен та доступний для змін під час виконання. Тому практично кожний метод, змінну чи клас можна замінити на тестову копію. Це насправді дуже потужно, та ми постійно цим користуємося. Для прикладу, можна навіть змінювати константи.

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


14.01.2025

Тести з базою на Go

Як я два роки тому писав, так і досі користуюся бібліотекою testfixtures для підготовки тестової бази. Фікстура — це такий файл (шаблонізованого) YAML, в якому сидить зміст однієї таблиці.

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

Одним словом, я все більше дивлюся, як уникати бази в тестах взагалі. Для когось це, може, очевидний підхід, але я звик до Rails та factory_bot, де наочно та стисло можна створювати довільні ієрархії обʼєктів. Втім, factory_bot спирається на ActiveRecord, а для Go в мене нічого схожого немає.

Базу можна замокати — для того є бібліотеки sqlmock чи pgxmock. Вони дають можливість задати результат для кожного запиту. Що приємно, не обовʼязково писати весь запит, достатньо підрядка, наприклад, з використанням sqlc вистачить назви запиту.

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

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

Для повного комплекту гарно ще вкрити тестами модуль sqlc, щоб переконатися що самі запити працюють вірно.


13.01.2025

Недоінжинірінг та переінжинірінг

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

🌱 З мого досвіду можу сказати одне: завжди легше спочатку зробити недостатньо, а потім доробити, ніж потім прибирати зайве. Це трохи йде всупереч з Інженерною Етикою, яка вимагає від нас вкласти в роботу стільки зусиль, скільки є часу. Втім, краще хай зусилля йдуть на планування мінімального рішення.

🚧 Чому? Ну, по-перше, складні рішення не тільки важче писати, а й важче потім переробляти. Особливо коли це включає зміни до схеми даних та до інфраструктури, де додати нове відносно легко, а от змінити… та інколи в умовах бізнесу буває просто неможливо!

🏔️ Звідки ще й друга причина: на практиці ніхто не буде переробляти готове, аж доки зовсім не припре. Доведеться жити з тією складністю… яка тільки накопичується та множиться.

Тому закликаю вас обирати недоінжинірінг приблизно завжди.


12.01.2025

Браузер, який я хочу

🌐 Ділюся ідеєю браузера, яка в мене крутиться в голові декілька місяців (десь після переходу на Obsidian Canvas)… але, зрозуміло, поки не вистачає часу спробувати.

💭 Я хочу браузер, інтегрований в базу знань. Та орієнтований на задачі. Я боюся сказати “дослідження”, бо це зараз має свої конотації зі всілякими zettelkasten та Roam Research, тобто з побудовою графа знань, а мені таке зазвичай не потрібно.

🕸️ …Мені потрібно: знайти трекові світильники, перенести сайт на Cloudflare та розказати про браузери. Кожна така задача тягне за собою низку питань, а відповідно — пошуків в інтернеті. От хочу бачити сторінки в контексті задачі. Причому щоб історія теж автоматично привʼязувалася.

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

⚙️ В цілому, ідея більш реальна, ніж здається (я ще в школі робив “браузери”, бо в нас блокували Internet Explorer - одна компонента на Delphi та кілька кнопок це все, що потрібно.) Зараз, мабуть, можна привʼязатися до Electron, чи до WebKit. Але часу немає, тож поки тільки можу фантазувати.

📌 Цікаві поробки в цьому напрямку: SenseMap. Horse browser. Perplexity.AI. Буду радий дізнатись ще.


11.01.2025

Мʼясне сортування

В мене в застосунку для GTD є режим FVP для простого вибору наступної задачі. Він був трошки напівавтоматичний, а саме: переглядати задачі в послідовності, та не стрибати туди-сюди, доводилося вручну — за звичайним списком. Тобто все одно перед очима був повний список.

(Нагадаю, що весь сенс алгоритму FVP - це такий класичний пошук найпріоритетнішої задачі в масиві — запамʼятовуємо першу задачу, перебираємо всі інші та порівнюємо з нею, як найдемо більш приорітетну — запамʼятовуємо її та йдемо далі. Ну всі такий код писали. Але тут воно працює “на мʼясі”.)

👈 Сьогодні, щоб полегшити собі життя, впровадив спеціальний режим, де показуються тільки дві задачі - “найважливіша” та наступна з не перебраних, Залишається тільки обрати між ними (і так N разів). Такий собі “тіндер для задач” замість списку. 👉

Насправді колись я робив цілий сортувальник в такому стилі. Але для FVP потрібно тільки обрати одну задачу, та “мʼясна складність” алгоритму лише O(N).