• JSAM: a simple JSON writer and reader

    JSam is a lightweight, zero-deps JSON parser and writer. Named after Jetstream Sam.

    • Small: only 14 Java files with no extra libraries;
    • Not the fastest one but is pretty good (see the chart below);
    • Has got its own features, e.g. read and write multiple values;
    • Flexible and extendable.

    Installation

    Requires Java version at least 17. Add a new dependency:

    ;; lein
    [com.github.igrishaev/jsam "0.1.0"]
    
    ;; deps
    com.github.igrishaev/jsam {:mvn/version "0.1.0"}
    

    Import the library:

    (ns org.some.project
      (:require
        [jsam.core :as jsam]))
    

    Reading

    To read a string:

    (jsam/read-string "[42.3e-3, 123, \"hello\", true, false, null, {\"some\": \"map\"}]")
    
    [0.0423 123 "hello" true false nil {:some "map"}]
    

    To read any kind of a source: a file, a URL, a socket, an input stream, a reader, etc:

    (jsam/read "data.json") ;; a file named data.json
    (jsam/read (io/input-stream ...))
    (jsam/read (io/reader ...))
    

    Both functions accept an optional map of settings:

    (jsam/read-string "..." {...})
    (jsam/read (io/file ...) {...})
    

    Here is a table of options that affect reading:

    option default comment
    :read-buf-size 8k Size of a buffer to read
    :temp-buf-scale-factor 2 Scale factor for an innter buffer
    :temp-buf-size 255 Inner temp buffer initial size
    :parser-charset UTF-8 Must be an instance of Charset
    :arr-supplier jsam.core/sup-arr-clj An object to collect array values
    :obj-supplier jsam.core/sup-obj-clj An object to collect key-value pairs
    :bigdec? false Use BigDecimal when parsing numbers
    :fn-key keyword A function to process keys

    If you want keys to stay strings, and parse large numbers using BigDecimal to avoid infinite values, this is what you pass:

    (jsam/read-string "..." {:fn-key identity :bigdec? true})
    

    We will discuss suppliers a bit later.

    Writing

    To dump data into a string, use write-string:

    (jsam/write-string {:hello "test" :a [1 nil 3 42.123]})
    
    "{\"hello\":\"test\",\"a\":[1,null,3,42.123]}"
    

    To write into a destination, which might be a file, an output stream, a writer, etc, use write:

    (jsam/write "data2.json" {:hello "test" :a [1 nil 3 42.123]})
    
    ;; or
    
    (jsam/write (io/file ...))
    
    ;; or
    
    (with-open [writer (io/writer ...)]
      (jsam/write writer {...}))
    

    Both functions accept a map of options for writing:

    option default comment
    :writer-charset UTF-8 Must be an instance of Charset
    :pretty? false Use indents and line breaks
    :pretty-indent 2 Indent growth for each level
    :multi-separator \n How to split multiple values

    This is how you pretty-print data:

    (jsam/write "data3.json"
                {:hello "test" :a [1 {:foo [1 [42] 3]} 3 42.123]}
                {:pretty? true
                 :pretty-indent 4})
    

    This is what you’ll get (maybe needs some further adjustment):

    {
        "hello": "test",
        "a": [
            1,
            {
                "foo": [
                    1,
                    [
                        42
                    ],
                    3
                ]
            },
            3,
            42.123
        ]
    }
    

    Handling Multiple Values

    When you have 10.000.000 of rows of data to dump into JSON, a regular approach is not developer friendly. It leads to a single array with 10M items that you read into memory at once. Only few libraries provide facilities to read arrays lazily.

    It’s much better to dump rows one by one into a stream and then read them one by one without saturating memory. Here is how you do it:

    (jsam/write-multi "data4.json"
                      (for [x (range 0 3)]
                        {:x x}))
    

    The second argument is a collection that might be lazy as well. The content of the file is:

    {"x":0}
    {"x":1}
    {"x":2}
    

    Now read it back:

    (doseq [item (jsam/read-multi "data4.json")]
      (println item))
    
    ;; {:x 0}
    ;; {:x 1}
    ;; {:x 2}
    

    The read-multi function returns a lazy iterable object meaning it won’t read everything at once. Also, both write- and read-multi functions are pretty-print friendly:

    ;; write
    (jsam/write-multi "data5.json"
                      (for [x (range 0 3)]
                        {:x [x x x]})
                      {:pretty? true})
    
    ;; read
    (doseq [item (jsam/read-multi "data5.json")]
      (println item))
    
    ;; {:x [0 0 0]}
    ;; {:x [1 1 1]}
    ;; {:x [2 2 2]}
    

    The content of the data5.json file:

    {
      "x": [
        0,
        0,
        0
      ]
    }
    {
      "x": [
        1,
        1,
        1
      ]
    }
    {
      "x": [
        2,
        2,
        2
      ]
    }
    

    Type Mapping and Extending

    This chapter covers how to control type mapping between Clojure and JSON realms.

    Writing is served using a protocol named jsam.core/IJSON with a single encidng method:

    (defprotocol IJSON
      (-encode [this writer]))
    

    The default mapping is the following:

    Clojure JSON Comment
    nil null  
    String string  
    Boolean bool  
    Number number  
    Ratio string e.g. (/ 3 2) -> "3/2"
    Atom any gets deref-ed
    Ref any gets deref-ed
    List array lazy seqs as well
    Map object keys coerced to strings
    Keyword string leading : is trimmed

    Anything else gets encoded like a string using the .toString invocation under the hood:

    (extend-protocol IJSON
      ...
      Object
      (-encode [this ^JsonWriter writer]
        (.writeString writer (str this)))
      ...)
    

    Here is how you override encoding. Imagine you have a special type SneakyType:

    (deftype SneakyType [a b c]
    
      ;; some protocols...
    
      jsam/IJSON
      (-encode [this writer]
        (jsam/-encode ["I used to be a SneakyType" a b c] writer)))
    

    Test it:

    (let [data1 {:foo (new SneakyType :a "b" 42)}
          string (jsam/write-string data1)]
      (jsam/read-string string))
    
    ;; {:foo ["I used to be a SneakyType" "a" "b" 42]}
    

    When reading the data, there is a way to specify how array and object values get collected. Options :arr-supplier and :obj-supplier accept a Supplier instance where the get method returns instances of IArrayBuilder or IObjectBuilder interfaces. Each interface knows how to add a value into a collection how to finalize it.

    Default implementations build Clojure persistent collections like PersistentVector or PersistenHashMap. There is a couple of Java-specific suppliers that build ArrayList and HashMap, respectively. Here is how you use them:

    (jsam/read-string "[1, 2, 3]"
                      {:arr-supplier jsam/sup-arr-java})
    
    ;; [1 2 3]
    ;; java.util.ArrayList
    
    (jsam/read-string "{\"test\": 42}"
                      {:obj-supplier jsam/sup-obj-java})
    
    ;; {:test 42}
    ;; java.util.HashMap
    

    Here are some crazy examples that allow to modify data while you build collections. For an array:

    (let [arr-supplier
          (reify java.util.function.Supplier
            (get [this]
              (let [state (atom [])]
                (reify org.jsam.IArrayBuilder
                  (conj [this el]
                    (swap! state clojure.core/conj (* el 10)))
                  (build [this]
                    @state)))))]
    
      (jsam/read-string "[1, 2, 3]"
                        {:arr-supplier arr-supplier}))
    
    ;; [10 20 30]
    

    And for an object:

    (let [obj-supplier
          (jsam/supplier
            (let [state (atom {})]
              (reify org.jsam.IObjectBuilder
                (assoc [this k v]
                  (swap! state clojure.core/assoc k (* v 10)))
                (build [this]
                  @state))))]
    
      (jsam/read-string "{\"test\": 1}"
                        {:obj-supplier obj-supplier}))
    
    ;; {:test 10}
    

    Benchmarks

    Jsam doesn’t try to gain as much performance as possible; tuning JSON reading and writing is pretty challenging. But so far, the library is not as bad as you might think! It’s two times slower that Jsonista and slightly slower than Cheshire. But it’s times faster than data.json which is written in pure Clojure and thus is so slow.

    The chart below renders my measures of reading a 100MB Json file. Then the data read from this file were dumped into a string. It’s pretty clear that Jsam is not the best nor the worst one in this competition. I’ll keep the question of performance for further work.

    Measured on MacBook M3 Pro 36Gb.

    Another benchmark made by Eugene Pakhomov. Reading:

    size jsam mean data.json cheshire jsonista jsoniter charred
    10 b 182 ns 302 ns 800 ns 230 ns 101 ns 485 ns
    100 b 827 ns 1 µs 2 µs 1 µs 504 ns 1 µs
    1 kb 5 µs 8 µs 9 µs 6 µs 3 µs 5 µs
    10 kb 58 µs 108 µs 102 µs 58 µs 36 µs 59 µs
    100 kb 573 µs 1 ms 968 µs 596 µs 379 µs 561 µs

    Writing:

    size jsam mean data.json cheshire jsonista jsoniter charred
    10 b 229 ns 491 ns 895 ns 185 ns 2 µs 326 ns
    100 b 2 µs 3 µs 2 µs 540 ns 3 µs 351 ns
    1 kb 14 µs 14 µs 8 µs 3 µs 8 µs 88 ns
    10 kb 192 µs 165 µs 85 µs 29 µs 96 µs 10 µs
    100 kb 2 ms 2 ms 827 µs 325 µs 881 µs 88 µs

    Measured on i7-9700K.

    On Tests

    One can be interested in how this library was tested. Although being considered as a simple format, JSON has got plenty of surprises. Jsam has tree sets of tests, namely:

    • basic cases written by me;
    • a large test suite borrowed from the Charred library. Many thanks to Chris Nuernberger who allowed me to use his code.
    • an extra set of generative tests borrowed from the official clojure.data.json library developed by Clojure team.

    These three, I believe, cover most of the cases. Should you face any weird behavior, please let me know.

  • Flyway

    Волею судеб я использую джавную библиотеку Flyway для миграций. Должен сказать: ее писали клоуны.

    Вот как это проверить: запускаем миграции на Postgres 15.8, версия Flyway 7.5.4. Все благополучно работает. А если поднять версию до последней 11.7.2, получим исключение:

    Unsupported Database: PostgreSQL 15.8

    Не менял абсолютно ничего, никаких настроек, только бампнул версию. И вот пожалуйста.

    Внимание, вопрос: что же такого случилось, что Постгрес 15.8 вдруг не поддерживается? Почему спустя три мажорных релиза он отвалился? Слишком новый? Слишком старый? И что делать?

    Между прочим, Постгрес 15.8 — относительно свежий релиз (последняя версия, если что — 17). С каких щщей он попал в немилость?

    Что творилось в голове у джавистов, которые писали Flyway, я ума не приложу.

    Мне приходилось писать свои миграционные движки, и могу сказать: да, это работа не на один день, конечно. Хорошенько все потестить, а потом стабилизация. Но наколбасить 11 мажорных релизов, которые вдобавок тупо не работают — это надо уметь.

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

    Выпустили SDK 2, а там те же самые проблемы.

    Словом, ты вырос и сказка кончилась. Программист в корпорации X может получать 400 тыс. долларов в год и писать лютейший быдлокод. А нам, потребителям, с этим жить.

  • Кнопка Summarize

    Компания Эпл следует тенденции: сует AI в каждую щель. Так, в почтовом приложении над каждым письмом теперь кнопка “Summarize”, которая, как предполагается, покажет краткую версию письма.

    Разумеется, это полная шляпа. Работает только с английским текстом и text/plain. Если вам прислали графоманию на русском, и к тому же в виде HTML-таблиц — кнопка скажет, что “не шмогла”. В целом, эта кнопка скорее не работает, нежели работает.

    Все, что остается пользователю — это отключить кнопку, но не все так просто. Я снял нужную галку, кнопка пропала, но потом появилась. Может быть, нужно перезагрузиться, но ситуация уже абсурдна.

    Гугл и поддержка Эпла (читай — AI) как дурачки советуют снять эту галку. То, что это не работает, никого не интересует.

    Когда уже мир отпустит? Начинает немного раздражать.

  • Ссылки должны быть записаны

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

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

    Почему второй способ лучше? По многим причинам, и самая важная – взрослый, организованный подход. Вместо того чтобы сидеть на ссылках, как царь Кощей над златом, мы делимся ими с другими. Возможно, в будущем к этой задаче кто-то вернется и найдет эти ссылки. Их проще скопировать и переслать. Если работа основана на сторонних материалах (код, замеры быстродействия), я считаю правильным ссылаться на источники, чтобы было понятно, откуда решение.

    В комментариях кто-то писал: моя задача требует документации, ссылок на то, се, пятое-десятое плюс макеты в Фигме. Так добавь эти ссылки в задачу! Или ты ждешь, что каждый участник будет искать эти ссылки? Это же свинство.

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

    Или ты открыл десяток вкладок и тебя экстренно перекинули на другую задачу, а она тоже требует 10 вкладок. Так и будешь хранить первые десять? Я понимаю, что есть разные профили, окна, группировки… но не говорите, что все это работает как надо, я даже слушать не хочу. Разве не было такого, что браузер вылетел и все забыл? Или обновился и показывает одинокую вкладку “What’s new”?

    Запись ссылок важна даже если работаешь в одиночку. Скажем, пилю я свой клиент к Постгресу и мне нужны:

    • ссылки на документацию
    • ссылки на исходники Постгреса
    • ссылки на чужие клиенты, чтобы подсматривать решения
    • вопросы на StackOverflow
    • всякие мейл-листы.

    Поэтому я завожу в репозитории файл links.md и пишу туда ссылки.

    Когда кто-то говорит, что у него пять сотен вкладок, увы, я не верю, что в них возможен какой-то порядок. Это самообман. Все сводится к принципу: записал – отпустил. Пока что-то не записано, оно не свободно. И мы тоже от него не свободны.

  • Табы и закладки

    Раз уж заговорили о браузерах, выскажу еще одну мысль.

    Меня удивляет, как много обсуждают табы и закладки. Когда выходит какой-нибудь Вивальди и об этом постят новость, комментари сводятся к табам и закладкам. Когда будут вертикальные табы? Когда группировка? Когда автозакрытие как в расширении X? И так далее.

    Иногда мне даже жаль разработчиков, потому что сделать так, чтобы понравилось всем, невозможно. Всегда найдется чудак, которому нужны табы по диагонали, в крапинку и с синхронизацией через Амазон в докере. Приходится объяснять, что этого не планируется, а он будет поливать разработчиков дерьмом.

    Поэтому расскажу, как управлять табами в любом браузере: будь то Хром, форк Фаекфокса или что угодно. Записывайте: когда у вас много табов, зажмите клавиши Ctrl/Command + W. Все табы закроются. После этого откройте табы, что нужны для текущей задачи. Конец.

    Я использую эту схему много лет. Расплодились табы – сношу к чертям и начинаю сначала. Не нужны расширения и синхронизации, вертикальные-горизонтальные и прочий бред.

    Уже рассказывал: человек шарит экран, и у него в Хроме 30 вкладок. И не вздумай говорить, что ты ими управляешь!!! Табы сжаты до размеров иконки – как ты найдешь нужный таб? Только перебором: вот этот таб, ой, не тот, ой, другой, ага, вот этот. В чем прикол тыкать каждый раз как слепой щенок? Это как набрать в руки десять предметов и утверждать, что можешь управиться с каждым.

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

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

    Решение простое: храните ссылки в файлике, который синхронизируется через Dropbox, iCloud или что там у вас. У меня это файлы music.md, postgres.md и другие. Содержимое примерно такое:

    # Silent Hill 2 Remake OST
    https://www.youtube.com/watch?v=UFBq69uB-es&list=PLjvrSyTT3pvSbHUpqC3sSC3b9Fq7NuF6b
    
    # J. S. Bach - Organ Works - Lionel Rogg - DISC 2/12
    https://www.youtube.com/watch?v=rudjAUtfx-g
    
    # Toccata & Fugue in C Major, BWV 564
    https://www.youtube.com/watch?v=kxtJ_av5NHo
    
    # Pink Floyd - Obscured By Clouds (1972) [Full Album]
    https://www.youtube.com/watch?v=Te_-nISxLVI
    

    Поскольку это маркдаун в Емаксе, то работают всякие плюшки. Клик на ссылку открывает ее в браузере, есть быстрый переход по заголовкам, просмотр оглавления и так далее. Можно делать несколько уровней, например так:

    # Ambient
    ## Portal 2
    ## Klaus Schulze
    # Rock
    ## Queen
    ## Pink Floyd
    

    и оно будет красиво отображаться в виде дерева. Но мне достаточно одного уровня. Схема проста как лопата, в ней нечему ломаться. Обновление браузера и расширений на нее не влияют. Что еще нужно?

    Конечно, кто-то всплакнет, мол, неудобно на телефоне. Ну и пусть – пока ты не за компом, надо смотреть на солнце, а не тупить в экран. А если не дочитал статью, то кинь ссылку себе в Телеграм, в чем проблема?

    Главная мысль этого поста: не быть рабом своих желаний. Хочется такие-сякие табы и закладки – перехочется. Бери то, что не сломается ни при каких обстоятельствах. На долгой дистанции это единственный вариант.

  • Летнее время

    Как известно, перевод часов на летнее время несет сплошную пользу. Прямо так хорошо от него, так хорошо, что аж сам себе завидую. И под это дело потерял день рабочего времени, и остальные тоже.

    На работе я занимаюсь отчетностью. В Амазоне у меня зашедулено много отчетов, каждый из которых уходит своим потребителям. Большинство из них я сделал прошлой осенью и зимой, когда у заказчика было время +1. А весной произошло вот что.

    Если не указать в Амазоне часовую зону cron-выражения, то по умолчанию берется UTC. Это хорошо, потому что точка отсчета фиксирована. Но одно и то же время UTC в зависимости от времени года дает разное локальное время. Например, зимой время 08:00 am UTC будет 9:00 am UTC+1, а летом – 10:00 am UTC+2.

    Это значит, что после перевода часов потребители получат отчеты не в 9 часов, а в 10 по местному времени.

    Начались жалобы: что-то подумал, что все сломалось, кто-то не успел предоставить отчет к созвону, где-то упал скрипт, который перекладывает отчеты в другое место. Починил так: нужно указать под cron-выражением местную зону, например Europe/<City>, и сдвинуть часы так, чтобы они совпадали с зимним временем, по которому работало раньше. На то, чтобы разобраться, задеплоить и проверить, ушел день. В первый раз я поднял часы на +2 вместо +1, и пришлось переделывать.

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

    У меня стойкая ассоциация: каждые полгода страна садится голой задницей на гвоздь. За полгода рана заживает, и кажется, что в этот раз обойдется без последствий. Но нет: снова боль, снова проблемы, крики. Никогда такого не было, и вот опять.

    Спрашивается: сколько можно? Сколько еще нужно выбросить времени, денег, нервов, здоровья, чтобы чиновников отпустило? Десять лет? Сто лет? Уверен, в будущем над нами будут смеяться: представляете, дети, эти придурки на рубеже тысячелетий гоняли часы туда-сюда, чтобы сэкономить тысячу долларов на 20 миллионов человек. Примерно как раньше сжигали людей, чтобы задобрить бога – с таким же результатом.

    Слышал, что за перевод времени люто топил Яков Перельман. Что неудивительно: более яростного человеконенавистника нужно еще поискать. Может, хватит брать с него пример?

  • Иконки в едином стиле

    Беда, когда у компании несколько программ, и дизайнера просят сделать иконки “в едином стиле”. Почти всегда получается шляпа.

    Каноничный пример – Гугл. Пока у него не было тыщщи сервисов, у каждого была своя симпатичная иконка. У почты – конверт, у звонков – телефонная трубка, у карт – карта. Но потом позвали дизайнера и сказали делать в одном стиле. В результате получились козявки с вариацией цветов: желтый, зеленый, красный, синий.

    Поставтье хотя бы две такие иконки рядом, и вы запутаетесь.

    Радует, что подобную иконку не сделали Ютубу: проект настолько самобытен, что ни у дизайнера, ни у менеджера не поднимается рука.

    Иные иконки вообще лишены смысла, например у Google Authenticator. Старая иконка представляла собой замок сейфа в виде буквы G. Казалось бы, все логично: сейф, безопасность, буква. Новая иконка – это какая-то снежинка. Что хотел сказать автор? Помню как обновил Google Authenticator и десять минут искал иконку сейфа. И до сих пор не могу привыкнуть, что теперь это снежинка.

    Более-менее было нормально у Адоба времен CS4. Они стилизировали иконки под периодическую таблицу Менделеева. Смысл в том, что каждый элемент (то есть программа) занимает свою уникальную роль, обладает особыми свойствами. Различать программы помогало следующее:

    • на каждой из них было имя программы из двух букв, например Ps, Ai, Ae;
    • иконки были разного цвета. Фотошоп – голубой, Иллюстратор – оранжевый, Индиз – малиновый и так далее.

    Позже Адоб пошел по пути упрощения: иконки стали почти черно-белыми, а цвет остался только в контуре. Предполагается, что пользователь должен парсить все эти Ps, Ai, Ae, Id, Au, Pr и так далее.

    Похоже, никто не может решить задачу иконок “в едином стиле”. А если и может, то не справляется с давлением менеджмента. Так зачем вообще браться за это?

  • ROW CHECK и безопасность

    Работал я в одном стартапе на Кложе. Код прошел через десятки разработчиков и представлял лоскутное одеяло: разные подходы и библиотеки. Каждый разработчик городил что-то сбоку, а не исправлял текущее положение дел. Была своя ORM с километрами кода и склейкой SQL-строк. Много там всего было, и в том числе база данных.

    Эта база прямо сейчас стоит в памяти. Она тоже прошла через серию разработчиков, каждый из которых знал, как делать правильно. Одни ребята забивали на нормализацию; другие решили, что добавлять колонки утомительно и сделали поле info с типом jsonb, в которое валили все подряд. Были материализованные вьюхи, обновлять которые было затратно, и которые без конца обновлялись из-за косяков в очереди сообщений. Ни одна запись физически не удалялась, а помечалась флагом is_deleted = true. Много багов было связано с тем, что данные выбирались без этой проверки.

    Но это не все. Основатель фирмы считал себя специалистом по безопасности и придумал вот что. В Постгресе есть штука под названием ROW CHECK: проверка доступа на уровне записи. Если некая функция my_check(row) возвращает null или false, то клиент словно не видит этой записи. Это медленно, но работало.

    Почему директор так сделал? Он хранил в одной таблице данные разных клиентов и опасался, что из-за ошибки в коде один клиент увидит данные другого. Поэтому каждая таблица хранила избыточные айдишки, и в рамках каждого запроса выставлялась переменная current_owner. Функция ROW CHECK проверяла, что ее значение совпадает с айдишками записи.

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

    К счастью, наши часовые зоны сильно отличались, поэтому на починку базы бросали другого человека. По понедельникам он рассказывал, как просиживал выходные за восстановлением бекапов, импортов CSV и отладкой. Были и другие, не столь фатальные случаи, связанные с активностью директора на проде.

    Забавно, но наибольший урон базе нанес именно директор, а не хакеры или кривой код. Хакерам, видимо, стартап был не интересен, хотя в самописной ORM были дыры для инъекций. Код, хоть и был не супер, не страдал тем, что читал чужие данные. Свои удаленные – да, но не чужие.

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

    В более широком ключе: запрет хорошо работает только тогда, пока его соблюдают те, кто принимал. Не важно, о чем идет речь: о детях, собаках или машинах. Если говорящая голова топит за очередной запрет, но не готова применить его к своим детям, собакам или машинам, сказанное не имеет смысла.

  • Форки Фаерфокса

    Заметка от нечего делать. Пробую форки Фаерфокса: поставил WaterFox и LibreWolf. Первый очень понравился: старый рубленый интерфейс, доступны опции, которых нет в официальной версии. Нет телеметрии и всякого дерьма вроде Покета и синхронизаци. Очень шустрый. Потыкал Ютуб – все ролики работают. LibreWolf вроде тоже неплох, присматриваюсь.

    Пока что один минус: не работает webm, но стерпим и это.

    Офицальный FireFox в последнее время не радует. Он сливает все больше данных, на сайте меняются формулировки: вместо “не передаем” пишут “улучшаем экспериенс”. Браузер обрастает Покетами-шмокетами, телеметрией и так далее. Нельзя отказаться от обновлений, нужно писать свои полиси.

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

    Пытался сидеть в чистом Хромиуме, но в нем не работает половина Ютуба. Причем даже не видео, а музыка из игр и фильмов. Спасибо, не надо.

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

    Так что хоть мелкая, но отрада в наши дни: сидеть на форке некогда великого продукта.

  • Cloud-driven development

    Расскажу об одной стремной вещи, которую называю “Cloud-driven development”. Это когда разработчик тестирует код не локально, а в облаке.

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

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

    Я как-то пытался играть по этим правилам. Быстро выяснилось, что окружение нужно готовить. Ой, у тебя старые сервисы, задеплой к себе эту фигню и эту тоже. У твоей облачной учетки нет пермишенов? Скажи девопсу Джорджу, чтобы добавил. Он в отпуске? Ну, жди. Потом не хватает каких-то креденшелов, токенов-шмокенов, их нужно откуда-то догонять.

    Короче, выясняется: разработчик настроил себе уютненькое окружение и ушел в закат. Чтобы сделать то же самое мне, нужна неделя. А ведь мог же поднять Докер и помочь всем. В конце концов, не знаешь Докер, попроси кого-нибудь помочь, а пока что тестируй на локальной базе, локальном S3 – таких заглушек полно.

    Кончается тем, что я пишу и Докер, и интеграционные тесты, которые ходят в локальные сервисы, чтобы приблизить локальную разработку к облачной. Причем зачастую в счет личного времени, потому что давай-давай.

    Я уж не говорю о том, что Cloud-driven development страшно не эффективен. Каждый деплой занимает в лучшем случае 10-15 минут, и за это время ты все равно не возьмешься за другую задачу. Разработчик смотрит Ютуб или ходить курить. Вроде работает, а на самом деле решето: сплошная пустота.

    Я бы вообще не давал разработчикам окружений. Это дорого и вдобавок развращает. Вот тебе Докер с базой, Редисом и Кафкой, пиши, тестируй. Пока локально не заведется, ни о каких облаках не мечтай.

    В качестве побочки всплывают проблемы конфигурации. Выясняется, что ни один сервис нельзя направить на локалхост: везде захардкожено что-то вроде "aws." + region + ".amazon.com". Лишний день уходит на то, чтобы сделать хосты конфигурируемыми.

    Читали же: “квартирный облачный вопрос только испортил их” (с)…

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