Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

15.04.2023

Робота з відволіканнями — підсумки тижня

Звітую про підсумки тижневої спроби знизити кількість відволікань.

На наступному тижні буду працювати з планами реалізації.


14.04.2023

Чим круті канали в Go

Сьогодні переробляв надсилання подій в AWS Kinesis Firehose на пакетний режим, методом PutRecordBatch. В самому по собі API нічого цікавого немає, проте реалізація цього на Go досить цікава.

Оскільки події відбуваються в багатопотоковій системі, то щоб зібрати їх до купи, потрібна якась модель асинхронної поведінки. Та де в гіршій мові треба було б брати мьютекс, в Go є елегантне та простіше в розумінні рішення — канали.

Тож запускаємо окрему горутіну-колектор, де без кінця крутиться цикл та зчитує з каналу нові події. Як тільки подій набереться достатньо — відправляємо їх до Firehose. В такій моделі список подій належить тільки горутині-колектору, тому немає проблем з синхронізацією доступу. Замість того ми плануємо потік даних по каналах — власне, саме канали й будуть єдиним місцем, де події переходять з однієї паралельної горутіни до іншої.

Канали — то моя найулюбленіша особливість Go, хоча, треба зізнатись, місць, де їх доводиться впроваджувати, зустрічається небагато. Головне, що треба усвідомити про канали — що вони синхронні. Цим Go відрізняється від більшості асинхронних моделей, де відправлення повідомлення не гарантує його прийняття (модель акторів, наприклад.) В Go, якщо одна горутіна кладе дані в канал, то можна бути впевненим, що інша забирає їх у той самий час. (Якщо канал не буферизований.) Це дозволяє впевнено міркувати про потік даних в системі. Операції з каналами створюють в програмі точки синхронізації, та багатопотоковий алгоритм набуває схожості до часового механізму. Особливо це корисно, коли треба ізолювати власність над даними — як в сьогоднішньому випадку.


13.04.2023

T-Shaped Full-Stack Developer

Буває що до посади “фул-стек інженер” ставлять зневажливо: якщо одна людина робить все, то вона нічого не зробить добре. Але то, я думаю, найбільш небезпечна помилка — вважати, що фул-стек має все робити сам.

Головна функція фул-стек інженера — мати розуміння всієї технічної складової продукту. Це суттєво спрощує керування розробкою, порівняно з тим, коли кожною частиною займається хтось інший. Фул-стек інженеру можна доручити цілу фічу, без нарізання по підсистемах. Або виправлення помилок невідомо де. Існування ролі фул-стек інженера обґрунтовано потребами бізнесу.

Але ж зрозуміло, що весь стек — це широченна галузь, та ніхто її не знає досконало від початку та до кінця. Тому в хорошій команді (та компанії) фул-стек розробники підкріплюються вузькими спеціалістами — по базі даних, по CSS, і так далі.

Такий підхід не обмежений програмуванням. Наприклад, в медицині є терапевт, що готовий прийняти тебе з будь-якою скаргою. Та від рутинних болячок саме терапевт й лікуватиме. Але якщо виникає потреба в більш серйозних діях, то терапевт делегує лікарю-спеціалісту. Терапевт — то є фул-стек лікар. 👩‍⚕️

Проте, чесно кажучи, найняти та утримувати вузького спеціаліста — складно. Простіше, коли в кожного фул-стек інженера є своя заглиблена спеціалізація, з якою він може допомогти колегам. Це й називається T-образний інженер. Тобто “—” то є фул-стек, а “|” то є твоя спеціалізація. В більшості випадків T-образний інженер працює над загальними задачами, але якщо в команді виникають складнощі, то перемикається в режим допомоги.

Хороша команда — це там де знаєш, хто в чому спеціаліст та до кого звернутись за допомогою. Продукту немає ніякої вигоди в тому, що кожен самостійно робить поганенький CSS.

