Канал
У Льоні сьогодні
Нотатки архітектора систем та майстра на всі руки. Що я зробив, що з того вийшло, і що це все значить.
-
SwiftUI - рефакторинг та оточення
Сьогодні додав маленьку фічу до свого стохастичного таймтрекера — а саме, можливість передивитися всі пінги, які відмічені конкретним тегом. Що корисно, щоб згадати минуле. Щоправда, я досі не зробив перегляду більше за 50 пінгів, бо для того потрібне ліниве завантаження — тож не дуже далеке минуле.
Фіча звучить зовсім простою. Запит для останніх пінгів вже є. Додаєш до нього фільтр по тегу (до речі, із CTE) та виводиш списком. Але є й проблема.
Натискання на пінг відкриває форму редагування. Така можливість, звісно, потрібна й на новому списку. Ця форма не така проста річ — бо вона є модальним вікном, та на екрані ніколи не повинно бути двох форм. Тому її стан є глобальним.
Цей стан в мене поки зберігався просто в
@State var formModelна структурі застосунку, та передавався глибше через@Binding. Цього разу я прикинув, через скільки шарів доведеться прокинути@Bindingта вирішив, нарешті, переробити.У SwiftUI є можливість, схожа на контексти в React - це Оточення,
@Environment. Нею можна передати вглиб структури презентації якесь значення. Але — саме значення, не змінну, редагувати його не можна.Або… можна? Спочатку я спробував передавати функцію-сетер,
setFormModel. Це спрацювало! Далі пошукав більш ідіоматичне рішення та знайшов, що взагалі-то в@Environmentможна передатиBinding(FormModel), та тоді його можна буде призначати черезformModel.wrappedValue = newFormModel.(
Binding, який лежить в основі моделі стану SwiftUI, як раз і є абстракцією над гетером та сетером, яка загортає змінне значення у незмінну обгортку.)Код став гнучкіше та чистіше. До речі, в останньому SwiftUI цей
@Environmentзначно спростили, так що якщо давно не бачили — подивіться.🕜🏷️ Нагадаю, що я пишу Ping - застосунок на iOS для нетрадиційного обліку часу. Якщо цікаво, до бети можна доєднатися тут.
-
Кращий час для виправлення технічного боргу може бути зараз
Сьогодні така тема проскочила, що от є у вас база даних. І вона може застаріла. Але оновлювати її не хочеться — бо складно, ризиковано, доведеться вимикати сайт, і так далі.
Втім, якщо подумати про майбутнє. Якщо цей проєкт успішний, тобто кількість користувачів його зростає, то нам заздалегідь відомо, що в майбутньому ризики, навантаження, збитки від простою тільки зростуть. Тож виходить, що нема сенсу відкладати ризиковані зміни — краще робити їх якнайшвидше.
Звісно, не в кожного проєкту та не в кожний момент така траєкторія. Коли проєкт йде на спад чи впав у стагнацію, то взагалі не варто поки чіпати ніяких баз. До того ж така праця створює видимість прогресу, хоч нічого для успіху проєкту не робить.
А коли проєкт тільки-но починається, то може здаватися, що треба все тримати в порядку та чистоті. Бо ти ж закладаєш самий фундамент, а він мусить бути надійним. Але ні — як на мене, щоб досягти успіху, краще розвʼязати собі руки та залишити довершення на пізніше. (Це й оновлення залежностей стосується.)
Виходить, чим успішніше проєкт, тим менше треба відкладати на технічний борг. На сьогодні логіка така.
-
Застосунок для покращення оцінок часу
В мене в житті багато справ, які я гадки не маю, скільки тривають. Сьогодні за вечір зробив маленький застосунок для айфона з маленькою метою: вести облік того, скільки займає та чи інша задача. (Тут йдеться про безперервні задачі.)
Ідея в тому, що якщо давати оцінку, а потім заміряти справжній час виконання, то можна побачити розбіжності та згодом зробити оцінки точніше. Я не раз чув про такий підхід, але не знайшов застосунків. (Здивуйте мене!) Хоча, звісно, застосунок дуже простий — таке собі поєднання таймера та секундоміру.
Технічно таймер взагалі легко реалізується. Я тільки запамʼятовую, коли почалася задача. Далі залишається щосекунди перемальовувати в належному стані. Оскільки цей таймер не зупиниться, аж поки я явно не скажу, що закінчив, то решта логіки відбувається за натиском кнопки “Закінчити” - зберігаю час та ще й обчислюю середнє значення.
Бо так, я передбачив, що задачі ж повторюються, та чим більше вимірів, тим більше впевненості. Тому кожен “експеримент” відбувається не в ізоляції, а в контексті якоїсь “активності”.
Та ще є сповіщення, які я вже навчився робити по-всякому. Одне сповіщення — на “заявлений” час виконання, друге — на середній час (тобто “емпіричний”). І ще можна повторювати в час, кратний заявленому - щоб зовсім сумно стало.
Окрім прямої користі — самодосліджень — застосунок також мотивує робити справи без відволікань! От зараз пишу пост та вже відчуваю підвищену концентрацію.
-
Як я робив TG-бота для самоменеджменту
Прочитав нещодавно в пана Рожкова про бота для обліку пального. Та відразу згадалося, як я колись такого нагородив… аже поділитися захотілося, бо проєкт нікуди не пішов. Йдеться про бота для обліку власних задач за моделлю GTD.
Власне, ось навіть код виклав.Я писав його влітку 2017-го, але сьогодні легко завів. Бо це Go, а не React Native. У Go підтримка легасі — це одна з головних цінностей дизайну. Ще треба зазначити, що в Telegram дуже зручна модель розробки ботів, тож ти його можеш запустити в себе локально та створити власного бота. Поки програма запущена, бот буде доступний будь-звідки через інтерфейс Telegram.
Як бот влаштований. Фактично бесіда з ботом — це “вікно” в дані, яке фокусує нашу увагу на чомусь одному (наприклад, поточній дії.) Задача бота — просувати це вікно, а також пропонувати ефективні та вчасні операції в поточному контексті. Це досить особливий бот, можу порівняти його з людиною-тренером, або з інтерактивною літературою, тільки ще й з твоїми даними всередині.
Головна складність в тому, що він повинен памʼятати стан бесіди, причому навіть після довгої паузи. А бесіда має складну блок-схему (“обробка вхідних” в GTD вже складна!)
Я роздивлявся різні моделі та вирішив розбити спілкування на пари “питання-відповідь”. Кожне питання має: код-ідентифікатор, “шаблон інтерфейсу” - текст та кнопки, та обробник відповіді.
(До речі, оці кнопки - це лише “заготовлені відповіді”, тобто немає різниці, натиснути на кнопку чи ввести її текст вручну. А також тому можна обробляти кнопки та вільний ввід одночасно, в одному “питанні”.)
Обробник інтерпретує відповідь, робить належні зміни в базі, та надсилає повідомлення про результат. Результатом обробника завжди є нове питання, яке набуває чинності та отримує шанс побудувати вже власний “інтерфейс”. Наприклад, ось питання action_suggestion з ілюстрації.
Таким чином вдалося ізолювати логіку кожного кроку. Щоправда, я на той час не здогадався класти додатковий стан, як-от поточну дію, в поле JSONB, а записував прямо в спільні поля в моделі користувача. Це погано, бо в кожного питання може бути своє розуміння “поточної дії”.
Мені цей проєкт, з одного боку, подобався та досі подобається за його інтерфейс. Є щось особливе в тому, щоб працювати з даними в режимі діалогу (що на цей день стало тільки актуальніше.) А з іншого, діалог обмежує. Зокрема як тільки треба передивитися всі дані в базі, та ще й робити над ними зміни. Були в мене фантазії гібридного застосунку — тобто такого, де діалогова модель доповнює традиційну - але руки не дійшли.
-
OmniWOPE: публікація ілюстрацій в Mastodon
Хотів сьогодні насправді писати зовсім інший пост, але для нього потрібна була ілюстрація. А їх мій скрипт для публікації — себто OmniWOPE - не вмів викладати в Mastodon. Нарешті цей недолік виправлено, та зображення повернуться в мій канал.
Технічний прототип в мене вже рік був готовий. Ну тобто я міг завантажити дану світлину в Mastodon. Що, до речі, відбувається у два кроки: спочатку медіафайл окремо, потім — статус із посиланням на ID файлу.
Але я довго думав, як же ж організувати статуси. Додавати зображення прямо у головну статтю мені не подобається, бо це спонукає клієнтів показати її як “статус-зображення”. Нарешті вирішив публікувати як відповідь до головного статусу — це все ж загально прийнятний спосіб.
Ще виявилося, що треба ж оголосити тип медіа. (До речі: сьогодні дізнався, що саме “тип медіа” сучасна назва, а не “тип MIME”.) Досі для Telegram мені було потрібно тільки знати, чи є ілюстрація зображенням чи відео. Та нарешті, чомусь вбудований у Go метод multipart.Writer#CreateFormFile не дозволяє призначити тип медіа! Та найкраще рішення для того — скопіювати та виправити код методу.
На сьогодні все. Нову версію вже можна забрати з GitHub.
-
Графічні клієнти для Git
Я вже багато років майже всі коміти в Git роблю з графічного інтерфейсу. Інакше мені роботу важко уявити, бо я люблю коли мої коміти акуратні та впорядковані.
В гарному графічному клієнті можна переглянути зміст приблизно так само, як ти його бачиш на якомусь GitHub, тільки ще заздалегідь. Хоча це з термінала теж можна. Зате далі можна додавати зміни до коміту не тільки файл за файлом, а й рядок за рядком. Або ще корисно — навпаки, видаляти зміни.
Без такого інструменту я б не зміг ефективно готовити змістовну історію, бо майже ніколи не вдається робити зміни ідеально в тому ж порядку, в якому я хочу закріпити їх в історії. Також я завжди перевіряю, що в коміті немає зайвих змін, та з графічного клієнта можна дуже швидко прибрати конкретні рядки.
Цікаво, що все інше я роблю через термінал. Хоча, наприклад, для перегляду гілок теж є багато програм; втім, ми користуємося простою моделлю гілок, тому терміналу вистачає. Ну, хіба що ще
git blameз редактора зручно робити.Щодо конкретних програм: колись дуже давно все почалося з git gui - стандартного інтерфейсу. Здається, це було в мій час Linux на Windows, та я відкривав той GUI через Xming. Але наче цей GUI досі живий Потім я переїхав на мак, а тут
git guiнемає. Знайшов gitx - наче якщо не клон, то дуже схожу програму. Вона досі підтримується, та я досі її можу рекомендувати для macOS. Хоча останні роки мої коміти робляться всі через VSCode, тож у твоєму IDE теж може бути гарний редактор комітів — залишається ним користуватися. -
Швидкі задачі
Є велика мудрість в тому, щоб помітити, які важливі задачі можна зробити швидко. (Я думав про розробку, але насправді це будь-чого стосується.)
Так, є архетипова ситуація, де розробник каже на задачу - “ой, справ на пʼять хвилин” - а вона обертається цілим днем. Це не скасовує того, що, є й такі задачі, які дійсно можна зробити, покрити тестами, та оформити за лічені хвилини. Особливо коли це стосується прибирання (тобто видалення) коду, або дрібних змін в інтерфейсі.
Швидка задача — не обовʼязково неважлива. Зазвичай коли якийсь контекст закривають, залишаються недоробки, чи ще краще — їх виявляють з часом. А потім дивишся, а користувачі страждають через якусь дрібницю.
Але як тільки така робота стає поза поточним контекстом, планувати її стає важко. Керування проєктами гарно працює з епіками на тижні та місяці роботи, а не на хвилини. Мені стає боляче слухати про “заплануймо це на після оцього”, коли йдеться про годину чи дві роботи.
Тому, я гадаю, чудово, коли розробник може сам помітити та виконати таку роботу прямо сьогодні, без відкладання на невідоме майбутнє. Звісно, для того треба вірно оцінити обсяг роботи, а також бути обізнаним про пріоритети та життя-буття користувачів.
-
ZIP проти .tar.gz
🗜️ На мою думку, необхідно знати рівно два архівних формати (“формати” насправді умовно, про що нижче.) Різниця між ними дуже суттєва.
Справа тут в чому. Алгоритми стискання всі працюють на потоках даних. Про файли та каталоги вони нічого не знають. Тому коли ми хочемо стиснути каталог, доведеться спочатку перетворити його на потік даних.
.tar.gz - це насправді комбінація двох архівів (за принципом Unix “кожна програма робить щось одне”.) tar зберігає каталог файлів у вигляді потоку. Щоб прочитати один з файлів, доведеться бігти по архіву, поки його не знайдеш. gz стискає один файл — це не обовʼязково файл
.tar, може бути будь-який, наприклад,.json.gz- якщо вам потрібно стиснути лише один файл,tarне потрібний.ZIP зберігає файли каталогом, де кожний файл стиснутий окремо. Тобто на верхньому рівні ZIP файлу сидить каталог, в ньому можна знайти будь-який файл та розпакувати. На ZIP побудовано багато форматів файлів — це JAR, DOCX, ODT тощо. На відміну від
.tar.gz, ZIP-архів можна швидко передивлятися, розпаковувати окремі файли, та навіть редагувати архів — бо в.tar.gzможна тільки створити його наново.Алгоритмів стискання існує багато - gzip, bzip2, xz, Zstandard. Обирати варто тоді, коли у вас дійсно є потреба заощадити якнайбільше; в іншому разі я б брав всюдисущий
gzip. До речі, ZIP теж підтримує багато алгоритмів, хоча “нормальний” такий саме, як і в gzip - DEFLATE.А ще окрім стискання є шифрування. Тут ZIP програє, бо в нього досі немає шифрування аж всього змісту архіву. А з
.tar.gzвсе просто — зверху накидається шифрування, та виходить.tar.gz.encчи.tar.gz.pgp.Виходить, вся різниця в тому, що
.tar.gzсуттєво простіше, зате у.zipможна швидко передивлятися зміст архіву та редагувати його. Але головне, на мою думку, дотримуватися цих двох поширених форматів та не лізти в екзотику. -
Oblivion
Сьогодні абсолютно несподівано вийшла оновлена версія Oblivion: Remastered. Звісно, я поки нічого, окрім вступу, в ній не бачив, але поки виглядає як дуже цікавий ремастер, де наче оновлений графічний рушій з Unreal Engine сидить на старому ядрі. Не виключаю, що вони перевіряють такий підхід для використання в нових іграх, а не тільки для ремастера. В ремастері є стара знайома тека
Data, наповнена старими знайомими файлами Oblivion, тож я гадаю, можливо й моди будуть працювати.Але хотілося натомість згадати, як вийшов оригінал Oblivion. Ще до того ми з друзями були фанатами Morrowind та чекали виходу наступника. Обіцяли, що це буде “гра для компʼютерів майбутнього” та буде надзвичайно крутіша за Morrowind. В ту епоху (2004) інтернет був дуже обмежено, тому більшість інформації розходилася плітками. Чесно, набагато цікавіше уявляти, яка вона буде та гра з описів, ніж бачити трейлер. Хоча зрештою всі ті фантазії виявлялися хибними.
Хоча що було чистою правдою — на моєму компʼютері (здається, із Radeon 9250) Oblivion ледве йшов. Запускався — що вже успіх — але гальмував так, що доводилося знижувати якість та дальність зору до мінімуму — що не допомагало. Згодом знайшов програму, здається, Oldbivion, яка підставляла спрощені шейдери. Уявляєте, спрощені шейдери, щоб гра йшла! Перший та останній раз довелося таке робити.
В мене мішані відчуття від Oblivion. В ньому багато цікавих квестів та мальовничих локацій. Та я бозна скільки програв в нього юнаком. Але з тим в Oblivion жахлива прогресія та бойовка, яка з часом стає тільки нудніше. Тому не сумую за ним. Для мене, Morrowind досі є верхівкою формули Elder Scrolls. Та судячи з активної сцени модів для неї, не тільки для мене.
-
Резервне копіювання для Ping
Три місяці тому писав про резервне копіювання даних мого стохастичного таймтрекера Ping. Але нарешті тільки сьогодні вивів його в бету.
На жаль, забув випустити раніше. Але ось що допомогло. Моя власна копія почала зависати на запуску. В XCode дуже легко зрозуміти. що зависає — достатньо поставити застосунок на паузу. Тому коли дістався до XCode, то відразу побачив, що висне — не сама резервна копія — а її завантаження до iCloud, метод setUbiquitous.
Як мені здається, проблема була в тому, що моя альфа-версія робила копію на кожний запуск, та їх накопичилося дві з гаком тисячі в одній директорії в iCloud. Як мені здається, така кількість файлів уповільнює роботу iCloud. Бо більше нічого не змінювалося, а сам файл розміром лише 300 Кб.
Так що довелося закінчити фічу — робити копіювання раз на день, додати вимикач та допомогу. Завдяки використанню
@globalActorвиніс роботу з головного потоку. Тут у Swift цікаво:async/awaitроблять код рівночасним, але залишають виконання в головному потоці (тобто гальмують інтерфейс застосунку.) Щоб зробити код ще й паралельним, потрібно винести його в актора.Також зазначу. що бета-версії застосунків в Apple TestFlight згоряють за 90 днів. Тобто кожні 90 днів потрібно викладати нову версію, щоб бета-користувачі не втратили доступ. Та в мене сьогодні минуло 79! Радий, що через цю ситуацію встиг згадати та скоригувати курс.