Обновить
256K+

Проектирование и рефакторинг *

Реорганизация кода

64,4
Рейтинг
Сначала показывать
Порог рейтинга

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

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

Это применимо к подсистемам: двигатель, коробка передач в автомобиле, каталог или система заказов в e-com.

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

К чему эта мысль?

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

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

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

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

Не дублируйте контексты для домена, это плохо кончится.

Теги:
0
Комментарии2

Паттерны проектирования еще актуальны?

Вокруг все чаще говорят, что ИИ скоро будет писать код за нас. Логичный вопрос — нужны ли тогда паттерны? Зачем разбираться в паттернах GoF, если нейросеть и так сгенерирует рабочий код по описанию?

У меня ощущение обратное.

Я плотно вошел в разработку в 2019 году. Переходил из 1С в .NET. Книги по паттернам GoF у меня были, но долго лежали как «книга на полке». Казалось, они оторваны от повседневных задач. Теорию вроде понимал, но не видел, где это реально применяется.

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

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

Из этого и вырос мой pet-project gofinsights.com. Я делаю его тренажером по паттернам проектирования. Не просто «прочитал и забыл», а через практику, сравнение решений и постепенное распознавание типовых архитектурных ходов.

Сейчас там есть интерактивный квиз, где можно проверить базу и не перепутать Factory Method с Abstract Factory. Дальше хочу развивать проект в сторону более глубокого ИИ-разбора. Чтобы можно было не только узнавать паттерн, но и разбирать кодовые запахи, причины проблем и возможную эволюцию решений.

Как вы это видите? Паттерны проектирования все еще рабочая база для разработчика? Или с появлением ИИ они станут менее важны?

Теги:
+6
Комментарии1

Разрываем шаблоны: строим график с разрывом всего на 65 строк

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

Самые ходовые решения этой проблемы — разорванная ось (broken axis) или отдельные бины для аномалий. Проблема в том, что в Matplotlib нет готовой «кнопки» для создания гистограммы с разрывом. Но это легко* собирается руками на уровне нескольких осей.

Вот три рабочих подхода — выбирайте под свою задачу.

  1. Официальный пример из документации Matplotlib. 🔗 Ссылка на гайд. Отлично работает, когда выбросы зашкаливают по одной оси (X или Y). В посте разбирается как раз такой случай: гистограмма с волнистой линией обрыва.

  2. Библиотека brokenaxes делает почти всё сама. Устанавливается стандартно через pip. Вариант для тех, кто не хочет углубляться в ручную настройку.

  3. Логарифмическая шкала (часто — самый простой выход) Если выбросы строго положительные и отличаются на порядки, иногда достаточно двух строк: plt.xscale(«log») или plt.yscale(«log»). Никаких разрывов, никакой ручной работы — при этом график остаётся чистым и читаемым.

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.path import Path


def plot_broken_axis(
    labels: list[str],
    values: tuple[float, ...],
    ylim_low=(0, 12),
    ylim_high=(20, 25),
    **kwargs,
):
    """
    Строит график с разрывом оси.
    Валидирует входные данные и инкапсулирует логику отрисовки.
    """
    if len(labels) != len(values):
        raise ValueError("Длины labels и values не совпадают")

    fig, (ax_high, ax_low) = plt.subplots(
        nrows=2, figsize=(7, 4), gridspec_kw={"height_ratios": [1, 2]}
    )

    # Настройки столбцов
    kwargs.setdefault("color", "skyblue")
    kwargs.setdefault("edgecolor", "black")
    kwargs.setdefault("alpha", 0.85)

    ax_low.bar(labels, values, **kwargs)
    ax_high.bar(labels, values, **kwargs)
    fig.subplots_adjust(hspace=0.0)

    # Настройка осей
    ax_low.set_ylim(*ylim_low)
    ax_high.set_ylim(*ylim_high)
    ax_high.set_title("График с разрывом")
    ax_low.set_ylabel("Рейтинг в %")
    ax_low.set_xlabel("Языки")
    ax_high.spines["bottom"].set_visible(False)
    ax_low.spines["top"].set_visible(False)
    ax_high.tick_params(axis="x", bottom=False, labelbottom=False)
    # Рисуем разрыв оси (волна)
    offset, n_points = 0.03, 33
    pts = np.linspace(-offset, 1 + offset, n_points)
    wave = np.array([1 + (0, offset, 0, -offset)[i % 4] for i in range(n_points)])
    path = Path(list(zip(pts, wave)), [Path.MOVETO] + [Path.CURVE3] * (n_points - 1))

    opts = dict(transform=ax_low.transAxes, clip_on=False, zorder=10)
    ax_low.add_patch(mpatches.PathPatch(path, lw=6, **opts))
    ax_low.add_patch(mpatches.PathPatch(path, lw=3, edgecolor="white", **opts))
    return fig


