• Корректура в 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
    

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

  • Логи 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: в Телеграме можно запретить входящие звонки всем или с учетом контактов. В других программах, кажется, это невозможно.

Страница 25 из 87