-
Оружия черепашек-нинздя
Разговаривал с одним человеком и выяснил, что он не знает, какие оружия у черепашек-нинздя. Это должен знать каждый, и я не сомневаюсь, что и вы знаете. Просто на всякий случай повторю.

Итак, первый уровень, изи: Леонардо, который в голубой повязке, владеет двумя мечами. Это всем понятно.
Второй уровень, нормал: что у Микеланджело, который в оранжевой повязке? Это нунчаки: две палки на цепи, которыми проще убить себя, чем противника.
Теперь третий уровень, хард: что у Донателло, который в фиолетовой повязке? Простолюдин скажет, что у него палка, и от этого хочется плакать. Это бо! – или, чтобы было понятнее, шест бо. Посох из металла или с его элементами, чтобы не сломаться и сильнее бить противника. Бо полезен в бою, в быту, в походе, словом, универсальная для Востока вещь.
Последний уровень, найтмеар: что у Рафаэля, черепашки в красной повязке? Когда говорят, что трезубец, хочется кататься по полу от отчаяния. Это саи. С-а-и! Один сай, два сая, многое саев. Гугл-док не понимает и предлагает замену, но вы-то понимаете! Такие кинжалы с двумя лепестками по краям. Идея в том, чтобы принять меч противника между зубцами и резко повернуть. Твой рычаг больше, чем у противника, и он теряет оружие.
Все это я пишу по памяти безо всяких Википедий, и знал это лет с семи. А вот как без этого можно жить – я не понимаю. Молодежь пошла не та, зумеры не хотят работать и вообще все плохо.
-
Дизайн REST API
В Линкед-ине хвастает человек: мол, нейросеть задизайнила рест-апишку. Прикладывает скриншот сваггера. Я смотрю на него и киваю: да, все на месте, все как у современного разработчика. А еще я понимаю, что на скриншоте, как в капле воды, собрано все то, за что я не люблю рестовые апишки.
Во-первых, версия
v1в урле. Она всегда меня забавляла. Я работал во многих стартапах, и ни один из них не сменил версию. Гораздо чаще стартапы закрываются и продаются. Я считаю, апишка должна быть одна, и со временем какие-то методы добавляются, какие-то снимаются с обслуживания. А в той нумерации, что на скриншоте, слишком много пафоса. Примерно как объявить счетчик с шестью нулями для значения, которое в лучшем случае достигнет двойки. Даже если предположить, что появится новая апишка, просто сделай другой урл:/api/grpcили/api/graphql.Далее, иерархия. Те апишки, которые работают с матчем, требуют код турнира. Нужно передать две айдишки: турнира и матча. Зачем? Разве может матч относиться к разным турнирам? Это банальная избыточность, и делается она ради красоты. Должен передаваться только матч, а по нему легко найти турнир и проверить права.
Еще одна забавная вещь: метод
PUTне может покрыть все изменения, поэтому в ход идут костыли: частички:report,:reopenна конце урла. Это некрасиво, потому что по-хорошему операция определяется HTTP-методом, а тут урлы. Тот случай, когда шубу заправляют в трусы, а она не лезет.Ну и расцветка Сваггера как у цыган: зеленый, голубой, оранжевый, красный… все собрали.
На мой взгляд, нормальная апишка должна быть по принципу RPC. Один урл с двумя параметрами:
actionиparams. Метод один – POST. Внутри словарик с функциями и схемами, по которому раскидываются запросы. Все. Документашка со схемами строится одной командой.Апишка – довольно простая вещь. Она не должна быть сложной и требовать разного тулинга. Тот REST, что имеем сегодня – это вавилонская башня на ровном месте. Как и в библейской истории, разработчики сами не понимают друг друга. Но продолжают и продолжают строить эту башню.

