Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
09.11.2024
Переваги розробки наодинці
Професійне програмування включає багато підходів, які допомагають команді досягти успіху. Та коли ти раптом хочеш зробити проєкт самотужки, природно буде намагатися відтворити ці підходи. Деякі з них, безперечно, варті того (використання система контролю версій?) Але не всі (наприклад, вичерпне покриття тестами.)
На мою думку, взагалі немає такого, що розробка на роботі якась “правильна”, та до неї потрібно прагнути. Всяка розробка є компромісом, прагнути варто до компромісу, який влаштовує твої обставини. Тоді, несподівано, можна досягти значно кращої ефективності.
Коли працюєш на самоті, ефективність є головною перешкодою: бо часу замало та він скінченний. В команді головною перешкодою є комунікація та координація; фактори, які одинака обходять. Ще один аспект — продукти на роботі неодмінно обважені очікуваннями клієнтів, репутацією, та генерацією прибутку, а власні продукти — ні, або не відразу, або взагалі пишуться для себе.
Я не хочу перелічувати конкретних підходів, тут більше про правильне усвідомлення власних обставин, та те що не потрібно обтяжувати себе “діловими” рішеннями. Нема часу на тести? Ну не пиши. Треба зробити міграцію даних? Та ніхто не помітить, якщо це буде з 10-хвилинним (а може й добовим) простоєм. Хочеться взяти улюблену езотеричну мову програмування? Нікого не доведеться переконувати.
Також не обовʼязково витрачати час на правильну архітектуру, покриття всіх уявних випадків, та стовідсоткову надійність. Це не робить тебе гіршим розробником — тільки більш свідомим та прагматичним.
08.11.2024
Пошук перегонів даних в Go
🏁 Перегони даних — це коли два рівночасні процеси наввипередки оновлюють змінну, без відома про існування один одного (Мені не дуже зрозуміло, чому це називається перегонами.) В результаті отримуємо напівзіпсоване значення, в якому не врахована частина обчислень. Причому, звісно, рівночасність — непередбачуване явище, тому псування трапляється не кожного разу, а може взагалі чекати на високе навантаження в продакшні.
Одним словом. Шукав, чому випадково хибиться тест. Звузив причину до ділянки з використанням go-redis#ClusterClient.ForEachMaster - вона збирала значення з усіх серверів Redis в один масив. Спочатку думав, що тесту бракує очікування (ще одна класична причина випадкових хиб) та намагався виправити. Тут є легка перевірка, насправді: додай свідомо перебільшене очікування — може, 10 секунд — якщо це не виправляє тест, то проблема в іншому.
А потім помітив в документації по ForEachMaster, що вона-то рівночасна!. Звісно, через рівночасність зміст вихідного масиву був неповним, до того ж для кожного хибного випадку різний. Щоб виправити, я додав збір результатів через канал, але можна було б й мьютекс поставити. Такі API з прихованою рівночасністю мене обурюють, бо з ними хоч знаєш, а не зробиш правильно.
Тут я згадав, що в Go є детектор перегонів та раптом дізнався, що він не увімкнений за замовчуванням. Увімкнув — та дійсно, проблемний тест відразу “засвітився”. Хотів вже увімкнути для всіх тестів та локальних запусків, але виявив пару проблем з цим. По-перше, тести з детектором тривають в три рази більше. По-друге, як не дивно, але для компіляції з детектором потрібно увімкнути CGO, а це йде зі своїм пакетом ускладнень, як-от неможливість кроскомпілювати з macOS в Linux. Довелося відмовитись.
Нарешті, детектор знайшов ще одну гідну обурення ситуацію. Такого я ще не бачив. Офіційних серіалізатор Protobuf у JSON - protojson
- додає у випадкові місця JSON пробіли. Та цього навіть не можна вимкнути!. Пояснюють вони це тим, що “споживачі мусять не очікувати чіткого формату”, оскільки команда Protobuf з ним ще не визначилася (sic!) Тому поки відстежуємо обговорення, якому вже 4 роки, а в тесті довелося декодувати JSON та порівнювати не рядком, а за змістом.
07.11.2024
Майже синоніми
Тільки писав про переклади айтівської лексики, а тепер пишу коментар в коді англійською та виходить щось на кшталт “ці атрибути доступні тільки для читання… а оце поле можна писати”. Дивлюся та думаю: навіщо стільки слів, які достатньо схожі, щоб їх плутати, але все ж різні, щоб доводилося стежити за використанням. Це ж гірше ніж табуляція проти пробілів!
В мене в голові слова “атрибут” та “поле” це майже синоніми. А їхнє використання більше залежить від оточення: в Go “поля”, в Ruby “атрибути”. (Причому в Ruby атрибут — це суто домовленість, що пара методів читає/пише змінну екземпляру.) Хоча, думаю, читач здогадається, про що йдеться, але все одно варто дотримуватися заведеного набору слів. Що стає проблемою, коли працюєш в декількох оточеннях.
Атрибут, поле, властивість — тут майже повна взаємозамінність, хоча в кожній мові своя назва. Головне не плутати в одному реченні дві назви. Є ще стовпчик в базі.
Метод, функція, процедура — тут все розбігається… в Go функція сама собою — це “функція”, але в межах типу чи інтерфейсу це “метод”. А в Ruby всі функції то “методи”, от тільки чомусь є метод module_function
, який перетворює всі методи модуля в методи на модулі. На щастя, “процедура” майже не зустрічається… хіба що в SQL є й “функції” й “процедури”. А ще є параметр, аргумент, опція.
Обʼєкт, структура, запис, сутність, екземпляр, а також клас, тип, структура — тут потрібно бути обережним. В JS/TS є тип “обʼєкт”, а є “клас”, і різниця важлива. У Swift є “класи” та “структури” та це фундаментально різні речі (класи передаються за посиланням, а структури — за значенням.) В Ruby все є класом, навіть модулі.
А ще в кожного слова є його математичне значення. Та побутове значення. А ще різні бібліотеки можуть залучати “вільні” слова для внутрішнього використання (так, в Ruby обʼєкти типу ActiveRecord зазвичай називаються “запис”.) Все складно!
06.11.2024
Порожній список дій на роботі та висновки
Останнього тижня мені пощастило піти у коротку відпустку… та так вийшло, що я це зробив з абсолютно порожнім списком наступних дій. Це в мене безпрецедентно, та до того ж дуже приємно. Хочеться повторяти цей успіх, тому занотую спостереження.
Йдеться про список наступних дій; список проєктів, звісно, не залишився порожнім, бо там деякі справи тривають місяцями. Виходить, в цьому розділенні на два списки є додаткова користь — тільки один з них можна закінчити. По поверненню з відпустки я, звісно, пройшовся по проєктах та досипав ще дій — але визначним був той момент, коли дій не було. Хотілося б досягати такого щотижня.
Це моя перша роздільна система для роботи та дому. Раніше робота завжди була контекстом (текою, зоною) в спільній системі. Зараз в мене окремий компʼютер для роботи та я намагаюсь відокремити роботу від особистих справ, тому було логічно й системи задач зробити дві.
Виявилось, що розділення значно спрощує робочу систему: в ній лише один контекст, тобто єдиний список задач (дій.) Я просто відкриваю його та роблю дії по черзі; нові задачі додаються в кінець. Майже не доводиться думати, що робити далі. Зате тепер доводиться окремо робити щоденний та щотижневий огляд, що в цілому тільки перевага.
В домашній системі до такого успіху ще далеко, бо там все (поки?) вдесятеро складніше. Контекстів багато, проєктів багато, вони всі різні. А головне — потрібно вставати з-за компʼютера. Бо дійсно, зараз робота стала такою зоною комфорту, де все заздалегідь впорядковано — сиди та роби. Раніше так хіба з компʼютерними іграми було. Втім, такий успіх надихає влаштувати особисті справи так само.
Коли мене просять щось зробити, то воно йде або в кінець списку, або — якщо це терміново — то я роблю його невідкладно. Але головне, що є надійне місце для всього, що невідкладно не зробиш. Це звільнює від “режиму хомʼячка”, де бігаєш по колу та робиш останнє, що впало в очі.
До речі, майже всі нетермінові прохання я обробляю вранці під час щоденного огляду; протягом дня я їх записую в нотатки чи зберігаю в Slack, а вранці обробляю ці вхідні та перетворюю за GTD.
05.11.2024
Теги в каналі та HTML
Не так все просто з тегами, як я вчора розповідав. А саме, замінити тег всередині тексту не можна, якщо він вже є посиланням. Ну, наприклад, якщо я вирішив послатися на сторінку Вікіпедії про Go, а також й додав тег Go, то тегом повинна бути друга згадка, а не перша, яка в посиланні.
Іншою особливістю тут є те, що метадані (разом з тегами) є частиною вихідного тексту, а значить, зʼявляються не до, а одночасно з обробкою Markdown. Тому легше за все замінити теги вже в підготованому для Telegram тексті. Який є потворним гібридом HTML та текстової розмітки. Тож доведеться заміняти в HTML.
Як заміняти? Точно не регулярними виразами. Тут потрібний той чи інший парсер HTML. В Go для того є модуль net/html - я про нього вже згадував. Він надає, окрім іншого, токенізатор HTML, тобто спосіб перетворити HTML в послідовність токенів: “тег”, “текст”, “коментар” тощо.
Для такої задачі токенізатор краще, ніж розбір документа в дерево, оскільки дерево нас не цікавить. Нам важливо тільки одне: чи знаходиться текст всередині посилання. Тому заводимо прапорець “чи ми в посиланні” та біжимо по документу.Бачимо токен “відкрити тег A” - вмикаємо прапорець; “закрити тег A” - вимикаємо прапорець. Якщо зустріли токен “текст” та прапорець вимкнений — шукаємо в ньому теги. А решту токенів копіюємо в вихід, як є.
Замість складного обходу дерева чи пошуку батьків — простий прохід з тривіальним скінченним автоматом. Швидко та прозоро.
04.11.2024
Теги до постів в каналі
🏷️ Сьогодні дійшли руки додати до каналу теги - бо постів вже за 800, та регулярно виникає потреба послатися на ту чи іншу серію. Також це допоможе SEO.
Відразу натрапив на проблему. В мене ж пости йдуть на сайт через Hugo та в Телеграм. В Телеграмі теги робляться тривіально: додаєш до слова октоторп та він сам робить посилання на пошук за цим словом. У Hugo система в корні інша: теги належать метаданим поста, а не тексту, та на знаки решітки йому ніяково.
(Тривіальне рішення, певно, це писати тег і в метадані, і в текст, та закрити очі на зайві решітки на сайті. Але хіба це може задовольнити?)
Спочатку хотів зробити якийсь тег-шорткод, на кшталт того що маю для внутрішніх посилань. Це ніби працювало б, але в вихідному коді неохайно (особливо порівняно з хештегом!) та технічно складно.
🚿 Тоді сходив в душ та винайшов елегантне рішення: теги будуть в метаданих, а в тексті я буду автоматично додавати тег до першої згадки слова. Або, коли такої згадки немає, просто в перший рядок. Що й зроблено в цьому пості.
Окрема історія - що в Телеграмі складніше ретроактивно додати теги… якби я це робив вручну. Але оскільки для того є бот, який побачить зміну змісту та відредагує пости, то це можна зробити практично без зусиль… якщо нічого не поламається.
03.11.2024
./configure; make; make install
📖 Розгорнута відповідь на питання, для чого я використаю make. Make - це найстаріша система для збірки, тобто така що запускає на машині розробника специфічні для проєкту інструкції. Компіляцію, запуск тестів, налаштування середовища… власне, всі команди, які властиві проєкту, можна зібрати в набір Make-файлів та спростити команді життя.
🎅 Make - система, стара як Unix, їй майже 50 років, тому вона має деякі анахронічні особливості. В серці Make лежить можливість запускати команди для кожного файлу окремо та пропускати команду, якщо вихідний файл новіший за вхідний. Бачите, в C збірка починається з компіляції кожного файлу з кодом у проміжний файл .obj
, а потім вони складаються (лінкуються) в виконуваний файл. Тож в Make вбудований не тільки кеш збірки, а й домовленості, які з сучасними проєктами рідко справджуються.
🍃 Втім, я постійно беру Make для власних та робочих проєктів (з одним"але" 👇.) ЇЇ головна перевага: вона вже встановлена в будь-якому середовищі розробки та не залежить від мов програмування. Тож для роботи з Make не потрібно спочатку підготувати залежності Node, Ruby, Python тощо. Особливо на CI та в Docker це дуже зручно. Також синтаксис Make-файлів стислий та складається тільки з шелскриптів та мінімальної розмітки. Якщо вже комусь доведеться його вивчити, то вчити там небагато.
🚷 Власне, найбільший недолік Make для мене це як раз розбіжності з тими ж шелскриптами. Змінні оточення та вкладені виклики $()
тут доступні тільки через власний синтаксис — це не складно, але копіпастити з оболонки не вийде. Але це дрібниці.
🍝 Звісно, у Make є безліч альтернатив. Найменш люблю скрипти в package.json
. Коментарів — дзуськи! Лапки — маскуй! Залежності — тільки через зовнішній виклик. Жах.
🍣 А моя улюблена альтернатива — це Rake в Ruby. Її я беру там, де скрипти можна спростити метапрограмуванням або параметризацією. Бо в Rake можна писати на Ruby - наприклад, робити запити до серверів чи перетворювати дані. А для виклику оболонки в Ruby є синтаксис зі зворотними лапками `echo "Hello World!"`
, в якому не потрібно маскувати звичайні лапки. Та який-не-який Ruby теж встановлений на більшості систем.
🤙 Наприклад, в одному з проєктів в мене Rakefile, який генерує комбінації команд на кшталт rake start:foo:test
та rake stop:bar:dev
для запуску оточення Docker. Та на тому ж проєкті Makefile, який збирає виконувані файли Go з вкладених тек (make -C cmd/baz
) в паралелі (і таке можливо! make -j 8
).
🪶 Що мені цікаво спробувати, це сучасний підхід “документація як система збірки” на кшталт xc. Але, в 9 випадках з 10, Make повністю вистачає. (А в десятому допоможе Rake.)
02.11.2024
Коли я робив проєкт з картами
Пост пана Євгена про проєкти з картами нагадало мені про власний проєкт, який був досить серйозним, але зрештою не вийшов (про що нижче.)
Все почалося з хакатону в Запоріжжі у 2013 році. Мені хотілося зробити щось цікаве з айфоном та я придумав вимірювати акселерометром якість дороги. Якщо дорога рівна — акселерометр трясеться мало, якщо погана — то більше, а якщо вибоїна — то й взагалі сіпається. З айфона можна знімати показники акселерометра в часі, та з них теоретично можна все дізнатися про дорогу.
У звіті про хакатон вже розказано про першу технічну реалізацію, яка, на перший погляд, була успішною, але далі почалися… вибоїни.
Вибоїна перша: алгоритм обчислення якості дороги з акселерометра зовсім не очевидний. Бо в показниках безліч змінних. Окрім ям, це параметри автомобіля (наприклад, підвіска), швидкість та прискорення пересування, щільність дорожнього руху тощо. Таке слово як data science я тоді ще й не чув, певно.
Вибоїна друга: точки на карті не корисні. Потрібна привʼязка до дорожньої сітки. Дорожня сітка це взагалі складно (як мені досі здається), а тут ще й додавай метрики та ділянки (як зберегти те, що на вулиці посеред кварталу яма?)
Вибоїна третя: не зрозуміло, як з нього отримувати користь. Я уявляв собі попередження про погіршення якості дороги… але на той час я не знав, як це зробити технічно — можливо, що й не ніяк. Згадую, що на той час навіть щоб просто збирати дані, застосунок повинний був бути видимим на екрані. Тоді може маршрутизувати в обʼїзд ям? Але маршрутизація це ще складніше, плюс не ямами єдиними ми обираємо дорогу.
Зверху того, я робив проєкт не для себе, а для загального користувача. А це в тисячу разів ускладнює кожне рішення, кожну функціональність. Нарешті, я побачив, що є й інші схожі проєкти, наприклад, UARoads) (не знаю, яка в нього зараз доля), та зовсім закинув власний.
Якби зараз повернутися до того першого рішення, то я б радив собі забути про інших користувачів та спробувати суто для себе зробити застосунок, який повністю працює. Накладати точки на дорожню сітку, показувати їх шаром OSM. Потім поїздити позбирати дані деякий час. Та ніколи не намагатись розвʼязати все, відразу та для всіх.
01.11.2024
TSSpec - Перевірка збіжності фронтенду та бекенду
Цього тижня довелося виправляти доволі типову ситуацію: фронтенд в разі помилки показував не детальний текст з бекенду, а натомість загальну “щось пішло не так”. По-перше, дякувати тестувальнику, що це помітив. По-друге, інтеграційні тести покривали тільки успішний випадок.
По-третє, оскільки ця функціональність вже існувала, а моя зміна тільки додавала ще одну детальну помилку, тому я її додав у сервісі, написав на нього юніт-тест та був задоволений — якби не тестувальник, то нічого б не помітив.
Причина прозаїчна: фронтенд очікував помилку в хибному атрибуті відповіді та не знаходив її. Виправлення теж зрозуміле: написати інтеграційний тест на хибу та тривіально змінити назву атрибута на фронтенді.
Засмучує те, що фронтенд та бекенд живуть окремим життям, а перевірка взаємодії обмежується інтеграційними тестами. От якби вони обидва були на TypeScript, тоді можна вживати однакові типи для клієнта та сервера API. Так ця категорія помилок зникає, навіть якщо тестів не писати — ось вам ще одна величезна перевага TS.
А що коли бекенд на Ruby (on Rails)? Три роки тому я задав собі це питання та винайшов гем tscheck. Він поєднує типи TypeScript з тестами RSpec. В (нормальних) проєктах на Rails є юніт-тести контролерів, де ми перевіряємо варіанти їхніх відповідей. TSCheck дозволяє також перевірити, що відповідь контролера вірно реалізує деякий тип TypeScript. Це обмежена, але гарантія, що фронтенд та бекенд розуміють один одного.
Як воно працює? Простіше, ніж думаєте. Я будую файл TypeScript, що містить імпорт типу та константу зі змістом відповіді. Та передаю його у мовний сервіс tsserver - це той, що здійснює перевірку для VSCode тощо. У відповідь отримую перелік помилок: якщо вони є, тип не сходиться.
Є й інші розвʼязки цієї задачі — зокрема перевірка за схемою OpenAPI - але вони потребують додаткової роботи. А в мене все спирається на типи TypeScript, які у вас вже повинні бути. (Повинні бути, правда?)
31.10.2024
Чому вам потрібний маркетолог?
Кілька років тому ми випустили Сінтру — застосунок для обліку витрат за принципом щоденного бюджету. Він, хоча й має своїх користувачів, але все ж великого успіху не досяг. Сама Сінтра чудово зроблена в міру команди з двох людей — мене та Саші Зайцева, який уособлює для мене все, чого можна чекати від дизайнера. Де ж успіх?
Я, здається, несподівано натрапив на відповідь: жоден з нас не старався дуже її продавати. Чи це звучить очевидно? А якби мова йшла про твій сайд-проєкт?
Як інженери, так і дизайнери бачать продукт як предмет власної праці. Вона ніколи не завершена; завжди щось в продукті можна зробити краще. Хоч це й правда, але така перспектива призводить до надто критичного ставлення. Якщо продукт можна доробляти, чи готовий він до користувача? Інженер воліє сказати “ні”; тільки обставини змушують сказати “так”.
Тому з інженерів кепські продажники — принаймні, поки вони не усвідомлять свої вади. Продажник, як мама, любить продукт таким, який він є, та мріє про те, щоб його побачили якнайбільше людей. З усіма недоліками та недоробками, якщо в продукті є користь — залишається знайти для неї аудиторію. Просування неідеального продукту це не брехня та не обман, а, якщо хочете, віра в те, що добро (продукт) переможе зло (невиконані потреби користувачів.)
На жаль, якщо про ваш продукт ніхто не знає — то байдуже, наскільки він крутий технічно та який там дизайн. Через це загинає чимало гідних продуктів. Так що коли немає партнера-продажника — пора вмикати режим продажника самому та просувати, просувати, просувати.