(До речі, як на мене, “фул-стек” як назва посади стала архаїчна. Колись, у 2010-ті, всі вебдодатки робились за однією схемою — додаток на сервері, база даних, HTML, CSS, може трошечки JavaScript для блиску. Тож “фул-стек” значило вміння працювати з усім цим. Але зараз стек може бути абсолютно різний, а посада залишилась.)


12.04.2023

Парсинг та обробка HTML в Go

Якщо в Go доведеться парсити HTML, то для того є непоганий офіційний пакет golang.org/x/net/html. Він здійснює потокову обробку документа; тобто ніякого DOM-дерева не будує, а замість того пропонує пройтись по документу циклом та відреагувати на всі потрібні елементи. Це дуже відрізняється від роботи з HTML у JavaScript та від культової бібліотеки Nokogiri для Ruby.

Сучасні вебінженери звикли бачити HTML як дерево, та адресувати це дерево селекторами CSS: div#header a.nav-link. Це просто в розумінні, але складно алгоритмічно: бо потребує спочатку побудови дерева, а потім ще й пошуку по ньому.

Натомість при потоковій обробці ми маємо заздалегідь запланувати, що хочемо отримати з документа, та власноруч стежити за станом обробки. Наприклад, коли зустрінемо тег div, то перевіримо, чи є в нього атрибут id==header, та якщо є, увімкнемо змінну inHeader = true. А коли зустрінемо тег a, то перевіримо, чи увімкнена змінна inHeader, та якщо так, запишемо в масив navLinks.

Якщо ми хочемо отримати з документа не єдиний “селектор”, а декілька, то стану стає багато, та відстежити що до чого стає складніше. Тому потокова обробка тільки для особливо вибагливих до швидкодії випадків.

Але є ситуація, де потокова обробка прямо набагато краще. Це коли документ ми змінюємо. Річ у тім, що розбір документа в дерево — операція зазвичай незворотна; вона втрачає нюанси форматування, та особливо — помилки синтаксису. А з таким неформатним форматом як HTML, помилка синтаксису цілком може бути помилкою тільки для даного парсера — та її “виправлення” навпаки документ поламає (прибере “неправильний” тег, наприклад). Тому й варто нічого не міняти без потреби.

З потоковою обробкою оригінальний зміст документа не втрачається; те, що нам не цікаво, ми просто копіюємо у вихідний документ — байт в байт. А зміни можна робити як на рівні блоків, так і на рівні тегів або навіть атрибутів — як забажаємо.


11.04.2023

Підтести в Go з функцією T.Run

Сьогодні весь день писав тести на Go. Та, на сьомий рік написання тестів, нарешті дізнався про механізм створення підтестів: t.Run.

Нащо воно потрібно. Зазвичай в Go всі пишуть так звані матричні тести. Це коли всі параметри тесту та очікувані результати складаються в структуру, робиться масив таких структур, а потім цикл бігає по масиву та перевіряє код на кожному випадку. Для більш гнучкої мови програмування це може й дивно виглядало б, але на Go дуже зручно, бо копіпаста скорочується до мінімуму.

Я теж постійно використовую матричні тести. З ними є маленька проблемка: коли тест падає, то незрозуміло, в якому саме випадку. Тому я зазвичай до кожного очікування дописував повідомлення, по якому можна знайти випадок, якось так:

assert.Equal(t, e.expectedResult, myCode(e.param), e.name)

Та ще інша проблема: немає можливості запустити окремо один з випадків. Хіба що вдало закоментувати все зайве. От тільки в Go незручно коментувати шматки коду, бо компілятор почне журитися з невикористаних змінних.

Торік дізнався про існування пакета testify/suite. Їм можна загорнути набір тестів у спільний контекст, такий, як підключення до бази даних. Саме suite мене й наштовхнув на роздуми — а як це вони роблять тести у вкладеному форматі - Suite/TestName? Виявилось, що це вбудована можливість тестового середовища Go, називається підтести, та створюється функцією T.Run.

