• Мои объявления

    Авито, страница “Мои объявления” — разве это не забавно? На экране все что угодно, кроме моих объявлений. Огромная плашка, громадные пустоты. Слоеный дизайн, когда каждый слой, пусть даже занимает сантиметр в ширину, растекается на весь экран.

    Очередной калека-дизайнер, которому “не хватило места”. Важная часть уехала на экран ниже — потому что первый экран занял всякий шлак.

    Считаю, таких дизайнеров надо даже не учить, а лечить. Обучение бессильно, пусть действуют профильные специалисты.

  • Загрузка в Амазоне

    У веб-панели S3 есть особенность: если скачать оттуда файл, Амазон поправит расширение в зависимости от Content-Type, который назначили файлу при создании. Например, если у файла нет расширения, а Content-Type равен application/json, то Амазон допишет в конец .json, чтобы файлик открылся.

    Казалось бы, хорошо? А вот что имеем на практике.

    Если залить файл hello.json.gzip, внутри которого сжатый Gzip-ом JSON, и указать заголовки Content-Type: application/json, Content-Encoding: gzip, то при загрузке произойдет следующее.

    Файл будет декодирован Амазоном, чтобы клиенту не пришлось делать это руками. Не бог весть какая помощь, потому что и текстовые редакторы, и файловые менеджеры открывают gzip-файлы. Но ладно.

    После раскодировки Амазон смотрит: что там внутри? Application/json? Значит удалим .gzip и добавим .json. В результате получается файл hello.json.json. Я не шучу, проверьте сами.

    Второй случай: я залил в Амазон файл report.xlsx, но указал не тот Content-Type. Указал старый application/vnd.ms-excel для xls документов, а надо было такую колбасу: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. При загрузке Амазон молча исправил расширение с .xlsx на .xls. А Эксель тоже хорош: по клику на файл он пишет, что формат битый, ничего не знаю – нет бы первые 100 байтов проверить, тупица.

    На ровном месте Амазон заруинил файл, хотя никто об этом не просил.

    Понимаете, не нужно мне помогать! Не нужно что-то тайно переименовывать для моей же пользы. Если прям чешется в одном месте – спроси, и я нажму “больше не спрашивать”.

    Кроме того, надо помнить: в Амазоне работают не боги, а такие же кодеры, как и везде. Перед нами обычный баг, который живет в проде не один год, и никому нет дела. Баг состоит в том, что махинации с расширением нужно производить только если у файла нет расширения. Вдобавок у расширения приоритет выше, чем у Content-Type, потому что последний – это метаинформация, которая может потеряться или исказиться. Вероятность потерять расширение гораздо ниже, нежели Content-Type.

    Верю, что когда-нибудь в Амазоне это поймут.

  • Время в 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 — методы завершения дел. До сих пор помню восторг, когда перенес дела в список и казалось бы — бери с головы и делай. Но дела никогда не кончаются, список неограниченно растет с хвоста, и наступает апатия. Зачем вести список, если он никогда не кончится? Зачем разгребать это дерьмо, если всегда наложат сверх всякой меры? И вообще, кто мы, откуда и куда идем?

    Даже если в какой-то день порвать рубаху и выполнить десять дел, придет новых десять дел. Это как убраться в квартире навсегда, чтобы больше не убираться. Так не бывает.

    Я понял, что список дел — это бесконечный источник. В норвежских легендах была история о том, как разыграли Тора. Ему дали отпить вина из рога, который был соединен с морем. И даже он, будучи полубогом, отпил лишь часть. Ваш список дел — такой же рог, одним концом соединенный с бесконечностью.

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

    В какой-то день вообще устраиваю “выходной” — никаких сторонних дел, только работа и тупняк в интернете.

    Вот так, разгребая дела на минималках, подобно улитке по склону Фудзи, как-то справляешься. Список дел выглядит списком, но на самом деле он становится процессом. А у процесса нет конца.

Страница 2 из 79