Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
26.09.2023
Плагін для Obsidian - як вони випускаються
Мій плагін нарешті став корисним, а значить, настав час перенести його з тестової бази на справжню.
Всі плагіни, що в тебе встановлені, сидять в теці .obsidian/plugins
всередині сховища. Це значить, що в кожного сховища плагіни свої. Тому для розробки плагіну розумно створити окреме сховище документів та встановити плагін туди — тоді можна спокійно та без ризиків все протестувати.
Плагіни також синхронізуються між пристроями та навіть підключаються в мобільну версію. Так що плагін для Obsidian - це, можливо, найпростіший спосіб створити мобільний додаток. Навіть місце для зберігання даних та синхронізація буде. Та й дійсно, мій плагін без додаткових зусиль запрацював на телефоні. Тепер я навіть планую особливі екрани для телефону, щоб нашвидкуруч робити прості організаційні дії.
Як тоді зробити релізну версію плагіну та встановити її? Виявилось, дуже просто. Пакет складається з трьох файлів: коду, стилів, та маніфесту. Причому так для всіх плагінів. Інші ресурси — наприклад, зображення — ховаються в файлі з кодом, якщо я правильно розумію. Принаймні великих CSS в готових плагінах я не побачив.
Щоб випустити плагін для всіх (що я поки зробити не готовий), треба зробити репозиторій на GitHub, створити в ньому нову версію та підʼєднати до неї ці три файли як це зроблено тут. Трохи дивний спосіб випускати пакети, але він працює та не потребує додатково інфраструктури. Каталог плагінів теж розташований на Гітхабі.
А для себе достатньо створити нову теку в .obsidian/plugins
, скопіювати туди файли та увімкнути плагін в налаштуваннях — це все. В мобільній версії плагін зʼявиться сам (якщо увімкнена синхронізація, звісно.) Одним словом, хороше середовище для розробки, раджу спробувати.
25.09.2023
Логічна реплікація в PostgreSQL як засіб відстеження змін
Я вже писав про логічну реплікацію як найкраще рішення для часткової синхронізації двох баз PostgreSQL. Нова версія PostgreSQL 16, яка вийшла десять днів тому, містить ще декілька покращень для логічної реплікації. Я радий тому, що вона активно доробляється, оскільки, як писав, не все там ідеально.
А сьогодні про інше. Логічна реплікація дуже проста технологія — одна база зберігає журнал, інша забирає його по протоколу реплікації та відтворює зміни у себе. За цим протоколом не тільки PostgreSQL, а будь-яка програма може забирати зміни та робити з ними що завгодно.
Виходить, що з PostgreSQL можна зробити маленьку Кафку: замість топіка в нас буде журнал WAL, причому підписників як і в Кафки може бути декілька. З тою різницею, що PostgreSQL також зберігатиме дані в таблиці, як завжди. Точніше, у зворотному сенсі: до звичайних таблиць можна також додати сервіс, який оброблятиме зміни у формі подій. Може, для аналітики, може, для побудови похідного стану. Реплікація працює миттєво та не потребує сканування таблиці.
З технічної реалізації: ось пакет pglogrepl для Go. Я перевірив локально — все працює. Ми отримуємо зміни у вигляді структур — знати схему таблиці не обовʼязково. Налаштувати підключення до бази ледве складніше, ніж звичайне.
Може, спробую наступного разу, як доведеться будувати реактивну логіку навколо бази PostgreSQL.
24.09.2023
Думки про ремонт
В мене останні три дні було закінчення підготовки до ремонту. Було зроблено роботи на місяць, мабуть.
-
Мене підсилюють крайні терміни та взагалі кризи. На жаль, тому що хотілося б якось і без криз підсилюватись. А так — дивишся, скільки можна встигнути за три дні, та шкодуєш, що так не виходить завжди. Або принаймні, чому не можна так зробити відразу, а потім відпочивати. Але ні — задля результату потрібний тиск. (Штучні терміни зі мною, на жаль, не працюють.)
-
Ведення нотаток і тут допомагає тримати все в голові… а точніше, як раз не в голові. Нової інформації, а також контекстів, стільки, що в голові не утримаєш. Окремо виділю збереження всіляких світлин та малюнків — радий, що це легко робити в Obsidian. Часто робив так: знайшов річ (меблі, плитку, ще щось) - зберіг посилання — зробив скриншот та розмістив поруч.
-
Не знаю, хто як обирає побутову техніку, але я роблю перебір по декількох сайтах (різні категорії на різних сайтах зручніше) - щоб поступово зрозуміти, які фактори важливі. Зокрема по фільтрах — дивлюся на кількість позицій за варіантами, щоб зʼясувати, які фільтри варто обирати, а де вибору не буде. Також визначаю адекватну ціну. Далі звужую пошук та знову перебираю каталоги, щоб сформувати короткий список, який потім переглядається на наявність оглядів (на ютубі зараз можна цікаві огляди знайти) та згадок в статтях. (До речі, дарую ідею для стартапу: підбір кахлю за схожістю за допомогою нейронної мережі. Звісно, з агрегацією та перевіркою наявності.)
-
Сюжет “Відьмака 3” настільки кращий за “Кіберпанк 2077”, що навіть не віриться, що вони зроблені однією студією. Кожне завдання “Відьмака” відповідає світу, в якому потвори є реальною загрозою людям, життя знецінене, та насильство є нормалізованим. Втім, герой залишається моральним актором всередині цього світу, а не всупереч йому. Та гра помічає, коли ми вирішуємо завдання насильством. Тут, звісно, допомагає те, що моральність застосування сили — головне питання оригінальних романів.
23.09.2023
Чому саме дерево — найкраща структура для задач?
Наступна фішка для моєї системи задач в Obsidian - то підтримка вкладеності. Але не просто вкладеності пунктів — то зрозуміло. Мені хочеться, щоб вкладеність працювала через документи.
Тобто якщо задача має вигляд - [ ] [[ремонт]]
, то документ ремонт
з усім своїм змістом вважається частиною дерева задач. Це дозволяє досягнути довільного рівня вкладеності без створення непомірно довгих списків.
Мій поточний досвід показує, що при плануванні це допомагає. Реальні задачі мають категорично різні рівні складності, та загнати відразу все в красиво обмежені абстракції не виходить.
Точніше, не так: “картки” в Trello, або “епіки/задачі” в Jira зручні для виконання вже після того, як планування закінчене та ми маємо чіткий набір кроків. Хоч всім знайомо, що деякі картки виявляються складними та займають багато днів, а інші, що стоять поруч, виконуються за пʼять хвилин.
От тільки в мене ніколи не буває окремих етапів планування та виконання. Та завжди може зʼявитися потреба розбити один з пунктів ще глибше. Тому прийшов до того, що дерево задач — найбільш природна та зручна форма. Особливо коли це дерево зберігається в Markdown та легко може доповнюватись нотатками, посиланнями, ілюстраціями й так далі.
Проте видобувати з дерева потрібні пункти (наприклад, те, що я зараз роблю, або всі термінові задачі) вручну не є практичним. А коли йдеться про вкладені документи, то й зовсім нереальним. Тому будувати з дерева списки будуть мої віджети.
22.09.2023
Кілька порад з балансу роботи та життя
Попросили розказати. Я як людина що вже 13 років на віддаленій роботі, відчуваю себе дуже кваліфікованим. :) Принаймні, щоб розуміти розмір проблеми, бо сказати, що в мене той баланс ідеальний, ніяк не можу. Проте тепер всі на віддаленці, та напевно, мають ті самі проблеми.
З хорошого: гнучкий графік та віддалена робота надають можливість працювати в найкращі години. У кожного вони свої, в тому й справа. Я полюбляю працювати рано вранці (хоча з роками шоста ранку перетворилась на восьму.) А також мати можливість підстроїти власний графік під сімʼю.
З поганого: такий режим хоч і є ключем до чудового балансу, але й вішає на тебе відповідальність. Варто це усвідомлювати. Робота вдома не має ніякого явного кінця, тому закінчувати її потрібно свідомо. Або ніякого балансу не буде.
…Я довгий час думав що, якщо трохи переривати роботу, а потім трохи доробляти “в неробочий час”, то це й буде балансом. Але ж так не виходить тому, що є нюанс — я люблю свою роботу. Та в виборі: робота або спілкування, прибирання, і так далі — робота має фору. Власне, це і є першопричиною поганого робоче-життєвого балансу. Що з цим робити? Я маю декілька порад:
-
Виставити жорсткі робочі години. Нехай вони будуть нестандартні, якщо хочеш, але чітко окреслені. Наприклад, не працювати після шостої вечора.
-
Встановлювати цілі на день. Цілі мають бути маленькими та досяжними — не “працювати над Х весь день”, а “зробити підзадачу Х.1”. Це дозволяє відчувати завершеність роботи. Бо праця програміста не закінчується ніколи — тому трапляється таке, що складно закінчити робочий день з відчуттям досягнення.
-
Використати робочий компʼютер тільки для роботи. Так, я знаю, ніхто цього не робить, бо хіба воно зручно? От тільки якщо робочий компʼютер також використовується для відпочинку та розваг, то робота буквально ніколи тебе не лишає — ані в ліжку, ані на вихідних, ані у відпустці. Так само й телефон з “критичним месенджером” - якщо ти не на варті, нехай він залишається “у столі”.
Виходить, для гарного балансу доведеться власноруч відтворити частину аспектів роботи в офісі. Не все там погано. (Можеш ще й ласощів на кухню закупити.)
21.09.2023
Просто почни з чогось (себто, с плану)
Сьогодні почув про випадок, коли людина застрягла на простій задачі цілий день, бо не вистачало якоїсь інформації. Я цьому дуже співчуваю, бо в самого постійно виходить. В такому випадку відсутність ясності веде до ментального заціпеніння та міркувань по колу, з якого немає виходу. Про мікровідволікання я писав, а це, можна сказати, “макровідволікання”.
В ідеалі, це саме такий випадок, в якому має спрацювати чуйка на проблеми. Нехай задачу ми розвʼязати відверто не можемо. Розв’яжімо метазадачу, а саме, що робити з ходінням по колу.
Припустимо, що перемикнутися на іншу задачу ми не можемо. Може, наша нерозвʼязна задача супер важлива — або іншої наразі просто немає. Потрібно просуватись з цією.
Я б радив в такій ситуації почати з того, щоб відкрити порожній файл та записати все, що приходить в голову — питання, плани, рішення. Дилеми.
Далі, в більшості випадків для мене, магічним образом зʼявляється шлях вперед. В дилемі знаходиться компроміс — хоч би тимчасовий. Питання — розвʼязуються або відкладаються на пізніше. Головне, що замість міркування по колу я можу зробити крок до розвʼязку… та зазвичай цього достатньо, щоб розблокувати решту шляху.
Проте все ж на мою думку головною перешкодою є нездатність помітити, що ти зациклився. Якби на це вистачало самосвідомості, то все стає простіше.
20.09.2023
Інтеграція Svelte у плагін Obsidian
Дійшов до реалізації свого віджету для Obsidian. Як я писав, його нескладно інтегрувати через registerMarkdownCodeBlockProcessor
. Ця функція надає нам елемент HTML, в якому можна розташувати майже що завгодно.
Проте зовсім не хочеться збирати віджет з HTML вручну. Має сенс залучити фреймворк. Останній рік Svelte - мій вибір для простих задач. Хоча в принципі, в Obsidian можна інтегрувати будь-який фреймворк — бо на радість або на смуток, Obsidian цілком побудований на браузері (Electron), та дає такий само елемент-контейнер, який ми мали б в каркасі звичайного додатка.
Офіційна інструкція містить все, що потрібно щоб почати. Svelte абсолютно без проблем інтегрується в ESBuild (бо офіціально плагіни для Obsidian збираються саме ним.) Мені не зовсім зрозуміло, чому інструкція використовує store
для передачі в компоненти плагіну; обʼєкт плагіну, наскільки я знаю, ніколи не змінюється.
Інструкція не розповідає про те, як додати компонент Svelte в документ, як я це хочу — але це робиться очевидно. В обробнику блока створюємо компонент та передаємо йому наш кореневий документ. Готово. Ну, майже — ще добре було б видаляти компонент, коли він більше не потрібний, бо інакше, якщо я розумію правильно, буде текти памʼять. Для цього знайшов метод MarkdownPostProcessorContext.addChild(). Він дозволяє зареєструвати обробник на видалення блоку. В теорії, можна було б в цьому MarkdownRenderChild
інкапсулювати весь компонент, але мені поки простіше зробити клас з назвою SvelteDisposer
, який просто видаляє вказаний компонент при видаленні блоку.
Тепер, нарешті, можна робити всілякі цікаві віджети та звіти по задачах.
19.09.2023
Перевірка на наявність поля в TypeScript: від поганого до кращого
В продовження обмежень TypeScript. Припустимо маємо такий собі interface ListItem { taskDates?: { due: string } }
. Якщо цей пункт списку — задача, то він має дати. Якщо ні — то не має. Ми пишемо код, який щось робить з цими датами (наприклад, обирає задачі зі строком на сьогодні.)
Криво: Оператор невпевненості, або як його справді називають, “безпечної навігації”.
if (listItem.taskDates?.due === today) { ... }
Цей код нічого не каже про сенс наявності taskDates
. Він просто припускає, що їх може не бути. Він семантично порожній. Хоч такий код і працює, але його читач залишається здогадуватись про зміст самотужки. Потім проєктом розповсюджуються захисні конструкції, а з ними — невпевненість. Є значення? Немає? Перевірю на всяк випадок, хто його зна.
Чітко: Перевіряти на наявність поля на початку функції.
if (!listItem.taskDates) {
return; // або навіть throw
}
if (listItem.taskDates.due === today) { ... }
Тут навпаки ми явно кажемо: якщо дат немає — то ця функція втрачає свій сенс.
В залежності від контексту, можна або просто вертати, або кидати помилку (тоді зовнішній код зобовʼязується викликати функцію тільки коли дати є). Читач розуміє, що код розрахований саме на елементи, в яких є taskDates
.
Майстерно: обмежувати тип так, щоб виклик потребував чіткого набору полів.
interface TaskListItem {
taskDates: { due: string };
}
// або з utility-types:
type TaskListItem = Required<ListItem, "taskDates">;
Оскільки в TypeScript типи порівнюються за змістом, то коду, який викликає нашу функцію, не треба утворювати новий обʼєкт. Достатньо тільки перевірити, що поле присутнє, та обʼєкт типу ListItem
задовольнить тип TaskListItem
. Тобто десь все одно буде перевірка. Різниця тільки в тому, що краще розмістити її якнайвище в алгоритмі, та в такому місці, де конкретизація до пунктів-задач буде очікуваною: ззовні у нас всілякі пункти, а всередині — тільки задачі.
Як бачите, різниця тут не в правильності (всі варіанти однаково правильні), а в наочності в поясненні людям. В цьому треба спиратися на систему типів, а не сперечатися з нею.
18.09.2023
Не все так просто з Кафкою: стан
В продовження теми про вивантаження всього змісту з Кафки. При перевірці виявилося що обставини трохи складніше, ніж я думав.
Що треба зрозуміти — в семантиці Kafka дані течуть, як річка, та стан цієї річки складно вловлюється. Та друге — топіки розділяються на розділи. Кожний розділ — це окремий потік даних. Ми обовʼязково маємо переконатись, що кожний розділ оброблений. Дані з розділів надходять в невизначеному порядку. Навіть якщо ми ще не отримували з деякого розділу жодного запису, це не значить, що їх там немає.
Ключем до розвʼязку є зміщення даних в розділі. А саме. зміщення останнього запису з кожного розділу — доступне через API ListOffset. Залишається один раз зберігти поточне значення, а потім споживати, аж допоки не дійдеш до зміщення, яке записав на початку операції. Далі — вже тільки нові записи.
От тільки якщо в одному з розділів неспожитих даних немає, то доведеться чекати нових, щоб зрозуміти, що старих немає — та, можливо, взагалі не дочекатись. Щоб цього уникнути, потрібно також перевіряти зміщення, яке вже спожила наша група споживання — через API Offset Fetch. (Такі чудові назви API, не переплутаєш.) Якщо спожите зміщення дорівнює кінцевому, то на цей розділ можна не дивитись.
Розроблений таким чином алгоритм реально працює. Попри меншу передбачуваність, ніж я звик, все ж можна написати тести, які наповнюють топік, а потім з нього читають. Та це чудово, бо перевіряти таке вручну було б проблематично.
17.09.2023
Автосадівник для задач в Obsidian- нарешті працює
Радий поділитися, що вчорашні зміни в підході були плідними, та решта утілити була дописана швидко. Тепер в мене є плагін для Obsidian, який автоматично дописує дати (початку та кінця) до задач, а також пересуває задачі, які було закінчено раніше, в спеціальну “архівну” зону в кінці документа.
Все це працює завдяки прямому редагуванню функцією Editor.replaceRange. Позиції для змін я беру з синтаксичного дерева, яке робить Remark. Є тільки один трюк — щоб ці позиції залишалися вірними, всі зміни маю робити з кінця (бо при зміні документа псуються тільки ті позиції, які знаходяться після неї.)
Також допомогло те, що додавання дат обмежується одним рядком, тобто вертикальна позиція зберігається. Це добре, бо далі потрібно пересувати задачі до архіву. Тут синтаксично проста операція, бо в Markdown елементи списку завжди починаються з нового рядка. Тобто щоб пересунути елемент з усім його змістом на нове місце, можна просто вирізати та вставляти цілі рядки. (Так, до речі, моє рішення чудово працює з вкладеними задачами, проте до архіву пересуваються тільки завершені задачі верхнього рівня.)
Заголовок архіву теж додаю банально текстом, без всіляких синтаксичних дерев. А от перевіряю, що заголовок є, по дереву — бо дерево прибирає всі нюанси форматування та перевірка стає простіше.
Нарешті, всю обробку я переніс в подію Workspace.editor-change
, яка відбувається миттєво при будь-яких змінах — бо Vault.modify
відбувається при зберіганні файлу, тобто з затримкою. Тоді й зміст документа можна брати прямо з Editor.getValue, який є синхронною операцією (бо з Vault.read є ризик відстати від актуального стану). Виходить, вся логіка відбувається в контексті редактора, а не файлу, що… логічно. А щоб не обробляти буквально кожне натиснення на клавішу, використовую старий знайомий debounce.