if __name__ == "__main__":
    langs = ["Python", "C", "C++", "Asm"]
    pops = (21.8, 11.1, 8.6, 1.1)

    # Стиль xkcd
    with plt.xkcd(scale=1, length=300, randomness=30):
        plt.rcParams["font.family"] = "Comic Sans MS"

        # Вызов функции
        fig = plot_broken_axis(langs, pops)
        plt.show()

Литература:

  • Документация Matplotlib. 🔗 Ссылка на гайд

  • Bernd Klein. Numerisches Python Arbeiten mit NumPy, Matplotlib und Pandas

  • Sandro Tosi. Matplotlib for Python Developers

Теги:
+7
Комментарии0

РБПО по ГОСТ Р 56939—2024: вебинар №08 из 30 – Формирование и поддержание в актуальном состоянии правил кодирования

Компания ООО "ПВС" совместно с учебным центром "Маском" провела цикл вебинаров, посвящённых разработке безопасного программного обеспечения (РБПО). Совместно с приглашёнными экспертами различных компаний мы рассмотрели 25 процессов, приведённых в ГОСТ Р 56939—2024.

Предлагаем сегодня вашему вниманию вебинар цикла, посвящённый процессу, описанному в разделе 5.8. – "Формирование и поддержание в актуальном состоянии правил кодирования". На YouTube. Слайды.

Цели восьмого процесса по ГОСТ Р 56939—2024:

Обеспечение эффективной и единообразной организации оформления и использования исходного кода в соответствии с предъявляемыми к ПО требованиями.

Общее количество вебинаров — 30: каждому из 25 процессов ГОСТа посвящено по одному вебинару и 5 записано дополнительно на смежные темы. Запись всех вебинаров и подборка дополнительной информации доступна по ссылке: ГОСТ56939.РФ.

Теги:
+1
Комментарии0

