Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

Запретный плод для Java

Один из популярных вопросов начального уровня на Java-собесе — “можно ли переопределить статический метод?”. И правильный ответ — нельзя.

Однако, если очень надо, то это возможно:

public class Foo {
  public static void bar(){
    System.out.println("Foo");
  }
}
Foo.bar()

Mockito.mockStatic(Foo::class.java)
whenever(Foo.bar()).then { println("not foo") }
Foo.bar()

выведет:

Foo
not foo

Разумеется, если вам это вдруг нужно, то с большой вероятностью вы делаете что-то не то (мало того, что у вас рак в виде java, так еще и со статическими методами, которые требуют переопределения). Однако мне это пригодилось, когда понадобилось протестировать работу с переменными окружения, которые как раз засунуты в статический метод System.getenv() (для этого я использовал system stubs).

СсылкаКомментировать

Запретный плод

>>> (666).boo()
I'm just a 666, but boo!
>>> "I can be JS too! " + 2
'I can be JS too! 2'

Одна из фишек питона — возможность monkey-patching. Но многие считают, что у него есть предел, и нельзя патчить стандартную библиотеку. Но на самом деле можно:

>>> from forbiddenfruit import curse
>>> curse(int, "boo", lambda x: print(f"I'm just a {x}, but boo!"))
>>> (666).boo()
I'm just a 666, but boo!
>>> curse(str, "__add__", lambda x,y: x + str(y))
>>> "I can be JS too! " + 2
'I can be JS too! 2'

Это знание позволит вам поиздеваться над коллегами и еще меньше доверять написанному на питоне. Ходят слухи, что библиотека была сделана для тестов, но благими намерениями…

СсылкаКомментировать

Доступ к базе в приватной сети

Как посмотреть что-то в базе, если она в частной сети, в которую нет прямого доступа, а девопсы не настроили ничего вроде VPN? По идее, она должна быть видна приложению, но в стильно-модно-молодежной среде приложение лежит в контейнере, в котором ничего для доступа к базе (например, psql) нет. Решение достаточно простое — запустить одноразовый контейнер с чем нужно.

docker run --rm -it postgres:14 /bin/bash

или

kubectl run -it --rm tmp-psql --image=postgres:14 --restart=Never -n prod --command /bin/bash

Интересных флагов тут два: --rm, который говорит о том, что надо удалить под/контейнер после использования, и --restart=Never для того, чтобы создался только просто под, а не deployment.

(Как все просто было во времена железных серверов — просто провалился через ssh на хост и все).

СсылкаКомментировать

Тестовые флаги

Использование условий вида if (test) или if (user in testUsers) — это рак.

Во-первых, половина программирования, если не больше — про управление зависимостями. Если реально нужно отключить какую-то функциональность на тестовой среде (оплату какую-нибудь), то есть куча нормальных способов это сделать: например, подсунуть класс-заглушку или http-заглушку, если вы живете в микросервисной архитектуре.

Во-вторых, тестироваться будет не основной код, а костыли внутри условия. Причем подобные костыли часто просят добавить как раз тестировщики.

В-третьих, это источник багов (а иногда и дырок в безопасности). Сейчас не каждый понимает необходимость юнит-тестов, а уж если начнешь заикаться о тестировании тестового кода — сразу в дурку поведут.

Иногда подобным образом переключают часть функциональности (хотя для этого есть feature-флаги). Иногда так еще “упрощают” жизнь для нагрузочных тестов (т.е. тестируют не настоящую работу) или “для отладки” (хотя для этого есть ленивые логи с уровнями логгирования или внешняя инструментация).

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

СсылкаКомментировать

Suspend функции в Scala

В продолжение темы об излишних переусложнениях — на форуме Scala обсуждают предложение о добавлении корутин в стиле Kotlin. Со всеми недостатками двухцветных функций. Рекомендую почитать критику от де Гуза (автора ZIO) и сдержанный ответ Одерски (автора языка). Вкратце, suspend не нужОн, если зеленые потоки есть из коробки, и они планируются в рамках проекта Loom. А в Scala Одерски видит перспективы в более мощной типизации и в capabilities.

СсылкаКомментировать

Context receivers

В рабочем проекте попробовал добавленные недавно context receivers. Вкратце, это очередной сахар, который позволяет требовать, чтобы функция вызывалась в контексте, содержащем какой-то класс как this:

context(Logger, DBConnection, Request)
fun someFunc() {
  log(select(request.param))
}

Под капотом компилятор генерирует дополнительные параметры для вызова функции.

В текущей версии (1.6.21) все очень сыро — я споткнулся об пару багов компилятора и пару ошибок во время исполнения в довольно простых случаях. Для прода это точно не готово (я использовал в тестах). Могут получиться уродские лямбды:

suspend fun someFun(
  block: suspend context(Logger, DBConnection) Request.(Param) -> Unit
)

Компилятор не всегда понимает, из какого контекста метод (и я натыкался на случаи, когда ему не удавалось это объяснить). Как и с suspend, функции приобретают “цвет”. У this непонятный тип — вроде объединение, но явно это не получить. Наконец, форматирование съезжает:) Больше критики можно почитать тут, похвалу — тут, а больше вариантов применения — тут.

