-
Обновление Ютуба

Гугл грозился обновить плеер в Ютубе, и вот это случилось. До этого новый интерфейс появлялся у меня пару раз, но быстро пропадал. Подозреваю, его откатывали из-за ошибок. Но теперь все исправили, и он с нами надолго.
Напомню, в чем проблема. Новая панель почти в два раза выше, чем прежде. Приложил к экрану линейку: была сантиметр, стала 18 миллиметров. При этом все кнопки старые, нет ни одной новой. Просто теперь они собраны в капсулы, а еще им добавили отступы.
Новый воздушный интерфейс на редкость уродский. Слева и справа группы кнопок, а в центре — полоса перемотки. Она, словно барьер, режет видео пополам. Она залезает на лица ведущих, которые по недоразумению поставили их снизу.
Если когда-нибудь вы смотрели ролики по изучению языков, то знаете — субтитры добавляют на этапе монтажа. Обычно их ставят внизу, чтобы не заслонять лица. Еще во времена старой панели это было проблемой: поставил на паузу, чтобы прочитать — но панель загораживает. Приходится двигать мышкой, кликать на посторонние элементы, чтобы перенести фокус. А теперь панель стала еще выше!
Я как-то смотрел видео одного человека (неважно о чем, не хочу раскрывать увлечения). Судя по всему, он японец и не говорит по-английски, хотя читает и пишет отлично. Он записывает ролики и добавляет в них текстовые комментарии. С новой панелью прочитать их во время паузы невозможно. Полоса перечеркивает буквы.
Я не понимаю, почему панель должна налезать на видео? Кто сказал, что видео — это область, где можно ставить кнопки, бегунки и прочее? Нельзя! Оставьте видео в покое! На нем ничего не должно быть.
Опять же, не понимаю, почему нельзя сделать опцию “панель снизу”? Это буквально пара мест в CSS, чтобы сместить блок вниз.
В общем, главная медиа-площадка планеты — отличный пример того, как делать не нужно.
-
Postgres в Телеграме
Лишний раз убедился, что писать на чужих площадках — гиблое дело.
Где-то три года назад я вступил в группу Postgres в Телеграме; там было 11 тысяч пользователей. Только начал писать — какие-то тролли наставили кучу дизлайков. Я даже не понял: это заговор или что? Вышел.
Полгода назад снова вступил в эту группу, там уже 14 тысяч пользователей. Отвечал на вопросы, помогал. Сегодня ответил на сообщение админа — совершенно нейтрально, по существу — получил бан на час.
Что ж, если не хотите меня, то так и быть. “Мэссадж понятен даже идиоту” (с). Вышел, и третьему разу уже не бывать.
Вот за этим и хорошо иметь свой блог или канал — можно писать все, что хочешь. Поэтому я не вступаю в закрытые каналы, группы, клубы и прочие “илитные” тусовки. Там ты всегда под кем-то, а это рано или поздно выйдет боком.
-
Записи в Джаве
Кажется, с версии 17 в Джаве появились рекорды — они же записи. Клевая вещь, коротко про нее расскажу.
Запись — это класс с набором барахла, которое раньше нужно было писать руками. Только теперь это барахло производит компилятор. Например, если объявить запись с двумя полями:
public record User(name String, age int) {}, то получим класс с полями
nameиint. Однако:-
все поля финальные, их можно задать лишь однажды и никогда — изменить;
-
у класса готовый конструктор, в котором поля следуют в том порядке, что и в объявлении;
-
у класса готовые методы
.name()и.age(), которые ведут себя как геттеры; -
у класса свой метод
.hash(), который учитывает все поля; -
свой метод
.equals(), который сравнивает записи по значениям; -
удобный
.toString(): напечатав запись, вы увидите значения полей, а не[foo.bar.User x00x0s0ds](или что там печатает Джава по умолчанию).
Есть и другие плюсы, не помню все досконально.
Получается, на ровном месте появились нормальные неизменяемые классы. Всего-то двадцать лет прошло или около.
Хорошая привычка: пока это возможно, везде использовать рекорды, а к классам переходить, если состояние изменяется. В моем клиенте для Постгреса примерно так и сделано. Из 150 классов почти все из них — записи, и только корневой класс
Connection— обычныйObject.Чтобы вы не подумали, что все так хорошо, вот ложка дегтя. Когда классы неизменяемы, часто нужен “такой же, но золотыми пуговицами”. Другими словами, получить клон экземпляра с измененным полем. Кложуристы поймут без слов:
(let [user {:name "Ivan" :age 10}] (assoc user :name "New")) ;; {:name "New", :age 10}На мой взгляд, было бы логичным, если бы компилятор генерил методы
.withNameи.withAge, которые принимают значение и возвращают клон с новым полем. Но их нет, а прописывать вручную — скучно: это экраны кода, где, вдобавок, легко ошибиться.Четыре года назад в Джаве сделали шаг в верном направлении. Хорошо, но мало. Не пора ли сделать еще? Глядишь, так и придем в светлое будущее.
Кстати, в Питоне тоже относительно недавно появились
data-классы. Неужели адепты ООП стали что-то подозревать? -
-
Фронтендеры
Выскажу тезис в отношении фронтендеров. Может, он не самый приятный, но неплохо упрощает дело. Тезис следующий: фронтендер мыслит npm-пакетами. В мире фронтендера любая задача, любая проблема решаются одним способом — пакетом из npm.
У тезиса несколько следствий. Во-первых, когда есть несколько способов решить задачу, фронтендер выбирает тот, для которого есть npm-пакет. При этом не важно, насколько способ оптимален и удобен другим участникам, например бекенду или пользователям. Есть пакет — дело в шляпе.
Во-вторых, если нужного пакета нет, берется максимально близкий к нему и задача подгоняется так, чтобы ему удовлетворять.
Стоит только принять тезис и следствия, как многое становится очевидным. Например, нелепые решения, особенности передачи данных, требования коллег.
Доказательств этому я повидал много, вот хотя бы одно.
Есть SPA-приложение для сотрудников. По нажатию кнопки нужно скачать с бекенда CSV-файл. Как его передать, если на сервере только REST и JSON? Как скачать файл, чтобы он не открылся в текущей вкладке?
Фронтендер загуглил “javascript csv on client” и нашел npm-пакет. Он принимает массив словарей, записывает их в CSV-строку и заставляет браузер ее “скачать”, то есть сохранить на диск. Бекендер пожал плечами, мол как скажешь — и отдал на фронт список мап. Это не его дело, фронтендер так попросил, пусть и разбирается.
Все это работало, пока строчек было сто-двести. Но однажды мне велели сделать выгрузку на двести тысяч записей. Я сделал по образцу, и тут-то браузер накрылся. Вкладка окирпичивается, ничего не работает.
Далее, я был единственным, кто задался вопросом: что делают люди с этим файлом? Оказалось, открывают в Экселе. Как я уже рассказывал, открывать CSV в Экселе — это играть в русскую рулетку. Эксель вечно путает разделитель полей — запятую или точку с запятой — и даже больше: эта опция зависит от локали. Поэтому открывая файл, половина людей видели слипшиеся поля и матерились.
У Экселя есть костыль: если в первой строке CSV написать
sep=;, то будет использоваться именно этот разделитель. Но оказалось, npm-пакет ничего про это не знает. Нужно контрибьютить и пинать автора, чтобы принял изменение и выкатил версию.Наконец, я спросил прямо: если пользователи открывают файл в Экселе, уж наверное им нужен файл xlsx, а не CSV? Так-то да, но пакета в npm, чтобы собрать эксельку на клиенте, нет, поэтому пролетаем.
В итоге я сделал вот что. На бекенде пишу нормальный Эксель-файл. Кладу его в S3 и получаю подписанный урл, который действует 15 минут. В HTTP-ответ отдаю джейсончик с этим урлом:
{"url": "https://acme.amazonaws.com/path/to/file.xlsx?...", "expires_at": "..."}На клиенте: получаю джейсончик, достаю урл. Потом:
- создаю невидимый элемент
<a href="...">с этим урлом - добавляю этот элемент в тело так, что его не видно
- имитирую клик по нему методом
.click()
В результате файл загружается автоматом. Ну или может появиться окно с вопросом, что делать с файлом.
Тот npm-пакет я к черту выбросил, потому что он банально не нужен.
Как я и говорил, все упирается в тезис из начала заметки. Фронтендер не понимает проблему, которую решает. Он не понимает в целом как обмениваться данными, как делать удобно другим — коллегам, пользователям. Он мыслит npm-пакетами и подгоняет условия задачи под те пакеты, которые нагуглил.
Я, кстати, вообще считаю, что фронтенд — это не программирование. Современный фронтендер занят тем, что передает результат из пакета А в пакет Б. Это не хорошо и не плохо, это просто факт. Примерно как сборка изделия из готовых частей.
Знание этого принципа, на мой взгляд, полезно в следующем. Выбирая решение, нужно проверить, что у фронта есть для него пакет. Нужно следить, чтобы коллеги выбрали максимально адекватный их них. Такой, чтобы не повесил браузер, не скачал 100 зависимостей вроде
is_number,leftpadи так далее.Короче, следить за ними, как за перспективными детьми, которые в любой момент могут попасть в дурную компанию.
Ну и порой подсказать фронтендерам, что задача решается не пакетом, а пятью строчками на чистом js. Часто они этого не знают.
- создаю невидимый элемент
-
Don't guess — measure
Как-то я смотрел видос по Джаве, и там говорил пожилой дядечка. Жутко умный, с легким акцентом. Наверняка он известен в мире Джавы, но имени я не запомнил.
Так вот, этот дядечка раз десять повторил тезис: don’t guess — measure. Не гадай, а замеряй. Только имея на руках цифры, можно о чем-то говорить.
К чему это? Своего рода ответ разработчику, с которым недавно общался. У него функция с кучей расчетов, и он не укладывается в 30 секунд. Но говорит: ничего, возьму библиотеку X для быстрых расчетов, и все полетит.
На меня такие высказывания — красный флаг. Для начала хотелось бы понять: как устроена та “быстрая” библиотека? Она что, как-то особенно складывает числа — как не умеет язык из коробки? И насколько быстрее станет наш код? И что конкретно в нем тормозит? А если не станет быстрее — что будем делать?
Тащить новую библиотеку интересно, не спорю. Но вообще, в данном случае мы отталкиваемся от угадывания. Перепишем — ускорится. Наверное. А может, и не ускорится.
UPD: пока печатал, вспомнил, что это было за видео:
-
Cloud Driven Development (2)
Как-то я писал о том, что не люблю Cloud Driven Development. Это когда разработчик тестирует код в облаке. Написал код, задеплоил, дернул апишку. Она упала. Он смотрит логи, исправляет код, деплоит, дергает апишку. Она снова упала. Он смотрит логи, исправляет… короче, вы поняли. На девятой итерации код работает, и разработчик закрывает задачу.
Беда в том, что это долго и хрупко. Пока бегут тесты и деплой, проходит минут 7-10. Вроде бы это немного, но достаточно, чтобы без конца отвлекаться. Разработчик смотрит ютуб или бегает курить. Формально он работает, но на самом деле большую часть времени ожидает. И конечно, это хрупко. Когда код сломается и ты деплоишь себе, обнаружится, что нужно сделать сто вещей: тут накатить миграции, здесь создать в базе записи, там положить токен и так далее. Это нигде не написано, нужно бегать искать тех, кто знает.
Ирония в том, что последние три месяца я занимаюсь Cloud Driven Development. Пишу код, гружу в облако, дергаю. Смотрю логи, правлю, гружу, дергаю. И так по кругу. Я пытался писать тесты, но обнаружил, что они не отражают действительности. В облаке случается много того, чего не ожидаешь. То сервис отвалится, то файла нет, то внезапно троттлинг. Очень непредсказуемы очереди задач. Параллелится то, что не должно. Не параллелится то, что должно.
Амазон — огромный и непознанный, буквально как другая планета. Там все живет по своим правилам. Нужна ассимиляция по аналогии с переездом в другую страну. Выучи язык, культуру, порядки, тогда с тобой будут иметь дело.
У нас есть служебная страница, где вводишь трассировочный заголовок, и она покажет дерево обращений: от какого сервиса к какому, задержки, тайминги и так далее. Моя задача порождает дерево из 50 тысяч взаимных обращений. В результате страница не работает: либо падает вкладка (клиентский рендер на Кложа-скрипте), либо бекенд: данные не пролазят в лимит 6 мегабайтов (с учетом сжатия).
Теперь я остался без трассировщика. Только логи, только хардкор.
Что со всем этим делать — я не знаю. Видимо, остается только клауд-дривен-девелопить, чтобы в будущем про меня сказали: этот облачный чудак даже тестов не написал.
-
Файлы в API
Добавка к прошлой заметке. В комментариях написали, мол, у вас стремный фреймворк, потому что передает только JSON. Как отдавать файлы? Лепите костыли со своим S3.
Дело вот в чем. Во-первых, да: у нас можно передать только данные, а файлы — нет. Все прибито гвоздями: пришел джейсончик, ушел джейсончик. Никаких файлов. Даже нет урлов как в этом вашем REST: все валится в один урл и там разгребается по полю command. RPC на коленке.
Во-вторых, мне это нравится. Когда вспоминаю REST, комбинации методов и урлов, утилиты вроде Swagger и OpenAPI, темнеет в глазах — это натуральная истерия. Все делают REST и затягивают десятки утилит, генерят хендлеры, не понимая, зачем и какую проблему они решают.
В-третьих, файлы. Я понял, что генерация файла и его раздача — разные вещи, и делегировать их нужно разным системам. Например, вы генерите файл и отдаете клиенту в полете. А у клиента моргнула сеть, браузер делает повтор — и все по-новой. Вряд ли ваш код поддерживает заголовок Range, чтобы сгенерить байты с 100500 по 100999.
Кроме того, иной файл можно сгенерить однажды и раздавать многим людям. Какой-нибудь отчет, например, считается актуальным в течение часа, так что нет смысла генерить его на каждый чих.
Поэтому удобно, когда файл уходит в какое-то хранилище, а клиенту дают временный урл со словами: на, забирай. Хочешь — курлом, хочешь — качай в браузере.
Примерно так устроена работа с файлами у айти-гигантов. Например, чтобы загрузить файл в VK, сначала дергают апишку с семантикой: я собираюсь загрузить картинку, тип такой-то, размер такой-то, название “prikol_2001.jpeg”. В ответ прилетает урлец, куда шлешь файлик методом POST.
То же самое, чтобы скачать файл: шлешь запрос “дай такой-то файл, вот координаты”, получаешь временный урл.
Важно, что апишка сервиса остается однородной. Она полностью одинакова: пришли данные — ушли данные. А если нужны файлы или стриминг, этим занимаются другие сервисы.
-
Ажиотаж вокруг ИИ
Репост с гугло-переводом. Автор — Kira (McLean) Howe
Наша индустрия сейчас совершенно пьяна от ажиотажа вокруг ИИ. Если вы из тех, кто в этом году не стал злоупотреблять ИИ, наблюдать за этим больно.
Проблема не в самом ИИ, а в одержимости его инструментами. “ИИ” — это очень круто и, безусловно, полезно, но это всего лишь инструмент. Инструменты сами по себе не создают ценности, в отличие от тех, кто ими пользуется. Мы упустили из виду то, что действительно важно: результаты.
Мы уже видели этот фильм. Помните большие данные? Все разворачивали кластеры Hadoop для объёмов данных, которые поместились бы в один экземпляр Postgres. Потом контейнеры и Kubernetes — невероятные технологии, но годами мы тратили больше времени на поддержку кластеров, чем на создание чего-то, что действительно было бы нужно пользователям.
Потом микросервисы — в теории они великолепны, пока не начнёшь отлаживать распределённые системы, которые изначально не требовали распределения. Потом блокчейн, бессерверные технологии, GraphQL, сетки данных, отсутствие кода, метавселенная, “agile at scale” и т. д., и т. п. А теперь ещё и ИИ.
Схема каждый раз одна и та же. Каждый обещал революцию и в некоторых конкретных ситуациях её произвёл. Но для большинства команд они стали отвлекающими факторами — дорогостоящим отвлечением от реальной работы по созданию ценности. Каждый из этих инструментов или идей действительно заслуживает внимания, но внедрение или интеграция без чёткой цели — напрасная трата времени.
Инструменты сами по себе не создают ценности. Ценности создают результаты.
Сосредоточьтесь на результатах, а не на тенденциях. Если инструмент помогает вам двигаться быстрее или добиваться лучших результатов — отлично. Используйте его. Но если нет, он вам не нужен, и уж точно не нужен вашим клиентам, как бы громко ни звучала шумиха.
-
Webp
Webp — достаточно бесячий формат. Да, он на 30% плотнее JPEG, и это неплохо экономит трафик. Но беда в том, что с ним ничего нельзя сделать. Картинку Webp вы не вставите ни в один гугловый документ! — неважно слайды это или текст. В высшей степени странно: авторы формата сами же его не поддерживают.
Есть такой костылик: когда ищете картинки для презентации, ставьте расширение
Don't Accept Webp. Оно сводится к тому, что из заголовков Accept убирается элемент image/webp — мол, браузер его не принимает. Однако сервер все равно может вернуть webp, хотя вероятность этого и ниже.Еще вроде бы webp можно отключить в about:config, но я не нашел.
Кроме того, анимированный webp не работает в Waterfox, и порой это неудобно.
-
О перехвате исключений
Перехватывайте исключения только если у вас есть “план Б”. Другими словами, есть альтернативный способ решить задачу. Если его нет, то ловить исключение не нужно.
Практика показывает, что “плана Б” почти никогда не бывает. Сценарий всегда один: взяли то, получили другое, прибавили третье — вот и результат. Отсюда следствие: поймав исключение, вы ничего толком не сделаете.
Я уже приводил примеры. Нет файла на диске — с этим ничего поделать нельзя. Нет файла в S3 — тоже. Не задана переменная среды (пароль или токен) — без нее никуда не пойдешь.
Почти каждая ошибка означает, что задача в целом провалена, и пытаться дальше бесполезно.
По этой причине, кстати, в Джаве нынче используют unchecked-исключения, то есть те, которые не нужно объявлять в сигнатуре метода. Каждый, кто хоть немного писал на Джаве, знает — если использовать checked-исключения, каждый метод обрастает ими, как дно корабля — морским желудем. Эта зараза ползет по коду: метод, который вызывает зараженный метод, вынужден либо унаследовать бороду исключений, либо заткнуть их все и кинуть unchecked-исключение.
С одной стороны теоретики заливают о контрактах и безопасности кода. А с другой стороны — практика, которая доказывает: это неудобно. Знаете шутку: в теории между теорией и практикой нет разницы, а на практике есть? Это как раз тот случай.
Перехватывать исключения имеет смысл в редких случаях, например:
-
поллинг. Бывает, вы посылаете сервису задание: вычисли то-то и положи результат в S3. После этого начинаете поллить файл. На этом шаге логично перехватывать
FileNotFoundили похожие ошибки — до определенного лимита, конечно. -
Запасной источник данных — иногда одни и те же данные можно добыть разными способами: из базы данных и временного хранилища. Если второе отказало, можно сходить в базу, перехватив исключение.
-
Троттлинг — если вы часто ходите в какой-то ресурс, он может сказать “умерь пыл”. На уровне кода это будет исключение
RateLimitExceptionилиHTTPErrorс кодом 429. Их можно поймать, поспать секунду и повторить. -
Ошибки ввода-вывода. Бывает, моргнула сеть, подвис роутер и скачать файл не удалось. Поможет перехват IOException и повтор операции. Только не забывайте, что все продвинутые клиенты учитывают сетевые сбои. Внутри у них своя логика повтора в случае IOException.
Еще один случай — когда нужно показать красивое сообщение об ошибке. Даже если было исключение, некрасиво вываливать стек-трейс (а то и вовсе небезопасно) — нужно завершить программу культурно. Программисты на Питоне, мотайте на ус: ваши программы часто валятся со стек-трейсами, стоит только ввести не те данные.
Иногда ошибки кидают намеренно — опять же, по нескольким причинам. Во-первых, встроенная функция может вернуть
null, однако это не имеет смысла. Я уже приводил пример: не задана переменная среды: токен, пароль и так далее. Продолжать без нее нет смысла. Поэтому вместоSystem/getenvвызывают свою функциюget-env!, которая кинет исключение, если переменной нет.В противном случае вы будете долбиться в сеть с заголовком
X-Authentication: aws-null-null, и ничего хорошего из этого не выйдет.Другая причина — внести ясность в чужие исключения. Порой их сообщения лишены деталей и потому бесполезны. Например, “file not found” — какой файл? “Port is busy” — какой порт? “HTTP 404” — какой урл? Когда вызов один, разобраться еще можно. Но представьте, что у вас цикл, пул тредов — желаю счастливой отладки.
Поэтому такие исключения оборачивают своими, которые говорят:
file C:\windows\mustdie.text not found,HTTP 404: GET http://test.com,port 5432 is busyи так далее.Поймать исключение и подавить его, не имея на то причины — один из худших паттернов в программировании. Недавно я добился того, чтобы один сервис отвечал ошибкой, если нет файла в S3. До меня было так: разработчик читал его из S3 и парсил JSON-библиотекой. Все это он оборачивал в try-catch с логикой: если пошло не так, записать в лог и вернуть nil. В Кложе nil ведет себя как пустая коллекция, поэтому на вычислениях это не сказывалось, просто они были пустыми.
В день было по 50.000 (прописью — пятьдесят тысяч) подобных ошибок. Все они были в логах. Кто-то хоть раз их читал? Разумеется, нет.
Проблема вскрылась лишь в тот момент, когда код слегка изменили. В результате получалась мапа с нуллом в ключе:
{nil ...}, и наш JSON-сериализатор падал: мол, не знаю, что делать с null-ключом. А до этого проблем не было. Не падает — значит, все хорошо.Так вот, возвращаясь к первоначальному тезису. Перехватывая исключения, думайте про “план Б”. Действительно ли он у вас есть? Лично я сомневаюсь. “План Б” — это буквально три-четыре случая, а все остальное — трусость, неопытность, желание замести проблемы под ковер.
Крайне вероятно, что имеет место второе, а не первое.
-