Пара наблюдений (записанных наспех, извините) вокруг ситуации с ИИ:

  1. ИИ умеет писать код, но плохо (= хуже, недостаточно хорошо) умеет поддерживать его. Почему же так? ИИ обучался на основе данных из Интернета. В Интернете очень много кода, но код — это результат процесса разработки. Процесс разработки во многом идёт «в головах» и «на бумаге» отображается редко и не полностью, в отличие от кода. Единственный (основной) источник знаний ИИ о процессах — разные статьи и тому подобное, объём их на порядки меньше объёма готового кода;

    1. То же самое с проектированием архитектуры систем;

    2. Из этого следует, что ИИ неплохо подходит для написания write‑only кода;

    3. Дежурные истины: сложность не всегда в написании кода. Часто/обычно она в его поддержке, развитии, интеграции, в анализе предметной области;

    4. ИИ тоже член команды. Если вы пишете напр. пет‑проект и все традиции оформления кода, именования объектов, ведения документации и тому подобное находятся в вашей голове, вам либо придётся как‑то их сформулировать, чтобы ИИ писал в том же стиле, либо терпеть чужеродно выглядящий код;

    5. ИИ плохо пишет то, по чему мало информации в Интернете;

      1. Наличие в том же Интернете документации не поможет;

      2. Не недооценивайте объём туториалов и готовых решений на Python'е;

  2. При приведении в споре своего опыта работы с ИИ обязательно нужно указывать название модели;

    1. Научно‑технический прогресс измеряется по лучшим представителям;

    2. Вайб‑кодинг тоже бывает разного уровня профессионализма. Уровень более низкий: «написал (бесплатному) Дипсику, чтобы он сделал хорошо», уровень выше: «написал [такому‑то, более хорошему, ИИ], что нужно сделать, с чем интегрироваться, как оформлять, что выдать в ответе; перечитал запрос в поисках неточностей в ТЗ...», уровень выше: «использую плагин в IDE, стандартизировал некоторые требования» и тому подобное;

  3. Текст, который выглядит, как написанный ИИ, написан либо ИИ, либо человеком, который (видимо) обчитался таких же текстов и перенял стиль;

    1. Как было раньше: если статья написана без орфографических ошибок и повествование более‑менее внятное (не как у меня ^_^), это значило, что автор хотя бы статью вычитал, спланировал, отредактировал и тому подобное, то есть вложил силы, потому что написать неграмотно и невнятно может каждый. Сейчас же ИИ пишут как раз таки грамотно, внятно и структурировано, то есть теперь для такого же результата силы вкладывать не нужно;

  4. ИИ могут воспринимать рекомендации и явные разрешения что‑то делать как руководство к действию. Например, «допустимо использовать НазваниеБиблиотеки» резко повышает шанс того, что ИИ именно её и возьмёт;

  5. В вопросе ИИ люди делятся на «философов» и «инструментальщиков». Характерные фразы первых: «чёткого определения интеллекта нет», «ИИ не AGI», «ИИ называть ИИ некорректно» и тому подобное. Часто из тезисов выше ими делается вывод, что всё, что называется ИИ — чепуха. «Инструментальщики» же не заботятся о том, что как называется, им ясно, что даже если ИИ не может делать всё, то он уже может делать много что, поэтому является полезным (как можете догадаться, позицию первых я не разделяю и считаю непродуктивной);

    1. Если где‑то из‑за замены ручного труда на труд ИИ упало качество производимого продукта, но использование ИИ не прекращается, это говорит плохо не об ИИ, а о потребителях продукта. Ибо спрос рождает предложение, и если он удовлетворяется контентом неприемлимо низкого качества (по вашему мнению), то так было бы и без ИИ;

  6. По моим ощущениям, качество вывода ИИ падает от каждого чиха в промпте. Если вы использовали неоднозначное слово, добавили требования по оформлению, забыли уточнить какую‑то мелочь, попросили сделать несколько вещей за раз — качество будет ниже. (Вместо «напиши модуль с документацией и тесты» лучше в разных чатах «напиши документацию», «напиши модуль по документации», «напиши тесты по документации». Разделение труда — великая вещь.)

Теги:
Всего голосов 5: ↑5 и ↓0+5
Комментарии3

Учимся писать промпты правильно

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

Мой подход  "надо сделать вот это" и дальше уже куда поведет его и куда поведет меня. И это действительно было проблемой, когда я работал в агентах без режима планирования. Доходило до смешного, ты хотел с ним поговорить, он начинает херачить, ты ему "стоять нахер", он там уже наломал дров. В итоге постоянно откатывался сам либо просил откатывать его. В целом, это тоже хороший опыт, понимания того как вообще такие системы работают, где у них границы, как лучше формулировать и так далее.

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

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

Вдруг меня осенило, слишком сильное обдумывание до начала работы в агентской разработке (последние полгода), стало чем-то сродни преждевременной оптимизации. Поработав с большим числом моделей и агентов, могу сказать, что если вы будете сами все продумывать, то заставите работать более менее нормально даже самые тупые модели, но вы никогда не поймете границы их возможностей, где они могут еще больше взять на себя освободив вас от рутины. А модели и агенты постоянно развиваются, там где раньше надо было подсказывать, теперь они могут сами. Плюс если вы работаете над AGENTS.md, скилами и mcp, то и тут мы ловим буст.

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

Для упавшего ci это выглядит так:

  1. Поправь тесты

  2. Прошу посмотреть последний запущенный билд на github actions

Баг в браузере:

  1. прошу открыть страницу (он это делает через mcp chrome) и самому изучить

Для рефакторинга:

  1. Переводим вот это на это

  2. Вот эталон (тут ссылка на файл)

Для фичи:

  1. Нужно реализовать такую фичу

  2. Даю пример или говорю с помощью какого инструмента

И все. Самые продвинутые модели типа opus 4.6 или codex 5.3 справятся самостоятельно с большинством острых углов. Сами спросят объем, посмотрят разные варианты, изучат доку и так далее. Модели по тупее, не сделают глубокого анализа и ничего особо не спросят, но именно тут вы поймете где их надо вести и включать свой мозг чаще. Хотя хорошие модели настолько сильно помогают, что я физически не могу использовать более простые для задач планирования. Они хороши в авторежиме только для работы по аналогии, рефакторинг, написание тестов и так далее.

