Pull to refresh
45
1
Алексей @jdev

Эксперт по эффективной разработке, Kotlin техлид

Send message

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

Да, это правда. В моём случае так и есть - последние 4 года я либо делаю проекты с нуля, либо реорганизую их.

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

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

И главное - не ясна сама цель. Красота ради красоты?

Цель для меня - психологическое благополучие - у меня низкая стрессоустойчивость, поэтому в условиях горящих сроков и критов на проде мне жить некомфортно. А цель для бизнеса - минимизация суммарной стоимости владения продуктом. Структурный дизайн - это часть моего комплексного подхода к разработке бакендов. И опыт показывает что на горизонте от 6 месяцев работы этот подход позволяет снижать трудозатраты на разработку, в том числе за счёт снижения количества багов.

А вы уверены что найдутся специалисты могущие поддерживать такой стиль кодирования чтобы у вас сложилась команда?

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

Вот придёте вы ко мне в команду - будьте уверены что я вам не разрешу так писать код и не важно насколько это правильно и согласуется ли с вашим внутренним миром.

Значит у вас всё хорошо со скоростью и качеством разработки:)

Так же не ясно как отразится ваш подход на производительности

Если вы любите хайлоад, то лучше меня знаете, что все оптимизации делаются на основе конкретных цифр, конкретного кода. Поэтому в общем случае и правда не ясно.

Конкретно в моей практике нехайлоада (там где вопрос производительности вообще возникает) производительность только улучшается, так как в 100 случаях из 100 она упирается в неэффективную работу с БД.

Однако он упоминает термин «сбалансированность», который, как кажется, несколько притянут за уши.

В оригинальных книгах речь не о сбалансированной форме дерева, а о балансе уровня абстракции основных ветвей.

The Practical Guide to Structured Design, с. 182
The Practical Guide to Structured Design, с. 182

Перевод:
Хилая афферентная или эфферентная часть структурной схемы часто является признаком дисбаланса. Но я должен подчеркнуть, что баланс - это не визуальная характеристика. Система сбалансирована тогда и только тогда, когда она имеет дело с логическими данными, находящимися на ее вершине. Сбалансированные системы могут выглядеть — и часто выглядят — довольно асимметрично. Форма структурной схемы определяется задачей, а не эстетическими соображениями.

Так что мы, в итоге, имеем? – Множество обработчиков событий! Поэтому, при оптимизации алгоритмов, нужно вести речь о взаимоотношениях этих обработчиков между собой. Думаю, здесь и кроется вся сложность алгоритмов. Именно, в наличии избыточных связей между этими обработчиками либо объектами, к которым они имеют доступ. Иначе говоря, состояние одного объекта может изменяться разными способами, что нарушает принцип единственной ответственности.

Тут вам, возможно, будет интересен другой мой доклад - Рациональный подход к декомпозиции систем на модули или микросервисы. Я там как раз рассказываю о своём подходе к декомпозиции системы на минимально сцепленные модули-объекты. Правда я сейчас отказался от него:)

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

Да это так, но тут сильно повлияла специфика Joker - там ПК очень любит "мясцо"

Вот здесь и «зарыта собака». И вы, и я хотели бы больше «пояснений» по этим моментам. Но, единственное, что я понял из схем автора, так это то, что надо избегать, по максимуму, горизонтальных структурных связей за счет объединения соответствующих блоков кода.

Написать гайдлайн по проектированию кода трансформаций я тоже собираюсь. Пока что планирую взять за основу Stratified Design. Он хорошо расписан в книге Grokking Simplicity и есть доклад на эту тему от автора книги (сам не смотрел ещё)

К такому проекту я и на пушечный выстрел не подойду:)

Но если проект на 3M строк и 100 разработчиков попилен на 30 сервисов по 100К кода и покрыт качественными тестами - каждый сервис мигрируется также в пределах рабочего дня. Тот же проект, когда он был уже на 50К строк Котлин кода (и я его уже этом размере попилил на два сервиса) на минорные версии обновлялся за минуты.

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

Я в прошлом году переводил проект меньше раз в 7 (~300 Котлин файлов), но порядка на 2-3 быстрее:

Для перехода я:

  1. очевидным образом, обновил версии

  2. заменой по проекту поправил javax.* -> jakarta.*

  3. Поменял com.github.tomakehurst:wiremock-jre8:2.35.0 -> com.github.tomakehurst:wiremock-jre8-standalone:2.35.0. Без этого были проблемы со спекой сервлетов, что ли

  4. В конфиге Spring Security заменой по файлу поправил antMatchers -> requestMatchers

  5. Руками поудалял ConstructorBinding

  6. Больше всего времени ушло на то, чтобы реанимировать вытягивание доков к эндпоинтам в сваггере из котлин доков. Для этого пришлось руками добавить зависимость runtimeOnly("com.github.therapi:therapi-runtime-javadoc:0.15.0"), а допетрить до того, что этот джарник пропал из зависимостей (без ошибок) пришлось самостоятельно

  7. всё. Все 100+ тестов (преимущественно пользовательских/внешних/функциональных/е2е) прошли, приложение благополучно задеплоилось на тестовый стенд. И потом обошлось без факапов в проде