Підтести це те, чого мені не вистачало в матричних тестах, бо тепер достатньо просто загорнути тіло тесту в t.Run та отримати як підпис кожного підтесту в результатах, так і можливість фільтрувати їх ключем --run. А з пакетом testify/suite можна ще й додавати до підтестів контекст.

for _, e := range examples {
  t.Run(e.name, func(t *testing.T) {
    assert.Equal(t, e.expectedResult, myCode(e.param))
  })
}

Може, це й щось очевидне, але я загуглив пару топових туторіалів та вони матричні тести показують, а підтести — ні. Отакої. TIL.


10.04.2023

Оновлення для go-global. Особливості рефлексії для типу map в Go.

Сьогодні зробив для go-global - нашого пакета для завантаження конфігурації з AWS Parameter Store - маленьку фічу. Пакет цей бере дані з Parameter Store. Там вони мають пласку форму “ключ-значення”, причому значення можуть бути тільки рядками. Але ми хочемо отримати їх у вигляді конфігураційної структури з вкладеністю та з типізацією. Для цього й існує go-global.

Але річ у тім, що він не підтримував тип map - тільки структури та, з недавнього часу, масиви. Структури це добре, коли всі ключі відомі. Але зʼявилася потреба завантажувати словник з невідомими ключами.

Здавалося б, закинути дані в map - це ще простіше, ніж у структуру. Навіть не треба шукати поле за тегом. Але насправді це невеличке розширення функціонала призвело до серйозного перероблення та переосмислення всього підходу.

Як воно взагалі все працює: через рефлексію. В першій версії бібліотеки я крок за кроком спускався по конфігураційній структурі, поки не знаходив reflect.Value, що відповідає місцю, куди треба записати параметр. А потім записував в нього значення. В принципі нічого складного там не було; найскладніше — це, мабуть, створення нових екземплярів обʼєктів, коли стикаєшся з нульовими вказівниками.

Але. Тип map, як виявилось, все це ламає. Бо він в Go особливий. Елементи map, не підлягають адресації. На них не можна зробити вказівника — це раз. Але також, як наслідок, при рефлексії неможливо отримати таке посилання на елемент, яке дозволяє запис. Елементи треба записувати спеціальною функцією SetMapIndex. Так що мій рекурсивний пошук місця призначення більш не підходив.

(Обурений екскурс. Ці приховані особливості всіляких типів — те, що я в Go понад усе не люблю. Абстракція “простих” типів тече. В нульовий слайс можна записувати, а в нульовий меп — ні. Оці проблеми з адресацією. І таке інше… Після того, як я нахвалюю Go, соромно початківцям про ці деталі розповідати.)

Спочатку хотів синтезувати якийсь замінник для “адреси” місця призначення. Може, повертати функцію-сеттер. Але так і не зміг розвинути цей підхід.

Натомість перегорнув весь алгоритм догори ногами. По-перше, тепер я спочатку будую з параметрів абстрактне дерево. Потім, замість пошуку кожного параметра окремо, я роблю обхід цього дерева в глибину, та наповнюю конфігурацію, починаючи з найглибших частин.

Що це дає: коли справа доходить до типу map, то ми спочатку повністю наповнюємо значення кожного елемента, а потім записуємо його в map функцією SetMapIndex. Якось так. В результаті вийшло навіть не дуже складно, хоча без повного переосмислення я б до такої версії не прийшов.

Та, до того ж тепер я відокремив всю цю складну логіку від конкретно AWS Parameter Store, та її можна досконало протестувати. А в майбутньому, може, й додати інші сховища; взагалі для цього є Viper, але в нього філософія відрізняється від нашої.


09.04.2023

Почав робити фотозвіт про походи

В рамках фреймворку “хочу — не хочу” взявся, нарешті, за звіти про подорожі. Річ у тім, що в мене лежать сотні красивих та цікавих світлин, з яких давно мрію збирати звіти, але, як це буває, ніяк не знайду часу.