Даже если вы что-то забудете или пропустите сразу, все это можно будет доуточнить, попросить показать примеры кода, сходить открыть браузер. Если в процессе появляются вещи, которые агент делает из раза в раз, например, пытается выяснить где что-то лежит, как работать с какой-то частью системы, то постепенно это выносится в AGENTS.md и с определенного размера и уровни сложности (когда уже нужны скрипты например и команды) выносится в skill.

Теги:
Всего голосов 2: ↑2 и ↓0+2
Комментарии0

Привет, Хабр.

В дружественном нам блоге "SSP-Soft" недавно вышла рецензия на изданную нами книгу «UX для бизнеса: как создавать цифровые решения, ценные для бизнеса и пользователей». Книга поднимает важные, но зачастую упускаемые вопросы, связанные с проектированием удобных сайтов и приложений. Как сделать интерфейс одновременно технологически надёжным, интуитивно понятным и при этом рассчитанным на целевую аудиторию? Как добиться, чтобы в интерфейсе было удобно быстро сориентироваться, но при этом не упускалось ничего важного? Наконец, как учесть в интерфейсе интересы всех, кто будет им пользоваться - от маркетолога и дата-аналитика до простого пользователя?

В книге раскрыты ключевые аспекты оценки ценности продукта, диагностики проблем и определения оптимальных путей развития проектов. Изложены методы принятия обоснованных решений на всех этапах жизненного цикла проекта — от идеи до реализации. Рассмотрены принципы проектирования интерфейсов, структур и продуктов для разных сфер — от простых интернет-магазинов до сложных экосистем. Описаны различные типы продуктов и сервисов, включая контент-тяжелые платформы,  программное обеспечение для бизнеса (SaaS/PaaS), социальные сети, игры и инструменты машинного обучения. Подробно рассмотрены подходы к оптимизации процессов дизайна, принятию приоритетов и организации взаимодействия внутри команды и с внешними заинтересованными сторонами.

Теги:
Всего голосов 2: ↑2 и ↓0+3
Комментарии2

Привет, Хабр!

С некоторым опозданием предлагаем вам почитать обзор на переведённую нами книгу Диего Пачеко и Сэма Сгро "Принципы модернизации программных архитектур". Обзор сделали в своём корпблоге специалисты дружественной нам компании SSP-Soft. Книга появилась в продаже в начале декабря, пока только бумажная версия.

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

Приятного чтения!

Теги:
Всего голосов 2: ↑2 и ↓0+4
Комментарии0

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

API First и Богатая доменная модель не являются взаимоисключающими понятиями, если правильно выстроить процесс и архитектуру.

Главная проблема: API First фокусируется на контрактах внешнего мира, что часто приводит к созданию DTO/Model классов, которые являются чистыми структурами данных без поведения (анемичная модель).

API First — это про контракты, а не про реализацию. Не позволяйте инструментам кодогенерации диктовать структуру вашей доменной модели!

Правильный workflow:

1. API Design First: Создайте/обновите OpenAPI спецификацию.

2. Генерация DTO: Сгенерируйте или создайте классы для API‑контрактов.

3. Projection First: Спроектируйте доменную модель независимо от DTO, фокусируясь на поведении и инвариантах.

4. Создание Mapping Layer: Напишите преобразователи между DTO и Domain Objects.

5. Реализация Application Service: Тонкий слой, который координирует работу домена.

Стек технологий, который помогает:

MapStruct: Для автоматизации маппинга между DTO и Domain
JUnit 5: Для тестирования доменной логики
OpenAPI Generator: Для автоматической генерации DTO из спецификации
ArchUnit: Для проверки архитектурных правил (например, запрет на анемичные модели)

// ArchUnit тест, проверяющий, что в доменных классах есть поведение
@ArchTest
static final ArchRule domain_models_should_have_business_methods = 
    classes()
        .that().resideInPackage("..domain..")
        .and().areAnnotatedWith(Entity.class)
        .should().containAnyMethodsThat(
            DescribedPredicate.describe(
                "have business methods", 
                method -> !method.getName().startsWith("get") && 
                         !method.getName().startsWith("set")
            )
        );