-
Просто берите SQLite
Тезис к размышлению: при помощи Postgres и SQLite можно решить (почти) все насущные задачи. Без распределенных кэшей, очередей, облачных паб-сабов и так далее.
Насчет Постгреса все ясно: я уже писал о нем отдельно. А что SQLite?
Замечаю одно и то же: иной раз требуется хранить в памяти какие-то данные. Сначала это словарь вида id -> сущность. Потом нужен поиск по полю сущности. Строится обратная мапа поле -> список сущностей. Потом нужная вторая сущность и перекрестные ссылки. Код превращается в ад.
Наблюдаю такое в трех сервисах. Ребята читают из файлов сущности, строят прямые мапы, обратные, перекрестные… потом ходят по ним безо всяких проверок, ловят нулы и странные результаты.
А решение простое: запиши свое барахло в SQLite! Из коробки получишь индексы, поддержку целостности, универсальный интерфейс, поддержку JSON и миллион расширений. И появился SQLite не вчера, а старше иного разработчика. И тестов под него написано чуть ли не сотня тысяч.
SQLite отлично подходит, чтобы не шатать боевую базу. Забрал данные, сложил себе и делаешь что хочешь. Можно делать ночную выгрузку, чтобы всякие отчеты принимали SQLite, а не ходили в прод.
Если данные не помещаются в памяти, можно скинуть их на диск. Красота же?
Есть и другие рецепты использования SQLite, например использовать его для обмена данными. Каждый, кто сталкивался с сериализацией, знает, какая это боль и сколько в ней подводных камней. Один из способов их обойти – кидаться файлами SQLite.
За счет SQLite можно выкинуть просто нелепое количество кода и самописных решений. Разумеется, не всегда – но по моему опыту, очень-очень часто.
-
О кложурных редьюсерах
В последнее время я немного подавлен работой. Делаю одну задачу уже много месяцев, при этом она довольно тупая и бестолковая. Омрачает то, что в процессе я нахожу баги, порой довольно серьезные. Чувствую себя так, словно заставили идти через поле коровьих лепешек.
Часть багов связана с инфраструктурой и очередями задач. Пересказывать их довольно скучно, и вряд ли вам будут интересно. Но один бажок на тему Кложи и параллельных вычислений мне запомнился, и сейчас его расскажу.
Как вы знаете, Кложа хороша своими коллекциями – это прямо алмаз, одно из немногих утешений в айти. Вместе с коллекциями прилагаются штук триста функций: всякие
map,reduceи так далее, включая экзотику.Начиная с какой-то версии в Кложу завезли параллельные map и reduce. Пакет называется
clоjure.core.reducers. Сигнатуры параллельных функций почти идентичны оригиналам. Идея в том, что ты такой хоп – заменилreduceнаr/reduce, и вычисления раскидались по ядрам. Нашлись коллеги, которые, не читая документации, так и сделали – а я пожал их плоды.Есть огромных файл, где каждая строка – джейсончик. Нужно построить мапу
{id -> entity}, чтобы быстро дергать из нее сущность по айдишке. Скажем, в Питоне это делается так:{entity["id"]: entity for entity in file.read_lines() }В Кложе это тоже решается тремя строчками. Но коллега использовал не простой reduce, а который параллельный. Логика такова: коллекция большая, пусть колбасится параллельно. И вот я вызываю функцию, которая строит эту мапу, и замечаю – в ней нет половины ключей. Пропали. Что такое?
Дебажил я передебажил и выяснил вот что.
В документаци
clоjure.core.reducersнаписано, что коллекции не всегда обрабатываются параллельно. Критериев несколько, например коллекция мала и нет смысла ее делить. Но главный критерий таков: коллекция не должна быть ленивой. А те коллекции, что коллеги читают из файла, ленивы. Это нормально, потому что файл огромный и читать в память все разом нельзя. Но получается, что вся параллельность идет псу под хвост – параллельныеr/mapиr/reduceсводятся к последовательным аналогам.Другими словами, параллельной обработки никогда не было. Вызовы есть, но эффекта нет. Никто этого не замерял и не проверял.
Почему же у меня возникла ошибка? Дело в том, что я передал вектор – не ленивую коллекцию. Она попадала под критерии параллельности, и произошло вот что. Пакет
clоjure.core.reducersиспользует подходForkJoin. На этапеForkколлекция бьется на части, и каждая часть обрабатывается в своем потоке. Получаются, скажем, два словаря{id -> entity}для каждой части. Далее наступает фазаJoin– их нужно объединить. Функцияr/reduceпринимает дополнительную функцию для сборки финального результата, но ее не передали. А если ее нет, вызывается редуцирующая функция. Она приняла два словаря и неправильно их обработала, в результате чего пропала половина данных. Нужно было передать туда функцию merge, но никто не знал, для чего это в принципе.Когда я это исправил, нашел еще один косяк у себя самого. Когда записи индексируют по
id, с объединением проблем нет, потому что ключи уникальны. Но представим, что выполняется группировка по какому-то рейтингу. В этом случае результат такой:{"a" [1 2 3], "b" [5 4 1]}Если дать эту задачу на параллельное вычисление, они могут вернуть что-то такое:
{"a" [1 2], "b" [5]} {"a" [2 3], "c" [11]}То есть и в первом потоке был такой ключ, и во втором. Если тупо объединить словари, получится вот что:
{"a" [2 3], "b" [5], "c" [11]}, то есть из “a” пропадет 1. Ошибки не будет, все пройдет молча, и догнать причину будет сложно. Правильная функция объединения будет такой:
(partial merge-with into)С ней получим результат
{"a" [1 2 3], "b" [5], "c" [11]}.Ради интереса прошелся по коду коллег: почти везде используются ленивые коллекции. Это значит, что параллельные вычисления банально не работают. Я хотел поправить, но плюнул: давайте-ка сами.
В сухом остатке:
-
человек подключил либу для параллельных вычислений
-
вычисления всегда протекали последовательно
-
при попытке вычислить что-то параллельно получали ошибочный результат. Исключения нет, все тихо, ищи сам.
В Гарри Поттере была фраза: “не доверяй тому, что мыслит, если не знаешь, где у него мозги”. Я бы перефразировал: не доверяй быстрым библиотекам, если не понимаешь, за счет чего достигается скорость. Реалии таковы, что в погоне за скоростью срезают углы. Это ни хорошо ни плохо, это факт, который нужно знать.
Внедряя параллельные вычисления, уделите хотя бы толику внимания тому, какие сюрпризы вас ожидают.
-
-
Обновление Ютуба

Гугл грозился обновить плеер в Ютубе, и вот это случилось. До этого новый интерфейс появлялся у меня пару раз, но быстро пропадал. Подозреваю, его откатывали из-за ошибок. Но теперь все исправили, и он с нами надолго.
Напомню, в чем проблема. Новая панель почти в два раза выше, чем прежде. Приложил к экрану линейку: была сантиметр, стала 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 мегабайтов (с учетом сжатия).
Теперь я остался без трассировщика. Только логи, только хардкор.
Что со всем этим делать — я не знаю. Видимо, остается только клауд-дривен-девелопить, чтобы в будущем про меня сказали: этот облачный чудак даже тестов не написал.