-
Запуск InDesign
Готовлю книжку к печати и поэтому вынужден иметь дело с Адобом. Избежать этого шага невозможно — некоторые программы заменить нечем. Несмотря на то, что в адобовских программах я работаю буквально 20 лет, поминутно матерюсь из-за их косяков. Покажу серьезный прокол, который заслуживает отдельного поста.
Перед вами видео, где я запускаю Индизайн. Обратите внимание, что приложение стартует быстро: буквально пару секунд занимает сплеш-скрин, а дальше распахивается окно. Это, кстати, не заслуга Адоба: просто в процессе записи я несколько раз открывал и закрывал его, и поэтому оно прогрето. Холодный запуск будет медленней.
Но дело не в этом. После того, как окно распахнется на весь экран, программа вступает во вторую фазу: загружает внутренний экран с последними файлами и рекламой новых фич. Эта загрузка длится десять секунд. Целых. Десять. Секунд. Пока он не просрется, не видно последних файлов и кнопок создания нового документа.
В итоге сидишь и тупишь в пустоту, пока что-то там не загрузится. Однако если нажать Ctrl+O, откроется диалог выбора файла. Выходит, программа прекрасно работает, даже если главный экран не загрузился! Если выбрать в диалоге файл с проектом, он откроется мгновенно. Можно не ждать эти 10 секунд, а открыть файл ручками и работать. Это подтверждает второе видео:
Такие вот интересные программы Адоба.
Чем-то это напоминает современные веб-приложения: программа загрузилась, но это присказка, не сказка. Мы только загрузили скрипты, далее они выгребут свое дерьмо с серверов и только затем покажут приложение. На телевизорах такое постоянно: первый прогрес-бар показывает загрузку приложения на уровне ОС, а приложение запускает второй прогресс-бар для загрузки своего барахла.
Печально, что эту ерунду нельзя объединить в один шаг, потому что как пользователю мне эти фазы до одного места: один хрен программа не работает. Вдвойне печальней, что это зараза переходит на десктоп.
Подозреваю, что упомянутый главный экран — ничто иное, как Хром + Node.js + React. Это видно по косвенным признакам: он похож на Слак и прочие поделки, которые не могут быть отрисованы частично, а только целиком. Возможно, скрипты ломятся в сеть, но блокируются из-за санкций или бог знает чего. Ожидание в 10 секунд похоже на таймаут, заданный при отправке HTTP-запроса. Спасибо, что хотя бы выставили его: по умолчанию он равен 30 секунд.
Странно, что последние файлы отображаются внутри браузера, хотя никак не связаны с ним. Это банальная глупость, деградация разработки. Надо сказать, я не удивлен. В Адобе много что делают на Node.js, например дашборд, из которого ставятся приложения. Я как-то шарился по папкам и нашел ворох скриптов. Плюс часто выскакивает диалог с текстом “приложение node хочет доступ к такому-то сертификату.”
В сотый раз повторю тезис: сапожник без сапог. Фирма, которая тридцать лет пишет настольные программы под Винду и Мак, не может нормально показать последние файлы. Для загрузки приложений делается поделка па Хроме и Js, глючная и тормозная. Деградация софта в угоду менеджменту: сделать тяп-ляп, но зато быстро и получить повышение.
Хочется верить, что индустрия печати пересядет с иглы Адоба на что-то другое, но пока что просвета не видно.
-
Avoid code you cannot debug
This is a small tip I’d like to share with Clojure programmers.
In a project, avoid code you cannot debug. It’s simple: if you can put a tag like
#debug
or similar somewhere in the middle, run a test and hang in a debugging session, you’re good. But if you cannot, you’ll be in trouble one day.Thus, any kind of DSL or yet another “smart” solution is a source of potential problems. Take Meander, for example. Imagine I have a map like this:
{:name "Ivan" :address {:city "Chita"}}
and I want it to become this:
{:name "Ivan" :city "Chita"}
With Meander, I would write:
(m/match {:name "Ivan" :address {:city "Chita"}} {:name ?name :address {:city ?city}} {:name ?name :city ?city})
and it works fine. But one day, my datasource suddenly returns a user without an address:
{:name "Ivan" :address nil} ;; or just {:name "Ivan"}
which is completely fine because a user might have no address associated with them. By passing that map into
m/match
, I expect it to return{:name "Ivan" :city nil}
but no: there will be an exception:(m/match {:name "Ivan" :address nil} {:name ?name :address {:city ?city}} {:name ?name :city ?city}) Unhandled clojure.lang.ExceptionInfo non exhaustive pattern match {}
The line “non exhaustive pattern match” tells nothing to me nor the ex-data does. The message is fuzzy, there is no context, the ex-data has nothing useful (it’s an empty map). Having such an entry in logs or Sentry would not help you in a bit.
Moreover, you cannot debug it. The
m/match
macro expands into a huge block of code. Debugging it somewhere in the middle would be quite challenging.Now compare it with a plain function that splits the data step by step:
(defn remap-user [entry] (let [{username :name :keys [address]} entry {:keys [city]} address] {:name username :city city}))
First, it works with both maps:
(remap-user {:name "Ivan"}) => {:name "Ivan", :city nil} (remap-user {:name "Ivan" :address {:city "Chita"}}) => {:name "Ivan", :city "Chita"}
Second, I can always put a debugging tag into that function and observe the local variables, the state and even run some expressions. With Meander, it’s just impossible or only possible with certain effort.
Third, if a city is required, I’d put something like this:
(assert city "The city is missing")
and get a clear exception I want.
Vast Meander patterns are completely unmaintainable. Pass something weird and you’ll get a “non exhaustive pattern match” message with no idea about what went wrong.
Finally, debugging is crucial. If you cannot hang in the middle of execution and observe the state, that’s bad. Most Clojure programmers believe it’s a special language liberating you from debugging errors, but it’s not true. Debugging has not gone anywhere even with such a great language as Clojure.
-
Пауза в Ютубе
Не помню, чтобы кто-то жаловался вот на что. Когда ставишь Ютуб на паузу, появляется доселе невидимый бар управления, который ложится аккурат на субтитры.
Речь идет о субтитрах в обучающих роликах, в основном языковых. Но это может быть доклад, митап или сцена из фильма.
Со своими субтитрами Ютуб решил ловко: в момент паузы они приподнимаются. Но не могут же сдвинуться буквы на видео! Частично проблему можно решить, изменив размеры окна, но это уловки для бедных.
Спрашивается, почему бы не вынести кнопки в отдельную полосу? Да, они займут место, ну и что? Почему проблему интерфейса рашают за счет контента? Почему интерфейс налазит на видео, вместо того чтобы быть снизу?
То есть Гугл решил: внизу видео что-то не нужное, поставим туда кнопки. А там, на минуточку, бывает текст, и его даже порой читают. Например я.
В других сервисах бывает еще хуже. Поставил на паузу — и появляются темные градиенты, жирные блямбы, выезжают скрытые кнопки, рекомендации и все прочее. Я просто хочу что-то рассмотреть или прочитать, почему мне мешают? Дизайнер, добавь в угол экрана две белые палочки — знак паузы. И не в центр, потому что в центре самое нужное, а в угол. Этого хватит. Но нет, нужно загадить как можно больше пространства.
Кстати, один из способов решить эту проблему — смотреть Ютуб в VLC, о чем я писал раньше. Слегка экстремально, но проблемы субтитров нет.
-
Корректура в PDF
Если кто не знает, откуда столько ненависти к PDF, сейчас объясню.
У PDF одно назначение — просмотр и печать документов. Это конечный артефакт, который получают при компиляции проекта — будь то Индизайн, Латех, что угодно. Править PDF после его получения — это то же самое, что править байтики в exe-файле, вместо того, чтобы поправить код.
Один раз я даже писал про возможности Preview по работе с PDF, но все это уловки для бедных, когда нет исходника.
Тезис о том, что с PDF нельзя работать как с документом, подтверждает следующий опыт: середина 2023 года, тридцать лет с момента появления PDF — и не найти программы, которая бы работала с ним без ошибок.
Если конкретно, то я готовлю вторую книгу к печати. Заказал корректуру, говорю: готов распечатать и прислать любой службой за мой счет. Нет, берем только PDF. Ладно, выслал, получил обратно с комментариями.
Теперь самое интересное. Внос правок сводится к шагам: перешел к очередному комментарию, поправил исходник, удалил коммент. Что же пошло не так? А вот что.
Preview на Mac M1 не может удалить некоторые комментарии. Те, что с типом Highlight — да, а Note — нет. Специально проверил, старый Preview на Интеле может, но поведение неочевидно. В целом работа с аннотациями в Preview глючная: коммент может висеть еще долго после удаления. Иногда фокус смещается произвольно и удаляется не тот коммент.
Не все это можно передать в картинках, но например: слева у комментария есть опция удаления, а справа у Note — нет.
Для демонстрации других косяков нужно записывать гифки, к чему я не готов.
Вы скажете — браузер. Не вопрос, открыл в Хроме, а там такое:
Плюс комменты нельзя удалять, read-only.
Может, Акробат наше все? Ну, уже сам факт, что установка требует sudo, внушает опасения. Ладно, поставил. Документ не открывается перетаскиванием в программу — только File -> Open, только хардкор. Удаление комментов работает, но если скопировать текст из PDF, получим такое:
Âòîðàÿ ãëàâà ïîñâÿùåíà ðåëÿöèîííûì áàçàì äàííûõ,
Словом, кто вносил корректуру, тот в цирке не смеется.
Повторю тезис из начала заметки: любая работа с PDF — это боль по определению. Он специально сделан так, чтобы правки были максимально затруднены. Бинарный формат, девять редакций, адское легаси. Любая программа, которая предлагает редактирование PDF — это н…балово. Оно работает только в примитивных случаях, а как что-то серьезней — сплошная беда.
Поэтому, кстати, я считаю, что корректор, который не работает с бумагой — это слабый корректор. Хороший как минимум не должен ее избегать.
UPD
Продолжение заметки. Добрые люди подсказали два варианта: PDF Expert и Master PDF Editor. Первая не обслуживает пользователей из России, но ставится из brew. Оказалось, она не работает без облачной учетной записи. Попап закрывает интерфейс, хоть усрись, но создай учетку. Скриншот не сохранил, прогу удалил. Поставил Master PDF Editor, на первый взгляд все отлично. Посидел два часа, вносил корректуру. Сохранил файл и перекинул на другой комп. Открываю, а там:
Знаете, это уже не смешно. Похоже, на вопрос из прошлой заметки — есть ли в природе программа, которая работает с PDF без ошибок — ответ все-таки отрицательный.
Эту работу я как-нибудь доделаю, но она стоила много нервов. Очень, очень много нервов.
-
Строки по 80
Иной айтишник нет-нет да пожалуется на разбиение строк по 80 символов. Считаю нужным прояснить ситуацию.
Коротко: если это текст в системе контроля версий — код, readme или вроде того — он должен быть разбит. Не обязательно по 80 символов, можно 90, 100 или 120. Главное выбрать число и следовать ему.
Обыватель думает, что число 80 связано с перфокартой. Да, на них действительно было столько дырок, потому что 80 символов умещалось в терминале. Но дело не только в этом. Откройте любую книгу и посчитайте число знаков в строке. Их будет от 60 до 80, в среднем 65. Почему?
Ответ никак не связан с перфокартой. Эти 65-70 знаков в сочетании с 10 шрифтом дают строку длиной 12 см. В свою очередь 12 см — та длина, которую глаз читает на комфортном расстоянии без поворота головы.
Как-то так:
Откройте любой журнал. Две колонки по 50 символов, потому что шрифт меньше. Сделайте в одну — никто не прочтет.
Теперь я растянул редактор на всю ширину. Как это читать? Вы будете крутить головой, как лошадь на привязи:
А так нормально:
Тут влетает бунтарь с криками: как так, за меня решили, задели хрупкие чувства! Дайте свободу, я сам настрою. Мне не жалко, но есть проблема: редактор приходится ужимать до такого состояния:
У меня так-то 400 файлов открыто, и под каждый менять размер окна — не много ль чести? Можно вынести файл в отдельное окно, но опять же — заморочки.
Далее — конфликты. Если параграф не нарезан на строки по 80 символов, исправить конфликты невозможно. Пожалуйста, прочтите это предложение еще раз: исправить конфликты невозможно. Без нарезки параграф — это единая строка, и если Вася исправил букву в начале, а Петя — в конце, это конфликт. Пишите претензии создателям Git, CSV и другим системам контроля версий. Их рабочая единица — строка, и других не предвидится.
Подобные конфликты решаются вручную и больше никак. Продвинутые тузлы подсветят изменения внутри строки, но что-то сложнее пары опечаток решить невозможно. У нас уже было такое: вместе с коллегой поправили документацию в markdown. В итоге я откатил свои правки и накладывал повторно на его мердж. Спасибо, больше не надо.
Наконец, в хорошем редакторе проблема нарезки не стоит в принципе.
C-x f
задает ширину колонки,M-q
поджимает текущий абзац. Чтобы поджать весь документ, жмемC-x h
иM-q
. Занимает секунду.Кстати, принцип, когда подряд идущие строки объединяются в параграф, а пустая строка отбивает параграфы, придумали не в markdown. Это сделал Кнут для своего TeX, за что ему честь и хвала.
Вот, пожалуй, все, что нужно знать про нарезку строк.
-
Гигиена в Git
Несколько правил, чтобы держать Git приличном виде.
Одна задача — один коммит. Слияние ветки происходит только через squash, другие методы запрещены.
В сообщении к коммиту указан только номер задачи и ее заголовок, например
[FOOBAR-123] Make some crap
. Иная графомания нежелательна — идите за ней в задачу.Перед слиянием коммиты переносят наверх с помощью rebase относительно родителя. Так вы лишний раз убедитесь, что все в порядке.
После слияния ветка удаляется автоматом.
У каждого PR есть expire. Висит две недели — значит никому не нужен и удаляется автоматом. В следующий раз команда будет расторопней.
Чем больше веток — тем хуже. В идеале это master с тегами релизов и feature-ветки. И больше ничего.
Пуш в мастер запрещен всем, кроме одного человека (техдиректора).
Слияние без апрува запрещено (а лучше двух).
Kdiff3 — пожалуй, лучшее средство разрешения конфликтов:
> brew install --cask kdiff3 # ~/.gitconfig [merge] tool = kdiff3 [diff] tool = kdiff3 [mergetool "kdiff3"] path = /Applications/kdiff3.app/Contents/MacOS/kdiff3 [difftool "kdiff3"] path = /Applications/kdiff3.app/Contents/MacOS/kdiff3
UPD Ограничение на один коммит не касается локальной ветки, в которой вы пилите задачу. Их может быть хоть два, хоть двести. Важно, что при слиянии методом squash они объединяются, и в главной ветке остается один коммит с номером задачи.
Бывает, коллега делает задачу неделями и после аппрува жмет “Merge”. В истории оказывается 50+ коммитов с подписями “working”, “fix tests”, “fix linter”, “updated” и так далее. Спрашивается, зачем это команде? В своей ветке делай что хочешь, а в общей соблюдай приличия.
-
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
Заодно можно посчитать, сколько было транзакций, насколько они пересекаются и, возможно, разнести их по времени.
-