-
Как наполнить базу сгенерированными джейсонами
Предположим, у вас Postgres с миллионами JSONb-документов. Вы хотите проверить нагрузку на стейджинге, но данные с прода брать нельзя – их нужно сгенерить. Тут начинаются проблемы.
Размножить один и тот же джейсон будет неправильным, потому что все они дадут одинаковое значение индекса. В итоге индекс будет “перекошен”: миллион записей попадут в один блок, а остальные сто тысяч будут равномерны. В реальности так не бывает.
Второй вопрос – как генерить. Можно взять язык, на котором вы пишете, и наколбасить случайные словари в CSV. Потом сжать в gzip, перетащить на сервер с базой, распаковать и вставить через COPY IN. Но придется писать код и воевать с передачей файлов по SCP/SSH.
Я склоняюсь к вот такому костылику.
Берем с прода любой JSON, записываем в локальный файл с красивым форматированием. Например:
{ "meta": { "eyes": "brown" }, "attrs": { "email": "petr@test.com", "name": "Petr Ivanov" }, "roles": [ "user", "admin" ] }
Здесь он маленький для экономии места, а на проде может быть пять экранов. После этого пропускам файл через серию регулярок:
cat sample.json | sed \ -e "s/\"/'/g" -e 's/: /, /g' \ -e 's/{/jsonb_build_object(/g' \ -e 's/}/\)/g' \ -e 's/\[/jsonb_build_array(/g' \ -e 's/\]/)/g'
Получается SQL-код, который строит тот же самый JSON:
jsonb_build_object( 'meta', jsonb_build_object( 'eyes', 'brown' ), 'attrs', jsonb_build_object( 'email', 'petr@test.com', 'name', 'Petr Ivanov' ), 'roles', jsonb_build_array( 'user', 'admin' ) )
Наберите в консоли
SELECT
и вставьте эту колбасу. База выплюнет в точности тот JSON, который был в файле.Если форматировать JSON лень, добавьте в пайп утилиту
jq
— по умолчанию она просто форматирует документ.Это был только один джейсон. Теперь размножим его с помощью
select ... from generate_series...
select jsonb_build_object( 'meta', jsonb_build_object( 'eyes', 'brown' ), 'attrs', jsonb_build_object( 'email', 'petr@test.com', 'name', 'Petr Ivanov' ), 'roles', jsonb_build_array( 'user', 'admin' ) ) as document from generate_series(1, 5) as x;
Выборка вернет столько документов, сколько чисел в диапазоне
generate_series
. Но это один и тот же документ, что не подходит. Пройдитесь по значимым полям документа и замените статичные строки на форматирование, например так (переменнаяx
ссылается на текущее значениеgenerate_series
):select jsonb_build_object( 'meta', jsonb_build_object( 'eyes', format('color-%s', x) ), 'attrs', jsonb_build_object( 'email', format('user-%s@test.com', x), 'name', format('Test Name %s', x) ), 'roles', jsonb_build_array( 'user', 'admin' ) ) as document from generate_series(1, 5) as x;
Новая выборка станет такой:
document ------------------------------------------------------------------------------------------------------------------------- {"meta": {"eyes": "color-1"}, "attrs": {"name": "Test Name 1", "email": "user-1@test.com"}, "roles": ["user", "admin"]} {"meta": {"eyes": "color-2"}, "attrs": {"name": "Test Name 2", "email": "user-2@test.com"}, "roles": ["user", "admin"]} {"meta": {"eyes": "color-3"}, "attrs": {"name": "Test Name 3", "email": "user-3@test.com"}, "roles": ["user", "admin"]} {"meta": {"eyes": "color-4"}, "attrs": {"name": "Test Name 4", "email": "user-4@test.com"}, "roles": ["user", "admin"]} {"meta": {"eyes": "color-5"}, "attrs": {"name": "Test Name 5", "email": "user-5@test.com"}, "roles": ["user", "admin"]} (5 rows)
Вставим выборку в таблицу документов:
create table documents(id uuid, document jsonb)
Для этого допишем в запрос шапку
INSERT
:insert into documents (id, document) select gen_random_uuid() as id, jsonb_build_object( '__generated__', true, 'meta', jsonb_build_object( 'eyes', format('color-%s', x) ), 'attrs', jsonb_build_object( 'email', format('user-%s@test.com', x), 'name', format('Test Name %s', x) ), 'roles', jsonb_build_array( 'user', 'admin' ) ) as document from generate_series(1, 500) as x returning id;
Обратите внимание на поле
__generated__
: я добавил его, чтобы отличить обычный документ от сгенерированного.Запустите запрос, и в таблице окажется миллион случайных документов. Вставка может занять до пары минут, потому что JSONb — дорогое удовольствие.
Если что-то пошло не так, удалите сгенерированные документы запросом:
delete from documents where document -> '__generated__' = 'true'::jsonb;
Удобство в том, что не нужны питоны-джавы, все делается силами SQL. Не придется генерировать CSV и перетаскивать на сервер. Просто выполнили запрос — и пошли дальше.
Обязательно сохраните скрипт в недрах проекта. Он понадобится если не завтра, то через месяц, а если не вам, то коллеге. И вы такой раз — как Джонни Старк на картинке.
-
Помешательство
Может быть, писал про это раньше, лень искать.
Допустим, у нас современное веб-приложение: на сервере только REST API, а рендер силами JavaScript. Одна из апишек отдает сведения о покупках: число, сумму и дату последней покупки. Что-то вроде такого:
{ "total_orders": 7, "total_sum": "523626", "last_order_date": "2023-12-23T12:23:55Z" }
(сумма в копейках, если что)
Ожидается, что на клиенте мы покажем эту информацию так:
Вы совершили семь покупок на сумму 5.236 рублей 26 копеек. Последний раз вы покупали 23 декабря, 20 дней назад.
Чтобы отрендерить этот блок, нам понадобятся:
- библиотека “число прописью”, чтобы 7 стало “семь”;
- склонение с учетом числительных: 1 покупка, 3 покупки, 5 покупок;
- форматирование суммы;
- форматирование дат;
- вычитание дат (20 дней назад);
- возможно, мультиязычные шаблоны, если в приложении несколько языков.
И думаю: есть же люди, которые всерьез делают это на JavaScript(!) в браузере(!!). Подключают тонны библиотек из npm, пишут экраны кода, компилируют мегабайтные бандлы. Запускают всю эту машинерию, кодят месяц и в итоге получают что-то похожее на результат. Который, конечно, работает только в Хроме на 4К-мониторе. На мобиле обрежется, в Фаерфоксе разъедется, в Сафари будет белый экран.
Что движет этими людьми, интересно? И кто за них отвечает? Что за извращенное удовольствие: из всех вариантов выбрать самый хрупкий и тяжелый?
Похоже на массовое помешательство.
-
Прощай, Goo.gl!
Новость о том, что Гугл отключает сокращалку goo.gl, вызвала вьетнамские флешбеки.
Пятнадцать лет назад я был ее активным пользователем. Я тогда жил в Чите и работал в славной компании “Читаэнергосбыт”. Параллельно я пытался сделать какой-нибудь стартап. А поскольку ничего придумать не мог, все поделки сводились к перекладыванию данных: из RSS в Твиттер, из одной соцсети в другую и так далее.
В те времена у Твиттера не было сокращалки урлов. Вставил в сообщение ссылку — и прощай 40-50 символов. Сегодня это звучит дико, но тем не менее. Это был золотой век всяких tr.im, bit.ly и goo.gl — пока у каждой соцести не появились внутренние сокращалки.
Пока я тестировал свои поделки, сократил около 15 тысяч урлов. Я даже написал библиотеку-клиент, и это был, наверное, мой первый опенсорс.
Так вот, возвращаясь к закрытию сокращалки. Если бы я услышал эту новость хотя бы год назад, я бы вознегодовал и написал гневный пост: как так, денег и поддержки не просит, зачем закрывать? А сегодня мне безразлично.
Во-первых, технически забрать данные об урлах проще простого. Не нужные никакие экспорты, достаточно баш-скрипта с курлом и флажком “don’t follow redirects”. Если у вас на сайте есть гугловые ссылки, заменить их нормальными — дело часа. Это технический аспект.
Второй аспект — моральный. Сокращалки урлов устарели и считаются моветоном. Ими пользуются мошенники, СЕО-дрочеры и всякий озабоченный люд, которым важно, сколько раз нажали ссылку, вместо того, что было за ней. Если я вижу сокращенную ссылку, я никогда не нажму на нее, и советую вам то же самое.
У сокращалок урлов был короткий период расцвета. Тогда еще не знали последствий, и сокращать урлы считалось крутым и молодежным. Были плагины для форумов и вордпрессов, которые сокращали все ссылки и подкачивали в админку стату переходов. Но все это в прошлом: у крупных игроков свои методы отслеживания ссылок, а мелким игрокам пользы от сокращалок нет.
Третий аспект касается денег. Обязательно найдется тот, кто скажет: у Гугла денег куры не клюют, пусть поддерживают. Дело в том, что это противоречит миссии Гугла. Его цель — зарабатывать деньги. Это ясно уже лет десять, и почему сокращалка урлов, не приносящая денег, должна быть исключением? Она не приносит денег, поэтому ее нужно закрыть.
Ситуация, когда какой-нибудь менеждер толкает речь на совете директоров о том, что мы должны поддерживать пользователей, потому что обещали им… и все такие: да, что же с нами стало, давайте оставим, только бы не сделать пользователям плохо… Представили картину? Так не бывает. Это возможно в фильме, но не в реальности.
На эту тему советую почитать “Цель 2” Голдрата, причем не обязательно целиком, а хотя бы первую главу. Там как раз заседание директоров, и акулы рвут на части благие намерения. Почему — хорошо описано в диалоге с одной из акул во второй главе.
Так что все своим чередом. Конфето-букетные отношения между пользователями и Гуглом закончились, остался один расчет. Гугл закрыл еще один сервис. Эпоха ушла, жизнь продолжается.
-
Ввод денег
Интересно, бывают ли безглючные виджеты для ввода денег? Например, чтобы ввести тысячу сорок три рубля и тринадцать копеек — при этом так, чтобы не помянуть разработчика и его родню.
По умолчанию в поле стоит ноль, и запросто бывает так, что курсор падает перед ним. В результате цифра умножается на десять. Что-то вроде такого:
|0 123|0
Ни один виджет не работает со вставкой. Разработчик думает, что клиент сел — и такой ввел шесть цифр по памяти. На практике люди копируют цифры изо всяких экселей и платежек. Везде беда с десятичным разделителем, пробелами и позицией курсора.
Зачем эти потуги, если результат все равно бажный?
Интересное решение я видел у Пейпала. У них ввод суммы сделан как в старых терминалах. Нужно ввести сумму с точностью до цента, при этом цифры наползают справа налево. Постарался изобразить это табличкой:
state input result 0.00 1 0.01 0.01 2 0.12 0.12 3 1.23 1.23 4 12.34 Все жестко, никаких вариантов. Не уверен, что такой ввод подойдет нам, потому что у нас нет таких терминалов. Но взять на заметку стоит: меньше вариативности — больше надежности.
PS: не привожу скришноты, потому что не вижу смысла. Любой банк, любое приложение. Виджеты денег не работают нигде.
-
И ещё об AWS SDK
Последняя заметка об AWS SDK и все — закрываю тему, чтобы не утомлять.
Почему я взъелся на этот SDK? Потому что в мире Кложи это своего рода проклятье. Нужно работать с сервисами Амазона и казалось бы — бери Джавный SDK и пиши обертку. Но библиотеки Амазона ужасны: нужно 25 пакетов ради HTTP-запроса, а классы написаны по отвратительному паттерну. Вдобавок это добро не компилируется Граалем — а у нас все на нем. В результате у нас самописные клиенты к S3, Dynamo, CloudWatch и десятку других сервисов.
Меня это совсем не радует, потому что клиенты реализованы частично. Если нужна новая апишка, то открывай доку и пиши код. Самая жесть с сигнатурами. За короткое время я нашел два бага в библиотеке aws-sign, которая подписывает запрос. Отлаживать сигнатуры — такая грусть и боль, что и не хочется вспоминать.
Узнав про SDK 2.0, я подумал, что пора перевести наш зоопарк на официальные библиотеки AWS. Пусть их поддерживают другие люди, а нам не придется кувыркаться с сигнатурами и прочим. Быстрый гуглеж показал, что SDK 2.0 дружит с Граалем, и я засел.
Написал обертку, компилирую — не работает. Полез в интернет и выяснилось, что… даже руки опускаются от отчаяния. Релиз, который поддерживает GraalVM и компиляцию native-image, был год назад. За это время код поменяли, а нового релиза с Граалем не делали.
Может быть, со стороны это звучит непоятно, но я поражен. Либо ты поддерживаешь GraalVM, либо нет. Он накладывает ограничения на Джава-код, поэтому важно принять решение и следовать ему. А у ребят из Амазона получились релизы Шредингера: этот поддерживает, тот нет, а следующий, возможно, будет снова поддерживать.
Технически это лечится дополнительным шагом в CI, где текущая ветка компилируется Граалем, и если что-то не так — изменения не принимаются. Новый шаг добавляется в CI за пару часов без преувеличений. И только после этого можно писать в readme: да, мы поддерживаем Грааль. А не так — один раз получилось, значит, получится всегда.
Мысль о стажерах, которых сажают писать SDK, пока нет другой работы, крепнет во мне все сильнее.
-
SDK, работа над ошибками
Давайте поможем инженерам из Amazon с их SDK 2.0. Разберем наиболее серьезную ошибку – частичную инициализацию объекта.
В прошлой заметке я упоминал, как создаются объекты в SDK. Если коротко, у классов скрыты конструкторы, и нужно пользоваться билдером. Получается что-то вроде такого:
GetObjectRequest request = GetObjectRequest.builder() .bucket("acme-releases") .key("path/to/file.txt") .ifEtagMatches("....") .ifModifiedSince("...") .build()
Билдер не знает, какие из параметров обязательны, а какие нет. В примере выше код работает. Если убрать вызов .bucket или .key, все скомпилируется, но при запуске получим:
Execution error (IllegalArgumentException) at ....xml.internal.marshall.SimpleTypePathMarshaller Parameter 'Bucket' must not be null
Обратите внимание, что ошибка приехала из какого-то XML-маршаллера, хотя никакого XML и тем более маршаллизации здесь нет. Тело пустое, и просто составляется URL.
Подчеркну: программистам из Амазона вполне ОК, что объект инициирован частично. Спрашивается, что можно сделать с объектом
GetObjectRequest
, если у него не заполнен бакет? Ничего. Зачем тогда позволять такую ситуацию?Как они вообще представляют работу со своим SDK? Пользователь садится и перебором проверяет, какие поля нужны, а какие нет? Ладно я знаю, что бакет и ключ необходимы, но ведь кто-то не знает. И узнает он только когда бахнет прод.
Проблема решается просто. Все поля объекта делятся на обязательные и нет – по аналогии с аргументами
args
иkwargs
в Питоне. Обязательные поля потому так и называются, что без них невозможно дернуть конструктор или порождающий статичный метод. В нашем случае обязательны бакет и ключ. С ними код становится таким:GetObjectRequest request = new GetObjectRequest( "acme-releases", "path/to/file" ).withEtag("...") .withModifiedSince("...")
либо то же самое с билдером:
GetObjectRequest request = GetObjectRequest.builder( "acme-releases", "path/to/file") .etag("...") .modifiedSince("...") .build()
То есть хоть разбейся в лепешку, но обязательные параметры передай.
Ну? Что мешало так сделать? Здесь даже паттерны сохранены, чтобы не пострадало чувство прекрасного.
Заметим, что я не говорю отсебятину: все это сказано в книге Effective Java авторства Джошуа Блоха. Он так и пишет: используйте неизменяемые объекты, не допускайте частичной инициализации, требуйте обязательные поля сразу – не надейтесь, что кто-то заполнит из позже. Кумир джавистов говорит, как делать правильно. Почему в Амазоне решили, что сами с усами?
Впрочем, пока я писал это, подумал – может, все гораздо проще? Может быть, за SDK сажают мидлов и стажеров, пока они без задач? Скажем, наняли стажера, а у тимлида релиз, погружать человека некогда, поэтому его сажают за SDK. Вполне похоже на правду.
У нас так было в Датаарте: пока человек без работы, его сажали за всякий внутренний хлам. Форму заказа пиццы, аукцион парковочных мест, каталог сотрудников. Все это было крайне низкого качества, потому что шло через десятки джунов и мидлов без какого-либо контроля качества.
Может и в Амазоне такой же порядок? Кто знает, расскажите. Потому что чем иначе объяснить такое качество SDK – я не знаю.
-
SOLID
На мой взгляд, у SOLID есть важное и полезное применение — унижать людей на собесах. Очень помогает в найме, когда на должность претендует сто человек, а нам не нужны неудачники. Уверен, в Амазон брали только тех, кто этот SOLID знает. Результат налицо.
О других преимуществах SOLID мне неизвестно.
-
Паттерны
Одна из худших вещей в айти – это паттерны. Когда я слышу “паттерн”, меня трясет. Хочется, чтобы паттернов было как можно меньше, а может быть, однажды мы доживем до счастливого дня, когда в программировании не будет паттернов.
Откуда такой радикализм? Дело в том, что паттерн – это костыль. Помните картинку с жуком, где слева баг, а справа фича? То же самое с паттерном. Это прокол языка, который решили подпереть костылем.
Builder, Visitor, Singleton – все это ошибки в дизайне языка. Чемпионами по их количеству являются C++ и Java. Вряд ли кто-то в здравом уме назовет их хорошо спроектированными языками.
Builder – прямое следствие того, что нет параметров по умолчанию. Visitor – цена за убогую систему типов и ООП в целом. Singleton вообще рак мозга – кто-то решил, что объект нельзя создать дважды, хотя внятного объяснения этому нет.
Если взять любую книгу по Джаве, половина ее будет о том, как делать не надо и какой паттерн брать взамен. Не лучше ли убрать из языка то, чего делать не надо? Я понимаю, легаси и все такое, но все же.
Книги Банды Четырех и талмуды толщиной с руку, где учат паттернам, наводят гнетущее впечатление. Столько труда потрачено на костыли, чтобы обойти ошибки языка!
Паттерн – это о том, как сделать удобней компилятору или IDE. Ни слова о том, чтобы сделать удобно потребителю кода.
Беда паттернов в том, что они плохо влияют на программиста. Он буквально тупеет, не понимая, что по-прежнему пишет плохой код, просто теперь можно сослаться на книги или громкие имена. Мышление паттернами – это натуральная деградация. Есть надежда, если Джава-программист поддерживает форму, ковыряясь с Лиспом или Хаскелем. Но если он видит только паттерны с десяти до шести, это путь к деградации и больше никуда.
Пример из реальной жизни – Amazon AWS SDK для Джавы. Казалось бы: самое популярное облако, самый популярный язык, самые сильные программисты. Почему же они пишут абсолютное говно, которым нельзя пользоваться? Потому что в голове паттерны, а не задача сделать удобно.
Если вы не знаете, все пакеты SDK V1 следуют паттерну POJO. Это когда каждый класс SDK имеет нулевой конструктор и пачку геттеров и сеттеров. Например, какой-нибудь GetObjectRequest создается так:
GetObjectRequest request = new GetObjectRequest(); request.setBucket("some_bucket") request.setKey("path/to/file.txt") request.setIfModifiedSince(...)
И таких классов тысячи – в буквальном смысле. Нигде не сказано, какой из сеттеров является обязательным. Можно запросто убрать вызов
setBucket
, и запрос будет без бакета. При запуске кода вы получите исключение в рантайме.Паттерн есть? Есть. Удобно? Нет. Вы же не будете гонять код вручную на S3 каждый раз, когда меняете какой-то сеттер. Поэтому все сводится к тому, чтобы поправить код, прогнать CI и задеплоить на тестовое окружение, чтобы дернуть там и убедиться, что работает. Займет час. Умные ребята поднимают для этого Докер с локальным min.io, но так бывает не везде.
Написав тысячи битых классов, инженеры Амазона почесали голову и сели за AWS SDK V2.0. На этот раз с другим паттерном – Builder. Теперь каждый класс создается цепочкой:
GetObjectRequest request = GetObjectRequest.builder() .bucket("some_bucket") .key("path/to/file.txt") .ifModifiedSince(...) .build();
Казалось бы, удобно, современно, все как у Джошуа Блоха. Однако я по-прежнему не вижу, какие параметры обязательны. Легко пропустить метод .bucket и получить объект, который не знает, в какой бакет обращаться.
А вот реальный пример с тегом. Тег – это объект, который хранит пару ключ-значение. Других полей у него нет. Казалось бы, сделай конструктор с двумя полями, чтобы было так:
Tag tag = new Tag("release", "test-123.4");
Но нельзя, у нас же паттерн! Нужен билдер и методы для каждого поля. В результате имеем:
Tag tag = Tag.builder().value("42").build(); #object[....s3.model.Tag ... "Tag(Value=42)"]
Смотрю и не понимаю – как так? Почему позволено иметь тег, в котором нет имени поля?! Оно же Null и в будущем свалится с ошибкой. Как вообще можно создать тег без имени и значения? Кто был тот тупица, который все это дизайнил? Кто нанял клоунов в Амазон за 250 тысяч в год, чтобы они писали подобный код?
Они наколбасили сто новых пакетов и классов с новым паттерном. Но как было невозможно пользоваться, так и осталось. Как так получилось?
Понятно, это вопросы в никуда. Программисты пишут SDK по паттерну – как сказал знакомый джавист, “ходят строем”. Главное, чтобы строй шел ровно, а куда он придет – и надо ли было идти – мало кто задается вопросом.
Пожелаю читателю использовать паттерны как можно меньше. Если вы слышите “паттерн” – это тревожный знак.
-
Clojure AntiPatterns: the with-retry macro
Most of clojurians write good things about Clojure only. I decided to start sharing techniques and patterns that I consider bad practices. We still have plenty of them in Clojure projects, unfortunately.
My first candidate is widely used, casual macro called
with-retry
:(defmacro with-retry [[attempts timeout] & body] `(loop [n# ~attempts] (let [[e# result#] (try [nil (do ~@body)] (catch Throwable e# [e# nil]))] (cond (nil? e#) result# (> n# 0) (do (Thread/sleep ~timeout) (recur (dec n#))) :else (throw (new Exception "all attempts exhausted" e#))))))
This is a very basic implementation. It catches all possible exceptions, has a strict number of attempts, and the constant delay time. Typical usage:
(with-retry [3 2000] (get-file-from-network "/path/to/file.txt"))
Should network blink, most likely you’ll get a file anyway.
Clojure people who don’t like macros write a function like this:
(defn with-retry [[attempts timeout] func] (loop [n attempts] (let [[e result] (try [nil (func)] (catch Throwable e [e nil]))] (cond (nil? e) result (> n 0) (do (Thread/sleep timeout) (recur (dec n))) :else (throw (new Exception "all attempts exhausted" e))))))
It acts the same but accepts not arbitrary code but a function. A form can be easily turned into a function by putting a sharp sign in front of it. After all, it looks almost the same:
(with-retry [3 2000] #(get-file-from-network "/path/to/file.txt"))
Although it is considered being a good practice, here is the outcome of using it in production.
Practice proves that, even if you wrap something into that macro, you cannot recover from a failure anyway. Imagine you’re downloading a file from S3 and pass wrong credentials. You cannot recover no matter how many times you retry. Wrong creds remain wrong forever. Now there is a missing file: again, no matter how hard you retry, it’s all in vain and you only waste resources. Should you put a file into S3, and submit wrong headers, it’s the same. If your network is misconfigured or some resources are blocked, or you have no permissions, it’s the same again: no matter how long have you been trying, it’s useless.
There might be dozens of reasons when your request fails, and there is no way to recover. Instead of invoking a resource again and again, you must investigate what went wrong.
There might be some rare cases which are worth retrying though. One of them is an
IOException
caused by a network blink. But in fact, modern HTTP clients already handle it for you. If youGET
a resource and receive anIOException
, most likely your client has already done three attempts silently with growing timeouts. By wrapping the callwith-retry
, you perform 9 attempts or so under the hood.Another case might be 429 error code which stands for rate limitation on the server side. Personally I don’t think that a slight delay may help. Most likely you need to bump the limits, rotate API keys and so on but not
Thread.sleep
in the middle of code.I’ve seen terrible usage of
with-retry
macro across various projects. One developer specified 10 attempts with 10 seconds timeout to reach a remote API for sure. But he was calling the wrong API handler in fact.Another developer put two nested
with-macro
forms. They belonged to different functions and thus could not be visible at once. I’m reproducing a simplified version:(with-retry [4 1000] (do-this ...) (do-that ...) (with-retry [3 2000] (do-something-else...)))
According to math, 4 times 3 is 12. When the
(do-something-else)
function failed, the whole top-level block started again. It led to 12 executions in total with terrible side effects and logs which I could not investigate.One more case: a developer wrapped a chunk of logic that inserted something into the database. He messed up with foreign keys so the records could not be stored. Postgres replied with an error “foreign key constraint violation” yet the macro tried to store them three times before failing completely. Three broken SQL invocations… for what? Why?
So. Whenever you use
with-retry
, most likely it’s a bad sign. Most often you cannot recover from a failure no matter if you add two numbers, upload a file, or write into a database. You should only retry in certain situations likeIOException
or rate limiting. But even those cases are questionable and might be mitigated with no retrying.Next time you’re going to cover a block of logic
with-retry
, think hard if you really need to retry. Will it really help in case of wrong creds, a missing file, incorrect signature or similar things? Perhaps not. Thus, don’t retry in vain. Just fail and write detailed logs. Then find the real problem, fix it and let it never happen again. -
Глобальное потепление
Уже год я преподаю английский: одному человеку и бесплатно. Первые полгода занимались по моей программе, которую я составил по книге Мерфи. Затем перешли на учебник Language Leader, по которому я учился когда-то сам.
Он хорошо составлен: он интересен взрослому, потому что авторы добавили много фактов из реальной жизни. Из него буквально фонит то, что было в мире на момент его составления. В том числе поэтому проходить некоторые темы забавно.
Одна из таких тем — погода. Лет 10-15 назад весь мир стоял на ушах из-за так называемого “глобального потепления”. Ему приписывались любые природные явления: шторм, торнадо — потепление, озоновая дыра — потепление, ядовитые испарения в тундре — потепление.
Забавно, как в учебнике повторяется то же самое. Упражнения самых разных форматов — текст, диалоги, грамматика, — и везде потепление, потепление, потепление. Других причин не бывает. А если и есть причина, то какая у нее первопричина? Угадайте.
Поэтому я воспринимаю учебник как исторический документ. Читая его, видно, как штырило людей на эту тему в прошлом. Гринпис и прочие радикальные группировки штурмовали платформы Шелл, а их отгоняли водометами. Население “прикладывало линейку” — если сегодня потеплело на градус, а завтра на два, то через месяц потеплеет на тридцать.
Сегодня, когда глобальное потепление уже выдохлось, повестка изменилась. Теперь у нас “борьба с изменением климата”. Это более гибкая конструкция, потому что нигде не говориться, как долго нужно бороться. Горизонт работ заложен уже в постановке задачи.
Грета куда-то пропала, а у меня смешной стикер-пак с ней. Пропадает без дела.
Хороших выходных!