Без сахара подобные задачи можно решать созданием одного класса Context и вызова функций с дополнительным параметром(ами) или через receiver: fun Context.someFunc(). Была бы поддержка типов-объединений, то тогда это было бы гораздо более читаемо (особенно с typealias). Ну а так вообще это все ради того, чтобы неявно передавать аргумент в функцию. И подобную проблему уже давно решили с помощью имплиситов в Scala. В общем, мне кажется, что начинается излишнее переусложнение, которое еще и хреново реализовано.

СсылкаКомментировать

Разделение рабочего и личного на ноуте

Весьма скоро после перехода на удаленку рабочие задачи и личные почти полностью стали выполнятся на одном компьютере.

Сначала я делил их путем создания второго пользователя. Но это оказалось не очень удобно — надо было переключаться, ради какой-то мелочи это было делать лень. Кроме того, некоторые вещи в личном и рабочем пересекались. Например, я городил какие-то костыли с правами, чтобы Dropbox считал двух пользователей за одного.

Потом я пробовал разделить устройства — держал древний домашний стационарник в спящем режиме и включал при необходимости. Но много работать на компе, которому недавно исполнилось 10 лет — так себе удовольствие.

Сейчас я разделяю два пространства попроще — просто два рабочих стола (виртуальных) для смены контекста, и выделенный браузер для чисто рабочих задач. И еще одно место, где требуется разделение — git. Там настроено два профиля под разные директории:

$ cat ~/.gitconfig
[includeIf "gitdir:/home/ov7a/github/**"]
  path = ~/.gitconfig.personal
[includeIf "gitdir:/home/ov7a/work/**"]
  path = ~/.gitconfig.work

$ cat ~/.gitconfig.personal 
[user]
name = ov7a
email = home@address.ru

$ cat ~/.gitconfig.work 
[user]
name = Vlad Chesnokov
email = some.work.email@company.com

Т.е. все репозитории в рабочей папке будут иметь рабочий акк, а в папке под всякую дичь — личный.

И… все, пока потребности разделять что-то еще не появилось.

P.S. В первое время удаленки я даже заморачивался и разделял работу и дом сменой футболки, и это реально помогало. Но летом в жару 30+ я на это быстро забил:)

СсылкаКомментировать

Локальный запуск билда Gitlab

Как отладить непонятную проблему в CI, если нет доступа к внутренним логам? Запустить локально ее агент, благо сейчас все в контейнерах.

Но что делать, если в локально запущенном агенте билд все равно проходит? Даже если использовать агент с нужной версией?

Один из вариантов ответа — переменные окружения, которые локально, разумеется, отличаются. Однако это легко поправить — можно запустить любую команду, которая выплевывает эти переменные (например, env) и скопировать себе (со всеми секретами и приватными ключами, которые любезно будут выведены). В моем случае проблема была в том, что кто-то запихал в переменные окружения много всего, а в одной из задач gradle они копировались в SystemProperties. При запуске этой задачи в отдельном процессе она тупо давилась их количеством.

СсылкаКомментировать

Рейтинг постов канала в Telegram

Накалякал скрипт, который качает посты из указанного чата в телеге и сортирует их по метрикам. Внезапно, самым сложным оказалось найти версию библиотеки Telethon, которая и реакции поддерживает, и еще не сломана в процессе обновления на v2. Другую я заленился смотреть, но благо узнал, что можно ставить pip-пакеты прямо с GitHub’а:

pip install 'git+https://github.com/LonamiWebs/Telethon.git@539e3cb8081acbd9a5cc7a61c0731ca62842597e'

К сожалению, сам анализ никаких больших открытий не дал — посмотрел топ-посты в паре каналов с мемчиками, да и все.

СсылкаКомментировать

SQL на csv

Прикольный рецепт как выполнять SQL запросы на CSV-файле: данные загружаются в in-memory sqlite, а там уже можно делать с ними что угодно.

Чтобы не запоминать зубодробительную строку, можно добавить функцию в ~/.bash_aliases:

function sqlcsv() {
  filename=$1
  shift
  sqlite3 :memory: -cmd '.mode csv' -cmd ".import $filename data" "$@"
}

Причем можно использовать как интерактивный режим, если передать только имя файла:

$ sqlcsv rus_cities.csv 
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> select * from data where source='омск' limit 3;
"омск","кадников","о"
"омск","казань","о"
"омск","калач","о"

так и неинтерактивный, если передать один или несколько запросов:

$ sqlcsv rus_cities.csv "SELECT DISTINCT(source) from data where source like 'ц%';" \
  "SELECT COUNT(DISTINCT(source)) from data WHERE source like '%ц' or source like '%цы';"
"цивильск"
"цимлянск"
"циолковский"
15

Конечно, это все можно сделать и в Excel/аналоге, но я лично потрачу больше времени на гугление нужных формул, чем на написание SQL-запроса.

СсылкаКомментировать