Теги:
Всего голосов 1: ↑0 и ↓1-1
Комментарии0

5 Ошибок Рефакторинга

А ты надел каску перед рефакторингом ?
А ты надел каску перед рефакторингом ?
  1. Добавлять в рефакторинг улучшения
    Строго отделяйте рефакторинг т.е. преобразование кода из одной формы в другую с полным сохранением поведения от любых даже самых незначительных улучшений, оптимизаций, украшательства и тд и тп

  2. Делать один огромный коммит
    Делайте много коммитов, каждый на свой шаг рефакторинга. Рефакторинг это как ходьба по заминированному лабиринту, нужно обязательно записывать все ходы и иногда отступать на шаг или N шагов назад и искать другой путь.

  3. Рефакторить без промежуточных проверок
    Когда вдохновение несет и хочется "прибраться" и тут и там и везде и некогда останавливаться можно "пролететь поворот" и даже не один. Лучше всего делить рефакторинг на логические этапы. "Дешевые" по времени и ресурсу проверки можно и нужно запускать как можно чаще: компиляция, тесты, запуск приложение локально. Между крупными этапами желательно проводить регрессионное тестирование. И самое отличное поэтапный релиз рефакторинга, чтобы провести не только синтетические проверки, но самую важную "проверку продакшеном"

  4. Затягивать и долго не релизить рефакторинг
    Топ выбрасываний рефакторинга на моей практике происходило из за желания довести его до окончательного окончания, всё всё исправить, привести в идеальную симметрию и тд и тп. Чем дольше человек очищает код, пока параллельно идут продуктовые спринты, тем больше он несет накладных расходов(мержить то надо) и тем больше падает вероятность успешной интеграции ветки рефакторинга с основной и его успешного релиза.

  5. Не думать о запасном варианте
    Не смотря на все многоступенчатые системы проверки качества вашего кода всегда есть далеко не нулевая вероятность ошибки, особенно когда "наводишь порядок" в самом ключевом месте системы (а где еще как не в таких местах наводить порядок).
    В таких ситуация очень полезно оставлять запасной вариант, например флаг переключения на "абсолютно старый код", лучше всего налету без рестартов.

    В своем канале о разработке в стартапах делюсь опытом и рассказываю еще больше удачных примеров и факапов. Буду рад видеть каждого! Заходите!

    Всем удачного рефакторинга!

Теги:
Всего голосов 1: ↑1 и ↓0+1
Комментарии0

Почему нужно использовать DTO

Data Transfer Object, термин, который для разработчиков на статических языках является чем-то самим разумеющимся, но вот остальные его могут не знать (даже если пользуются). Хотя в эпоху интеграций, фронтенд-бекенд, сервис-сервис, очереди, это крайне важная конструкция.

DTO это очень промежуточный объект между моделью в вашем коде и данными, которые вы отдаете наружу или принимаете от внешней системы.

  • Модель => DTO => json/protobuf/sql...

  • json/protobuf/sql... => DTO => Модель

Нафига? Почему не сразу преобразовывать из, допустим, json в нашу модель или наоборот? Тем более во всех экосистемах есть механизмы, которые позволяют упаковывать любые объекты, задавая правила преобразования через метаданные, аннотации или еще как-то. Пример из Java:

@Entity
public class User {
    @Id
    private Long id;
    @JsonIgnore              // приходится скрывать
    private String passwordHash;
    @JsonProperty("created_at")
    private LocalDateTime createdAt;

    // getters/setters ...
}

var json = new ObjectMapper().writeValueAsString(dto);

Существует масса причин, почему это плохая идея. Для начала, это банальное нарушение MVC архитектуры. Модель начинает знать как о представлении, о том какие поля надо выдавать наружу, какие нет, как их переименовывать и так далее. Если это кажется натянутым, то вот вам реальные последствия.

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

DTO позволяют отделить представление от модели в коде, создавая по сути промежуточный слой. Имея его, вы можете независимо развивать свою модель и API для взаимодействия с ним. И да, это один из аспектов MVC, конкретно Model-View.

