• Извинения за неудобства

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

    Конечно, нашему “ПАО ТНС Энерго Воронеж” (что за бредовое название) не знакомы эти правила. Что с них взять? Но давайте поучимся чужих ошибках.

    В этом месяце ребята лоханулись: разослали квитанции с битыми QR-кодами. Ну, бывает, вдруг Node.js-программист обновил package.json? Важно то, что с этим сделает руководство. А оно пишет: берите коды из прошлых писем и примите извинения. Но в кодах зашито назначение платежа, и Сбербанк не дает его поменять. А платеж по реквизитам это просто ад: у “ПАО ТНС” сто филиалов, и какой выбрать — неизвестно.

    Конечно, надо было сгенерить новые квитанции и разослать вместо сломанных. Простое правило: кто накосячил, тот и исправляет.

    Повторюсь, к ПАО ТНС Энерго Воронеж претензий особо нет. Большая фирма, бюрократия, и ничего кроме “примите извинения” они не могут. Для нас главное — не допустить такого в своей работе.

  • Ввод по маске

    Каждый мамкин фронтендер знает: если в форме есть поле телефона, ставим плагин jQuery под названием Masked Input и не паримся. С ним нельзя ввести буквы, скобки, дефисы и прочий мусор.

    Но мамкин фронтендер не знает: если вставить в поле “89623289677” (скажем, из Экселя или менеджера паролей), то всратый плагин поместит восьмерку после +7 и отбросит последнюю цифру. Результат будет как на второй картинке.

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

    Недаром говорят: благие намерения ведут в ад. Хотели сделать удобно на фронте, в итоге добавили баг.

  • Ошибки

    Главная беда этой нашей айтишечки — сообщения об ошибках, не связанные с их устранением. Примеров можно найти миллион, но вот вам один.

    Собираю одну фигню в докере, и команда apt-get update падает с сообщением, что репозиторий не подписан:

    The repository 'http://archive.ubuntu.com/ubuntu focal InRelease' is not signed.
    

    При этом на другом ноуте все собирается как надо.

    Оказалось, что это мулька самого Докера, и лечится она командой prune:

    docker image prune -f
    docker container prune -f
    

    Очевидно, без гугла и SO эту проблему не решить в принципе.

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

    Задача хорошего программиста в том, чтобы давать понятные сообщения об ошибках. Наверное, вас тоже бесило, когда Java говорит: port is already in use. Блин, какой порт? У меня их десять в проекте, тяжело добавить цифру в сообщение?

    Или тот же Вим. По всему миру люди страдают, не знают как из него выйти. Вопрос на SO набрал миллион просмотров и растет дальше. И что делают разработчики вима? Вместо того, чтобы добавить бар с действиями как в Nano, объясняют, что пользователи тупые.

    Напоминает мост в Питере со знаком “Газель не проедет”, где разбились сто Газелей. Вместо того, чтобы порвать на тряпки мэра и начальника ГИБДД, ставят знаки и постят картинки в Контакте.

    То же самое происходит у нас. Вместо понятного сообщения мы ждем, что его предоставит вышестоящий слой. Но это бессмысленно: не сделаем мы — не сделает никто.

  • Фаервол

    Лучшая вещь, что случилась со мной в последнее время — это установка фаервола на всех маках. Вот как это произошло.

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

    Идея как это победить простая — нужно отобрать у программы доступ в интернет. Я пошел настраивать встроенный фаервол, но оказалось, он убогий: закрывает только входящие соединения, а исходящие не трогает. В результате даже если добавить в список программу, она спокойно идет в сеть.

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

    Можно настроить гибкий контроль по урлам. Например, дать программе доступ к хосту sync.blabla.com для синхронизации, но запретить обновления по хосту update.blabla.com.

    Можно дать временный доступ по номеру процесса. Когда программа запуститься в cледующий раз, номер будет другим, и Lulu спросит опять.

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

    Для себя я сделал так: удалил все правила, кроме Эпловских, а затем настраивал вручную. Запретил все программы Адоба, офисного пакета, всякие редакторы вроде Саблайма — их задача работать с текстом, а не лазить за обновлениями. Запретил обновки для Dash, программы оффлайн-документации, и много что другое.

    На старом ноуте обнаружил несколько вирусов. Это программы с названиями типа robstoaf, прописанные в автозагрузке. Время от времени они ходят в сеть на левые хосты. Возможно, крадут историю браузеров или это ботнет. Так что не слушайте байки про то, что под Мак вирусов нет.

    Что немаловажно, Lulu запрашивает подтверждение, когда левая программа хочет добавить себя в автозагрузку. Всякое дерьмо вроде Zoom, MS Teams прописывают себя там, и в итоге запускаются в фоне, даже если вы не просили. Больше этого беспредела нет.

    Программа так понравилась, что я хотел перевести автору денег, долларов сто. Хотел скинуть с Пейпала, а у него только патреон — то есть регистрируйся, вводи персональные данные, карту, подтверждай — извините, нет.

    Кроме Lulu, у автора есть смежные утилиты для безопасности, например монитор клавиатуры на предмет прослушки и что-то еще второстепенное.

    Словом, не знаю как жить без Lulu: теперь это первая программа, которую я ставлю на Мак. Как раз недавно ставил ее на свежий ноут. Неистово рекомендую!

    Картинки, описание и загрузка здесь: https://objective-see.org/products/lulu.html

  • CSS is Awesome

    Все видели картинку, где текст из заголовка не влезает в рамку. Ха-ха, кривой CSS, технологии, которые мы заслужили (с).

    Хорошо, посмеялись, а теперь вопрос: как должно быть?

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

    Проблема в том, что на картинке типичное UB — undefined behaviour, неопределенные поведение. В литровую банку пытаются налить два литра воды. Простыми словами, впихнуть невпихуемое. Это невозможно, поэтому кто-то окажется в проигрыше.

    На самом деле на картинке все правильно: при всем уродстве мы смогли прочитать текст. Если бы он был обрезан, мы бы его не увидели, то есть не получили бы вообще ничего. А так получили хотя бы часть, пусть и фраза там бестолковая.

    Эту картинку я считаю крайне неудачной. Кто-то хотел пошутить, что CSS глючный, но выбрал плохой пример: выставил дурацкие ограничения (малую рамку и огромный шрифт) и смотрит, как программа не справляется. А она, между прочим, справилась — донесла до нас текст.

    Так что шутка на троечку.

  • Фильмы про бомбу

    В фильмах про ядерную бомбу повторяется дурацкий штамп. Якобы ученый работает из интереса, не задумываясь над последствиями, только по зову сердца. Но ВНЕЗАПНО, эффект превосходит все ожидания, бомба оказывается разрушительной, подтягиваются политики, и ученый такой — ох, что я наделал, нельзя допустить, АСТАНАВИТЕСЬ!!!111 Всякие метания и попытки вернуть все взад.

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

    Так что не надо этой лапши про внезапное прозрение. Ну а если с кем-то и правда такое было, то надо было думать раньше.

  • With-http: a Clojure library for testing HTTP

    The with-http library provides a macro to stub HTTP calls with a local Jetty server. it’s declarative, flexible, and extremely useful. I’ve been copying it through many projects, and now it’s time to ship it as a standalone library.

    ToC

    Installation

    Lein:

    [com.github.igrishaev/with-http "0.1.1"]
    

    Deps.edn

    {com.github.igrishaev/with-http {:mvn/version "0.1.1"}}
    

    Pay attention: since the library is primarily used for tests, put the dependency in the corresponding profile or alias. Storing it in global dependencies is not a good idea as it becomes a part of the production code otherwise.

    About

    The library provides a with-http macro of the following form:

    (with-http [port app]
      ...body)
    

    The port is the number (1..65535) and the app is a map of routes. When entering the macro, it spawns a local Jetty server on that port in the background. The app map tells the server how to respond to calls.

    Now that you have a running server, point your HTTP API clients to http://localhost:<port> to imitate real network interaction. For example, for prod, a third-party base URL is https://api.some.cool.service but for tests, it is http://localhost:8088. This can be done using environment variables or the Aero library.

    Why not use with-redefs, would you ask? Well, although with-redefs looks like a solution at first glance, it’s questionable. Using with-redefs means lying to yourself. You temporarily mute some pieces of the codebase pretending it’s OK, but it’s not.

    Often, bugs lurk in the code that you actually substitute using with-redefs, namely:

    • you’ve messed up with MD5/SHA/etc algorithms to sign a request. Calling localhost would trigger that code and lead to an exception, but with-redefs would not.

    • you process the response poorly, e.g. not taking Content-Type header or non-200 status code into account.

    • you cannot imitate delays and timeout exceptions when interacting with HTTP API.

    The good news is, that the with-http macro can test all the cases mentioned above and much more.

    The App routes

    The app parameter is a two-level map of the form:

    {path {method response}}
    

    For example:

    {"/foo" {:get {:status 200 :body "it was GET"}
             :post {:status 201 :body "it was POST"}}}
    

    Calling GET /foo and POST /foo would return 200 and 201 status codes with different messages.

    The response might be:

    • a Ring map;
    • a Ring handler function that accepts a request map and returns a response map;
    • an instance of java.io.File;
    • a resource: (clojure.java.io/resource "some/file.txt");
    • a string.

    Other examples:

    {"/foo" {:get (fn [{:keys [params]}]
                    (log/infof "Params: %s" params)
                    {:status 200 :body "OK"})}}
    
    {"/some/json" {:get (io/resource "file.json")}}
    

    The path might be a vector as well. During the preparation step, it will be compiled into a string, for example:

    {["/foo/bar/" 42 "/test"]
     {:get {:status 200 :body "hello"}}}
    
    ;; becomes
    
    {"/foo/bar/42/test"
     {:get {:status 200 :body "hello"}}}
    

    This is useful when the paths contain parameters.

    The make-url function helps to build a local URL like http://localhost:<port>/<path>. Its second path argument is either a string or a vector which gets compiled into a string:

    (make-url PORT "/foo?a=1&b=2")
    ;; http://localhost:8899/foo?a=1&b=2
    
    (make-url 8899 ["/users/" 42 "/reports/" 99999])
    ;; http://localhost:8899/users/42/reports/99999
    

    Default handler

    The App mapping has a default handler which gets triggered when the client calls a non-existing route. By default, it’s 404 status page with the following JSON payload:

    (def NOT-FOUND
      {:status 404
       :body {:error "with-http: route not found"}})
    

    You can override it by adding the :default key to the app map. The value might be a map, a function, a file and so on.

    {"/foo" {:get {:status 200 :body "hello"}}
     :default {:status 202 :body "I'm the default!"}}
    

    Basic test

    A simple test to ensure the macro works:

    (deftest test-with-http-test-json
    
      (let [app
            {"/foo" {:get {:status 200
                           :body "test"}}}
    
            url
            (make-url PORT "/foo")
    
            {:keys [status body]}
            (with-http [PORT app]
              (client/get url))]
    
        (is (= 200 status))
        (is (= "test" body))))
    

    JSON

    The Ring handler function produced from the app mapping is wrapped with wrap-json-response and wrap-json-params middleware layers. It means the body of the response might be a collection that gets dumped into JSON:

    (deftest test-with-http-test-json
    
      (let [body
            {:hello [1 "test" true]}
    
            app
            {"/foo" {:get {:status 200
                           :body body}}}
    
            url
            (make-url PORT "/foo")
    
            {:keys [status body]}
            (with-http [PORT app]
              (client/get url {:as :json}))]
    
        (is (= 200 status))
        (is (= {:hello [1 "test" true]} body))))
    

    Slow responses

    To imitate slow responses, provide a function that sleeps for a certain amount of time:

    {"/foo" {:get (fn [_]
                    (Thread/sleep 10000)
                    {:status 200 :body "OK"})}}
    

    Then ensure you pass the timeout limit into your API call.

    Files & Resources

    Storing JSON responses in files is a good idea. Here is how you can serve them with the macro:

    {"/foo" {:get (io/file "dev-resources/test.txt")}}
    

    or

    {"/foo" {:get (io/file "dev-resources/test.json")}}
    

    Capturing requests

    Another trick to improve your tests: ensure you pass the right parameters or headers to the HTTP API. Provide an atom and a handler function closed over that atom. Each time you receive a request, save it’s data to the atom and then validate them:

    (deftest test-with-http-capture-params
    
      (let [capture!
            (atom nil)
    
            app
            {"/foo" {:get (fn [{:keys [params]}]
                            (reset! capture! params)
                            {:status 200 :body "OK"})}}
    
            url
            (make-url PORT "/foo?a=1&b=2")
    
            {:keys [status body]}
            (with-http [PORT app]
              (client/get url))]
    
        (is (= 200 status))
        (is (= "OK" body))
        (is (= {:a "1" :b "2"} @capture!))))
    
  • Скриншоты на винде

    На винде есть утилита для скриншотов рабочего стола, называется “Ножницы” или вроде того (clip). И надо сказать, программа люто, бешено тупая.

    Делаю скриншот и хочу сохранить — открывает диалог с именем “безымянный.PNG” (в английской версии untitled.PNG). Не будем комментировать расширение капсом; но блин, почему файл безымянный? Что мешало назвать его текущей датой с точность до минуты?

    Далее, если в папке уже есть “безымянный” с прошлого раза, программа предложит его перезаписать. Что мешает проверить, что такой файл уже есть и хотя бы поставить единичку (двойку, тройку)?

    В итоге как дурак дописываешь в конец чушь вроде 123, чтобы сохранить скриншот и куда-то отправить.

    Вот как на Маке: когда делаешь скриншот, он сперва улетает в угол, и в течении 10 секунд его можно куда-то перетащить без сохранения на диск. А если не успел, он сохранится на рабочий стол с датой и временем в названии. Не нужно париться, сохранил или нет, перезаписал или нет.

    Что мешало сделать также Микрософту? Они вообще своими программами пользуются?

  • Мы заслужили

    Выражение “X, который мы заслужили” стало затасканным штампом.

    • Киберпанк, который мы заслужили.
    • Футбол, который мы заслужили.
    • Релиз, который мы заслужили.
    • Пайплайн, который мы заслужили.

    Блин, кто мы? Чем и как заслужили?

    Лет пять назад это было туда-сюда, но сегодня терпеть уже невозможно.

  • Картинки в Телеграме

    Телеграм бесит ещё одной вещью. Если добавить к тексту картинку, текст поджимается под её ширину. Зачем, какой смысл? Текст превращается в мышиный хвост, справа сплошная пустота. Половина полезного места уходит в унитаз. Всё это ради чего? Чтобы что?

Страница 9 из 74