-
Apple VR
В рекламе Эпла есть совершенно криповый момент. Дочки играют на полу, а батя смотрит на них сквозь гарнитуру — снимает.
Выглядит он как Дарт Вейдер или инвалид, вынужденный таскать какой-то девайс. Уж наверное игра с детьми — тот случай, когда нужно убрать гаджеты и предаться живому общению. А батя напялил ведро на голову и стал похож на киборгов из “Призрака в доспехах”, которым ставили бюджетные протезы вместо глаз.
Я даже в телефон не смотрю при детях, чтобы не подавать плохой пример. А тут такое, жесть.
-
Десять советов, которые я дал бы себе в молодости
Есть такой пошлый формат — кто-то успешный дает советы молодой аудитории. Заводи знакомства, качай софт-скилы, фокусируйся, помни о главном. Вся эта олдовая мудрость, поучение молодых, вы поняли.
Каждый раз удивляюсь одной вещи. Почему-то никто не спросит докладчика: милейший, а ты следовал своим советам в 20 лет? Ответ очевиден — конечно нет. Он просто херачил, шел напролом к цели и вот пришел. Теперь его пригласили выступать перед публикой.
Как правило, человек плохо оценивает себя. Даже если он видит, что известней и богаче других, то находит этому нелепые объяснения. Это ошибка выжившего: причины успеха вовсе не те, что ты несешь со сцены. Они могут быть сомнительными, криминальными или вообще неизвестными. Это может быть совпадение времени и места. Но что-то нужно сказать, и он говорит, хотя все это не имеет смысла.
В самом деле: начните делать то, что советовали Стив Джобс и Билл Гейтс, и ни к какому успеху вы не придете. Более того, вы же не пойдете с них догонять: смотри, я делал как ты говорил, но не помогает. Зачем тогда слушать, если нет гарантий?
Советы успешных людей — это тьфу и растереть. Они пришли к цели без каких-либо советов, а значит, вы можете так же. Вполне возможно, в зале сидит потенциальный Билл Гейтс, но вместо того, чтобы идти к мечте, он слушает чью-то блажь.
Сюда же относятся курсы успешного успеха, интервью Дудей, всякие блоггеры и прочее. Пока вы их слушаете, вы не делаете свое и льете воду на чужую мельницу.
Поэтому один совет, который я могу дать — поменьше слушать чужих мнений. Делай то, что считаешь нужным и иди с этим правилом через всю жизнь.
-
SQL и DISTINCT ON
Еще одна полезная штукенция в SQL — это оператор
DISTINCT ON
. От обычногоDISTINCT
он отличается тем, что определяет уникальность записей не по всем полям, а только указанным. Эта особенность позволяет писать интересные запросы.Предположим, мы ведем таблицу курсов валют относительно рубля. Вот ее структура:
create table rates ( id serial primary key, value text not null, instant timestamp without time zone not null default now(), cost bigint not null );
и данные:
insert into rates (value, cost) values ('usd', 7968); insert into rates (value, cost) values ('usd', 7988); insert into rates (value, cost) values ('usd', 8031); insert into rates (value, cost) values ('eur', 8623); insert into rates (value, cost) values ('eur', 8581); insert into rates (value, cost) values ('eur', 8699);
Величины хранятся целым числом в центах. Все запросы я специально ввел по одному, чтобы у курсов были разные даты.
У подобных таблиц особенность: часто нужны их срезы по датам, например первые или последние на текущий момент. На первый взгляд не ясно, как это делать. До того, как я узнал про
DISTINCT ON
, я делал весьма топорно: сперва группировал по валюте, чтобы получить максимальную дату:select value, max(instant) as instant from rates group by value; value | instant -------+---------------------------- eur | 2023-06-02 22:17:04.902392 usd | 2023-06-02 22:16:57.007947
, а затем оборачивал это в подзапрос и соединял с основной таблицей по валюте и дате:
select rates.* from rates join ( select value, max(instant) as instant from rates group by value ) as sub on rates.value = sub.value and rates.instant = sub.instant; id | value | instant | cost ----+-------+----------------------------+------ 6 | usd | 2023-06-02 22:16:57.007947 | 8031 9 | eur | 2023-06-02 22:17:04.902392 | 8699
Это неуклюже и требует индексов для эффективного JOIN.
Гораздо лучше смотрится с
DISTINCT ON
. Укажем в запросе, что записи уникальны в разрезе валют. В этом случае в выборке останется одна запись сusd
и одна сeur
, а остальные будут пропущены. Остается отсортировать их так, чтобы первыми шли записи с максимальной датой или минимальной.Пример с минимальной датой:
select distinct on (value) * from rates order by value, instant; id | value | instant | cost ----+-------+----------------------------+------ 7 | eur | 2023-06-02 22:16:59.46867 | 8623 4 | usd | 2023-06-02 22:16:50.603444 | 7968
А это — с максимальной, потому что записи следуют по убываю
instant
:select distinct on (value) * from rates order by value, instant desc; id | value | instant | cost ----+-------+----------------------------+------ 9 | eur | 2023-06-02 22:17:04.902392 | 8699 6 | usd | 2023-06-02 22:16:57.007947 | 8031
Обратите внимание, что сортировка
order by
начинается с того поля, которое указано вdistinct on
. За счет этого база эффективно пропускает дубли без промежуточных списков.Если записей много, понадобится индекс на поле уникальности и критерий сортировки, в нашем случае
value
иinstant
:CREATE INDEX rates_last_idx ON rates (value, instant desc);
Похоже решаются другие задачи, связанные со временем или версиями, например прогноз погоды, версионирование статей или сущностей.
-
Логи SQL (2)
В последнее время я много занимаюсь SQL, поэтому вот еще пара приемов.
Настройка
log_duration = on
добавляет в лог длительность каждого запроса. Удобно, потому что избавляет от мусорного кода, который перед каждым запросом запоминает текущее время, выполняет его, находит новое время и печатает разницу в консоль. Postgres берет все это на себя:statement: create table table8855 (id integer) duration: 11.607 ms statement: BEGIN duration: 0.046 ms statement: insert into table8855 values (1), (2) duration: 0.368 ms statement: ROLLBACK duration: 0.052 ms statement: select * from table8855 duration: 0.414 ms
Вторая настройка
log_line_prefix
отвечает за информацию, которая показана в логе перед запросом. По умолчанию это время с точностью до миллисекунд и номер процесса:2023-06-02 14:45:52.395 MSK [57002] LOG: statement: BEGIN
Я предпочитаю вот что:
log_line_prefix = '%t [%v]'
-
%t
выводит время без миллисекунд, что короче; -
%v
означает виртуальный номер транзакции. Виртуальный потому, что до ее завершения система не знает, какой номер будет в итоге. Поэтому выводится пара[tmin/tmax]
— диапазон транзакций, доступный запросу в текущий момент.
Зачем это нужно? Бывают баги, когда разработчик ошибается с транзакциями, например открывает ее, но шлет запросы через старое соединение. Если что-то упадет в процессе, изменения будут частичными, что выливается проблемы на проде.
С логами ниже легко понять, кто на ком стоял. Запросы с меткой
[3/5]
относятся к одной транзакции, а[4/2]
— к другой.[3/4]LOG: create table table8856 (id integer) [3/5]LOG: BEGIN [3/5]LOG: SET TRANSACTION ISOLATION LEVEL SERIALIZABLE [3/5]LOG: insert into table8856 values (1), (2) [3/5]LOG: select * from table8856 [4/2]LOG: select * from table8856 [3/5]LOG: COMMIT
Заодно можно посчитать, сколько было транзакций, насколько они пересекаются и, возможно, разнести их по времени.
-
-
Логи SQL
Расскажу один прием, полезный в разработке.
Если я работаю с PostgreSQL, то включаю логирование всех выражений. В Докере это делается передачей параметра
-E
как в примере ниже:version: "3.6" services: postgres: image: postgres:14 command: -E
С ним все запросы видны в терминале, где запущен Докер.
Если база работает нативно, в файлик
postgresql.conf
добавляю выражение:log_statement = 'all'
Оно там есть из коробки, но закомментировано. В отдельной вкладке вызываю вечный
tail
:> tail -f '/path/to/postgresql.log'
Далее я пишу код, гоняю тесты и посматриваю логи. Зачем? Потому что за много лет я выявил столько чуши, что просто не описать. Например:
-
запросы
WHERE ID IN
на тысячи айдишников; -
группировка по всем(!) полям таблицы:
GROUP BY id, name, email, etc...
; -
молчаливый откат транзакции без выброса исключения. Запрос упал, но разработчик молча идет дальше;
-
изменения без транзакции там, где она должна быть;
-
мусорные запросы в
pg_catalog
и другие служебные таблицы, потому что какие-то свойства не заданы в коде; -
чтение записей поштучно по ID в цикле (200, 500 шагов и больше);
-
просто всякие дикие конструкции.
Все это я собираю и открывают тикеты. Некоторые даже удается исправить.
Лог базы — своего рода осиное гнездо. Открой его — и почти наверняка уйдешь в ступор от того, какую дичь генерит ORM или “умная” библиотека. Или разработчик, вооруженный всем этим добром.
Так и живем.
-
-
Задачник по Паскалю
В сотый раз повторяется одно и то же. Человек учит язык — неважно Питон, Кложу, что угодно — и спрашивает: посоветуйте задачки. Ему кидают ссылки на Exercism, Project Euler и похожие сайты. Мой ответ не меняется годами: покупаешь задачник по Турбо-Паскалю за 300 рублей и решаешь на своей Кложе, Питоне или PHP. Или просто качаешь из интернета первый попавшийся.
Думаете, кто-то так делает? Конечно, нет. Мало того, поднимается драма: как так, у нас современные языки, а ты со своим Паскалем! Оставь дерьмо мамонта в покое.
Сколько себя помню, всегда поражала эта избирательность. Эти задачи буду решать, а эти не буду. Эти современные и хорошие, а эти старые. Иной раз мне доказывают, мол, концепции Паскаля плохо ложатся на Кложу, поэтому решать их неудобно.
Я аж представил себе. Начальство: Иван, вот тебе задача, надо сделать то и это. А я такой: ребята, она плохо ложится на концепцию языка, не буду делать. А они: конечно, дорогой, вот задача получше, а эту делать не будем. Исправим, чтобы ложилась.
Думаю, ясно, что решать надо все задачи. В том числе те, что в старых учебниках. А почему именно из учебников, тому следуют пункты.
-
Сразу много задач. Купив сборник, вы получите не две-три интересных задачки, а две-три сотни.
-
Если это толковый сборник, задачи в нем собраны по разделам: арифметика, циклы, массивы, связные списки, файлы, графика и так далее. В каждом разделе задачи идут по нарастанию сложности. Это позволит набить руку в той теме, где вы плаваете.
-
Поскольку задачи составлены для школьников и студентов, они перекликаются с учебной программой. Решая задачи, вы заодно ее подтяните.
Примеры задач, которые можно сделать на любом языке:
-
квадратное уравнение. По коэффициентам a, b, c найти корни или сообщить, что их нет.
-
игра “угадай число”. Компьютер загадывает число от 0 до 100, ваша задача — угадать его. На ваши попытки компьютер отвечает “нет, больше”, “нет, меньше” или “угадал”.
-
пересечение прямоугольников, точки и окружности по их координатам
-
задачи на массивы и связные списки, чтобы лучше понимать коллекции
-
графики, исследование функций.
-
запись данных о сотруднике в файл и их чтение.
Что из этого плохо ложится на современный язык? Может, за “не хочу” стоит “не могу” или “лень вспоминать”?
Какой смысл открывать условный Exercism, регистрироваться, подтверждать почту, отписываться от рассылки? Чтобы что? Я уже накидал задач на неделю, сиди решай. В учебнике их на год. Что ты хочешь найти? Чего не хватает?
Наконец, еще один пункт в пользу сборника задач — это работа с книгой. Не знаю как это работает, но бумажная книга эффективней, чем тупление в монитор. Для меня это совершенно очевидно. Скорей всего, включается другой отдел мозга, но объяснение не так важно.
Изучать язык тяжело. Красивые сайты пытаются это скрыть или развлечь пользователя, потому что их цель — донаты и показ рекламы. У задачника этой цели нет. Это более хардкорный вариант, но тем он и лучше.
-
-
Звонки в мессаджерах
Совет — чтобы не бесить людей, звоните на обычный мобильный. Не в Телеграм, не в Ватсап и не Вайбер. Мало что раздражает так сильно, как звонки в какой-то программе.
Только что звонил родственник в Телеграме. Это просто п..здец, простите. У меня три устройства: телефон, ноут, комп. Все три одновременно завыли, на каждом распахнулось окно. Ощущение, что воздушная тревога. Через две секунды вызов на телефоне пропал, но продолжился на ноуте. Что происходит?
То же самое с Ватсапом. Сидишь, быдлокодишь под музыку, весь в себе. Внезапно подскакиваешь от звонка и окна на весь экран. Просто издевательство.
Даже если не обращать на это внимание, есть важный момент. У звонков в Телеграме и иже с ним ужасное качество. Звук пропадает, сыпется, словом — гораздо хуже мобильной связи.
Причина в том, как устроены звонки в приложениях. Каждое из них звонит на местный номер. Там сервер, который заворачивает звук в трафик и шлет куда-то далеко. Оттуда он направляется в страну получателя, и ему звонят с местного номера. Получается звонок с местного на местный с целой пропастью посередине. Даже если собеседники в одном здании, маршрут выходит изрядный.
Конечно, при звонке из России в условную Турцию вариантов не остается — не платить же опсосу по 70 рублей за минуту. Здесь Телеграм и Ватсап выручают. Но удивляют нищеброды-соседи или знакомые из твоего города, которые упорно звонят из приложения. Зачем? Чтобы сэкономить пять минут? Они везде идут пакетами, что там экономить? Хуже пенсионеров, честное слово.
Раздражает еще то, что на Айфоне последние номера хранятся с учетом приложения. Например, когда мне звонят с Ватсапа, я сбрасываю и перезваниваю с мобильного. Но если нажать на последний вызов, выскочит проклятый Ватсап. Приходится заходить в контакт и жать на тот же номер с мобильным типом.
Раз и навсегда: для связи внутри страны — только мобильная связь. Мессенджеры — если вы в разных странах или важна приватность, причем по обоюдному согласию. В остальном звонить из приложения — днище и зашквар. Объясните, пожалуйста, тем, кто этого не понимает.
PS: в Телеграме можно запретить входящие звонки всем или с учетом контактов. В других программах, кажется, это невозможно.
-
PG: Postgres-related libraries for Clojure
The PG project holds a set of packages related to PostgreSQL. This is a breakdown of my unsuccessful attempt to write a PostgreSQL client in pure Clojure (stored in the
_
directory). Although I didn’t achieve the goal, some parts of the code are now shipped as separated packages and might be useful for someone.At the moment, the most interesting modules are
pg-copy
andpg-copy-jdbc
thatCOPY
the data using the binary Postgres format which is faster than CSV.Table of Contents
-
Свое
Иногда в фирме пишут велосипеды даже с учетом того, что есть открытые библиотеки. Раньше я по-разному к этому относился, а теперь пришел к компромиссу.
С одной стороны, кустарные поделки напрягают меня как разработчика. Устраиваясь в фирму, меньше всего хочется встретить свою ORM, свой роутинг или шаблонную систему. Обычно это глючный код, написанный по принципу “потому что можем”. Хочется работать с популярными библиотеками, у которых документация, сообщество, канал в Слаке.
С другой стороны, свои решения продвигают фирму на рынке и в сообществе. Это привлекает разработчиков, служит рекламой, упрощает найм. В вакансиях можно указать, что задание должно быть сделано именно на этих библиотеках, потому что на них построены решения фирмы.
Правда, чтобы это работало, фирма должна как можно раньше выкладывать свои решения в open source. Это то же самое, что с ребенком: если вечно держать его взаперти, будет вред. Библиотека должна быть на Гитхабе, должно быть сообщество, словом — фирма должна как можно сильнее пиарить свой код, чтобы считаться центром идей в своей области. И программисты будут в нее стремиться.
Удивляет порой, что у фирмы есть неплохие решения, но они киснут в приватных репозиториях — без документации, без развития. Что бы не выложить их в паблик? Я вообще не припомню кода, который нельзя было бы опубликовать именно по причине кражи бизнеса. За любым кодом стоит понимание его командой и видение будущего. Скачав исходники Яндекса вы не сделаете новый Яндекс. Единственное опасение — это ошибки и низкий уровень кода, за который стыдно. Именно это и лечит open source: его код сразу готов к жизненным трудностям.
Бесит, когда делаешь тестовое задание на популярных библиотеках, а в команде говорят: забудь, чему тебя учили, у нас своя атмосфера. Ребят: свою атмосферу нужно проталкивать вовне! Задавать стандарты, вовлекать людей, а не держать в тайне от мира. Вы что-то скрываете, а скрывать нечего.
-
Уровень
Пока мир сходит с ума по искусственному интеллекту, всплакну о низком уровне разработчиков. Подкатило, нужно выплеснуть.
Ситуация: разработчик пишет функцию
get-by-id
, чтобы достать сущность из базы. Не моргнув глазом, он передает ее вmap
на пять тысяч элементов. Это, на минутку, один запрос, а запросов может десятки в секунду. Подобные вещи приходится ловить в code review и объяснять, что один запрос лучше, чем пять тысяч.Идет 2023 год, а программисты пишут SQL конкатенацией строк. В порядке вещей код на два экрана с
format
,str
иjoin
, который ни понять, ни отладить. Полученный запрос уходит в базу, и дай бог, чтобы оно работало. Если передать nil или пустой список, получим битый SQL, потому что автор этого не предусмотрел. И конечно, инъекции во все поля.Почему-то программисты не могут записать и получить данные из базы. Им нужна ORM, и чтобы она сразу мапилась на REST. Получается километр глючного кода без документации и поддержки. Автор ORM отлынивает от задач под видом ее доработки. Часть команды уходит в партизаны: работают с базой через SELECT и UPDATE в обход ORM. Так спокойней, главное чтобы автор ORM не зашел в пулл-реквест.
Разработчики игнорируют линтеры. Проект уже не раз сменил команду, люди пишут в разных редакторах, и ни у кого он толком не настроен. Кривые отступы, экраны закомментированного кода, неиспользуемые импорты и переменные. Это в порядке вещей.
Программисты не доводят дело до конца, хотя в бóльшей степени это упрек руководству. Например, у кого-то задача, чтобы была документация Swagger. Человек пишет генератор json-файла, покрывает тестами, все готово. Осталось захостить, чтобы документацию увидели клиенты. Но прилетает горящая задача или девопс-инженер уходит в отпуск. В итоге документация есть, но ее никто не видит. Задача не достигнута, время потрачено зря, но это никого не смущает. Недостигнутые цели я вижу постоянно.
Сюда же относятся висящие пул-реквесты. Открываешь борду и видишь у кого пять, а у кого семь пул-реквестов. Зачем программист писал код, если его не принимают? Если бы он смотрел Ютуб, эффект был бы тот же. Почти во всех системах можно задать auto-expire, не говоря уж о ботах, которых полно.
Беда с локальным окружением. Программисту лень потратить день на
docker-compose.yaml
, чтобы сервисы работали локально. Приходится объяснять, что локальный ресурс лучше стейджинга где-то в Амазоне.Иной программист генерит айдишники для базы вручную. Берет рандом от 0 до 9999 и густо перчит номером треда, числом миллисекунд и фазой Луны. И это работает в проде.
Программисты любят кокетничать, что уперлись в базу: терабайты данных, не вывозит нагрузку, все дела. При этом в базу уходят кривейшие запросы, а сама она превратилась в свалку, потому что там хранят кэш, логи S3 и черт знает что.
У программистов туго с отладкой. Под отладкой я имею в виду остановку кода на середине, чтобы выяснить локальные переменные. Это могут единицы. Остальные либо ставят принты и мотают экраны логов, либо вообще сдаются. Иные заявляют, что в божественной Кложе отладчик не нужен. Это вообще за гранью.
И наконец, главное: карго-культ. Так писали до нас, так пишем и мы. Кривой нейминг? Дурацкая организация кода? Ничего, консистенси важнее. И хотя прошлый разраб видел Кложу второй раз в жизни, все следуют его бредовым начинаниям.
Все это я видел даже когда писал на Дельфи и 1С до прихода в промышленную разработку. Меняются лица, а косяки остаются с нами. Конечно, я их тоже совершал, но будучи записанными, они переживаются легче.
Все, отпустило, работаем дальше.