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

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

Главный метод в Kotlin jar

Если написать такую программу

fun main(args: Array<String>) {
    println("Hello, dudes!")
}

и скомпилировать ее через kotlinc 1.kt -include-runtime -d hello1.jar, то при запуске через java -jar hello1.jar получим приветствие. Однако если то же самое сделаем с эквивалентным кодом

object Main {
  @JvmStatic
  fun main(args: Array<String>) {
    println("Hello, dudes!")
  }
}

то результат будет уныл:

no main manifest attribute, in ./hello2.jar

Почему не генерируется манифест можно посмотреть в исходниках компилятора. Через пару прыжков выходим на MainFunctionDetector, где, продравшись через условия, можно понять, что главными считаются только top-level функции, и в манифест Main-Class добавляется только в первом случае. Хотя казалось бы, раз уж есть код для поиска главного метода, то ничто не мешает проставлять Main-Class во всех случаях. Звучит довольно низкоуровнево, ведь у всех есть система сборки или IntelliJ на худой конец, но это все-таки баг.

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

Насколько быстро комп делает "типовые" операции?

Занимательный тест: https://computers-are-fast.github.io

Который вдобавок еще и забавный с учетом сегодняшнего индустриального стандарта, где на железе виртуалки, в виртуалках кубер, в кубере докер, в докере виртуальная машина языка, в ней — абстракции фреймворка, потом уже собственно код (причем это еще не самый страшный кейс), и все это еще и по HTTP.

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

Сериализация DTO python в JSON

В продолжение предыдущего поста. Чтобы иметь возможность сериализовать dataclass в JSON, можно сделать что-то вроде трейта и примешать его к датаклассам.

from dataclasses import dataclass, asdict

class Serializable:
    def _asdict(self):
        return asdict(self)

@dataclass(frozen=True)
class Person(Serializable):
  name: str
  age: int

p = Person("Jeshua", 33)

requests.get("http://httpbin.org/anything", json=p).json()

Эта штука вам вернет...

TypeError: Object of type Person is not JSON serializable

Но если поставить модуль simplejson, который можно считать девелоперской версией встроенного json, то ответ будет нормальным:

{... 'json': {'age': 33, 'name': 'Jeshua'}, ...}

Как же это работает? Во-первых, в модуле requests есть развилочка:

try:
    import simplejson as json
except ImportError:
    import json

Во-вторых, в модуле simplejson есть встроенная поддержка namedtuple: если у класса есть метод _asdict, то он будет вызван для получения словаря, из которого потом будет сделан JSON. Т.е. с simplejson namedtuple можно просто передавать как параметр в dumps без лишних телодвижений. А благодаря Serializable датакласс маскируется под namedtuple.

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

DTO в python

Еще в древнем 2.6 были namedtuple:

from collections import namedtuple

Person = namedtuple("Person", "name age")
p = Person("Jeshua", 33)
print(p.name)

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

Потом в 3.5 появились типы:

from typing import NamedTuple

class Person(NamedTuple):
  name: str
  age: int
p = Person("Jeshua", 33)
print(p.name)

Все то же самое, только запись поадекватнее, но это как-то пролетело мимо меня.

Наконец, в 3.7 появились dataclasses:

from dataclasses import dataclass

@dataclass(frozen = True)
class Person:
  name: str
  age: int

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

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

Хранение нескольких версий продукта в git

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

Однако доклад все равно полезный — в нем и кратенько про git и github flow рассказано, и проблема более-менее четко обозначена, и показаны варианты решения с плюсами и минусами.

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

Hello world на kotlin native

  1. Скачиваем релиз, распаковываем.
  2. Запускаем kotlinc-native -help:
Error occurred during initialization of VM
Could not reserve enough space for 3145728KB object heap
  1. Офигеваем от того, что этой штуке нужно 3 гига, запускаем с ограничением:
_JAVA_OPTIONS="-Xmx256M" kotlinc-native -help
  1. Делаем 1.kt с чем-то похожим на котлиновский код:
fun main() {
  println("https://t.me/minutkaprosvescheniya/120")
}
  1. Пробуем скомпилировать:
_JAVA_OPTIONS="-Xmx256M" kotlinc-native 1.kt -o 1
  1. Офигеваем от того, что надо скачать "немного" зависимостей — 600 мегабайт (в 2000-х за такое расстреляли бы).
  2. Офигеваем от того, что виртуалка у нас немного старая, i386, и kotlin-native поддерживается для arm32, win x86, watchOs x86, wasm32, MIPS, умных часов, но не для linux 32-bit, мол никому не надо — тебе надо, ты и делай.
  3. Повторяем шаги 1-6 для компа поновее.
  4. Офигеваем от того, что нельзя никак убрать расширение .kexe, потому что "это хороший способ идентифицировать файлы".
  5. Запускаем ./1.kexe, получаем результат.
  6. Продолжаем офигевать от зрелости технологии.