Одним з аспектів прокрастинації є те, що хочеться відразу дуже технічно крутий підхід, на кшталт статей з New York Times з анімаціями, картами, крутою типографікою і так далі. Звісно, все це я не вмію робити. Тому, щоб зрушити справу, хочу почати з чогось простого На жаль, я не ідеальний, тому найпростіше рішення — пост зі світлинами — мене не задовольняє. Як не задовольняє і галерея без тексту.

Зі зробленого сьогодні: обрав похід (а це теж не так просто; треба було щось ізольоване, але цікаве); відфільтрував світлини від дублікатів та сміття; написав текст. Тепер залишилось зібрати все до купи та, напевно, видалити ще багато фотоматеріалу.

Для перегляду світлин обрав поки lightGallery. За задумом галерея має бути не одна, а по одній на кожний параграф, та ще й з цікавим макетом. Тож наступна технічна задача — організувати структуру документа з тексту та галерей. Та, в ідеалі, щоб все залишалось в Markdown.

Знайшов отакі рекомендації по розміру картинок від Shopify; виходить 2240x1260 пікселів як максимум. До речі, щоб підігнати картинки під розмір однією командою — беру ImageMagick - командою mogrify -resize 2240x1260 *.jpg. А для остаточної оптимізації розміру є утиліта ImageOptim, якою я теж постійно користуюсь.

Ще є корисний додаток PeakFinder, який підписує гори; причому він вміє це робити як по камері, так і по старій світлині. Та, він навіть працює на macOS, хоча і в версії для iPad.

(Якщо є готові, відкриті інструменти для створення матеріалів “як у Нью-Йорк Таймз”, буду дуже рад дізнатись.)


08.04.2023

Що робити з рефлекторними відволіканнями?

Отже, вчора я зʼясував, що головна перешкода в роботі — то рефлекторні відволікання, що виникають, коли натрапляєш на складне місце. Поставлю мету їх позбавитись. В ідеалі, робота над задачею має починатись, тривати безперервно, та закінчуватись. Або свідомо перериватись на відпочинок. Але не на спонтанну перевірку новин.

(О, до речі, всі активні відволікання — тобто повідомлення та інше — в мене давно вимкнені назавжди — окрім найкритичніших. Бо фокусуватися на роботі, коли булькають месенджери — це взагалі для мене неможливо.)

Як вже писав, спочатку треба зупинити себе. Поставити, так би мовити, breakpoint. В цьому допоможе Screen Time. Він потрібен не для того, щоб все заблокувати та викинути ключі — я доросла людина. Головна мета тут — побачити, коли відвертаюсь. Блокую ресурси — додатки чи сайти — на які заходжу частіше за все. (Список таких ресурсів треба неодмінно оновлювати за актуальністю.)

Тепер, наступного разу, як під час роботи побачу екран блокування, задам собі питання — чому відвернувся? Що стало причиною? Яке питання чи ускладнення? Його треба записати, щоб не загубити. Далі розвʼязую питання будь-яким доступним способом. (Можна тут згадати про “стратегії навскіс” - це набір “порад” для розблокування творчих задач.) Але взагалі, в типовій ситуації немає великої проблеми в тому щоб просуватися далі; головне це залишатися сфокусованим на роботі.

Звісно, всіх ситуацій Screen Time не покриє, і так само треба стежити, коли хочеться сходити за кавою, або перевірити телефон, або робити ще щось, окрім поставленої задачі.

Тобто моя нова стратегія така: помічаю, коли відвернув увагу від роботи — записую причину — розвʼязую — рухаюсь далі. Помітити — ключове, та записати — теж ключове. Спробую тиждень та розкажу.


07.04.2023

Відволікання

Сьогодні пост про відволікання. В контексті, звісно, програмування. Для мене відволікання - YouTube, перевірка стрічок, копання в інтернеті — безумовно є головним крадієм результатів та часу. Останнім часом я спостерігав за собою, бо хочеться покращити робочий процес.