Готовые DTO гораздо легче чем модели конвертировать в типы на TS если у вас есть такая потребность. Например мы наши DTO (используем Alba), превращаем в типы TS с помощью готового инструмента (Typelizer). С моделями так легко не получится.

За это конечно придется заплатить. В проекте появится папка, с большим количеством файлов. Но это с лихвой компенсирует все описанные выше проблемы. DTO очень простые и для их создания далеко не всегда надо с нуля писать классы. В той же java они генерируются с помощью mapstruct, в других языках свои механизмы.

Но это только базовая история. Если мы еще подключаем инструменты генерации из sql (как в go) или openapi как везде, то те самые DTO создаются вообще автоматически на основе описаний.

INSERT INTO links (original_url, short_name)
VALUES (sqlc.arg(original_url), sqlc.arg(short_name))
RETURNING *;

DTO:

type CreateLinkParams struct {
	OriginalUrl string `json:"original_url"`
	ShortName   string `json:"short_name"`
}

Причем для update будет создана своя структура:

type UpdateLinkParams struct {
	OriginalUrl string `json:"original_url"`
	ShortName   string `json:"short_name"`
	ID          int64  `json:"id"`
}

Здесь отличается только id, но в реальных кейсах, отличий в создании или обновлении одной сущности обычно значительно больше, поэтому количество DTO тут становится еще больше.

DTO, кстати, должны быть имутабельны, иначе туда потечет логика

Больше про разработку в моем телеграм-канале Организованное программирование

Теги:
Всего голосов 9: ↑7 и ↓2+6
Комментарии1

Я переписывался с товарищем на тему архитектуры и сформировал более цельное понимание построения ООП-кода :)

Чуть-чуть обо мне: в разработке 12+ лет, сделал 50+ различных приложений и ещё немножко игр, это только для бизнеса, плюс еще мои личные эксперименты и petproject-ы.

Делая все эти приложения я пришел к тому что есть разные подходы к проектированию\моделированию систем, в частности с помощью ООП-кода:

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

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

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

3. "God Mode" - когда ты делаешь все что душе угодно (читай как и то и другое, "Action" и "Strategy" в одном флаконе)

И так же я пришел к тому что есть несколько слоев моделирования\проектирования:

(как сказали бы в Теории Систем - есть надсистема, есть система и есть подсистема, а Шрек описал бы это как слои лука, а Осел рассказал бы что это напоминает слоёный торт 😁)

1. Уровень реальности в которой есть человек пользователь, используемый девайс (десктоп, ноутбук, смартфон, и т.п.), сервер с бэкендом; правила их взаимодействия (интерфейсы, протоколы, договоренности); и процессы в которых это взаимодействие происходит (циклы, цепочки событий, потоки данных)

2. Уровень приложения в котором есть сущности бизнес-логики(игровой логики), например Hero, Enemy, Obstacle, Menu, Map, Score; правила их взаимодействия; и процессы в которых происходит взаимодействие

3. Уровень инструментов в котором есть сущности языка программирования такие как примитивы(Int, Long, String, Bool), структуры данных, основные компоненты игрового движка(операционной системы); правила их взаимодействия; и процессы в которых происходит это взаимодействие

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

На каждом слое может использоваться как ООП подход так и ФП, так и нечто смешанное что добавляет удобства.

К чему это все? Наш разговор начался с того что я вбросил мнение что самая чистая архитектура обычно это чистейший ООП-код, и удобство архитектуры зависит как раз от навыка моделирования систем этим ООП-кодом.

Такие дела :)

Я по прежнему считаю что ООП рулит! 😁

А что думаешь ты про ООП, архитектуру и чистый код?

Делись своим опытом в комментариях, или нет..

Теги:
Всего голосов 6: ↑2 и ↓4-2
Комментарии19

Я исследовал тему связности и связанности в построении кода и вот к чему пришел:

Не существует плохих\хороших\идеальных связности и связанности кода.

Мне кажется проблема и решение глубже - сколько людей столько и вариантов осмысления и построения "модели", столько вариантов же coupling & cohesion. У каждого что-то свое.

Строить приложение от архитектуры - такое себе. Архитектура для приложения, а не приложение для архитектуры. Тогда архитектура будет основана на реальных задачах а не на поиске идеала.

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

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

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