СсылкаКомментировать

Состояния процесса в linux

Когда-то давно я думал, что root всемогущ и может убить любой процесс. Однако если процесс находится в состоянии Uninterruptible Sleep (оно же D), то он может так конкретно повиснуть, что поможет только перезагрузка, о чем даже в отделе травили страшилки на вечер пятницы:) А происходит это из-за того, что процесс ждет I/O и на сигналы не реагирует. И если что-то пошло не так, то так он и будет висеть до следующей перезагрузки, несмотря ни на что (такое было на проде).

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

Подробнее про состояние процессов можно почитать в статье.

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

Как тестировать работу с реляционной БД

Самые банальные варианты включают в себя:

  1. Тестирование на тестовом стенде (для особых извращенцев — на общем стенде, для супер-извращенцев — резервирование ресурса путем устных переговоров с коллегами, ну а у темных властелинов тестовый стенд — это прод).
  2. Тестирование с БД, которая разворачивается локально (возможно даже сама, возможно в докер-контейнере).
  3. Тестирование с in-memory аналогом.

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

С одной стороны, завязываться на реализацию БД как-то странно, особенно с учетом того, что вряд ли к базе идет обращение напрямую, а не через ORM и/или DSL, и может еще и через liquibase. С другой — эти абстракции дырявые, и тот же liquibase поле с типом TEXT создаст как TEXT (он же VARCHAR) в Postgresql, но как CLOB в H2. Вдобавок, у различных СУБД есть свои "особенности", от которых не всегда получится абстрагироваться — помню как года 4 назад во времена импортозамещения у меня пригорело от того, что Oracle не различает пустую строку и null. Вдобавок всякие статьи говорят о том, что не-не, нельзя делать unit-тесты c in-memory H2, если у вас на проде Postgresql, у вас прод сгорит и вообще мы все умрем.

В моем идеальном мире все СУБД и прослойки для общения с ними написаны нормально и такие проблемы не должны возникать. В чуть менее идеальном — эти отличия несущественные и легко проверяются интеграционными тестами, если надо поддерживать несколько СУБД, или влияют только на производительность, но не на интерфейсы/поведение.

Но этой заметки не было бы, если бы не потекшие абстракции: при обновлении H2 столкнулся с проблемой, что они решили кидать исключение про то, что не поддерживают индексы на CLOB поля (хотя раньше просто ничего не делали). И вот сошлись звезды в лице связки liquibase + H2, и несмотря на то, что никакие нестандартные вещи не использовались, все поломалось. Конкретно в этом кейсе удалось извернуться, но ситуация насторожила: получается, что SQL не такой уж и стандарт...

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

Полезные и не очень сайты для curl

Полезные:

  • https://httpbin.org/ — универсальная штука для отладки HTTP-клиента. Самый частый кейс у меня был curl https://httpbin.org/ip, но подойдет для кейсов, когда надо выяснить, что ваш клиент сует в данные кроме того, что сказали (например, какой user-agent).
  • https://restcountries.eu/ — выводит основные данные о стране: curl -s https://restcountries.eu/rest/v2/name/russia.

И среднего уровня бесполезности:

  • Погода: curl -s http://wttr.in
  • Зойдберг: curl zoidberg.live
  • Fuck off as a service, местами может заменить httpbin: curl 'https://www.foaas.com/zero' -H'Accept: application/json'
  • Ну и классика — Звездные Войны, можно сделать аналог самостоятельно: curl https://asciitv.fr
СсылкаКомментировать

Mock и Wiremock

Сейчас довольно популярно тестирование с моками — объектами-заглушками, имитирующими реальное поведение чего-либо. Для любителей ФП звучит дико, конечно: зачем что-то имитировать для чистых функций?

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

Добавьте сюда еще микросервисы и получится, что надо имитировать и проверять HTTP-вызовы. Появляется специальный web-сервер, вся задача которого — отдавать заранее заготовленный ответ на нужный вызов и записывать все обращения. В качестве примера можно привести WireMock. Хотя если такая штука нужна на 1 раз, то, имхо, гораздо проще взять первый попавшийся скрипт на питоне, поднимающий локальный HTTP, и вкорячить туда нужное, чем возиться с настройкой чего-то посложнее.

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