Канал
У Льоні сьогодні
Нотатки архітектора систем та майстра на всі руки. Що я зробив, що з того вийшло, і що це все значить.
-
Гарна тема — запорука вдалої доповіді
У світі публічних промов TED займають особливе місце. Я не раз чув, як на них рівняються та аналізують, як себе поводять ведучі — міміку, жести, та інше. Втім слоганом TED є “Ідеї, які варто поширювати.” Та, я думаю, з TED краще перейняти не манеру викладання, а в першу чергу здатність обрати влучну тему.
За моїм досвідом, саме гарна тема є запорукою вдалої доповіді. Та навпаки — з поганою темою мало що зробиш хорошою грою. Навіть не тому, що “треба бути професіоналом”, а просто тому, що тоді доповідь втрачає свою базову цінність. Було декілька разів, коли я спочатку погоджувався виступати, а потім вже шукав тему. Виходить кисло.
Тема доповіді має бути така, щоб тобі було цікаво її розповідати. Якщо хочеться робити доповіді — варто спочатку відшукати таку тему. Вона не обовʼязково має бути революційною — більшість доповідей такими не є. Головне, щоб тобі було цікаво. Далі залишається знайти підхожу аудиторію (тобто переходити до моїх попередніх міркувань).
Дійсно, щоб доповідь добре сприймалася, потрібно викладати її з натхненням та емоціями. Але на моєму досвіді, вони беруться не з акторської гри, а натурально з цікавого матеріалу. Більш ніяк. Далі — справа практики. Але чесно, коли ти бачиш крутого викладача на TED, то в першу чергу це спеціаліст, що розповідає про свою рідну тему, яку жадає передати світу. А не актор з відпрацьованою технікою.
-
Чому деякі відповіді HTTP в Ruby отримують кодування ASCII-8BIT, а інші — ні
Почну з цього: в Ruby рядки мають кодування — на відміну від JS, Go, та різних інших мов. Тобто кожний конкретний рядок має конкретне кодування. Причому Ruby намагається приховати це, тому операції над рядками тихо перекодовують дані. Наприклад, якщо скласти два рядки, то результат отримує кодування першого рядка.
Як я писав в минулому, наразі ми майже завжди маємо справу з кодуванням UTF-8, тому важко помітити, що існують інші — принаймні, якщо ми не працюємо з двійковими даними. Так, зазвичай запит
Net::HTTP.get()повертає рядок в UTF-8. Оскільки типова вебсторінка має саме це кодування (я навіть не зміг знайти протилежний приклад), то можна подумати, що звідти воно й береться. Але ні — насправді просто береться кодування за замовчуванням в Ruby.Тому що Ruby взагалі не інтерпретує кодування, яке зазначене в відповіді HTTP. Цього не роблять тому, що інтерпретація не завжди однозначна. Ось обговорення. Є спеціальна опція response_body_encoding, яка або вказує кодування явно, або просить його все ж таки знайти з відповіді. (Або можна просто зробити
.force_encodingрезультату, якщо вже знаєш, що туди передати.)Звідки все ж таки тоді
ASCII-8BIT? На це є точна відповідь. От звідси. А саме: якщо зміст відповіді передається стиснутим, тобто зContent-Encoding: gzip, то вмикається класInflater, який примусово перемикає результат в кодування ASCII-8BIT. Бо це є кодуванням для двійкових даних (те ж саме, щоEncoding::BINARY.)Мабуть, очікується, що саме двійкові дані будуть стиснуті? Але це навіть протилежно дійсності — бо найбільше користі від стискання текстів - HTML, CSS і так далі. Зображення, наприклад, самі по собі є стиснутими. А так виходить, що ми залежимо від налаштувань вебсервера, тобто
.force_encodingвсе ж варто робити, принаймні для послідовності. -
Оновлення для Hacker News Feed
Сьогодні зробив два оновлення для своєї стрічки для Hacker News. Стрічка існує з минулого грудня та має своїх користувачів.
Перше оновлення дуже просте — тепер є коротша стрічка https://feeds.leonid.codes/hacker_news_lite.rss. Вона береться тільки з головної сторінки Hacker News та містить від 20 до 30 позицій.
Друге — отримав скарги на те, що стрічка місцями має зіпсоване кодування. Тут вийшло складно. Ну, тобто зрозуміти, що не так не дуже складно — код сторінки в Ruby отримує кодування
ASCII-8BITзамість UTF-8, відповідно на виході зайвий раз перекодовується в UTF-8 та при цьому псує типографічні символи.(Ці символи в оригіналі UTF-8 займають декілька байтів — наприклад em dash це
0xE2 0x80 0x94. Кодування ASCII-8BIT бачить ці байти як 3 різних символи, які поодинці конвертуються в 3 знаки UTF-8, які вже нічого не мають спільного з тире.)Прямолінійний розвʼязок короткий — зробити документу
.force_encoding('UTF-8')та й годі. Але ж мені треба зрозуміти, що є первопричиною. Зрозумів. Про це, мабуть, завтра, бо розʼяснення довге. Поки запрошую підписатись на стрічку Hacker News. -
Типи для представлення масивів даних в JavaScript
-
String- найбільш базовий тип; для тексту підійде, а для двійкових даних — ні. Технічно рядок може містити двійкові дані, але оскільки за стандартом рядки мають кодування UTF-16, то без особливої обробки вони будуть зіпсовані при перетвореннях. Та в будь-якому разі, це застарілий підхід, бо зараз є достатньо альтернатив. -
ArrayBuffer - справжній сучасний тип для збереження двійкових даних. Що важливо -
ArrayBufferможна передавати (transfer) зі сторінки до вебворкера та назад без копіювання. Сам по собіArrayBufferє “примітивним” типом, а працювати з ним зручно через “типізовані масиви”UInt8Arrayта інші, а також через цікавий клас DataView, яким можна читати з масиву довільні дані. -
Buffer (той, що в Node.js) - в браузері можна замінити пакетом buffer - використовує всередині той самий
ArrayBuffer. Так що пакети для Node.js, яким потрібний Buffer, теж можна підключати без зайвих витрат. -
Blob дуже схожий на
ArrayBuffer, але за природою зовсім інша істота. ЯкщоArrayBuffer- це масив даних в памʼяті, то Blob - це абстракція масиву даних, який може знаходитись будь-де. На практиці це “будь-де” значить або в памʼяті поточного процесу, або в спільній памʼяті браузера, або на диску. (КласFileє нащадкомBlob, до речі.) Через цеBlobне можна змінювати, а читати дозволено тільки асинхронно. Зате можемо створювати на них посилання та в деяких випадках заощаджувати ресурси (якщо не створювати його власноруч). Втім, для роботи з невеликими даними безпосередньо в браузері від Blob немає користі. -
Як
ArrayBuffer, так іBlobможна вказувати як тіло запита HTTP, або навпаки, як тип результату. Тут треба дивитись — якщо будемо працювати з даними, кращеArrayBuffer, якщо просто передавати далі — кращеBlob.
-
-
Звідки росте дерево задач?
Як я писав, дерево задач - зручна форма для деталізації планів. За останні тижні переконався в цьому. Obsidian дає неперевершену можливість доповнювати задачі кроками та нотатками. Зокрема допомагає те, що можна винести задачу, яка розрослась, в окремий документ. Так можна зовсім не боятись записувати все, що спало на думку.
Проте я зрозумів, що дерево задач, яке я можу вільно редагувати, відкриває й доповнення в напрямку “вгору”, тобто, до якої більшої цілі належить задача. Це здійснює моє давнє бажання формулювати високорівневі плани. Бо типові менеджери задач починаються з тактичного рівня проєктів та мало піклуються причиною існування цих проєктів. Що й призводить врешті решт до довгого списку задач та виснаження від керування ними.
А так можна окреслити, хоча б приблизно, стратегічні цілі та погрупувати задачі по нім. Це відразу спрощує пріоритизацію. Задачі всередині мають схожу тему, та легше побачити порядок виконання. Цілей просто менше, та головне що взагалі зʼявляється можливість побачити їх та обрати наступну. Звісно, можна наробити більш ніж один рівень цілей, якщо це має сенс.
Наприклад: з 40 активних задач по робочому проєкту можна виділити: поточний епік (насправді, для нього я відразу зробив вкладене дерево); задачі по інфраструктурі; покращення процесів; відомі баги та недоліки. Тоді наступного разу, коли зʼявиться вільний час від роботи над епіком, я можу переглянути цей список, обрати, наприклад, що хочу витратити його на покращення процесів, та потім йти по списку конкретних покращень.
Якщо це очевидні речі, добре — бо на роботі є менеджмент. Тому й приклад я взяв знайомий. А в особистому житті рідко так саме ясно, за що хапатись — тут і допоможе стратегічне дерево.
-
Використання Promise для побудови лінивих обчислень
Натрапив на цікаве використання Promise в JavaScript. Зазвичай Promise це механізм асинхронного виконання, більше необхідна поступка ніж щось корисне. Проте є цікавий аспект: один раз виконаний Promise зберігає своє значення і далі.
Коли у вас уже є Promise, на ньому можна викликати
.then()(та.catch()) скільки завгодно разів. Такі виклики призведуть до невідкладного виконання відповідного обробника. Після завершення поточного фрейму виконання, звісно, тобто семантика зберігається.Що це нам дає? Зустрічаються випадки, коли один та той самий результат асинхронного виконання потрібний у двох місцях. Наприклад: завантажуємо з сервера текст поста, який можна або редагувати, або переглядати. Причому ми хочемо завантажувати його тільки якщо відкритий той чи інший режим. Та точно не хочемо повторювати це двічі.
Можна звісно зробити менеджер стану (як Redux), а також механізм повідомлення про готовність. Може, використати EventEmitter та підписки.
Але як альтернатива до всього цього, можна зберігати сам Promise. Тобто результат виконання
fetch(). При першому виклику робимо завантаження та зберігаємо Promise; а при наступних просто повертаємо той самий Promise. Споживачі нічого не знають про ці деталі реалізації, а просто отримують Promise з потрібними даними. В тому числі не буде проблеми, якщо другий виклик відбудеться до отримання результату, тобто Race Condition.Так можна будувати й складніші ланцюги перетворень — я навів найпростіший приклад. Тобто проміжні результати виконання теж зберігати так само. Можна мати цілий граф, як в reselect, тільки асинхронно. Насправді коли про це згадав, то дійсно вся ідея схожа на reselect, та, можливо, може бути прямо там реалізована.
-
Помилка тих, хто починає з AWS: не створити декількох акаунтів
Може, найбільш відома помилка у роботі з AWS - це коли зловмисники отримують доступ до ключів. Та і я про це вже писав. Ризики, повʼязані з ключами, можна значно знизити, якщо окрім головного акаунта мати також акаунт-пісочницю.
Ось неочевидна порада: акаунтів в AWS можна створити стільки, скільки вам потрібно — це нічого не коштує. Навіть є функція AWS Organizations, щоб поєднати всі акаунти під єдиним дахом платежів. Але її використання не обовʼязкове, можна просто декілька разів зареєструватись.
Пісочниця не тільки прибирає ризик проникнення до даних клієнтів. Наявність пісочниці миттєво звужує коло людей та потреб, які вимагають доступу до головного акаунту.
В пісочниці можна (та потрібно) розташувати стейджинг. З Terraform відтворити зменшену копію продакшна можна майже без зусиль. Тому можна навіть декілька стейджингів. Також робити всілякі експерименти. Тоді в продакшні навколо живих сервісів не будуть зʼявлятися тестові, з незрозумілим рівнем безпеки.
-
Найбільша помилка тих, хто починає з AWS: власні витрати
Я вже писав про те, як легко в AWS спалити гроші. Проте не встиг розповісти, що можна робити, щоб запобігти цьому.
Коли робиш свій перший проєкт на AWS, майже напевно натрапиш на неочікувані витрати, які вас не збанкрутять, але неприємно здивують. Зайві $100 на місяць можуть зʼявитися практично з повітря.
Мій улюблений приклад: якщо “по уму” зробити безпечну мережу, де внутрішні ресурси сховані в приватну підмережу, то для доступу з цих ресурсів в інтернет доведеться також додати шлюз NAT. Здавалось би, це абстрактна сутність, така сама як підмережі. Але ні - NAT-шлюз коштує $30 на місяць. За ці гроші на fly.io вже можна захостити потужний додаток. І це тільки “фундамент наших витрат”, так би мовити.
Перше, що варто зробити на новому акаунті AWS - це налаштувати повідомлення про перебільшення балансу. Так можна швидко помітити надзвичайні витрати. Поки бюджет проєкту невеликий, це надійний засіб уникнути несподіваного рахунку наприкінці місяця. (Так само варто зробити для всіх хмарних хостингів.)
Також можна налаштувати Cost Anomaly Detection - вона повідомляє про надзвичайні витрати за окремою категорією, а не загалом. Тому це кращий механізм для більших проєктів, де значного перебільшення бюджету раптово не відбудеться, проте 100 pounds is still 100 pounds. Cost Anomaly Detection точно не проґавить зайву копію СУБД, забуту зі стрес-тесту.
Завтра — про другу за важливістю помилку.
PS: також завтра я візьму участь в стрімі DOU про безпеку AWS тут. Запрошую долучитись!
-
Робота з файлами безпосередньо в браузері
Трапляються задачі, коли файли нам не приходять з бекенду, а утворюються саме в браузері. Тоді варто знати, що сучасні браузери здатні створювати файли практично для будь-якого застосування, та бекенд нам більше не потрібний.
-
Класичний механізм data-url згодиться не тільки на включення зображень в CSS, але і як заміна будь-якого посилання — наприклад, як джерело зображення, яке створене динамічно та підключене вже в HTML. Інтуїтивно здається, що посилання мають бути короткими, але це зовсім не так. Найменше обмеження наразі в Firefox - 32 Мб. Обмеження на Data URL в Chromium та Webkit абсолютно космічні - 512 Мб та 2 Гб відповідно.
-
Blob підійде там, де довжелезні посилання не підходять (наприклад, як адреса сторінки). На Blob теж можна утворити посилання функцією URL.createObjectURL та використати його будь-де, поки живий документ або вебворкер, де це посилання було створене. Блоби теж мають обмеження до 2 Гб — є де розгулятись.
-
В адрес блобів є один недолік — вони не дозволяють вказати назву, а генерують її з UUID, тож під час завантаження будуть збережені в файл з назвою з UUID та навіть без розширення. Якщо ми хочемо надати файл для завантаження, то це, звісно, неприйнятний UX. Тому є інша можливість, а саме атрибут
<adownload>. В ньому як раз можна вказати назву файлу для завантаження. Це працює також з Data URL, а ще з коли справжнє посилання не містить корисного імені файлу (наприклад,/files/123/export). -
Нарешті, ще є зручна бібліотека FileSaver, яка абстрагує всі ці подробиці та просто дає функцію
saveAs().
-
-
Як працювати з кодом JavaScript, коли вже звик до TypeScript?
Коротка відповідь — навпомацки. Відсутність типів відбирає приємне відчуття що код “звʼязався до купи.” Втім — доводиться стикатися з проєктами де TypeScript немає.
На щастя, VS Code й у JavaScript робить часткову типізацію. Підказки до коду та автодоповнення враховують ті типи, які можна зʼясувати з оточення. Це більш потужно, ніж можна подумати — на типах з пакетів залежностей можна далеко доїхати.
Але щоб дійсно досягти бажаної типізації, можна впроваджувати типи у JSDoc. Фактично це ті ж самі типи, тільки обʼявлені всередині коментарів (та тому на них не діє автоформатування, що дратує мене щоразу.) Не чув, щоб хтось був проти додаткових коментарів, тому це вірний шлях для проєктів з багатьма учасниками та великою базою коду.
В JSDoc можна робити все те ж саме, що й просто в TypeScript - імпорти з інших модулів, складні типи, дженеріки, оголошення типів за іменем. Не раджу, наприклад, власний підхід JSDoc до типізації обʼєктів тегами
@property; краще просто оголосити тип-обʼєкт в синтаксисі TypeScript в тезі@typedef, а потім використати його.На останнє, якщо в файлі JavaScript написати магічний коментар
//@ts-check, то для нього увімкнеться повноцінна перевірка на типи та вкаже всі помилки. Так хоч би тимчасово (або ні) можна переконатись в правильності коду та отримати ту дозу дофаміну.