Ну и когда я говорю ООП, это не значит что я буду писать абстракцию на каждый чих, влоть до Int, Long и т.п., нет, это значит что я начну с самых больших MyApp { UserClient, ServerClient, DeviceClient } и законтачу их между собой логикой приложения, а там дальше буду создавать абстракции по необходимости, если будет удобно и полезно что-то добавить и переиспользовать то я добавлю и переиспользую (вот кстати хороший критерий - моделировать сущность когда надо что-то передавать между главными абсракциями(надсистемами)).

ООП рулит :)

P.S. И не надо стремиться к идеалу, иначе тут можно скатиться в подмену задач, и начать делать не данное конкретное приложение, а предложенный кем-то идеал архитектуры.

Теги:
Всего голосов 3: ↑1 и ↓2-1
Комментарии9

Ближайшие события

Как правильно откатывать миграции? Если коротко, то никак.

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

Ролбек, в идеале, это просто переключение с одной версии кода на другую. Но ведь тогда возможны ошибки связанные с изменениями в базе? Если делать через жопу, то возможны. При правильном подходе, база всегда обратно совместима как минимум на одну версию. Только в этом случае мы можем обеспечить и бесшовный деплой (zero downtime deploy) и практически моментальный откат.

А это значит, что нельзя менять тип у колонок (если тип сужается), нельзя менять именования таблиц и полей. Если это все таки нужно, то существует немало техник, позволяющих сделать переход через создание новых сущностей и синхронизацией либо через код либо через саму базу (например с помощью триггеров). По этой теме даже написали целую книгу "Refactoring Databases: Evolutionary Database Design".

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

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

Больше про разработку в моем телеграм-канале организованное программирование

Теги:
Всего голосов 8: ↑6 и ↓2+5
Комментарии2

Выводим Бугаенко на чистую воду разбирая ООП

Топ Перлов

  • Любой массив байт должен уметь работать с файлами, сетью и тд.

  • Программа должна не падать на ошибках, а продолжать работу с фейковыми объектами.

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

  • Я придумал новый язык, и чтобы он не так сильно тормозил, надо встроить GC в CPU.

Упомянутые ссылки

Копилка благодарностей

Теги:
Всего голосов 13: ↑5 и ↓8-3
Комментарии2

мои способы работы с AI контекстом (субъективное мнение):

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

  2. пользоваться только английским для кода, prd и дизайна

  3. сокращать код - как показывает практика файлы больше 500-600 строк агенту труднее прочитать. Тут есть несколько стратегий:

  • пользоваться препроцессорами, например можно сильно срезать времени если вместо JSX/TSX/html использовать pug

  • пользоваться и писать библиотеки. есть проблема - оформляем в абстрактную библиотеку, пишем доку и readme

  • предвидеть контекст - часть текста просто можно не писать, а просить догенерить, потому что получится эффект снежного кома, начинаем катить, смотрим куда катится - сокращаем

  1. меняем JSON на YAML или что-то близкое - это срежет кууучу лишнего хлама в виде ; }{ "" и прочих символов

  2. генерим code maps - mermaid и другие виды диаграм

  3. ANSII - визуализация простых идей, но не все модели хорошо работают с этим форматом, есть проблемы с layout. например я пользуюсь gemini

  4. подстраивать архитектуру и код под задачи и бизнес, не наоборот. Developer | Designer | AI as User.

  5. разбивать на мелкие подприложения если нужно

Надеюсь, что пост окажется полезным :-)

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

Спасибо за ваше время и хорошего дня!

Теги:
Всего голосов 6: ↑2 и ↓4-2
Комментарии0

мои способы работы с AI контекстом (субъективное мнение):

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

  2. пользоваться только английским для кода, prd и дизайна

  3. сокращать код - как показывает практика файлы больше 500-600 строк агенту труднее прочитать. Тут есть несколько стратегий:

  • пользоваться препроцессорами, например можно сильно срезать времени если вместо JSX/TSX/html использовать pug

  • пользоваться и писать библиотеки. есть проблема - оформляем в абстрактную библиотеку, пишем доку и readme

  • предвидеть контекст - часть текста просто можно не писать, а просить догенерить, потому что получится эффект снежного кома, начинаем катить, смотрим куда катится - сокращаем

  1. меняем JSON на YAML или что-то близкое - это срежет кууучу лишнего хлама в виде ; }{ "" и прочих символов

  2. генерим code maps - mermaid и другие виды диаграм

  3. ANSII - визуализация простых идей, но не модели хорошо работают с этим форматом, есть проблемы с layout

  4. подстраивать архитектуру и код под задачи и бизнес, не наоборот. Developer | Designer | AI as User.

  5. разбивать на мелкие подприложения если нужно

Надеюсь, что пост окажется полезным :-)

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

