• Пустая истина (1)

    Расскажу, как однажды погорел на забавной вещи под названием “пустая истина”. Это было лет пять назад, когда я ничего про это не знал.

    Значит, смотрите: когда мускулистые греки работали над логикой, они ввели в том числе предикаты. Например, белый? – это предикат. Если применить его к любому предмету, получим истину или ложь.

    Также греки придумали пакетную версию предикатов, батч, так сказать. Это супер-предикаты “каждый из”, “любой из”, “ни один из”. Все они принимают другой предикат и множество объектов. Далее они редьюсят множество логических результатов в один (простите за функциональные термины).

    Если на столе три белых камня, то выражение “все камни – белые” вернет истину. Если один – тоже истину. Если сто белых и один черный – ложь. А что случится, если камней нет? Греки почесали бороды и сказали – будет тоже истина, только назовем ее пустой.

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

    На мой взгляд, греки подложили нам свинью. Истина, как известно, одна – не бывает двух разных истин. Когда вводят истину с какой-то характеристикой, получается черт знает что. Вроде бы это истина, но какая-то другая, что требует особого обращения с ней.

    Напоминает диалог Булгакова об осетрине второй свежести: голубчик, это вздор! Свежесть бывает только одна – первая, она же последняя. Если осетрина второй свежести, значит, она тухлая.

    Знал же человек!

    Интересно, что греческая вторая свежесть, тьфу, пустая истина идеально ложится на быдлокод! Это при том, что программировать греки не умели.

    Предположим, нужно написать функцию, которая принимает предикат и список объектов. Функция возвращает истину, если предикат справедлив для каждого элемента. Вот как выглядит самая тупая реализация:

    func is_every(fn_pred, items):
      for item in items:
        if not fn_pred(item):
          return false;
      return true;
    

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

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

    (every? policy-match? permissions)
    

    Оказалось, Рич Хикки знал эти штучки, и для пустого множества every? возвращает истину. Это легко проверить:

    (every? int? [1 2 3])
    true
    
    (every? int? [1 "a" 3])
    false
    
    (every? int? [])
    true
    

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

    С тех пор у меня отпечаталось в подкорке, что перед every? должна быть проверка на пустоту. Пустая истина может трактоваться как угодно, но мне нужна точность.

    Второе – я не согласен с греками. Видимо, они еще не знали про NULL и неопределенность, плохо понимали троичную логику. Увы, наш мир сложнее, чем true и false, есть нуллы и другие досадные вещи. Но нужно жить с ними, а не сводить к каким-то сомнительным истинам.

    Кстати, пустая истина позволяет сказать жене: все мои любовницы – брюнетки. Если у вас нет любовниц, это тоже будет истиной. Разве что придется потратить время на объяснение, но ничего. Истина дороже.

  • Подорожание Google Workspace

    Гугл пишет, что поднимет цену на подписку Google Workspace. Это для тех, кто привязял домен к Гуглу и тем самым создал мини-организацию из одного человека. Правда, со временем я открыл учетки для других членов семьи, включая маму, потому что никто не помнит пароль, да и пройдет авторизацию Гугла сегодня не каждый. Так что сейчас в моей организации пять человек.

    Интересна причина роста цен — это внедрение AI и некие фичи:

    The updated subscription pricing reflects the significant added AI value, as well as the many new features we have introduced and are launching to Google Workspace editions

    В тот момент я подумал: Гугл напоминает российский Газпром. Потому что все развлечения Газпрома оплачивает потребитель — покупку футбольных клубов, километры списанных труб, бонусы менеждеров. За все это платит простолюдин, неважно какие успехи рапортует Газпром по телевизору.

    То же самое с Гуглом — казалось бы, он и так гребет деньги лопатой за рекламу; собирает и продает личные данные; везде где можно предлагает платный Gemini. И все равно этого мало, поэтому пусть заплатит потребитель.

    Тем чудикам, которые топят за ИИ, советую подумать: на ровном месте мы получили прибавку к цене только потому, что компания внедряет ИИ. Такая вот новая нормальность.

  • Кнопки в Гитхабе

    У Гитхаба странный интерфейс — посмотрите на картинки ниже.

    Первая картинка: я хочу смержить пул-реквест. Нажимаю кнопку Squash and merge, ожидаю, что произойдет то, что написано на кнопке — логично же?

    Но мерджа не происходит. Вместо этого появляется форма с двумя полями, а кнопка Confirm squash and merge проваливается ниже. Как у Чуковского: “и подушка как лягушка ускакала от меня”. Нужно мотать вниз и жать ее еще раз.

    Вот эти убегающие кнопки — бич Гитхаба. Вроде нажал, а всплыло что-то другое. Кстати, после мерджа обычно я удаляю ветку, и кнопка Delete branch оказывается на 10 сантиметров ВЫШЕ. То есть сначала нажал кнопку на высоте X, потом X+10 см, потом снова X-10 см. Дизайнеру Гитхаба это норм — не жмет, не чешется.

    Вы, конечно, скажете: надо запросить описание коммита. Ну вот ниже на картинке есть форма комментариев — она статична и не появляется по клику. Можно сделать такой же статичный инбокс для мерджа. Еще лучше — сделать так, чтобы кнопка не уплывала вниз, а оставалась на месте, при этом поле повляется ПОД ней. Чаще всего я ничего не пишу, поэтому просто нажал бы кнопку еще раз.

    Решений может быть много, но то, что сейчас — крайне неудачно.

  • Игрушечный парсер

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

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

    Так что если вы студент(ка) и пишете парсеры, загляните в репозиторий.

    Там простой модуль с комментариями и базовыми парсерами. Также есть два модуля demo1 и demo2. В одном пример с префиксной нотацией, во втором — с инфиксной. Второй пример интересен тем, что там используется рекурсивный парсер, и поэтому нужны конструкции declare и var.

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

    Если будут вопросы, пишите.

  • Поддержка и Джира

    Как-то раз я общался с поддержкой крупного сервиса. У них что-то поплыло, консоль залита кровью, все ответы — 500. В углу виджет поддержки, и я пишу: ребята, у вас все плохо. Хром такой-то, вот скриншот.

    Сотрудник отвечает: ах, спасибо, что известили, сейчас поправим. Скажите, какой у вас хром? Я говорю, вот же написал: версия 100500.42. Он такой — отлично, считайте уже сделали.

    Через минуту: еще одна деталь, какая у вас операционная система? Мак, говорю. Отлично, чиним в поте лица.

    Через минуту: а какая версия системы? 14 с копейками. Спасибо, уже патчим прод.

    Через минуту: какая у вас часовая зона? Тут я слегка разозлился и ответил, что хватит приседать на уши. На что сотрудник ответил, что ему нужно заполнить тикет в Джире: там 20 полей, и все обязательны. Уже заполнили 6, осталось 14. Без заполнения он не сможет создать заявку, и она не пойдет программистам. И вообще ничего не будет.

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

    Вот и отлично!

  • Неэффективный ввод и вывод

    Мое частое замечание к коду — неэффективный ввод-вывод. Примеры:

    • чтобы пройтись по строкам файла, человек читает его в память целиком и разбивает символами \r\n. Рано или поздно прилетает CSV на 5 гигабайт, и машине становится плохо.

    • То же самое с джейсоном: есть стрим, но разработчик читает его в гигантскую строку, а потом парсит ее.

    • Нужно записать в файл 100 тысяч строк? Человек джойнит их разделителем, получает километровую строку и пишет в файл.

    • Различные кодирования — base64, gzip и другие — делаются также: данные читаются в память целиком, из них получается результат тоже в памяти.

    • При загрузке файла в S3 он целиком читается в байтовый массив, затем массив передается в запрос.

    При этом разработчик обмазывает код вызовами gc в надежде, что это поможет.

    Сколько подобных ошибок я исправил — не перечесть. В числе прочего был сервис, который падал от недостатка памяти, хотя ее было выделено запредельное количество. Оказалось, разработчик делал все из списка выше. Он получал огромные файлы, читал их в память, парсил, кодировал в JSON и gzip, используя строки и массивы. Когда код падал от OOM, он поднимал лимиты в облаке.

    Это лишний раз подтверждает: сколько памяти ни дай, плохой код сожрет ее всю.

    А решение простое — байтовые и символьные потоки. Ту же Джаву можно ругать за многое, но в ней очень хорошие потоки (абстрактные классы Input- и OutputStream, Reader и Writer). У них много наследников, каждый из которых делает свою работу. Например, буферизирующий поток, который сглаживает неравномерность сети и файлов. Потоки для сжатия, когда пишешь в него, а данные сжимаются в полете. Потоки, связанные с файлами, сокетами или устройствами. Потоки с подсчетом текущей строки и символа, потоки-пайпы (piped) для “переливания” данных между тредами — всего этого навалом.

    Легко найти сторонние потоки для подсчета MD5 и других хешей. Например, пишешь в условный MD5OutputStream, и хеш считается в полете. В конце вызываешь .getHash, и готово.

    Часто задача решается тем, что нужно построить стек потоков и скормить ему данные. Это труднее, чем прочитать файл в память и разбить на строки. Но не придется чинить в пятницу вечером.

    Уделите время потокам ввода-вывода. Это прям очень полезная вещь.

  • У заказчика праздник

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

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

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

    Честное слово, нам нужны дни, когда нет ни созвонов, ни чатов, ни почты. Дни, когда можно поработать в полном отрыве от команды и вечером написать, как много сделал.

  • Австралийское время

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

    Много воды с тех пор утекло, но порой я скучаю по такому формату. Разница с заказчиком в час-два означает, что ты не делишь с ним головные боли и причуды: планинги, ретро, спринты, тимбилдинги, уведомления в Тимс. Иной раз хочется, чтобы ничего этого не было. Чтобы был только git pull и git push, ну и зарплата. А на тимбилдинге и без меня обойдутся.

    Это так, минутка малодушия. Вздохнул и пошел дальше.

  • Логирование в Джаве

    Есть в Джаве одна вещь, которая сделана очень, очень плохо — минус бесконечность по любой шкале. Это логирование, причем как встроенное, так и сторонее. Справедливости ради, оно плохо сделано везде, но это слабое оправдание.

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

    Хорошим примером служит история с Log4j. Когда я читал, что было под капотом, вставали волосы во всех местах. Ощущение, что разработчики объехали все сумасшедшие дома, записали пожелания пациентов и выполнили их дословно. Добавьте в шаблоны Тьюринг-полный язык? Хорошая идея. Хочу подгрузку классов по урлам? Считайте, уже сделано. Напишите фасад над фасадом над фасадом? Уже в этом релизе.

    Итог понятен: если громоздить подобные вещи, окажутся уязвимости. В результате целый месяц люди чинили интернет, корпоративный софт, Майнкрафт(!) и все остальное.

    Вторая причина объясняется одним словом — состояние. В Джаве и других языках логирование устроено одинаково. В памяти сидит глобальный маршрутизатор, который определяет, куда какие логи складывать. У этого подхода много бед. Первая — инициация происходит неявно, например при чтении файла logback.xml из ресурсов. Вторая — программная настройка довольно сложна: нужно два экрана кода, чтобы связать фабрики логгеров с фабриками аппендеров.

    Третья, которая заслуживает отдельного абзаца — это то, что я называю “воровство работы”. Как быть, если библиотека А использует log4shit, а В — log4crap? В этом случае log4crap переопределяет настройки log4shit, направляя поток сообщений в свой маршрутизатор. Повторюсь, все это происходит неявно, потому что покрыто “легковесными” фасадами.

    Долгое время я думал, что logback (вроде бы самая адекватная библиотека логирования) — самостоятельное решение. Оказалось, это всего лишь фасад над уродским slf4j. Представьте себе объем работы и глубину стека вызовов! Один вагон классов для базовой функциональности, второй — чтобы приделать ему человеческое лицо.

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

    Глобальное логирование простительно языкам эпохи Си. В последнем есть подсистема syslog (системный журнал), которую однажды открыл и пишешь из любого места программы. По современным меркам такой подход устарел.

    На мой взгляд, проблема решается тем, чтобы убрать из логирования состояние. Система предлагает интерфейс Logger с методами debug, info, error и другими. Есть реализации этого интерфейса для записи в консоль, файл, системный журнал. Конкретная реализация нас не волнует: мы просто передаем объект logger, трактуя его как экземпляр интерфейса.

    Этот логгер можно (и нужно) передавать в конструктор класса. Например, в каждое подключение к базе можно передать свой логгер. То же самое относится к парсерам, серверам и так далее.

    Это решает проблемы, упомянутые выше. Базовые логгеры для консоли и файла у нас есть. Можно сделать классы-комбо, которые принимают несколько логгеров и пишут сообщения в каждый. Можно сделать асинхронный логгер, можно прикрутить Кафку, CloudWatch, словом — что угодно. Достаточно унаследовать класс от интерфейса Logger и инициировать ресурсы в конструкторе.

    Попутно решается проблема тестов: я могу передать логгер, который складирует сообщения в память и потом их прочитать.

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

    Увы, сегодня мы бесконечно далеки от такого подхода. Во-первых, Джависты поднимут лай, во-вторых, уже написаны десятки тысяч библиотек на базе log4shit, log4crap и прочих поделок. Мы обречены поддерживать этот цирк.

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

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

  • Game Center

    В мире Эпла есть одна заноза в заднице, которая называется Game Center. Никто не знает, что это и зачем. Просто каждый раз он вылазит при запуске игры, что-то требует, ознакамливает, уведомляет. Почему нельзя спрятать это поделие под ковер — не ясно.

    Летел я недавно в самолете. Когда мозги уже совсем потекли, решил поиграть на телефоне. Включаю — на весь экран вылазит плашка “что нового в Game Center”. Абсолютно тупая и бессодержательная, ничем из этого я даже близко пользоваться не собираюсь. Барахло не умещается на одном экране, поэтому всплывает другой: помоги своим друзьям чего-то там.

    Почему я должен чем-то помогать? Почему нельзя сделать это автоматом, я без понятия.

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

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

    Как я писал, никто не знает, что такое Game Center и почему нельзя сделать его скрытой службой. Скажем, нужно хранить сохранения в играх между девайсами. Так записывай их в iCloud, делов-то. Если апишки iCloud не хватает, вынеси в отдельную службу. Но зачем без конца о ней уведомлять?

    Бред, просто бред и ничего больше.

Страница 3 из 99