Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
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
Не все так просто з Кафкою: стан
В продовження теми про вивантаження всього змісту з Кафки. При перевірці виявилося що обставини трохи складніше, ніж я думав.
Що треба зрозуміти — в семантиці Кафки дані течуть, як річка, та стан цієї річки складно вловлюється. Та друге — топіки розділяються на розділи. Кожний розділ — це окремий потік даних. Ми обовʼязково маємо переконатись, що кожний розділ оброблений. Дані з розділів надходять в невизначеному порядку. Навіть якщо ми ще не отримували з деякого розділу жодного запису, це не значить, що їх там немає.
Ключем до розвʼязку є зміщення даних в розділі. А саме. зміщення останнього запису з кожного розділу — доступне через 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.
16.09.2023
Автоматичне редагування документів в Obsidian
Повернувся до проєкту “автосадівника” дерева задач для Obsidian. На зараз ціль проста: щоб до задач автоматично додавався час створення та завершення, а також щоб завершені задачі переносились до “архівного” списку в кінці документа.
Сьогодні маю прогрес: базова версія працює. Проте зрозумів, що пара моїх підходів хибна. А саме:
Перетворення AST це чудовий підхід, але якщо інструмент не передбачає відтворення коду “як в оригіналі”, то покладатися на нього буде ненадійно.
Наприклад: Remark на виводі нормалізує символ списку — скажімо, до зірочки *
. Це само по собі не так погано. Але якщо поруч є два списки — один з зірочками, а інший з рисочками -
- то парсер бачив їх окремо. А коли другий список буде нормалізований у зірочки, то списки для парсера зливаються в один. Щоб виправити це, Remark додає між списками коментар HTML: <!---->
. Звісно, такого я зовсім не хочу.
Раніше я вже придумав тільки частково заміняти текст, тобто перетворювати тільки там, де є реальні зміни, але навіть часткова заміна має ризик зіпсувати документ… частково.
Напевно, все ж найнадійніший підхід це за допомогою AST тільки знаходити місце для зміни, а потім змінювати вхідний документ прямим підставлянням.
Тут наступне розуміння: в Obsidian активний документ варто редагувати тільки функцією editor.replaceRange. Вона безпосередню змінює зміст редактора та чудово співіснує з подальшим користуванням редактором. Мій перший намір був зберігати зміни в файл через vault.modify
, тобто “на диск”. Але це відразу почало створювати конфлікти з будь-якими змінами, які я одночасно зробив вручну. Не кажучи вже про те, що подія onModify
, яку я використовую для виконання свого коду, відбувалася й після моїх автоматичних змін теж (проте від цього нескладно захиститись.)
Нарешті, щоб зовсім не заважати користувачеві, ще треба перевіряти рядок, в якому знаходиться курсор (editor.getCursor
) та нічого в ньому не робити.
15.09.2023
Geometry Dash
Сьогодні буде про улюблену гру, але не мою, а сина - Geometry Dash. На перший погляд, вона майже нічого з себе не ставить — але при цьому у гри з 2013 року сабреддіт на 130 тисяч підписників та безліч щоденних відео на ютубі. Та й взагалі Google Trends каже, що вона лише в 7 разів менша за популярністю ніж світовий феномен Fortnite, при чому популярність GD тільки зростає, коли Fortnite йде на спад. Все це ознаки здорової спільноти, а значить, принаймні варто знати, що таке існує.
Механічно Geometry Dash є максимально простою грою: персонаж сам біжить по рівню, а все, що треба від тебе — це в правильний момент стрибати. Проте вона також є механічно надзвичайно складною: вимоги до точності стрибків з часом тільки зростають; перший рівень можна пройти за десяток спроб, але решта гри потребує годин тренувань. Тому зазвичай гравці порівнюють відсоток рівня, який вдалося пройти.
Можна зрозуміти, чому ця гра так захоплює (особливо дітей): завжди здається, що ось-ось, ще одна спроба — та ти пройдеш; завжди зрозуміло, що робити далі: вся гра — один нескінченний quick time event. Звісно, для стрімерів це теж непоганий формат.
Проте популярність Geometry Dash тільки наполовину полягає в простоті. Ключовою функцією тут є редактор рівнів та можливість ділитися ними онлайн. Створення рівнів це ціле мистецтво: тут не тільки можна “малювати”, а й залучити ряд логічних елементів-тригерів для утворення анімації та різних ефектів. Так люди фактично роблять цілі музичні відео на кшталт такого. Рівнів існує мільйони, з них 35 тисяч офіційно “оцінених” від тривіальних до “неможливих”. З часом виходять все “неможливіші” та гравці змагаються за те, хто скоріше пройде новий рівень.
А можна ще копіювати рівні та переробляти їх, або за допомогою мегахаку записувати макроси, що автоматизують проходження, або створювати рівні-випробування для друзів та змагатися, хто швидше пройде, або створювати рейтинги рівнів, або проходити випадкові чи нові рівні… Такий от цікавий куточок інтернет-культури зі своїми героями, подіями та історією.
14.09.2023
Чому "писати на тому, що знаєш" погана порада
На днях брав участь у дискусії чи Go гарна мова, щоб писати соціальну мережу. Чому ні, я вже все сказав, а тепер хочу поговорити про популярну відповідь “краще за все писати на тому, що знаєш”. Як людина, яка хотіла писати операційну систему на BASIC (даруйте, мені було 8 років!), можу сказати, що це погана ідея.
Взагалі, звісно, всі професійні програмісти пишуть на тому, що знають, щодня, та це виходить цілком справно. Але так відбувається тому, що ми не виходимо за межу вузької галузі, над якою працюємо.
Проте універсальних мов програмування не існує. Фахівці з фулстек веброзробки мають це знати — бо типовий додаток буде містити принаймні дві мови — для бекенду та для фронтенду. Але веб — це скоріше виключення, бо в більшості галузей розробник може все життя працювати одною мовою без нестачі проєктів.
Все то добре, поки ти в зоні комфорту. Але якщо область тобі невідома, то є шанси, що твоя робоча конячка не найкращий, або взагалі шкідливий вибір. Я б радив спочатку подивитись, що використовують типові проєкти — наприклад, через статті та презентації. Звідти й треба починати. Так, я не полюбляю Python, але якщо доведеться працювати з аналізом даних — візьму pandas.
Так, Railsware колись починалась як компанія, що пише на Ruby on Rails (та трішечки JavaScript зверху). Але зараз проєкти включають Golang, Node.js, Python, Java, та інші мови. Зовсім не тому, що проєктів на Rails не вистачає. А тому, що навіть в рамках одного продукту не все має сенс писати однією мовою. Компанія просто переросла прості проєкти.
Отже, якщо в тебе є мета робити проєкти з різних галузей, варто налаштуватись вивчати різні мови програмування. Або натомість заглиблюватись у свою галузь: теж гідне заняття. Тільки не намагатись все писати однією мовою; в академічних цілях це може бути цікаво, але в професійних — згубно.