Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
04.06.2024
Як я живу з <10 вкладок браузера
Хоч я розумію людей, в яких завжди в браузері безліч вкладок — та ще й через те бракує памʼяті (оперативної! або..?) - але сам все життя обмежуюсь мінімальною кількістю. Тобто від жодної до, може, двадцяти, якщо щось активно шукаю — але завжди намагаюся всі їх закрити.
Оскільки, на мою думку, безліч вкладок має обʼєктивні (витрати памʼяті) та субʼєктивні (це рудиментарна та неефективна система збереження контексту) недоліки, хочу поділитися тим, куди в мене ті вкладки зникають.
-
Обране. Багато сторінок я відвідую постійно. Легкий спосіб знайти їх швидко — додати в обране. Тоді й з рядка пошуку, й зі стартової сторінки (якщо така є) просто та швидко відкрити будь-яку закладку.
-
Список для читання. Незамінний інструмент, зберігає все, що я планую прочитати або роздивитись, але байдуже, коли. В мене для того є Reeder, але альтернатив багато. Для прикладу, цього року опрацював більш як 1000 сторінок з Reeder, які накопичились раніше. Уявіть собі, якби це були 1000 вкладок. А тепер знову 91 сторінка в черзі.
-
Нотатки. Тимчасові сторінки (наприклад, товару, який планую придбати, або пошуку, за яким стежу) скидаю в Drafts. Це чудова програма для всього тимчасового.
-
База знань. Всі посилання, що викликали інтерес, йдуть в Obsidian. Моя система організації — просто написати поруч влучні ключові слова; якщо щось шукаю довго, то як знайду, дописую всі ті слова, за якими не знайшов. А ще в мене є шорткат щоб копіювати посилання в Markdown, та він вже увійшов в памʼять мʼязів.
-
Групи вкладок. Єдиний випадок, коли мені дійсно потрібно тримати багато вкладок — це пошук або вибір альтернатив. На таке є сучасне рішення: групи вкладок; це як збережене вікно браузера, яке можна закрити, а потім відновити, коли це буде потрібно. В Safari навіть можна ділитися групою з іншими — наприклад, перекинути дружині варіанти меблів.
На мою скромну думку, безліч вкладок — це виліковне, та від неї варто звільнятися. А у тебе чому стільки вкладок?
03.06.2024
Тести на покриття коду (особливо на Ruby)
У нас в проєктах з моєї ініціативи є перевірка тестів на покриття коду. Брудну роботу виконує SimpleCov, але його результати можна ще опрацювати.
-
Результати з паралельних тестів ще треба обʼєднати. На те є довга інструкція; код писати доведеться, власним поділитися не можу, але скажу, що інструкція все пояснює.
-
На покриття потрібно дивитися файл за файлом. Інтегративна оцінка є “середньою температурою по палаті”, та головне — не повідомить про додавання нового непокритого коду — бо один пакет змін не зсуне суттєво покриття проєкту. А це задача №1 - уникнути написання коду без тестів.
-
Який відсоток покриття вважати гарним? Я спочатку намагався зробити хитро та перевіряти не фіксований відсоток, а інваріант “кожна зміна покращує покриття.” Мета була — уникнути явних виключень файлів з низьким покриттям з перевірки. Проте це виявилося ну дуже нестабільним в реаліях проєкту з багатьма гілками та паралельною розробкою. Тому зараз порогом є 90% покриття рядків та 80% покриття гілок… та все ж з набором файлів-виключень.
-
Про покриття гілок. Його часто складно досягнути, бо потрібно покрити кожну гілку обробки помилок. (В Go взагалі не уявляю як це.) З іншого боку… код ми написали, а запускати не плануємо? То може його прибрати чи замінити на узагальнений варіант?
…Я погоджуюсь, що покриття як абсолютна ціль — безглузда. Воно не підтверджує вірності коду. Але в інтерпретованій мові, фактично, поки не запустиш код, не знаєш, що він працює. В цьому і є сенс покриття — підтвердити, що код взагалі працює, що параметри вірні, і так далі. З іншого боку, нещодавно працював над покращенням покриття та додаткові 3% не виявили нових помилок.
02.06.2024
GTD: поточні справи проти планування
В мене була весь час ця ідея, що система GTD повинна охопити все життя. Ну воно десь так: порівняно з простим “списком задач”, GTD пропонує перелічити всі “відкриті цикли” в житті, щоб їх… поступово закрити. Проте ось що я тільки починаю розуміти: GTD розрахований тільки на поточні справи, а ніяк не на всі майбутні плани: обовʼязкові, чи можливі, чи бажані.
А я навпаки, зазвичай приділяю більше уваги тому, що я хочу зробити, та недостатньо тому, що заважає цим займатись: ті самі “відкриті цикли”. От, GTD призначений не для того, щоб робити нові проєкти всупереч незакінченим справам, а щоб ці справи закінчити та розвантажити голову.
Можна порівняти систему GTD з нашою “оперативною пам’яттю”: вона тримає короткотривалі обов’язки з метою завершити їх та рухатись далі. Власне, так і написано: проєкт — це “віха в землі”, яка фіксує ціль, якої ми хочемо досягнути; наступна дія — це найближчий крок до цієї цілі. Тепер мені здається, що ця модель навмисно мінімальна, щоб вписатися в будь-яку систему організації, яка вже існує.
Наприклад, якщо мої робочі задачі сидять в Jira, то мені не потрібна синхронізація з Jira, щоб використати GTD. Достатньо переписати в проєкти дві-три поточні задачі. А наступні дії можуть зʼявитися як самі собою, так і з плану реалізації чи інших підготованих заздалегідь матеріалів. Причому не потрібно переносити самі плани в GTD: достатньо щоб їх було легко знайти та передивитись (там же ж в Jira).
А відповідно, GTD дійсно можна займатися хоч в блокноті, бо саме таку інформацію й зручно зберігати в блокноті: короткочасну та стислу. А цифрові рішення навпаки, несуть ризик все ускладнити та загубитись.
01.06.2024
OpenSearch: база, де все відбувається не відразу
Нарешті зміг для себе зрозуміти, що мене понад усе дратує в роботі з OpenSearch: майже нічого в цій базі не відбувається синхронно. Я звик, що база даних є синхронною; звісно, реплікація займає час, але той вузол, з яким працюєш, все робить коли йому скажеш. В OpenSearch все не так.
Навіть з базовими діями — наприклад, індексацією одного документа — є спеціальний параметр refresh=wait_for
, який змусить виклик чекати, поки документ не стане проіндексований. За замовчуванням індексація буде відкладена. Це відразу помічаєш у тестах, бо без цього параметра вони ніколи не будуть стабільними.
А коли маєш справу зі складнішими операціями, взагалі краще думати про них як про процеси; в деяких випадках — буквально. Наприклад, в PostgreSQL масове оновлення (UPDATE ... WHERE
) теж може тривати навіть години, але таблиця може бути тільки у двох станах: не оновлена та оновлена. З OpenSearch масове оновлення опрацює скільки встигне та повернеться — далі запускай наново.
Так само і перетворення хоч виглядають, як матеріалізовані розрізи, але працюють сторінка за сторінкою, тобто результат завжди буде неповним: причому не в тому сенсі, що відповідати деякому старому стану вхідних даних — а бути десь посередині, причому ми ніколи не впевнені, де саме.
Все це добре, коли вас влаштовує кінцева узгодженість, а чи буде вона досягнена зараз, чи через хвилину — байдуже. Та це, певно, найбільша перешкода у використанні OpenSearch як традиційної бази даних.
31.05.2024
Setapp
Операційна система macOS завжди мала потужну екосистему якісних та платних застосунків. Настільки, що саме з переходом на macOS у 2011 році я почав віддавати за застосунки реальні гроші — на Windows та Linux не доводилось. А тут програма могла запросто коштувати десятки доларів. Тоді ще не було App Store та не було підписок; втім, було нормально брати гроші за кожну мажорну версію.
Ефективне використання macOS потребує кураторства власної колекції застосунків, яка розвʼязує твої особисті потреби. Для мене такими є Bartender, Yoink, BetterTouchTool, Dash та багато інших — колись писав цілу статтю.
Помітили спільне в цих посиланнях? Вони всі ведуть на Setapp - сервіс “оренди застосунків за єдиною підпискою”. За 9 доларів на місяць я отримую ці та десятки інших застосунків та більше не думаю, чи попросять вони гроші за наступну версію, чи є в них власна підписка (як зараз модно), та скільки взагалі коштує окремий застосунок. Тому я щасливо підписаний на Setapp без жодних роздумів.
Наприклад, за Bartender 1 я заплатив у 2012, а потім за Bartender 2 у 2015. У 2016 зʼявився Setapp, та наступну версію Bartender я встановив вже звідти, та так і продовжую користатися ним за єдину підписку — наразі версією Bartender 5. Так само з iStat Menus. А Dash з 2023 перейшов на модель підписки, проте мене це вже не турбує — бо в Setapp він доступний в повному обсязі за ту саму єдину ціну.
Наразі в Setapp доступні найкращі застосунки для macOS (ну, не всі). Може, їх не так легко знайти: точніше, я ніколи не шукав їх через Setapp, а скоріше, знаходжу в широкому інтернеті, а потім, наприклад, перевіряю, яка з 3 програм доступна в Setapp. Або коли потрібно щось разове, тоді можу звернутися до Setapp, щоб безплатно отримати якіснішу програму: може, архіватор чи клієнт випадкової бази даних.
За моїм реферальним посиланням можна отримати місяць безплатно.
30.05.2024
Механізм оновлення у SwiftUI
Коли йдеться по декларативні презентаційні фреймворки, я звик до React та його парадигми “воно само магічно перемалюється”: при найменшій потребі React викличе всю ієрархію компонентів, а потім завдяки Virtual DOM ефективно впровадить зміни у реальність. У Swift все не як в React: хоч код теж має декларативний вигляд, але він працює на підписках на зміни в даних за шаблоном Спостерігач та оновлює тільки там, де відбулися зміни.
З одного боку, це ефективно та круто, але з іншого, потребувало чітко розуміти які дані можуть змінюватись та стежити за анотаціями. Такої простоти, як в React, не було.
Але це поки використовувався фреймворк Combine (офіційний, від Apple). Він надає загальну модель асинхронної обробки подій, а як окремий випадок — протокол ObservableObject
для стеження за змінами в обʼєкті. Минулого року вийшов новий фреймворк - Observation. Більше деталей знайшов у супроводжувальному документі.
Observation, що цікаво, побудований не на підписці, а на механізмі зворотних посилань; серцем фреймворку є метод withObservationTracking, який зберігає всі звернення до відстежуваних атрибутів всередині та один раз викликає обробник, коли один з тих атрибутів змінився. (На наступний виклик ми можемо потрапити в іншу гілку коду та звернутися до інших атрибутів; тому щоразу перелік посилань будується наново.)
У SwiftUI все це приховано, та ми знову просто пишемо код, та він сам магічно перемальовується. Майже як в React. Але навіть краще ніж React, бо перемальовуються тільки ті компоненти, які залежать від атрибутів, що дійсно змінилися.
29.05.2024
Сповіщення на iOS: по-простому та по правильному
Повернувся до роботи над таймтрекером. Сьогодні вдосконалив, як працюють сповіщення-нагадування (чи не найважливіший компонент всієї системи.)
Взагалі, щоб відкрити застосунок за сповіщенням, робити нічого не потрібно. Окрім запланувати (або отримати) це сповіщення. Цим я й користувався до цього часу. Нагадування про пінг просто відкривали застосунок, а він на момент запуску зʼясовував, чи є актуальний на дану хвилину пінг, та показував в цьому разі форму.
Плюси — рішення тривіальне. Мінуси — якщо відкрити старе сповіщення, то застосунок форму не відкривав. Також нічого не відбувалося, якщо сповіщення приходить, а застосунок вже відкритий. (Хоча за три місяці такого не трапилось жодного разу. Статистично неймовірна подія!) Тобто, можна сказати, що це халтура, але для MVP мене цілком влаштовувало.
А правильне рішення це задекларувати делегат та обробляти відповідні події.
(В iOS обробка зовнішніх подій відбувається через модель делегатів. Кожне джерело подій — наприклад — центр сповіщень — оголошує інтерфейс делегату. Я створюю клас, який реалізує цей інтерфейс, та передаю його системі. Система викликає мій клас, коли це потрібно.)
Подій всього дві, та вони покривають всі мої потреби. Перша — для отримання сповіщень: як в момент запуску, так і коли застосунок вже відкритий. Друга — цікавіша — контролює, чи показувати сповіщення при відкритому застосунку. Так я можу обрати: показати власне інтегроване сповіщення, або просто залишити системне. Чим поки й скористаюсь.
28.05.2024
Планування: з простору проблем у простір рішень
Хотілося закінчити про процес планування, бо це досить важлива тема та я їй сам приділяю недостатньо уваги.
Коли простір проблем досліджений до достатньої міри (попросту кажучи, ідеї закінчились), то він перекладається на простір рішень. В моєму випадку рішення — апріорі застосунок, залишається перебрати кожну проблему, ризик, та користь та покрити їх функціями застосунку. При цьому можна відразу прибрати аспекти з низьким пріоритетом.
Отримуємо закінчену мапу продукту, який виконує всі ті потреби, які ми змогли уявити. У власній розробці помічаю антипатерн: я знаю, що “чогось” продукту не вистачає, але намагаюсь закривати це навмання — функцію тут, функцію там. Наявність мапи допомагає не тільки сфокусувати зусилля, а й мати осяжну ціль, поза якою продукт можна назвати “готовим”.
Наприклад: я окреслив ризик (та високий!), що людина не довірятиме запропонованій системі статистичного обліку. Такий ризик мені був відомий давно, але я нічого з ним не робив. Після формального прийняття ризику я додав епік “Підручник”. Бо дійсно, в цій програмі прихована математична модель, яка не стане зрозумілою тільки з використання та неявних ознак.
Або інший приклад: я з самого початку задумав, що теги повинні мати вкладеність (наприклад: “робота / програмування / проєкт X”). Але для вкладеності не знайшлося місця на мапі: вона не розвʼязує жодної потреби користувача. При цьому складності додає — як у розробці, так і у використанні. Тепер можна впевнено прибрати її з проєкту та розблокуватися.
27.05.2024
Планування проєктів схемою BRIDGeS
В інді-розробці часто проблема навіть не в тому, що немає часу робити, а незрозуміло куди рухатись. Особливо коли звик працювати в команді, де структура роботи задана згори. Може стати сюрпризом, що просто робити історію за історією недостатньо для успіху.
А взагалі ця задача стосується будь-якого продукту, не тільки інді, та не кожна команда вміє їх розвʼязувати. У нас в Railsware вміють, та навіть витягнули своє ноу-хау в фреймворк, який назвали BRIDGeS. Він чудово підходить для того, щоб зібратись гуртом навколо білої дошки, але нічого не заважає робити планування наодинці в Фігмі.
Ключовою особливістю Bridges є мозковий штурм. Ми починаємо зі збору уявлень про майбутній продукт, його користувачів, та їхні проблеми та ризики. Це найбільш плідний етап роботи — вилазять неочікувані, невимовлені аспекти. (Наприклад, в схемі зверху спочатку була тільки одна персона “Користувач”, але пізніше я помітив, що деякі ризики мають на увазі більш обмежені персони, та виділив їх окремо.)
Різні аспекти записуються на картки різного кольору та упорядковуються за сенсом. Це сильна сторона дошки — візуальний компонент вміщує купу інформації.
Коли всі уявлення записані на дошку, ми їх пріоритизуємо та розшукуємо розвʼязки, тобто компоненти системи, які розвʼяжуть ті чи інші потреби. На цьому етапі менше сюрпризів, проте доводиться шукати баланс між зазначеними ризиками та користю. Стає до нагоди як довідка та мапа уявлень, яку ми попередньо зробили.
Нарешті після того, як зрозумілі розвʼязки, ми перетворюємо їх на знайомий план епіків та задач. Ось так можна від абстрактного уявлення про продукт перейти до дій.
26.05.2024
Автопостинг з CI: зовнішнє сховище
Я зʼясував, що без зовнішнього сховища з CI постити не вийде. Займався пошуком такого сховища.
В мене тут є дві потреби: запобігання конфліктам та власне зберігання стану. Краще першу відразу відокремити: запобігання конфліктам вимагає синхронності, що відразу звужує можливий список. Думаю, краще для того покладатись на вбудований функціонал CI - щоб просто не запускав дві збірки одночасно. Принаймні, для першої версії цього достатньо. (Наприклад: Terraform з S3 може використовувати DynamoDB для замикання стану.)
Як щодо зберігання, то в мене спочатку думки пішли в бік баз даних, а саме Firebase та вище згаданої DynamoDB. Цих двох, бо їх достатньо легко розгорнути. Проте будь-яка база зробить рішення практично непереносним, а підтримка різних баз вимагатиме відносно складних адаптерів.
Тому вирішив залишитись в парадигмі “єдиний великий JSON”: всі дані зберігаються в один файл JSON, а далі ми цей файл зберігаємо… на S3. Це очевидний вибір, оскільки API S3 став стандартом де-факто для хмарного сховища. Плюс, користувачі Hugo вже можуть мати налаштований S3. Плюс, JSON-“базу” можна взагалі нікуди не відвантажувати, а тримати локально, якщо CI вам не потрібний.