-
Время в Interstellar
Вы же смотрели Интерстеллар? Помните высадку на планету с волной? А музыку в фоне, такую тревожную, помните? Тик-так, тик-так по нарастающей?
Этот тик-так — неспроста. Каждый тик равен 1.37 секунды, и за это время на Земле проходят сутки. Именно такое соотношение времени между Землей и той планетой из-за близости к черной дыре. Ганс Циммер гений.
Когда знаешь об этом, в сцену добавляется новый оттенок. Теперь уже не смотришь на метания за разбитым кораблем: слушаешь тик-так и представляешь, как впустую проходит чья-то жизнь — твоя, например.
Иногда по вечерам, вытряхнув себя из-за компа, я чувствую что-то похожее. Словно жизнь — это фильм, а в фоне кто-то цокает языком. Исправляешь чей-то быдлокод и косяки, потом идешь домой и думаешь — что я вообще делал?
Нечто похожее встретилось в дневниках Корнея Чуковского времен блокады Ленинграда: “дни сгорают как бумажные”. Потому он и великий писатель, что не уходят, не летят, а именно сгорают.
Впрочем, потом меня отпускает, и я снова иду править чужой код и косяки. И так постоянно.
-
Список через запятую
Одна из самых дурацких вещей в айти – это список через запятую, например:
(1, 2, 3) ["test", "foo", "hello"] [{:id 1}, {:id 2}, {:id 3}]
Каждый, кто работал с таким форматом, знает, какой геморрой учитывать запятые. Элементы нельзя просто записать в цикле. Нужно собрать их в массив, а потом join-ить запятой. Это сводит на нет стриминг элементов, когда их много. А чтобы работал стриминг, нужно завести флажок “запятая уже была”, выставить его в первый раз и постоянно проверять: была или не была?
То есть на каждом шаге из миллиона нужно делать эту проверку, которая сработала один раз. Из-за какой-то никчемной запятой.
Какие проблемы возникнут, если запятые убрать?
(1 2 3) ["test" "foo" "hello"] [{:id 1} {:id 2} {:id 3}]
Не вижу причины, по которой любой из списков не может быть распаршен. Все три читаются и подлежат парсингу на ура.
В одном скобочном языке запятые вообще считаются пробелами: они уничтожаются парсером, словно их нет. И это никак не влияет работу: все читается, и даже лучше: меньше визуального шума.
Список через запятую – рудимент, от которого пора избавиться. Случись вам изобретать свой формат данных – откажитесь от запятых как значимых символов.
-
Выпадашка в Хроме
На скриншоте — типичная ситуация наших дней. По клику на аватару появляется выпадашка, но содержимое не вмещается, и у выпадашки появляется прокрутка.
Окно браузера растянуто максимально, ничего не сжато. Зум нулевой. Никаких вредоносных действий с моей стороны. Просто дизайнеру “не хватило” места. Еще бы: если обернуть каждый элемент в паддинг, скруглить углы, добавить отступы где только можно, поместить всю фигуру в другой паддинг, сместить вниз — откуда ж возьмется место?
На полном серьезе спрашиваю: что происходит с фронтендерами? Может у них пост-ковидный синдром? Вакцина дала побочку? Иначе это не объяснить. Дай фронтендеру экран размером со стену — и он поместит все нужное в выпадашку, которая появится по клику на гамбургер. Внутри все “воздушное”, не хватает места, и появляется прокрутка.
Раньше можно было понять: разрешение 800 на 600, кривой IE6, умирающий Netscape, ранние оперы и фейерфоксы. У каждого багов — как блох на жучке. Но сегодня-то что? Везде ретины, 4К, только Хром и его поделки. Что мешает делать нормально: без выпадашек с прокрутками?
Какая-то загадка.
-
Датомик
Лет семь назад я увлекся Датомиком. Кто не знает, это база данных, написанная на Сlojure и Java. Среди ее плюсов – неизменяемость (данные только накапливаются), независимость от времени (можно вернуться в прошлое) и выразительный язык запросов Datalog, взятый из Пролога.
Я долго ходил вокруг да около, а потом повезло: семья уехала на неделю, и я провел это время, читая доки и экспериментируя. У меня тогда был пет-проект на Postgres, и я перевел его на Датомик. Позже я использовал его в других проектах, в том числе в Хайлоад-капах от Mail.ru. Я написал статью про миграцию с Постгреса на Датомик, и она даже попала на главную Хакер-Ньюз.
У Датомика есть важное свойство: он красивый. Бросил взгляд, и сразу мысль – да, круто. Это изящная абстракция, воздвигнутая на элементарных вещах. Мало проектов, где эти свойства – изящность и простота – выражены столь же ярко.
Я много играл с Датомиком и даже пытался реализовать его поверх реляционных баз. Кое-что мне удалось, но поделки я так и не довел до ума. Практика показала, что скучные Postgres/Maria удобней в работе.
Прежде всего, неизменяемость из коробки не нужна. Там, где нужно хранить историю, Postgres/Maria справляются за счет триггеров или запросов вида
INSERT ... FROM UPDATE/INSERT/DELETE
. Когда говорят об исторических данных, у меня сразу вопрос – как вы ими пользуетесь? Они вообще вам нужны?Далее: страшные буквы GDPR. Если пользователь хочет удалить свои данные, с Датомиком будут проблемы. В Датомике атрибуты не меняются, а добавляются новые с поздним временем. Поэтому, читая один и тот же атрибут в разное время, получим разные значения. Но GDPR требует, чтобы в базе физически не было личных данных. Если вы записали в базу атрибут
(42, :user/name "XXXXXX")
, то старый атрибут(42, :user/name "Ivan")
остался, и прочитать его – дело техники.В Датомике есть функция физической очистки атрибутов, но она дорогая и выполняется во время обслуживания, то есть требует остановки прода.
Можно отключить историю атрибутов, но тогда вопрос – зачем вообще история, которой так гордится Датомик?
Кто-то скажет: ладно, пусть имя пользователя будет без истории, а остальное с историей. Практика показывает, что GDPR требует чистки чуть ли не всех таблиц. Когда мы удаляли пользователей из приложения с короткими видео, то пришлось шерстить десятки таблиц: профили, лайки, коменты, друзья, подписчики, обращения, сообщения… это были недели ада. Если представить, что у каждой таблицы история, ситуация станет еще хуже.
У Датомика никакой поиск, нет сортировки и пагинации. Как с этим жить – решать вам. Кого ни спрашивал – каждый пилит свои костыли, каждый случай – свое маленькое приключение.
Есть еще кое-что: Датомик, при всей своей красоте, нарушает доменную область. Я как-то говорил о том, что главное свойство домена – его ортогональность другим доменам. Другими словами, у базы и кода на Сlojure разные зоны ответственности. Датомик стирает эту границу: он превращает базу в хранилище, к которой только он имеет доступ. Этому есть объяснение, поскольку физически данные хранятся как бинарные дампы с кусками индексов, и работать с ними умеет только Датомик.
На практике это выливается в то, что если я хочу поправить записи в Датомике, нужно писать код на Кложе, который выгребает данные, исправляет и записывает в базу.
Это резко контрастирует с Postgres/Maria, которые предлагают свои языки и инструменты для работы с данными. Это и есть домен, когда я могу исправить данные, не обращаясь к Кложе. Бывает, я сижу в psql днями и неделями, манипулируя данными на чистом SQL.
Датомик нарушает это правило. Да, у него есть веб-консоль, но по сравнению с psql она крайне уныла, а до программ уровня PGAdmin или DBeaver ей как до луны.
Датомик как мороженное: он красивый, но позже липнет и затекает туда, где ему не следует быть. Поэтому я прекратил с ним шашни и продолжаю работать с Постгресом. И кстати, Постгрес в последние годы просто летит в космос, нарадоваться не могу.
Выбирая Датомик, имейте в виду вышесказанное.
-
Кража дизайна
Иногда я слушаю дизайнера Женю Арутюнова, он говорит клевые вещи. Говорит мягко и с самоиронией. Не бывает так, что слушаешь тезис, а потом: кто не со мной, тот мудак.
У Жени как-то спросили, как не красть чужой дизайн. Он ответил: спокойно воруй, потому что даже если не будет последствий, ты увидишь, что чужой дизайн не решает задачу полностью. Чтобы решал, нужно поправить здесь, поправить тут, и в итоге краденый дизайн либо развалится, либо изменится так, что перестанет быть краденым.
То же самое можно сказать про код. Даже если вы украли чей-то код, заставить работать его на вас трудно. Адаптация кода под задачу займет столько времени, что проще написать свой. А если выигрыш и возможен, его трудно оценить.
Иные проекты тянут сотни зависимостей — их написали другие люди. И все-таки мы пишем и отлаживаем свой код в папке src. Есть даже похожие сервисы и бизнесы, и некоторые с открытым кодом. Но нет — не смотря на колоссальные объемы открытого кода, нас продолжают нанимать. Мы пишем код, ловим баги, сидим в отладчике.
Именно поэтому когда случилась колоссальная утечка Яндекса — 50 гигабайтов исходников — я даже ухом не повел. Их обсасывали на всех новостных ресурсах, но скажите: что вы хотели там найти? Обычный корпоративный код: прочитать JSON, проверить его, положить в базу, дернуть очередь, записать в лог, собрать эксепшены в сборщик ошибок.
По той же причине я скептичен к коду, написанному ИИ. Пусть он пишет тетрис и змейку, этого добра на Гитхабе пруд пруди. Как мне поможет ИИ, если нужно впендюрить очередной if цепочку бизнес-процессов, чтобы ничего не упало? Как он придумает новый твиттер? Как он придумает игру, где участники отрывают жопы от стула(!) и идут в парк ловить виртуальных зверей?
Идея первична, лишь затем следует код.
Вот почему, имея горы открытого кода, мы, словно герой Никулина, ищем “такой же, но с перламутровыми пуговицами”. Потому что требования. Потому что это наша работа.
-
Телефонный спам
Ситуация с телефонным спамом печальная. Звонки поступают часто, и самое главное — их качество растет феноменально. Живые люди уже давно не звонят, вместо них на проводе “интеллектуальные помощники” — боты с элементами ИИ.
Иногда я слушаю, что скажет тот или иной “помощник”, чтобы быть в курсе технологий. Повторюсь, качество просто запредельное. Хорошо подобраны фоновый шум, интонация, неточности разговорной речи. Бот определяет, когда его пытаются перебить и вежливо просит дослушать до конца, а потом обсудить вопросы. Дерево сценариев огромно, продумано много случаев.
Не сомневаюсь, что многие люди говорят с ботом как с живым человеком. Не зная заранее, что звонки автоматизированы, легко принять бота за чистую монету.
По-настоящему раздражает несколько вещей. Первая — именование спама. Подобно тому, как взрыв называют “хлопком”, а пожар — “задымлением”, спамерские услуги называют “информированием” населения, а ботов — “интеллектуальными помощниками”. Такая манипуляция вызывает тошноту.
Вторая вещь — полный беспредел со звонками при помощи ПО. Еще можно понять, когда нанимают студентов в колл-центр. Но когда скрипт фигачит столько звонков, сколько выдерживает сеть оператора до временного бана, это ни в какие ворота.
Третья вещь — должно быть предупреждение, что обзвон совершает робот. Помнится, такое хотели принять в США, чтобы, когда говоришь с ботом, было предупреждение: с вами говорит робот. А тем, кто предупреждение не ставит, выкатывать конский штраф. Не знаю, приняли или нет, но считаю, это должно быть.
С телефонным спамом борются и фирмы. Почти у всех операторов есть услуга “антиспам”, и она даже работает, я проверял. Тиньков разрывает звонок и шлет смс, что это спам. Но почему фирмы борются с фирмами при полном попустительстве закона?
Напоминает современный веб: трекинг — блокировщики, еще больше трекинга — еще больше блокировщиков, а при открытии вкладки закипает мой ноут.
Пожилые люди запуганы спамерами и мошенниками. Много раз наблюдал: у человека звонит телефон, неизвестный номер, он смотрит на цифры, пытаясь определить — спам или нет? Думает, местный или нет, боиться принять вызов… до чего довели!
В моем случае было три раза, когда я не мог дозвониться людям старше себя. Я не был в их телефонной книжке, и вдобавок у меня виртуальный оператор с чудными цифрами. Вместо привычных 933 человек видел загадочные 399 и поэтому не брал трубку. Приходилось писать в вацапы-вайберы, чтобы догнать.
От некоторых ребят я слышал, как они живут припеваючи, разрешив вызов только с номеров в телефонной книжке. У меня не получилось: стоило включить это правило, как не дозвонился курьер, сорвалось мероприятие, разминулся с сантехником и прочая бытовуха.
Объясните родственнику: если он сомневается в номере, пусть сбросит вызов и перезвонит через минуту. В лучшем случае он никуда не дозвонится, потому что у мошенников динамический пул номеров. Если это банк с кредитом, он услышит музыку и номер в очереди. Наконец, если это был обычный человек, он примет вызов.
И покажите, как блокировать номер из списка недавних вызовов, это несложно. Шанс, что через месяц позвонят с этого номера, хоть и мал, но есть.
-
Синтаксис Лиспа
Когда неподготовленный человек видит Лисп, он как-то реагирует: хихикает, лепит эмодзи, вовлекает других, словом — переживает. В такую минуту он напоминает школьника, который принес эротический журнал: смотрит на груди и попы, конфузится, краснеет, показывает другим под партой. Вроде бы интересно, но что с этим делать — не понятно.
Хорошо, если бы программистам объяснили: Лисп — лучший способ записать код. Любой язык можно улучшить хотя бы тем, что сделать синтаксис лиспо-подобным. Пусть даже парадигма останется прежней.
У скобочной записи есть преимущество: каждое выражение имеет начало и конец. Убедитесь, что прочли последнее предложение вдумчиво. В Лиспе каждая форма имеет начало и конец. В других языках — нет.
Предположим, я вижу выражение:
x = foo + bar
Значит ли это, что выражение закончено? Конечно нет. За
bar
вполне может быть продолжение:x = foo + bar * kek + lol
Кроме того, что выражение определяется “на глазок”, сюда вкрадывается приоритет операторов:
bar * kek
нельзя разорвать.В то время на Лиспе первое выражение будет таким:
(var x (+ foo bar))
Скобки задают границы. Если скобка закрылась, то выражение закончилось, точка. Все, что следует дальше, относится ко внешнему выражению. Второе выражение сводит на нет котовасию с приоритетом операторов:
(var x (+ foo (* bar kek) lol))
Все задано явно, вопросов быть не может. Вы, конечно, скажете, что приоритет умножения известен каждому школьнику? Тогда счастливой отладки дебажить что-то такое:
Some shit = foo && bar || test ^ foo;
Из сказанного следует, что в Лиспе удобно работать с выражениями. Например, я могу выделить текущую форму. Обратите внимание — форму! Не метод, не сложение чисел, не класс, а именно форму! Потому что в Лиспе все это — форма. Мне не нужны хоткеи “Select method”, “Select class”, “Select whatever”. Мне достаточно одной клавиши, чтобы покрыть все случаи.
Формы в Лиспе можно разбивать и объединять. Стоит нажать кнопку, и выражение
(+ foo bar)
становится просто+ foo bar
. Далее я могу что-то сделать с его элементами. Форму можно двигать выше, ниже по текущему уровню вложенности. Можно втолкнуть ее внутрь. Можно вытолкнуть наверх из-под условия.Форма может поглощать другие формы. Например, у меня есть код:
(do-some-stuff x y z)
Теперь нужно, чтобы форма была внутри условия. Прямо над ней я пишу:
(when some-condition) (do-some-stuff x y z)
Далее, находясь внутри
when
, я жму кнопку, и форма втягивает в себя следующую за ней форму, и получается:(when some-condition (do-some-stuff x y z))
Разумеется, есть другая кнопка, чтобы “выплюнуть” форму, и я получу то, что было до поглощения.
Каждый думает, что к нему это не относится, ведь он же пишет не на Лиспе. Но вот реальный пример на Джаве с цепочкой футур:
return prepare(sql, executeParams) .thenCompose((PreparedStatement stmt) -> sendBind(portal, stmt, executeParams)) .thenCompose((Integer ignored) -> sendDescribePortal(portal)) .thenCompose((Integer ignored) -> sendExecute(portal, executeParams.maxRows())) .thenCompose((Integer ignored) -> sendClosePortal(portal)) .thenCompose((Integer ignored) -> sendCloseStatement(stmt)) .thenCompose((Integer ignored) -> sendSync()) .thenCompose((Integer ignored) -> sendFlush()) .thenCompose((Integer ignored) -> interact(executeParams)) .thenCompose((Result res) -> CompletableFuture.completedFuture(res.getResult()));
Каждый видит в меру своей испорченности, но я вижу здесь Лисп. Ему немного не повезло: нужно только переставить скобки, и получится нормально. Но вот курьез: Джава-человек в упор этого не видит. Для него это по-прежнему код на Джаве, а переставишь скобки — и сразу смешно.
Теперь нужно изменить код так, чтобы
stmt
оставался в поле видимости на большее число шагов. Получится вот так:return prepare(sql, executeParams) .thenCompose((PreparedStatement stmt) -> sendBind(portal, stmt, executeParams) .thenCompose((Integer ignored) -> sendDescribePortal(portal)) .thenCompose((Integer ignored) -> sendExecute(portal, executeParams.maxRows())) .thenCompose((Integer ignored) -> sendClosePortal(portal)) .thenCompose((Integer ignored) -> sendCloseStatement(stmt))) .thenCompose((Integer ignored) -> sendSync()) .thenCompose((Integer ignored) -> sendFlush()) .thenCompose((Integer ignored) -> interact(executeParams)) .thenCompose((Result res) -> CompletableFuture.completedFuture(res.getResult()));
Знали бы вы, как тяжело это было сделать! Поскольку у нас не формы, а выражения, приходится выделять каждое мышкой, вырезать и копировать. Ошибся на одну скобку — и все, начинается ад. Окончание каждого метода неочевидно. Над таким примитивным рефакторингом я сидел полчаса. В Лиспе я бы подвинул формы, даже не отдав себе отчета в том, что делаю.
Что тут можно сказать? Есть вещи, которые хоть и были открыты давно, остаются непревзойденными. Синтаксис Лиспа — одна их них. Много воды утекло с 1958 года, но в плане работы с кодом удобней ничего не придумали.
Не обязательно писать на Лиспе, но нужно знать эту его сторону. Чтобы не выглядеть глупо, не хихикать и не прыскать в кулачок, когда случится увидеть Лисп.
-
Список дел
Как и все остальные, я пробовал разные способы GDT — методы завершения дел. До сих пор помню восторг, когда перенес дела в список и казалось бы — бери с головы и делай. Но дела никогда не кончаются, список неограниченно растет с хвоста, и наступает апатия. Зачем вести список, если он никогда не кончится? Зачем разгребать это дерьмо, если всегда наложат сверх всякой меры? И вообще, кто мы, откуда и куда идем?
Даже если в какой-то день порвать рубаху и выполнить десять дел, придет новых десять дел. Это как убраться в квартире навсегда, чтобы больше не убираться. Так не бывает.
Я понял, что список дел — это бесконечный источник. В норвежских легендах была история о том, как разыграли Тора. Ему дали отпить вина из рога, который был соединен с морем. И даже он, будучи полубогом, отпил лишь часть. Ваш список дел — такой же рог, одним концом соединенный с бесконечностью.
Эта аналогия в корне меняет дело. Я по-прежнему веду список дел — не в программе, а на бумаге — но твердо понимаю, что сделаю далеко не все. Я установил правило — выполнять пункты с минимальной производительностью, например — одно дело в день и не больше. Сделав одно дело, в идеале с утра, становишься героем на оставшийся день и не чувствуешь угрызений.
В какой-то день вообще устраиваю “выходной” — никаких сторонних дел, только работа и тупняк в интернете.
Вот так, разгребая дела на минималках, подобно улитке по склону Фудзи, как-то справляешься. Список дел выглядит списком, но на самом деле он становится процессом. А у процесса нет конца.
-
Снова о выпадающих меню
Пришел спам, хочу добавить отправителя в бан-лист. И вот опять: почему контекстное меню не упорядочено по алфавиту? Почему сначала идет Copy, потом Add, а потом Block? Почему Add to Contacts отделен черточками и находится посередине? Чем он отличается остальных групп меню?
Почему контекстные меню всегда такие? Почему в них ничего не упорядочено по алфавиту? Почему нужно каждый раз бежать по списку сначала за O(N)? Почему пункты относятся к разным группам безо всякого смысла?
Почему, почему, почему?
-
Контекст
Расскажу про ужасный паттерн, который разгребаю уже в третьем проекте. Что самое ужасное, он попадается в Кложе. Вы, наверное, слышали, что в Кложе только пони и радуга, там не отцветает жасмин и не смолкает пение птиц? На самом деле в ней так быдлокодят, что тушите свет.
Речь о паттерне “контекст”. Это когда через приложение прокидывается мапа, в которой:
- текущий запрос
- текущие пользователь, сессия, токен
- подключение к базе
- подключение к Эластику
- подключение к кешу
- прочитанные файлы-справочники, например классификаторы, валюты и прочее
- переменные среды
- настройки логирования
- подключение к очереди задач
- еще миллион разных ключей
Эта мапа гуляет по стектрейсу, при этом каждый участник что-то оттуда читает или складывает свое барахло. Увидев в коде ctx или context, нужно отматывать на три экрана вверх, чтобы понять, кто и что туда сложил.
Случайный принт этой мапы убивает Емакс, потому что он захлебывается в выхлопе и попытке его распарсить. Знаю как обойти, но все же.
Отдельные гении оборачивают мапу в атом, чтобы она стала мутабельной! В результате ищи-свищи, кто поправил это поле по всему стеку вызовов.
Вот прямо сейчас, дорогая редакция: человек кладет в контекст подключение к базе. Ниже по стеку он берет подключение из контекста, открывает в нем транзакцию и снова кладет в контекст поверх старого, чтобы все, кто ниже, работали с транзакционным подключением. Такая схема ломается в два счета, что я и сделал, а затем два дня искал, почему посыпались данные в базе…
Интересно, что в Кложе есть библиотеки Component, Integrant и Mount для управления системой компонентов. У каждой плюсы и минусы, но выбрать есть из чего — не говоря о том, что некоторые ребята пишут свои менеджеры систем. Но найдется умник, который скажет — это скучно, джава-вей, давайте через мапку прокидывать.
Другими словами, человек не знает, как организовать зависимости между компонентами. Он выбирает самое глупое решение — хранить все в одном месте — и маскирует провал рассказами о простоте и докладами Рича Хикии.
Словом, знайте — в проектах на Кложе много любительского быдлокода. Порой я жалею, что нет фреймворка уровня Джанги, где все прибито гвоздями и за шаг в сторону — расстрел. Глядишь, hammock driven-девелоперы чему-нибудь подучились.