Спасибо за ваше время и хорошего дня!

Теги:
Всего голосов 5: ↑2 и ↓3-1
Комментарии0

BPN vs MVM

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

В одном файле эта задача реализована в архитектуре BPN (Business Process Notation), о которой рассказывал раньше здесь. А во втором файле тот же код организован по архитектуре MVVM.

Код и в том, и в другом файле написан с помощью Claude Sonnet. В случае с BPN структурировал код вручную, следуя бизнес-процессам. А во втором случае попросил Клода сделать рефакторинг, используя традиционный современный подход и он выбрал MVVM.

Что можно сказать в итоге, сравнивая архитектуру в том, и другом случае. 

Объём кода

В BPN варианте 270 строки кода с комментариями, в MVVM - 524.

Т.е., в MVVM случае кода практически в 2 раза больше.

Количество сущностей, объектов.

BPN - один класс и 3 раширения к нему.

MVM - 6 классов, 1 структура, 3 протокола, каллбэки, фабрика, расширение.

Архитектура

BPN - монолит.

MVVM - вью и модель, анимация и аудио как сервисы, роутер, отдельная структура для хранения значений свойств и т.д.

Что лучше

Как всегда, каждый из подходов имеет свои плюсы и минусы.

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

Что такое “Модель”

Наиболее традиционны 2 понимания термина “модель”.

В одном случае, это структура данных, модель объекта.

Например:

struct Person {

let firstName: String

let lastName: String

var age: Int

}

В другом случае, под моделью понимается всё, что не относится к интерфейсу.

Но есть и третье понимание модели - это модель приложения, или модель отдельных процессов внутри приложения. Т.е., составные части приложения (процесса) и их последовательность.

В BPN файле такая модель проступает наглядно:

Модель процесса "Обратный отсчет"
Модель процесса "Обратный отсчет"

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

В файале MVVM блоков кода гораздо больше и нужно несколько раз проскоролить чтобы увидеть их в выпадающем меню. И увидеть целостную модель здесь сложнее.

Conclusion

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

BPN позволяет видеть целостную модель задачи (процесса, приложения).

Теги:
Рейтинг0
Комментарии10

Выводим Соера на чистую воду разбирая дискуссию с ним про принципы SOLID

Топ перлов

  • Если ты манки-патчишь объекты, то ты функциональщик.

  • Ты должен сначала залезть на гору, а потом уже решить надо было тебе сюда или нет.

  • Если люди по разному воспринимают принцип - это здорово, ведь он подталкивает людей к размышлению.

  • SOLID позволяет легче (т.е. не задумываясь) принимать не идеальные (т.е. сомнительные) решения.

Упомянутые материалы

Копилка благодарностей

Теги:
Всего голосов 10: ↑4 и ↓6-2
Комментарии0

Хочу поделиться с вами видением хорошей архитектуры Go проекта, к которой я пришёл на данный момент и также интересно послушать ваши варианты и мнения по данному поводу.

Моё видение:

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

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

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

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

То есть я считаю идеальной архитектурой для большинства Go проектов, работающих на основании адаптеров (к примеру, REST или gRPC сервис) гексагональную архитектуру с включением подхода DDD.

У меня остались также холиварные вопросы к вам. Как считаете:

  • Передавать в юзкейсы структуру или поля по отдельности?

  • Должно ли хранилище, в виде БД например, иметь валидацию данных? Операции по крону? Дефолтные значения полей?

  • Транзакции: где их начинать/заканчивать?

  • Когда и где вводить versioning: в HTTP‑уровне, в домене (разные агрегаты) или в репозиториях (Multi‑tenant)?

  • Должны ли доменные ошибки возвращать rich‑error (с кодом/контекстом) или достаточно обычных error с текстом?

Теги:
Всего голосов 3: ↑2 и ↓1+1
Комментарии3