• Выпадашки в Телеграме

    А вот прикол с выпадашками в мобильном Телеграме. Если открыть диалог в режиме просмотра, то он будет в выпадающем окне. Точнее, их будет два: диалог и контекстное меню. Если долбить кнопку Block User, то Телеграм выкинет бесконечное число попапов с затемняющим слоем. При этом диалог и затеняшка будут ПОД выпадашкой с диалогом.

    Когда вы закроете диалог, у вас будет сотня модальных попапов и абсолютно черный экран. Каждый затеняет то, что под ним на 5 процентов, и они суммируются.

    Лет 7 назад я делал мобильное приложение на гремучей смеси технологий: Кложа, React.Native и Objective-C для доступа к нативным штучкам. В том числе я делал интерфейс. И быстро понял: если лепить на каждый чих выпадашку, рано или поздно проиграешь. Легко сделать так, что человек ушел на другой экран, а выпадашка осталась. Либо ты не закрыл ее, и чел наспамил их триста штук.

    А когда выпадашка появляется из выпадашки или диалог из диалога, то это прямой билет в ад. Исключений не бывает. Поэтому не делай выпадашки там, где можно обойтись без них. Искренне желаю, чтобы и остальные разработчики поняли это скорее.

  • Ненависть к SQL

    Нынче такая мода — ненавидеть SQL. Проходя мимо, нужно обязательно поддать его ногой: и синтаксис уродлив, и плохо ложится на объекты, и джоины всякие… Под каждой статьей народ стоит в очереди, чтобы накласть комментарий: да, плохой. Да, фу-фу.

    Я считаю, что SQL замечателен, но сначала расскажу анекдот.

    Приезжает Тарантино на фестиваль. Журналист берет у него интервью и троллит: Квентин, вы замечательный режиссер, но вы так и не сняли фильма лучше “Криминального чтива”. Тарантино думает и отвечает: а кто снял?

    Вот так и с SQL — кто снял? У нас есть что-то лучше? Я лично не уверен.

    Напротив, я все больше убеждаюсь, что SQL удобен и гибок. Этим объясняется то, что он дожил до наших дней. Помните, в начале десятых годов каждый клоун кричал в Твиттере, что пришло время noSQL? Каждый день была статья “как я переехал на Монгу”. Кончилось тем, что Postgres сделал jsonb и съел Монгу с потрохами. А у тех, кто на нее переехал, начались другие проблемы, потому что чудес не бывает.

    Когда говорят про SQL, забывают, что он не зависит от языка, на котором пишут код. Ему не важно, что у вас — Питон, Джава или пы-хы-пе. Это по-настоящему классно. SQL дает универсальный доступ к данным из любой технологии. Им можно пользоваться без программного окружения, например в psql или PgAdmin.

    Помните аббревиатуру LAMP: Linux, Apache, Mysql, PHP? Вот что по-настоящему важно: SQL — это часть стека. Сегодня буквы поменялись, но принцип остался: это операционка, сервер, данные и язык, который ими управляет.

    Каждый элемент стека — как слой жидкости в лава-лампе. Можно перемешать слои, то есть внести хаос: взять базу, которая встраивается в язык; сделать проект на no-code-решениях, тем самым выкинув условный PHP. Такие случаи есть, и каждый из них обсуждают на айтишных сайтах. Но в долгосрочной перспективе слои лава-лампы приходят в норму. Потому что вещи должны быть простыми, но не проще определенного порога.

    Мало кто думает о том, что noSQL-решения дороги и трудны в развертке. Я уже писал, что Postgres и Mariadb работают на каждом утюге. Первый уже гоняют в браузере. А базы вроде Datomic или XTDB напоминают черепах на трех китах. Их работа сводится к оркестрации других хранилищ. Например, Datomic использует Посгрес или Марию для хранения индексов, плюс ему нужен мемкеш-кластер, плюс в облаке он ходит в S3. Базе XDTB нужен кластер Кафки. Вы точно готовы в этом разбираться? Осилите развернуть продакшен-версию локально?

    Разумеется, у этих баз есть dev-режим для локального запуска. Но в случае с Postgres я знаю, что локальная база идентична той, что в проде. В обоих случаях крутится один и тот же код. Я могу воспроизвести любую ситуацию локально. В случае с Датомиком это лишь имитация.

    Сюда же относится OpenSearch. Формально он open source, бери и пользуйся. На практике его развертка настолько сложна — нужно минимум три узла в кубере, — что все пользуются облачной версией поставщиков. А это дорого.

    Апишку OpenSearch вы видели? Казалось бы, HTTP и REST, шли джейсоны и живи спокойно. Я с ней работал и скажу — неудобно. Синтаксис жуткий, нужно учить с нуля. Тройная вложенность: мапа внутри мапы внутри мапы. Нельзя выбрать больше 10 тысяч записей за раз (Посгрес выплюнет миллион за долю секунды). Пагинация ужасная и состоит их двух апишек, а не одной. Наконец, я лично спровоцировал исключение, когда переполняется ByteArrayOutputStream, и запрос падает с ошибкой 500. Когда в последний раз вы видели переполнение буфера в Посгресе?

    Когда я стал работать в крупной фирме, поразился тому, что почти каждый знает SQL. Менеджеров и аналитиков этому учат на курсах. Человек, который путает Java и JavaScript, может написать сложный запрос на два экрана. Ощущение, что даже уборщица напишет выборку часов с нарастающим итогом. Было такое, что человек приносил запрос и просил поставить на ежедневную выгрузку. А это значит, он снимал с меня тяжкий груз — сопоставлять бизнес-требования с данными. Без него я писал бы запрос неделю, потому что не понимаю бизнес-жаргон. А он сделал это за меня, и осталось только завернуть запрос в техническую шкурку.

    В крупных фирмах SQL становится языком коммуникации, посредником между бизнесом и технарями. А что с условным Датомиком и Даталогом? Все это прибито гвоздями к Кложе; с ним у людей нет доступа к данным. К вам без конца будут приходить и просить: выгрузи то, выгрузи это, напиши такой запрос, сякой запрос. Вам это нужно?

    Некоторые сотрудники знают Питон и решают задачи скриптами с запросами к базе. Нейросеть пишет болванку, а они ее адаптируют. Что с Датомиком или XTDB? У них до сих пор нет клиента для Питона. Когда-то давно у Датомика был REST-интерфейс, но его упразднили. А XTDB вообще предоставляет Wire Protocol, притворяясь, что на том конце Postgres. Играет по правилам конкурента, лишь бы отхватить пользователей.

    Все говорит об одном: дай людям SQL, и они сами все сделают.

    Базы, которые встраиваются в язык, предлагают удобный доступ к данным. Он красивее и изящней, чем голый SQL — с этим я не спорю. Но повторю: данные нужны всем, а не только кложуристам. Мне приходилось писать запросы в веб-консоли Датомика, и уверяю вас: магия пропадает. Одно дело писать их в редакторе с балансировкой скобок, подсветкой синтаксиса и хоткеями. А когда перед тобой унылый text area — все, завяли помидоры. Написание запроса становится рутиной: не закрыл скобку, опечатался в ключевом слове. Набранные запросы копируешь в файлик, чтобы сохранились.

    Когда пишешь SQL днями напролет, воспринимаешь его как язык. На нем удобно выражать мысли. Например, я не вижу причины писать {select [foo bar baz]} вместо select foo bar baz — то есть ставить скобочки и структурировать данные. Пусть этим занимаются построители SQL. Недавно сотрудники Гугла выкатили пейпер, где на 20 страницах объясняли необходимость писать from users select id вместо select id from users. Ну ок, напишите препроцессор для клиента к БД. А разговоров-то было…

    Наша работа всегда будет сложной — иначе бы айтишники не получали как депутаты. Сложность следует из разнообразия случаев, с которыми сталкиваешься. Решения вроде Датомика и прочих noSQL-баз хороши в моменте. Они подкупают красотой в тех местах, где SQL не вышел лицом. Если хотите, это показ мод или акаунты моделей в Инстаграме. Но когда ты женился на модели, выясняется, что из-за длинных ногтей она не может почистить картошку или пришить пуговицу.

    SQL — это не про красоту, а про getting shit done. Понимание этого приходит на долгой дистанции.

  • Синглтон (2)

    Вдогонку о синглтоне — пара мелочей, о которых забыл вчера. Рассмотрим простую функцию:

    (func get-user-by-id [db id]
      (first (jdbc/execute! db "where id = ?" id)))
    

    Она принимает объект базы и работает с ним. Все прозрачно и логично. А вот варианты этой же функции, которые рассчитывают на глобальное подключение к базе:

    (func get-user-by-id [id]
      (let [db some.namespace/*DB*]
        (first (jdbc/execute! db "..." id))))
    

    или

    (func get-user-by-id [id]
      (let [db (db/get-or-create ...)]
        (first (jdbc/execute! db "..." id))))
    

    Оба варианта плохи по многим критериям. Если глобальный объект куда-то переедет, придется менять все функции. Нарушается связность системы — функции лезут в кишки, куда им лезть не следует. Объект может быть не инициирован — вполне возможная ситуация, если он включается по запросу. Наконец, функцию db/get-or-create нужно проверить на потокобезопасность, чтобы несколько потоков не наплодили более одного объекта.

    А теперь вернемся к первому варианту:

    (func get-user-by-id [db id]
      (first (jdbc/execute! db "..." id)))
    

    Очевидно, что когда функция принимает объект, все претензии снимаются. Ей все равно, синглтон это или нет, это не играет роли. Даже если в системе десять подключений к разным базам, на функции это не скажется. Наоборот, сплошная польза: я могу прочитать пользователя из мастера, из реплики, из исторической базы. В некоторых случаях это важно.

    Если отбросить красивые слова, синглтон — это глобальное состояние, подпертое костылями. А состояния лучше избегать — сегодня это ясно как день, причем независимо от языка.

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

    Повторюсь, сегодня нет причин использовать синглтон. Ни под каким видом. Знать, что это такое, нужно, но только за тем, чтобы не напороться на описанные грабли.

  • Синглтон (1)

    Казалось бы, сложно придумать паттерн хуже, чем синглтон. Но нет: на его примере объясняют, как хорошо иметь единственное подключение к базе данных. Специально проверил в Википедии и образовательных статьях.

    Делать подключение синглтоном — это примерно как подвесить больного человека вверх ногами и привязать к шее кирпич, думая, что ему станет легче. То есть двигаться в худшую сторону по градиенту — вектору, когда величина нарастает максимально быстро.

    Подключение к базе — и вообще любое сетевое подключение — никогда не должно быть синглтоном, и вот почему.

    Если подключение не потокобезопасно, то обращение к нему из разных потоков вызовет “рассинхрон”. Например, поток А отправил сообщение, а поток В прочел ответ. В лучшем случае конечный автомат выкинет ошибку, а в худшем — получим неопределенное поведение.

    Если же подключение потокобезопасно, то каждый запрос будет блокировать другие потоки: они будут ждать, пока соединение отпустит. В результате 4, 8 или 16 потоков будут работать как один.

    Конечно, если у нас скрипт на PHP для заказа пиццы, можно писать в таком ключе: объявить подключение на старте и ссылаться глобально. Для чего-то посложнее так лучше не делать.

    Вместо подключения должен быть пул соединений. Каждый раз из него занимают подключение, работают с ним и кладут обратно. Чтобы гарантировать возврат, процесс займа оборачивают в макрос или контекстный менеджер (with в Питоне, try with resource в Джаве).

    Но и пул соединений не должен быть синглтоном! Должна быть система компонентов с зависимостями. Пул — один из компонентов, а другие компоненты зависят от него. Специальный код строит граф зависимостей, включает компоненты в нужном порядке и передает им рабочие зависимости.

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

    Даже если какая-то сущность глобальна по причине легаси, не следуйте плохим практикам. Передавайте ее обычным аргументом. Быть может, ее получится убрать из глобального скоупа, и не придется рефакторить код.

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

    Синглтон — это прекрасно для собеса, но никогда не берите его в проект. Включайте, но не пользуйтесь. Пользуйтесь, но не включайте. Не считая этой мелочи, с ним все хорошо.

  • Новый интерфейс Ютуба

    Пишут, что Ютуб готовит новый интерфейс плеера. Разумеется, он более “воздушный”: кнопки обтянуты капсулами, вокруг которых пустота. Новые кнопки отнимают еще больше места от видео.

    Наверное, в десятый раз повторю тезис: современных фронтендеров (ну или веб-дизайнеров) нужно не учить, а лечить. Когда дизайнер отхватывает десятую часть экрана под кнопки и не видит в этом проблемы, ему нужна помощь специалистов. Что он делает в профессии, я не знаю.

    Разумеется, кнопки не должны наползать на видео, их место в отдельной панели снизу. Сюда же относится пауза: когда я нажал ее, не должно появляться затемнений, выпадашек с рекомендациями, блямбы с двумя палками и так далее.

    Все это бесконечно просто для понимания и бесконечно сложно для дизайнеров.

  • Mastercard нанимает

    Я подписан на кложурные вакансии в Linked In. Не планирую менять работу, просто чтобы иметь представление, кто нанимает. В числе прочих увидел Mastercard (SDET Backend API’s):

    We are the Platform & API Team which is responsible for the customer facing APIs to our products and are looking for a Sr Software Engineer for our Seattle or Vancouver Canada offices. As a Sr Software Engineer, we are looking for someone who thrives on designing, coding and maintaining high performance data processing applications in functional programming on the JVM platform (which is primarily in Clоjure), running on AWS. Our ideal candidate would have deep experience building internal and external latency sensitive APIs with functional programming using the most fitting tools and having the passion to champion new and exciting technologies to solve our unique and challenging problems.

    Так что вот! Источник: https://www.linkedin.com/jobs/view/4174329635/

  • Кэширование

    Небольшая заметка о кэшировании. Для начала тезис:

    Кэш — это лекарство, которое подчас опасней болезни. Пользоваться им нужно аккуратно, а еще лучше — не попадать в ситуацию, когда он нужен.

    Добавление кэша в проект несет много проблем. Например, на сколько его устанавливать? Если данные обновились, как его сбросить? Кажется, что ответить просто: давай на час, плюс добавим хук в ORM, чтобы после сохранения модели сбрасывался такой-то ключ.

    На практике все сложнее. Данные могут поступать не только через ORM, но и пакетным импортом или вставкой в таблицу из скрипта. Легко прозевать сброс ключа, и клиент увидит старые данные. Можно оказаться в ситуации, когда кэши будут зависимы: один строится на другом, и какой сбрасывать — не ясно.

    Помню, когда программировал на Python и Django, считалось нормой обмазываться кэшами на каждый чих. Фреймворк всячески поощрял это: были различные бэкенды кэша, декораторы, которые навесил на функцию — и готово. У кэшей даже были версии, представляете? С ними отстрелить ногу было еще проще.

    По наивности разработчики думают, что если запрос к базе медленный, то его нужно кэшировать. Это крайне наивно. База данных для того и создана, чтобы выполнять запросы. Современные базы могут выполнять одновременно много, очень много запросов. Например, в текущем проекте база, к которой я изрядно приложил руку, держит 300 транзакций в секунду — а внутри огромные JSON-документы.

    Вот что можно предположить, если база отвечает медленно:

    • не используется пул соединений
    • на каждый запрос открывается новое соединение
    • запрос делает полное сканирование таблицы (нет индекса)
    • индекс есть, но из-за кривого where он не работает
    • разработчик делает 100500 get-by-id в цикле
    • в запрос передают 65К параметров вместо массива или json
    • неотимальная вставка, например insert в цикле вместо copy from

    Ни один из этих пунктов не рассосется, если добавить кэш. Поэтому сначала нужно разобраться, что идет не так. А когда разобрались, проще исправить на месте, чем добавлять кэш.

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

    В широком смысле сетевой кэш — это вторая база данных. Если вы не справились с одной базой, с чего вы решили, что с двумя будет проще?

    Какие еще проблемы кэширования можно назвать? Да хотя бы следующие.

    Сериализация. Когда вы пишете данные в Memcache, как они сериализируется? Если это json, вы теряете даты и типы коллекций, например, множество становится списком. Если у вас словарь с целыми числами в ключах, это тоже станет проблемой: обратно вы получите строки. Хорошо, когда есть библиотека для бинарной сериализации. Но если один и тот же кэш читают в разных языках, это станет проблемой: вряд ли такая библиотека есть под все языки (кроме protobuf).

    Прогрев. Пока кэш актуален, все хорошо. Но когда тысячи клиентов начнут обновлять его, кэш-серверу станет плохо. Нужен фоновый процесс, который обновит кэш в фоне за несколько минут до того, как он протух. Кто будет следить за этим процессом?

    Транзакционность. В одном проекте мы так увлеклись кэшами, что словили глюк, когда несколько клиентов обновляли один и тот же ключ. Чтобы это разрулить, ведущий разработчик написал джанговский Cache с подобием транзакции. Когда кто-то хотел прочитать ключ foo_bar, код проверял, есть ли ключ foo_bar__lock. Если да, это значило, что в этот момент кто-то обновляет ключ foo_bar, и в доступе отказывали. Это был костыль поверх костыля, но мы очень гордились.

    Полнота. Другой пример, о котором писал в комментариях. Когда-то в Wargaming мы выкатили новый проект. Главная страница была тяжелой, потому что собирала данные из десяти сервисов, плюс было несколько языков. Всю страницу завернули в кэш, но не учли, что в ключе не было текущей локали. Вышло так, что первым посетителем был кто-то из Чехии. Декоратор посчитал страницу для чешского языка и положил в кэш. В течение часа посетители видели страницу на чешском. Кнопку для экстренного сброса кэша никто не предусмотрел. И кстати — такая кнопка нужна, и она тоже требует затрат.

    Резюмируя: лучший кэш — это его отсутствие. Если у вас нет кэшей в проекте — искренне поздравляю. Это сильно лучше той ситуации, когда кто-то его затянет.

  • Альтернатива для Германии

    Немного политики. Пишут, что партия “Альтернатива для Германии” признана экстремистской — в смысле не в России, а в самой Германии.

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

    Каждый европеец твердо знает: свобода и демократия есть только в Европе. И вот парни, которые чего-то достигли и мелькали в новостях, признаны экстремистами. Как-то не сходится: если они и правда клоуны, пусть наберут свои 0.1 процента и рассосутся. А если их поддерживает четверть Германии, это говорит о запросах общества, игнорировать которые — значит оттягивать неизбежное.

    Но все режимы одинаковы. Главное — заткнуть рот несогласным и протянуть еще лет пять, а там видно будет.

    UPD: в комментариях объяснили: этот тег дает право на прослушку и обыск без решения суда. Тоже ничего хорошего, кстати. Нужно только затем, чтобы создать базу для ареста.

  • Горький урок ABBYY

    Этим утром попалась интересная статья: “Горький урок ABBYY: как лингвисты проиграли последнюю битву за NLP”.

    Это история ABBYY: начало, быстрый рост, зенит, поражение на рынке с Гуглом, переезд и массовые увольнения. Разумеется, таких статей полно на Хабре, так что зачем еще одна?

    Дело даже не в том, что написано в статье, а как. Автор работал в ABBYY, и у него лингвистические образование. Поэтому текст грамотный, последовательный, без тупых мемов и нейро-картинок для привлечения внимания. Нет слов-паразитов и штампов; нет воды, налитой чатом-гпт. Читать такой текст сегодня — удовольствие на уровне физического.

    Содержание тоже не уступает: подробно рассказано, чем занимался ABBYY и какую Вавилонскую башню пытался построить. Интересны выводы, почему ABBYY с его ноу-хау и сотней лингвистов проиграл программистам из Гугла.

    Мне показалось, выводы справедливы и в других областях, например программировании. Но об этом, возможно, в другой раз.

  • Три состояния

    Упрощая, можно сказать, что человек бывает в трех состояниях: когда он один, когда с кем-то наедине и когда вокруг двое и более людей. В последнем случае неважно, сколько именно: два, три или сотня. Разница есть, но несущественная. Поэтому рассматриваю только варианты 0, 1 и 2+.

    В каждом из состояний человек ведет себя по-разному. Фактически это три роли, между которыми мы часто переключаемся.

    Понимание этих переключений помогает в жизни. Если человек хочет побыть один, не лезь с разговорами. Чтение книги и просмотр фильма относятся сюда же, потому что оба – про уход в себя.

    Если человек в компании, не начинай важный разговор. При свидетелях человек думает прежде всего о том, как прикрыть свой зад и не опозориться перед другими. Здравый смысл отодвигается на второй план: репутация важнее.

    Все сводится к тому, что любой разговор можно завалить, если начать его с человеком в неподходящем состоянии. С другой стороны – почти любой разговор можно завершить успехом, если собеседник в правильном состоянии. Осталось понять: где, когда и с кем, а остальное просто.

Страница 1 из 94