Всё это я сделал часа за два грустным вечером 5-ого января:)

Возможно секрет в том, что проект у меня был на Spring Data JDBC:)

3 не открывается

Блин, это я уже после правок поворошил блог и опять линку сломал 🤦‍♂️ снова поправил

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

О каких именно sql-е и идеях вы говорите?

И что такое ПП ?

Процедурное программирование. Я так называю подход, когда классы сущностей (данных) изменяемые и имеют только геттеры/сеттеры, а вся логика - в сервисах/юзкейсах

Я на Паскале писал ток лабы и курсовик в первом семестре первого курса, у меня с тех пор от собственного кода вьетнамские флешбеки, которые в моей голове намертво приколочены к BTP - короче я. ничего. не. забыл :)

На хабр пришло поколение людей, которые родились позже (де-факто) смерти Borland Turbo Pascal:)

В мокей картине мира Functional Core/Imperative Shell = структурный дизайн + неизменяемая модель данных.

Т.е. преимуществ нет, а большая для вас сложность закралась где-то в моём объяснении и/или вашем восприятии:)

В общем если вы владеете FC/IS - вы умеете и балансировку делать:)

Ещё, кстати, мысль дилетанта - а оно (архитектура) вам вообще надо?:)

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

А игры, кажется - максимально быстро запилил, зарелизал и ещё до релиза забыл про первую игру и начал пилить вторую.

Но это всё покрывает только примитивные программы, типа CRUD

Я правильно понимаю, что второй кейс с функцией назначения водителя вы тоже занесли в "примитивные программы, типа CRUD"?

В таком случае да, СД покрывает только их.

Но тогда боже упаси меня писать то, что вы называете сложными программами:)

Что делать с "Охота на хампуса" из "Чистой архитектуры")

Не заниматься ерундой делать оверинжиниринг, как пацаны из третьего кейса:

  1. Не выдумывать, что у вас будет ввод и из консоли, и из смс и вы всё это будете разруливать в одной кодовой базе. Сделайте бэк с игрой, смс-шлюз который дёргает бэк и настольное приложение которое дёргает бэк

  2. Переводы команд на разные языки вынесите в ресурсы. Соответственно, когда приходит команда - идёте в файлик с ресурсами и получаете каноничное имя

  3. Возьмите РСУБД (на такую игру - точно хватит) и храните данные только там. Следите за тем, чтобы это решение не текло через АПИ кода, хранящего состояние игры

Безусловно, бывают случаи, когда есть вариативность в технологиях - тогда вводите интерфейс между кодом управления и кодом реализации io и всё.

Но вообще, см. коммент выше - в докладе и статье я держал в уме разработку бэков информационных систем. Разработка игр, как и фронта, как и АСУТП, как и встроенного софта - имеют свою специфику и не факт, что идея сбалансированной формы там применима.

Есть некоторая проблема воспроизводимости во многих материалах по архитектуре ПО.
...
Например игры


Я к геймдеву вообще никакого отношения не имею, поэтому возможно фигню сморожу, но у меня такое мнение - львиная доля информации по архитектуре (включая мой доклад/статью), посвящена архитектуре (бакэндов) информационных систем. А "естество" бакенда сильно отличается от "естества" игры и поэтому эта информация плохо ложится на реалии разработки игр.

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

Опять же, для бэков у меня есть решение. Актуальную версию пока не расписал, но она базируется на алгоритме декомпозиции на базе эффектов (это тоже статья по мотивам доклада и этот доклад уже есть в публичном доступе на Ютубе).

Вообще, паковать одну коробку в несколько и несколько в одну выглядит разными бизнес-операциями

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

А за BoxesAggregate - спасибо. Вы, кажется, наконец-то мне объяснили идею "по агрегату на запрос", которую я полгода правильно понять не мог и из-за этого отвергал. Сейчас она заиграла для меня новыми красками, возможно утащу к себе.

Последние 4 года 90% моих тестов такие и есть - интеграционные, с БД в тестконтейнере и запросами по ХТТП.

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


Два оснонвых секрета:
1) Не использовать @DynamicPropertySource, потому что это приводит к инвалидации контекста в кэше и запуску контекста для каждого тест-кейса
2) Использовать RAM-диск для постгреса.

Вместо DynamicPropertySource я использую такой трюк:

@ContextConfiguration(
    // ...
    initializers = [TestContainerDbContextInitializer::class]
)

class TestContainerDbContextInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {

    override fun initialize(applicationContext: ConfigurableApplicationContext) {
        // это небольшая функция-расширение, которая просто перетирает 
        applicationContext.overrideProperties(
            "spring.datasource.username" to pgContainer.username,
            // ...
        )
    }
}

А чтобы посадить Postgres на рам-диск - такой:

        .withTmpFs(mapOf("/var" to "rw"))
        .withEnv("PGDATA", "/var/lib/postgresql/data-no-mounted")

В результате, у меня на i7-8700, 32 RAM, SSD интеграционные тесты выполняются от 14мс при тесте с моками в 163 мс:

А в проекте со скрина, я пошёл ещё радикальнее - отказался от @SpringBootTest и запускаю приложание руками, а в локальной разработке сначала ищу предзапущенную БД:

val context: ConfigurableApplicationContext by lazy {
    SpringApplicationBuilder(TestsConfig::class.java)
        .profiles("test")
        .build()
        .run()
}

@Import(
    QYogaApp::class,
    BackgroundsConfig::class,
    TestPasswordEncoderConfig::class,
    TestDataSourceConfig::class,
    TestMinioConfig::class,
    FailingController::class
)
@Configuration
class TestsConfig

private const val DB_USER = "postgres"
private const val DB_PASSWORD = "password"

val jdbcUrl: String by lazy {
    try {
        val con = DriverManager.getConnection(
            PROVIDED_DB_URL.replace("qyoga", DB_USER),
            DB_USER,
            DB_PASSWORD
        )
        log.info("Provided db found, recreating it")
        con.prepareStatement(
            """
                DROP DATABASE IF EXISTS qyoga;
                CREATE DATABASE qyoga;
            """.trimIndent()
        )
            .execute()
        log.info("Provided db found, recreated")
        PROVIDED_DB_URL
    } catch (e: SQLException) {
        log.info("Provided Db not found: ${e.message}")
        pgContainer.jdbcUrl
    }
}

Это позволяет сэкономить ещё пару секунд на инициализации тестов, что имеет существенное значение, когда ты делаешь зелёным один тест кейс.

А есть какие-то исследования, подтверждающие, что JPA это стандарт?

Я сам ограничился этим :)

У обеих компаний десятки миллионов клиентов.

Мне кажется тут надо смотреть не на количество клиентов, а на количество компаний/разработчиков.

Но вообще я вполне допускаю, что JPA стандарт только в моём инфопузыре и объективно она не так популярна, как мне кажется.

А и самое главное забыл - SDJ интегрируется с MyBatis из коробки:)

То есть вы можете писать в БД через SDJ, со всеми его плюшками, а читать через MyBatis со всеми плюшками ещё и MyBatis.

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

Не совсем понял ваш посыл.

Константин, проводил свои исследования в 60-ых - лет за 40-50 до хайпа ФП.

При этом мы не наблюдаем взрыва популярности языков программирования
которые хоть как-то поддерживают ФП парадигму более менее достойно,
вроде Scala, F#. Мы наблюдаем как множество экспертов-практиков пишут все в тех же языках с императивной парадигмой вроде Java, C# и т.д.

Я бы не стал связывать языки с поддержкой парадигмы и разработку в соответствии с парадигмой - на любом (Тьюринг полном) языке можно писать в соответствии с любой парадигмой, вопрос только в бойлерплейте. При том ФП парадигма + более-менее подходящий более-менее мейнстримный язык - это дешевле, на мой взгляд, чем ФП парадигма + чисто функциональный язык под который сложно найти разработчиков, библиотеки, документацию ко всему этому и решения не самых тривиальных проблем.

Но тут мы уходим в холивар, что такое ФП. В котором даже кложуристы с хаскеллистами не могут решить кто из них Труъ ФП.

Почему мы не наблюдаем большого количества больших и значимых проектов на Haskell?

Полагаю, основная причина - потому что в ВУЗах учат C/Java/Python etc.

а вот по повода второго, я бы с интересом посмотрел как бы Вы написали
в функциональном стиле что-то требующее производительности

Ненене, я в своём уме, я бы никогда не стал этого делать:) Этот пример не про то, что ФП быстрее, а про то, что ФП "понятнее" для компилятора, из чего я делаю предположение, что оно и для человека понятнее.

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

Ну тут мне кажется мы снова упираемся в вопрос, что такое ФП.

Например

// написано в браузере
fun calculateSalary(e: Employee): Int = 
  TODO() // тут чистая математика

fun main () {
    val e = db.getEmployee(readLine().toInt())
    val salary = calculateSalary(e)
    paymentGateway.pay(e, salary)
}

Для меня - ФП. Нужна ли для докторская для чтения этого кода? Нет

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

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

Другое дело, что исходных данных нет - это да. Но так я и говорил о Гипотезе:)

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

Вы меня не верно поняли:) Я делаю вывод на основании эмпирического исследования Константина и собственного опыта. А эксперты и книги - это шло в разделе "Косвенные доказательства".

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

Соответственно у вас должен быть какой-то инфраструктурный код, который занимается работой с очередью - с одной стороны помещает данные в очередь, а с другой стороны - достаёт.

А вот что будет перед и после этого кода - зависит от задачи.

Как вариант, у вас на обеих сторонах может быть по координатору

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

Координатор на выходе получает запрос от инфраструктурного кода и делает всё тоже самое, вплоть до публикации в следующую очередь.

Information

Rating
1,522-nd
Location
Кольцово, Новосибирская обл., Россия
Date of birth
Registered
Activity

Specialization

Chief Technology Officer (CTO), Software Architect
Lead
From 350,000 ₽
Functional programming
Object-oriented design
Design information systems
TDD/BDD
Kotlin
PostgreSQL
Java Spring Framework
Linux
Git
Docker