-
Уведомления о куках
В позднем СССР сложилась интересная ситуация. Воевать в Афганистане можно было с 18 лет, а пить спиртное — только с 21. Другими словами, не возбранялось легально убивать людей, а вот пить водку — подожди еще три года.
Мне это напоминает современный веб. Сегодня каждый второй сайт кричит, что он использует куки: всплывашки и выпадашки всех мастей. По требованию еврочиновников меня предупреждают, что сайт гоняет куки — мелкие статичные строки. Это при том, что браузеры существуют не первый год, и проблемы кук уже решены. Можно легко посмотреть, что передается в куках, удалить их, выставить запрет не передачу и так далее.
При этом еврочиновники не считают нужным уведомить пользователя, что любой современный сайт — это рантайм Джаваскрипта с почти неограниченными возможностями. В частности, меня не уведомляют о том, что:
-
сайт может вывалить 50 мегабайтов скриптов, сделать 250 запросов;
-
сайт может ходить на другие сайты, подтягивать виджеты, которые тянут другие скрипты, и так рекурсивно;
-
на сайте может быть пять трекеров, которые сливают всю подноготную о системе: юзер-агент, разрешение экрана, айпишник, локаль и прочее. Из них формируется устойчивый отпечаток, по которому за клиентом следят в других системах. Эти данные продаются третьим фирмам.
-
на сайте контекстная реклама;
-
сайт трекает движение мыши, шлет события на каждый клик
-
в сайт встроены анти-блокировщики, которые замедляют загрузку;
-
сайт может быть частью ботнета (координатором или жертвой);
-
сайт может трекать пользователя через данные в LocalStore и кастомные заголовки. Кук нет, отслеживание есть;
-
функции трекинга уже зашиты в апишку браузера и Джаваскрипта (Beacon API, Geolocation API, etc)
-
и еще штук двадцать пунктов.
Повторюсь, со всем этим у еврочиновников проблем нет, предупреждать не нужно. А вот куки им не понравились.
На этот счет у меня есть еще одна мысль, но о ней — в следующий раз.
-
-
Приши, улучшай
Кто читал Ильяхова, знает формулу “пиши, сокращай”. Суть в том, что хороший текст не пишут сразу. Пишется болванка, полная косяков, а потом по ней многократно проходят: здесь снимают стружку, тут, наоборот, доливают свинца. Только тогда текст становится сильным.
То же самое с кодом: недостаточно его написать и проверить. Когда тесты написаны и код рабочий, нужно пройтись по нему и причесать. Дать нормальные имена, вынести анонимные функции на верхний уровень. Одни участки кода разнести на промежуточные шаги, чтобы не было слишком плотно. Другие, наоборот, сократить.
Здесь нет точных правил, важно чутье — как воспримет этот код другой человек. Будет ему ли понятна логика, насколько легко внести изменения.
И если есть программисты, которые комитят рабочий код, то со второй фазой все очень плохо. Кажется, что когда код заработал, у программиста щелкает какое-то реле — работа сделана, закрывай таску. Потратить двадцать минут на причесывание кода — не знаем, не слышали.
И да, этим страдают в том числе кложуристы. Кто-то напел им, что на Кложе получается божественный код, который хорош сам по себе. Иммутабельность, лисп, дэ-эс-эль, бла-бла.
Читаю чужой код: коллекцию пропускают через пачку
map
иpartition-by
. В каждой из них — анонимная функция со вложенными reduce и другими анонимными функциями. Партицирование замкнуто на другой коллекции, которая что-то достает из первой. Неделимый блок кода размером с экран. Функция в функции внутри функции.Нечитаемый ад. Писал его не молодой программист, надо полагать, с опытом. И то же самое: как только он дошел до стадии “работает”, то оформил PR — и получил апрув коллег.
Давайте не будем так делать. Если код работает, это не значит, что задачу пора закрывать. От двадцати минут, потраченных сверху, вреда не будет — прод не сгорит, менеджер подождет. Зато спасет день коллеги, который вкатывается в проект.
-
Две системы клавиш
Это короткая заметка, которую хотелось бы развить в будущем.
Есть только два редактора, в которых хоткеи сделаны правильно — это Вим и Емакс. Не то чтобы я маньяк, но других вариантов не вижу.
Возьмем современный редактор и посмотрим на хоткеи. Нажатие на кнопку приводит к печати символа, и это логично. Чтобы вместо печати было действие, кнопку нажимают со служебной клавишей, скажем Ctrl, Alt, Command. Все это тоже логично.
Но вот беда — многие комбинации заняты дефолтами! Ctrl-Q, T, N, O, P — все это системные действия. Еще десяток клавиш уходит на навигацию каретки, еще десяток — на удаление строк, слов и символов, и привет — Ctrl уже исчерпан. Попутно нужно учесть, что на Маке роль Ctrl играет Command, поэтому удвоения емкости это не дает.
Остается Alt, но на него могут быть повешены хоткеи терминала и операционки, и в итоге какой-нибудь Alt-N просто не дойдет до редактора. Ну и часто на Альт тоже много чего навешено.
Только в двух редакторах подумали о том, как решить проблему комплексно: это Vim и Emacs. В первом разграничили режимы ввода и команд. Я пытался в Вим, но не зашло. Уважаю тех, кто сидит в нем, потому что нравится сама концепция.
В Емаксе хоткеи сделали последовательностями, или цепочками. Например,
C-x f
открывает файл,C-x o
открывает новое окно,C-x C-c
—закрывает Емакс. Подход с цепочками позволяет делать домены хоткеев, когда наC-x
вешаются базовые функции, наC-h
— все, что имеет отношение к справке, наC-x r
— операции с прямоугольниками и так далее. Все это легко наращивается вглубь, не мешая остальным.Недавно загуглил, как вызвать в Идее выпадашку с методами. Знаете как? Command + F12! Если учесть, что F-клавиши на маке работают с зажатым Ctrl, получается три кнопки одновременно! Пользователь Идеи должен быть осьминогом, чтобы с этим совладать. А в Xcode на полном серьезе одна из функций навешена на Shift+Alt+Command+V. Я уже не помню какая, но точно помню подсказку в меню.
Не то чтобы это большая проблема; можно продуктивно работать в любом редакторе. Но повторюсь, системно к проблеме хоткеев подошли только в двух редакторах: Виме и Емаксе. Все остальное — ситуативно и нерасширяемо.
-
Выпрямить данные
В кложурном чатике скинули очередную задачу: дана такая-то мапа, нужно перегнать ее в другую мапу. Примерный исходник:
(var DATA {:kek {:foo {:type :LOL} :bar {:type :KEK}} :owo {:pip {:type :AGA}}})
Ожидание:
{:LOL {:foo :kek} :KEK {:bar :kek} :AGA {:pip :owo}}
Это очень упрощенный пример. Грубо говоря, нужно вывернуть вложенную мапу наизнанку: поместить наверх то, что сейчас внизу.
Большинство кложуристов подходят к этой задаче как есть. Они берут мапу и прогоняют ее через комбо
map
,mapcat
и анонимных функций. Кто-то накручиваетreduce
внутриreduce
. В целом работает, но как это читать и отлаживать — я не знаю.Я тоже скинул решение, точнее его часть, и оно зашло: понаставили пальчиков и огней. Раз так, стоит рассказать подробней, тем более что я замечаю, что никто так не делает.
Итак, если посмотреть на исходную мапу, станет ясно, что когда-то она была плоской таблицей:
kek foo LOL kek bar KEK owo pip AGA
Из-за того, что в левой части были повторы, кто-то решил избавиться от них группировкой. Но данные никуда не пропали: мы по-прежнему знаем, у кого какой атрибут. Посмотрев на самый вложенный элемент, легко проследить его путь. Например, обнаружить, что AGA начинается с owo.
Так вот, чтобы переколбасить эту мапу во что-то другое, нужно сперва выправить данные — привести их к таблице. Поможет макрос for. Он принимает несколько коллекций и строит декартово произведение их элементов. Примечательно, что каждая следующая коллекция может быть получена из предыдущих элементов. Код ниже строит ленивую таблицу:
(for [[k1 submap1] DATA [k2 submap2] submap1 [_ item] submap2] [k1 k2 item]) ([:kek :foo :LOL] [:kek :bar :KEK] [:owo :pip :AGA])
Теперь когда мапа развернута, можно сгруппировать ее по-другому. Как именно — это уже тривиальное дело, потому что, имея плоские данные, это делается на раз-два. Кроме того, нужно спросить себя — действительно ли нужна новая группировка? Может быть, лучше оставить как есть? Возможно, на той стороне тоже хотели бы плоские данные.
Я как-то рассказывал о нелепой ситуации со вложенностью. На одной стороне человек потеет, чтобы построить из списка вложенную мапу вида:
{:node shit :children [{:node crap :children [{:node fuck :children [...]}]}]}
А на второй стороне другой человек потеет, чтобы обойти ее как список. Оба пишут быдлокод и матерятся, а ради чего — не ясно. Такую структуру даже не каждый кложурист обойдет, потому что не все знают про tree-seq и зипперы.
Возвращаясь к теме переколбаса данных: хорошо бы запомнить аналогию. Плоские данные — это как лист бумаги, из которого можно сложить самолет, кораблик или лягушку-квакушку. Но переход от одной фигуры к другой происходит через развертку — то есть откату к нулевому состоянию. Только потом можно переходить к новому.
Наверняка найдется японец, который может сложить самолет в лягушку, минуя лист. Точно так же можно написать быдлокод, который переколбасит одну мапу в другую. Но это сложно, не очевидно, хрупко. Лучше избегать.
Видеть простейшую форму и возвращаться к ней, когда что-то идет не так — важный навык.
-
Файловые пути
У меня пожелание: давайте не будем строить файловые пути конкатенацией строк. Не будем сами и не позволим другим. Как увидите в ревью что-то вроде
file_path = some_dir + "/" + file_name
, сразу пишите комментарий: не клей строки, используй функции, для этого предназначенные.
Достоверно известен случай, когда человек потерял бизнес из-за ошибки в файлах. Это был небольшой хостинг, и чел выполнил на всех машинах
sudo rm -rf $FOO/$BAR
. Беда в том, что переменные не подхватились, и получилосьsudo rm -rf /
.Вы скажете, что есть флаг
-e
, чтобы вылететь с ошибкой, если переменная среды не задана. Ну, допустим. Однако ничто не мешает сделать такую же ошибку в коде. Например, кложуристы (которые, как известно, боги программирования), строят пути конкатенацией строк:(str some-dir "/" subdir "/" file-name)
И не знают, что функция
str
молча пропускает нуллы. Это значит, еслиfile-name
равен nil, то путь получится/some/dir/subdir/
. Если передать его в функцию удаления, можно удалить всю папку.По закону подлости, если что-то “можно”, то оно случается. Сегодня отлаживал этот баг. Человек строит путь примерно так:
(str "/tmp/" (get-dir-name ...))
Этот путь передается в функцию, которая рекурсивно удаляет папку. Но во-первых, функция удаления была с багом и ничего не удаляла. А как только я починил, выяснилось: при особых условиях
(get-dir-name...)
выдает nil, и путь получается /tmp/. Функция исправно удаляет весь /tmp, в котором масса нужных файлов.Мораль: работая с путями как строками, легко отстрелить ногу. Вдвойне печально, что это делают снова и снова. Я с этим борюсь, и вы помогайте.
Если речь про Кложу без библиотек, испольуйте
io/file
:(io/file "/tmp" subdir filename) java.io.File<"/tmp/subsir/filename.txt">
Если один из аргументов nil, оно свалится с ошибкой. А так есть либы вроде
babashka/fs
. -
Зачем OpenAPI?
Чего не могу понять, так это одержимость OpenAPI. Казалось бы, нужна апишка на сайте — ну, сделай как тебе удобно. Но люди берут OpenAPI, крафтят спеку, генерят по ней контроллеры, схемы, тесты. Превозмогают, потеют и потом рассказывают: смотрите, наша апишка по стандарту OpenAPI.
А кого волнует этот ваш OpenAPI? Расстрою: никому не интересно, какая у вас апишка. Пользователю все равно, что гоняетя под капотом. Для программистов на Питоне, как правило, пишут клиентские библиотеки. Вызывая метод
client.get_user(id=42)
, программист в гробу видал, что там у вас —GET
,POST
, джейсон или XML. Никто на это не смотрит.Если точнее, на это смотрят только кложуристы, потому что для них клиентских библиотек никто не пишет. Но кого интересуют проблемы кложуристов? Они сами напишут клиент поверх чего нужно.
За много лет я не припомню, чтобы от OpenAPI была какая-то польза. А вот проблем — целый мешок. Это стандарт, которому нужно следовать; это определенные инструменты, которые навязывают игру. Инструмент X написан на Руби, ставь его и миллион пакетов. Инструмент Y написан на Ноде, ставь ее тоже и качай половину npm. Я неделю настраивал swagger в докере, чтобы он показывал веб-страничку со спекой. Команда привязала гирю к ноге и удивляется: почему разработка идет так медленно?
Когда мне нужна апишка, я делаю простой RPC: команда-параметры, команда-параметры. Все в теле запроса, а не как в REST, где один параметр в заголовке, второй в адресной строке, третий черт знает где. В теле гоняю либо JSON, либо message pack в зависимости от content type.
Это просто, это быстро, это прозрачно. В коде большая мапа вида
{action {doc ... schema-in schema-out handler ...}}
По текущей команде я вынимаю схему, проверяю вход, вызываю функцию
handler
с параметрами. Если дебаг, то проверяю выходные данные. Один раз настроил этот словарь и потом только наращиваешь.Если нужна документация, пишется код, который пробегает по словарю и рендерит markdown-файл. В нем список команд, описание из поля
doc
и схемы ввода-вывода. Если нужно, md-файлик рендерится в HTML или PDF.Но серьезным людям этого не понять. Им нужна OpenAPI-спека, чтобы что-то генерить и чему-то соответсвовать. Пишутся запредельные объемы тулинга под OpenAPI. Бывает, в Кложу приходит бывший рубист и заводит песню: мол, в моих Рельсах есть библиотека, которая по спеке сгенерит контроллер и модели, напишет тесты, а у вас в Кложе ничего нет… блин, потому я и довольный, что нет.
На самом деле я был разок в проекте на Кложе, где по OpenAPI-спеке генерили код. Два слова: это ужасно. Ни при каких обстоятельствах не сяду за это снова. Генерация — это стремно, это хрупко, это километровые диффы. Духота, трение и тошнота.
И никому не прихоит в голову спросить — зачем? Какую проблему ты решаешь своим OpenAPI? Зачем соответствовать чужому стандарту, который не контролируешь? Чтобы что?
-
Видео с митапа о Postgres
Появилась запись митапа:
Примерно час я рассказываю про всякие технические кишки, потом дискуссия с Николаем (CTO Самураев) и Владимиром (разработчиком JDBC-драйвера для Postgres).
Не обещаю, что будет интересно, скорее сильно на любителя.
-
Copilot и документация
Вчера провел с коллегой эксперимент.
Коллега подключил в редакторе Copilot и говорит — смотри, как круто он генерит комментарии к функции! Скажем, для функции
generate-time-buckets(account, time-frames)
пишет что-то вроде “generate time buckets for given account and time frames”.Мда, содержательно: для
generate-time-buckets
получить “generate time buckets”… Мне такой подход кажется малоинформативным: очевидно, Copilot берет сигнатуру и аргументы, конкатит их, разбивает на лексемы и причесывает, чтобы смотрелось по-человечески.Предложил эксперимент: пишем фукнцию, которая тупо складывает два числа:
return a + b
. Называем функциюdelete-file
и просим Copilot написать докстринг. Что он пишет? Правильно, “Delete а file”. Есть и второй вариант — “Delete a file from S3”, видимо, для корпоративных клиентов в облаке.Словом, теперь у нас ИИ-комментарии, а чего мы добились и какую проблему решили — не понятно.
-
Зачем спринты?
За все время работы в айти я не понял, зачем нужны спринты.
Как правило, спринт — это период от двух недель до месяца. В него набирают задач и пытаются их сделать к концу спринта. Потом спринты оценивают: этот был лучше, чем в прошлый раз, а этот хуже. Тут собрали двадцать стори-поинтов, а тут восемнадцать. Проводят ретро, которые убивают время.
Все это кажется мне бредом. Зачем делить время на равные участки? Зачем считать стори-поинты? Какое знание дает цифра 18 стори-поинтов? Чем это хуже, чем 20 поинтов?
В чем проблема, если задача из одного спринта переносится в другой? Кто от этого пострадал? Если задача не умещается в спринт, то может просто не впихивать невпихуемое?
Нужно обсудить процесс? Так проведи серию звонков 1 на 1 с разработчиками. Не обязательно ждать ретро.
В моем понимании процесс управляется релизами. Скажем, поставили мы задачу выкатить новый релиз через два месяца. Договорились, что в релиз войдет то, се, пятое-десятое. Не успеваем? Что-то упрощаем, что-то откладываем в следующий релиз.
Зачем нам спринты? Почему все следуют карго-культу гуглов-амазонов? Нужно думать своей головой, а не как менеджер Гугла.
UPD: сюда же относятся эстимейты. Воистину, самое тупое и бесмыссленое, что есть в айти. Эстимейтить все и вся, ошибаться на порядок и потом рвать задницу, чтобы успеть.
-
Шкала ООП
Когда говорят про ООП, упоминают всякие солиды, наследование и прочее. И не говорят вот про что (а надо бы).
Если в языке присутствует ООП, то типизация объектов размывается. Только структуры и функции дают четкие типы; объекты, наоборот, разрушают их путем абстракций. Они замыливают глаз.
Скажем, у нас есть классы
Dog
иCat
, унаследованные отAnimal
. Пока мы передаем собак и котов явно, все хорошо. Но когда мы передаем их как животных (Animal
), там может быть что угодно. В итоге на одни и те же данные смотришь по-разному.Если предположить, что
Animal
наследуется еще от чего-то, то получится шкала отObject
кDog
/Cat
, и как смотреть на объект — зависит от контекста. В некоторых случаях делают даун-каст и ап-каст: передают кота в виде объекта, а потом проверяют: если это кот, то одно, если собака, то другое, иначе эксепшен.Эту шкалу (
Object
—Animal
—Mammal
—Dog
) важно держать в голове и знать, где находишься сейчас. Лично мне она доставляет много хлопот, когда я пишу на ООП-языках, например Джаве. Шкала — это не вкл/выкл, а некая доля, позиция в в дереве, следить за которой сложнее.Может быть, кто-то лучше выразил эту мысль, но либо я не слышал, либо забыл.