Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
17.09.2024
Як безпечно показати HTML?
Уявіть що вам потрібно показати на сайті фрагмент чужого HTML. Сподіваюся, очевидно, що це відкриває загрозу злому: в першу чергу, через виконання JavaScript можна вкрасти авторизаційні кукі, іншу інформацію, та навіть викликати API та завантажувати сторінки сайту. Хочу розглянути способи захисту.
Найтрадиційніший спосіб, відомий по форумах — обмеження змісту HTML до безпечних тегів та атрибутів. Обовʼязково це повинен бути саме список безпечних, а не небезпечних елементів — але й так існує ризик всього не передбачити.
До того ж обмеження змісту передбачає контроль HTML на вході; вже готовий HTML можна зачищати. Але будь-який санітайзер HTML обовʼязково змінюватиме його, принаймні щоб виправити можливі помилки. Тож ми вже не побачимо вхідний HTML “як було” - що може бути небажано.
Другий поширений спосіб: віддавати сторінку HTML з іншого домену. Тоді ця сторінка не отримає кукі з нашого сайту. Але нюанс: тоді й авторизацію доведеться придумати суто за URL. Як відоме рішення, є підписані URL на S3.
Ізоляції даних можна досягнути й через використання Blob URL, колись я про це згадував. Його можна відкрити як на новій сторінці, так і у фреймі. Це рішення потребує JavaScript, зате не потребує додаткової авторизації.
До речі, у <iframe>
є корисний атрибут sandbox
, яким можна зовсім вимкнути JavaScript та інші функції — наприклад, форми. Підтримка його наразі цілих 97% - але я б все одно використовував його разом з іншими методами.
Нарешті, є радикальна опція “Opera Mini”: взагалі відображати HTML в ізольованому браузері на сервері, а клієнтові віддавати тільки його зображення. Так втрачаємо всяку інтерактивність, зате в нападників просто не залишається шансів.
16.09.2024
Чому "переписувати на мову" в принципі погана ідея?
Коли програма працює повільно, традиційно чуєш ідею — перепишім на мові Y! На мою думку, так варто робити тільки після того, як вичерпано можливості оптимізації. Яких завжди знаходиться багато. Звісно, переписати іншою мовою легше (в наш час, певно, майже автоматично) - але потенційний виграш тут менше, а підтримка такого рішення значно ускладниться, особливо коли йдеться про живий продукт, а не іграшкову програму.
Переписав вчорашній приклад на Go. Це не найшвидша мова, але мені найзручніша. Повільність Go здебільшого стосується збірки сміття, якого в цьому прикладі можна взагалі уникнути. Також програму на Go мені буде легко зробити паралельною. Ось gist з всіма розвʼязками.
🟰 Наївний переклад працює на 10% швидше за Паскаль. Зазначу що це вже не найгірше рішення — воно створює масиви заздалегідь, а не на кожну ітерацію. Але не бачу сенсу штучно погіршувати програму, порівняно з оригіналом. Різниця в 10% досить передбачувана — обидві мови типізовані, компілюються; не здивуюся, якщо машинний код програм майже однаковий.
➗ Перша оптимізація коду з купою арифметики: не повторюймо операції! Особливо ділення — найповільнішу з операцій. Інтуїція підказує, що арифметика — це швидко; але коли вся програма — це одна арифметика, мусимо звернути увагу на кожний вираз. Дещо можна було навіть винести з циклу та викликати у 200 000 разів рідше.
➖ Також приклад робив зайві обчислення заради виводу результату; вивід відбувався раз на 100 ітерацій, а обчислення — щоразу. Усунення повторюваних та зайвих операцій прискорило розвʼязок у 2 рази!
✖️ Друга оптимізація: використовуйте локальні змінні. Тут сам здивувався. Початкова програма весь стан тримала в глобальних змінних. Так, мені здавалося, я заощаджу на керуванні памʼяттю. Але ось що виявилось: перенесення всіх змінних всередину функції прискорив розвʼязок ще в 3 рази! В 3 рази! Я знаю, що глобальні змінні розташовуються в BSS-сегменті, а локальні — на стеку, але ніяк не очікував таку величезну перевагу останнього. Перевіряйте свої переконання!
➕ Отже, переписування зробило програму швидше на 10%, а оптимізація — на 82%. Та ті ж самі нехитрі оптимізації можна було зробити й на Паскалі. І це тільки найпростіша програма, без кешування, всяких неефективних структур даних, чи алгоритмів з експоненційною складністю. Не поспішайте переписувати — розумійте базові принципи, які роблять програми швидкими або повільними.
15.09.2024
З Pascal в NumPy
Натрапив на питання про те, як прискорити програму з обчисленнями на Pascal. Чудова задача на оптимізацію, подумав я, та вирішив спробувати NumPy, як топове рішення для обробки математичних даних… з яким я не мав практичного досвіду.
🐍 Перший очевидний результат: наївне переписування на Python уповільнює програму у 160 разів. Очевидний тому, що в Python повільний тип “масив” (насправді “список”) та й взагалі, все що я писав про повільність Ruby, стосується й пітону.
⛴️ Проте так само очевидно, що Python беруть для використання бібліотек на кшталт NumPy. Головною ідеєю тут є векторизація. Так називають два різних підходи. На високому рівні — це виклик функцій низькорівневої мови для обробки цілих масивів замість окремих значень. На низькому рівні — це використання спеціальних інструкцій процесора SIMD: єдина інструкція — багато даних. В разі NumPy обидва підходи мають місце.
🚤 Версія з NumPy за швидкістю близька до оригіналу на Pascal. Що досить непоганий результат, якщо врахувати, що програма написана на Python, де не потрібно оголошувати змінні та можна запускати програму по шматках в Jupyter.
↔️ Єдиним неясним місцем в перекладі було фільтрування масивів. Як виявилось, в NumPy для того є дві семантики: take
вибирає з масиву за масивом індексів, а extract
- за масивом-“маскою”. Але було неочевидно, що функції filter
немає. Зате приємно здивувало, що можна прочитати файл з рядками чисел однією функцією readfile
.
🐢 Чому вона не швидше? Для мене відкриттям стало, що NumPy не включає майже ніякого паралелізму. Тільки для окремих операцій — особливо над матрицями. А на цьому прикладі я бачив тільки завантаження одного ядра на 100%. Тобто, в цілому, як програма на Pascal, так і на Python/NumPy робила однакову кількість арифметичних операцій — не дивно, що швидкість приблизно однакова.
🐇 Можливо, десь SIMD чи інші оптимізації й дали прискорення, але з іншого боку, програма на Pascal виділяє памʼять тільки один раз — а на Python нові масиви створюються декілька разів на ітерацію. Що точно дає затримку.
🤔 Залишилося перевірити, якої оптимізації можна було досягнути низькорівневим рішенням.
14.09.2024
Чим зайнятись на телефоні?
В житті багато моментів, коли в тебе є телефон та декілька хвилин вільного часу. Я не з тих пуристів, які вважають, що будь-яке використання телефону є порочним та в ідеалі потрібно просто стояти в черзі та нудьгувати. Але дійсно, більшість занять з телефоном регресивні: перевірка оновлень по колу, думскролінг, прості іграшки.
Та я б хотів знайти активності (та звички), щоб робити в ті моменти очікування. На читання чи слухання чогось вагомого зазвичай не вистачає фокусу. А ось що поки придумав:
-
🃏 Практикування флешкарток з Anki. Тут навіть за хвилину встигнеш перевірити декілька карток. Можна вчити будь-що… якщо придумати, що, звісно! В мене найбільший успіх був з 5000 найчастішими словами німецької. Але технічно можна пристосувати від стандартної бібліотеки C до імен та світлин колег.
-
💭 Скид думок. Просто відкриваєш нотатку та записуєш, що в голові. Це взагалі хороша звичка, а тут вона дається мінімальними зусиллями. Можна навіть надиктовувати. В ідеалі ще не забути переглянути ті нотатки… але й без того скид думок — гарна терапія.
-
🖋️ Як варіант скиду думок — запис чернеток до постів, проєктів, віршів, романів, чого там у вас.
-
🪷 Ну й нарешті, якщо оточення дозволяє, можна й прибрати телефон та побути в моменті. А допоможе в цьому наш спонсор на сьогодні Headsp… жартую!
13.09.2024
SwiftUI на macOS: спостереження
За тиждень неквапливого програмування зробив собі на SwiftUI застосуночок для ведення списків по GTD. Декілька спостережень та висновків:
-
Дуже приємно працювати, коли дані сидять в JSON файлі. Резервне копіювання автоматично вже відбувається — як і для всіх моїх файлів. Міграції можна зробити хоч вручну. Експорт вже готовий. Імпорт нескладно зробити, може теж вручну.
-
Порівняно з вебом, зовсім немає “бекенду/фронтенду”. Це теж спрощує уявлення. Всі дії є безпосередніми: ніяких HTTP API.
-
Observation все ж дуже гарно працює. Будь-який клас з
@Observable
стає моделлю та долучається до оновлення виглядів. It just works! Причому ми вільні використовувати функції та проміжні змінні. Головне, щоб під час промальовки було звернення до властивостей об’єкта з@Observable
. (Або навпаки — якщо щось закешуємо та звернень не буде — то перемальовки теж.) -
Також до всього
@Observable
можна додати@Bindable
та писати дані прямо в модель. Це мінімізує потреби в локальному стані; він зберігає тільки дійсно локальні змінні як видимість діалогів, фокус полів тощо. -
SwiftUI взагалі теж приємний фреймворк, якщо звикнути до його ідіосинкразій. Головне навчитися рефакторити та різати компоненти. Порівняно з вебом, практично не потрібно “писати CSS”, тільки дещо коригувати те що є. З іншого боку… такого чистого аркуша, як у вебі, теж певно ніколи не буде. Але це більше задачка для дизайнерів.
🦀 Нарешті, щоб було веселіше, залишу вас зі статтею: Swift is a more convenient Rust.
12.09.2024
Docker як архівна система
На початку року вже писав, як використати Docker, щоб втекти з власного оточення в таке, що є зручним. Сьогодні нарешті випробував окремий варіант такого використання: повернення в історичне оточення.
Ситуація знайома до болю. Стикнувся з проєктом на JavaScript, який вже років 5 не підтримувався. Node.JS 12 - старіша за епоху Apple Silicon та потребує Python 2 для встановлення. А з нею - Node-Sass, Webpack 4 і все інше. На моїй машині все це не запускалося.
Зазвичай я намагаюся привести такий проєкт до життя — оновити залежності, причепурити. Але на цей раз проєкт не мій, а мені тільки декілька рядків виправити, зібрати та забути. Я вже майже змирився з тим, щоб перекласти задачу на супроводжувача, але тут згадав про Docker та про свій досвід запуску команд.
Знайшов на Docker Hub образи від Bitnami. Запустив оболонку, та, дуже несподівано, всі команди від npm install
до npm build
пройшли як по маслу!
docker run -v .:/mnt -it bitnami/node:12 /bin/bash
А взагалі в ідеалі можна було заздалегідь підготувати образ для підтримки, навіть зібрати його та зберегти. Навіть разом з усіма залежностями, базами, утилітами та всім іншим.
11.09.2024
Дизайн даних для інженерів
Мені як інженеру багато разів доводилося виводити дані… так би мовити, без дизайну. Зазвичай для внутрішніх потреб: нечасто дизайн покриває всілякі адміністративні панелі. Тому, гадаю, перша дисципліна дизайну для інженерів — то подання даних.
Щоб просто показати табличку, чи перелік, чи форму, багато думати не потрібно. Проте тут є аксіома/порада: будь-які дані можна подати в різному вигляді та завжди можна знайти щось краще, ніж вигляд за замовчуванням.
-
📰 Форматування. Взяти, наприклад, відбиток часу. Показати його в ISO 8601? Чи може тільки дату? Чи взагалі як відносний час? Число — з якою точністю? Рядок — як є чи скоротити частину, що повторюється у всіх записів?
-
🍱 Групування. Всякі дані мають внутрішню структуру. Можна зробити їх легшими до споживання, якщо розбити на логічні групи. Для того потрібно зрозуміти, на що буде дивитися читач. До того ж група дозволяє написати повторюване значення тільки один раз.
-
📈 Похідні. Часто важливіше бачити обчислені дані: відсоток, середнє, тривалість — а не сирі дані: кількість, початок та кінець проміжку. Також інколи можна обчислити корисний статус чи категорію (якщо такі цікаві читачу.)
-
💬 Назви замість ідентифікаторів. (Або навіть посилання, якщо є куди.) Звісно, в сирих даних частіше будуть ідентифікатори, проте для читача вони рідко наділені змістом.
-
🎨 Оформлення. Крапля кольору або емодзі багато роблять для сприйняття інформації. Так само як і корекція відступів, розміру шрифту і таке інше, але то вже заходить на територію Великого Дизайну.
-
✂️ Приховування неважливого. Менше = легше. Окрім того, щоб прибрати зовсім, можна сховати дані за підказкою або стиснути у вигляді (емодзі замість текстового статусу) або поєднати два поля в одне (назву та посилання).
Практикуватися в дизайні можна на адмінпанелях, звітах… навіть на записах в журналі та вирізках для слаку! Та від того повсякдення стане трохи зручніше та ефективніше.
10.09.2024
Бібліотека samber/lo: функціональні ідіоми для Golang
Випробував таку нову (для мене) бібліотеку - samber/lo. Знаєте пакет Lodash для JavaScript? Там, де купа зручних функцій для програмування у функціональному стилю? От це таке саме тільки для Go.
Бібліотека вийшла у 2022, після того, як в Go 1.18 зʼявилися дженерики. Та, завдяки дженерикам, вона є швидкою та безпечною.
Та вона дійсно дозволяє писати функціональний код на Go. Наприклад, ось обчислення першого року публікації кожного автора:
lo.MapValues(
lo.GroupBy(books, func(b Book) string { return book.Author }),
func(bb []Book) int {
return lo.Earliest(lo.Map(bb, func(b Book) int { return book.year }))
},
)
Написав приклад та подумав, що імперативне рішення може було б і ясніше. Не впевнений, що варто робити такі багаторівневі конструкції. З іншого боку, виклики в один рядок на кшталт Flatten
чи GroupBy
, чи Filter
здатні замінити багатомовні цикли та зробити код ясніше. А ще тут є функція Must
, якої теж постійно не вистачає, щоб висловити “якщо тут буде помилка, то я зовсім не знаю, що з нею робити”.
Також зазначу, що хоч дженерики здатні виводити типи — тому їх писати не потрібно — але функції потребують явного задання типів. Тому компактне в JS book => book.year
перетворюється на func(book Book) int { return book.year
. Та це помітно псує код.
Щодо ціни всіх цих абстракцій… Роб Пайк дозволив про це не думати. Я раджу всім спробувати у своїх проєктах цю бібліотеку та думаю, вона знайде свою нішу в вашому коді.
09.09.2024
Робочий час та календарний час
Робочий час — той, що ми витратимо безпосередньо на виконання роботи. Це те, про що зазвичай думають при плануванні часу. Але окрім нього є ще…
Календарний час — той, що мусить минути для завершення роботи. Та від нас не залежить. Наприклад: від створення пул-реквесту до його появи на продакшні може минути тиждень. Залежно від процесів, звісно.
Або інший приклад: якщо для побудови моделі нам потрібні три місяці даних, то доведеться чекати. Або поки наступить день бронювання. Або поки поштою щось приїде.
Таке розрізнення стане корисним під час планування. Хоча “календарні” витрати часто займають набагато довше — зате проходять без нашої участі. Особливо по адмініструванню зустрічаю багато задач, де роботи на пів години, потім чекати декілька днів, потім ще пів години.
Чим раніше почнемо “календарну” задачу — тим раніше вона закінчиться. Це теж варто враховувати у плануванні. Трапляється що робота починається з налаштування збору метрик чи додаткових журналів. Поки доробляємо решту — вже буде на що подивитись. А якщо не подумали та почали збір пізніше — доведеться чекати.
А ще “календарні” задачі можна робити паралельно. Теж якщо вірно розпланувати та помітити наявність затримок. Як частий приклад, PR-потяг (це коли наступний PR роблять до попереднього, а не чекають коли той приймуть.)
08.09.2024
SwiftUI та найпростіша модель даних для нього
Сьогодні зненацька зробив 50% мікрозастосунку для GTD (ой, лишенько.) Здебільшого отримував задоволення, хоча розробка зі SwiftUI це американські гірки від “як тут легко зробити Х!” до “ну чому Y абсолютно не піддається логіці?” До речі, писав у SweetPad - це доповнення для VSCode від друга каналу Євгена майже заміняє XCode.
Оскільки хотілося якнайшвидше перейти до бізнес-логіки, спробував таку модель даних: ніякої бази, просто класи, які я зберігаю у JSON.
Завдяки фреймворку Observation, Ui впевнено реагує на зміни даних. Є ще модифікатор @Bindable
, яким можна перетворити ті ж класи на привʼязки для текстових полів та інших місць, які потребують редагування. Тобто фактично одним класом AppState
можна покрити всі потреби застосунку в стані. Зрозуміло, що коли даних буде багато, така “база даних” стане повільною, але для керування власними задачами точно вистачить.
Також поки свідомо відмовився від синхронізації. Взагалі з рішеннями для синхронізації поки більше питань, ніж відповідей. Для мене головним є стабільна робота застосунку локально на десктопі. Поки всі рішення, що я бачив, цю стабільність порушують — зокрема, в мене поламався пошук у Reminders.app - тобто й Apple не може на власній платформі зробити надійну синхронізацію.