Найпростіше — то коли опиняєшся в стані потоку, бо тоді абсолютно байдуже, що відбувається навколо, та вся увага зручно орієнтується на задачі. Проте з відволіканнями це мало допомагає, бо потік викликають творчі задачі на грані здібностей — такі зустрічаються не кожен день. Не думаю, що самі відволікання заважають увійти в стан потоку; хоча припускаю, що вони заважають тим, що не дають “розбігтися”.

Коли треба приділяти до задачі особливу увагу — наприклад, при складному дебазі — будь-які відволікання відразу дратують та хочеться опинитись в повній тиші та ізоляції. Але тут великої проблеми не має, бо відволікатися не хочеться.

Для простої роботи є фонове відволікання — слухове. Можна слухати шум кафе, музику, подкаст, ютубчик. Це не заважає, а допомагає сконцентруватись на роботі, коли вона не дуже складна, а особливо — якщо монотонна. Моя теорія в тому, що такий фон створює “барʼєр уваги”, та допомагає залишатись на задачі, а не уходити думкою далі. Тобто увага може комфортно перемикатися від роботи на фонові звуки, а головне — назад.

Але типова повсякденна робота не має постійний рівень складності. Найбільшу проблему для мене ставить рефлекторне відволікання. В піки підвищеної напруги — наприклад, коли треба прийняти рішення, або коли щось не сходиться — я переживаю, фактично, рефлекторне бажання перемикнутися на щось інше: перевірмо, що там в новинах?. Рефлекторним я його вважаю тому, що зазвичай спочатку відволікаюсь, а вже потім це помічаю. Ще гірше, коли це відбувається несвідомо; бо при повертанні до роботи опиняєшся в тому самому складному місці, і в зовсім найгіршому сценарію знову відскакуєш до відволікань — так реально можна марнувати цілі дні. Аж допоки задача не стане терміновою. Терміновість, безумовно, допомагає концентрації.

Щоб не очікувати, поки все горить, треба щось робити. Про це завтра.


06.04.2023

Транспайлери JavaScript та цільова версія мови

JavaScript - це, наскільки я знаю, єдина мова програмування, яка компілюється сама в себе. Це створює унікальні обставини. Ніхто вже багато років не хоче писати на тому JavaScript, який працюватиме в будь-якому браузері (хоча за останні десять років ця планка просто злетіла завдяки зникненню Internet Explorer.) Ні, ми пишемо сучасною мовою з усіма можливостями - async/await, null coalesce і так далі. А які середовища будемо підтримувати — залежить від нашого компілятора, точніше, транспайлера.

Зазвичай налаштування транспайлера за замовчуванням всіх влаштовують, але деколи доводиться робити зміни; так в сьогоднішній версії пакета mailtrap ми за недоглядом втратили підтримку старих версій Node.js.

В TypeScript є опція target, яка вказує, який саме стандарт JavaScript буде підтримувати скомпільований код. Який стандарт обрати — вгадайте сами. (Довідка радить ES6.) До того ж є окрема інструкція по вибору версії для Node.js - Node Target Mapping. От на неї ми й отримали проблеми, бо поміняли ціль з es5 на es2020 та втратили підтримку Node.js старіше за 14.

До речі, на сайті Node.js є сторінка з переліком версій, що ще підтримуються; підтримка версії 14 припиняється наприкінці цього місяця. Але, як бачите, це не заважає деяким командам продовжувати користуватись і старішими версіями.

А в Babel це налаштовується через @babel/preset-env. Це дуже зручно, бо дає вказати не тільки конкретні версії браузерів, які ми хочемо підтримувати, а й просто сказати: “99.9% ринку” чи щось схоже. Далі, чим старіше обрані браузери, тим більше нашого коду буде замінено на спрощений.

На останок, компіляція в іншу мову програмування — дуже поширений підхід. Так роблять майже всі компільовані мови програмування. А саме, компілюють вони в мову C, для якої вже є надійний та перевірений компілятор та пакет інструментів GCC. Генерація машинного коду — складна задача, та простіше перекласти її на GCC, ніж робити це наново.