Pull to refresh

Comments 48

Если посмотреть с точки зрения объектно-ориентированной парадигмы, то озвученная проблема решается следующим образом:
Есть абстрактный тип данных «Договор» в виде интерфейса или абстрактного класса. Изначально, в первой версии ТЗ, система реализует N конкретных типов договора, каждый из которых представлен классом наследником абстрактного.
Весь код в системе, который должен обрабатывать различные конкретные типы знает только об общем интерфейсе.
Т.о. нет нужды плодить проверки if / else if / else по type_id, т.к. вся специфичная логика окажется локализованной в специальных классах. Это будет проще в поддержке, поскольку разработчикам не будет нужды беспокоиться о размазанной по программе специфике работы с разными договорами, можно будет мыслить на одном из уровней абстракций: либо конкретного подтипа, либо более общего кода.
>Есть абстрактный тип данных «Договор» в виде интерфейса или абстрактного класса.


В ООП проблема остается, абстракция — это обобщение. Свойства договора на момент ТЗ фиксированы. Это очевидно. Вопрос — что будет в будущем? Появится новый тип договора и заказчик не гарантирует, что он будет попадать под старые свойства. Приведенный метод сразу будет сигнализировать во всех местах, где нужно обрабатывать новый тип. Как такое можно сделать на ООП — вопрос открытый? Какие есть идеи?
Новый тип договора — новый тип в коде.
Меняются свойства — меняется код. Концепция Domain driven design. DDD.
Код должен быть покрыт тестами. Меняются требования — находим тесты их покрывающие, адаптируем к изменениям, убеждаемся, что тесты упали. Адаптируем код для прохождения тестов в условиях новых требований. TDD методология разработки.
тесты и ООП — это разные вещи, друг друга не исключают
А разве кто-то утверждает что исключают?

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

Мне не совсем понятна мотивация для использования Вашего подхода. В многих языках есть конструкция утверждений (assert), по логике в приведённом псевдокоде стоило бы использовать её.

Только зачем, если проблема в сообществе DDD давно решена более легкими в поддержке и изящными способами?
>В многих языках есть конструкция утверждений (assert), по логике в приведённом псевдокоде стоило бы использовать её


Главное, что бы принцип был понятен, а использовать assert или еще что-то другое — не важно. От перемены названия мало что меняется.

>Только зачем, если проблема в сообществе DDD давно решена более легкими в поддержке и изящными способами?

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

Если некоторые данные влияют на логику их обработки, значит это уже не просто данные, а часть кода. Помочь могут перечислимые типы, как писал ниже i360u.
Я вижу одно локализованное место для их использования: в коде инстанцирующем конкретные экземпляры объектов (репозиторий / фабрика), которые сопоставляют данные с подходящей реализацией.
Различия стоит локализовать в конкретных типах, а не размазывать по коду приложения. Иначе это будет напоминать ситуацию описанную в недавней статье: https://habrahabr.ru/post/312792/

>Вы предлагаете хардкодить в логике данные

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

Что-то вроде этого,… по моему мнению, код должен реагировать на незнакомые типы. Как именно он будет их определять — есть разные варианты. Хардкод, кончено, примитивный, но довольно наглядный.

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

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

if type_id in (1,2,3) then
 return new class instance;
else
 throw error;
end if;
interface Contract {}

class ContractA implements Contract{}

class ContractB implements Contract{}

class Contract_100500 implements Contract{}

class ContractRepository() {
  
  enum ContractType {
     A,
     B, 
     _100500
  }

public Contract getById(int id) {
     // вот здесь по хорошему _единственное_ подходящее место
     // для свитча по перечислению имеющихся типов
     // алгоритм инстанцирования сводиться к 
     // 1. пойти в хранилище, взять строку по ID,
     // 2. исходя из полученных данных инстанцировать нужную реализацию. 
  }
}



На самом деле с нормальной ORM и этого кода не надо, т.к. там скорее всего будет поддержка наследования сущностей исходя из данных.
с простым случаем разобрались наконец
Стойкое ощущение от вашего диалога, что вам нужно писать материал по теме, и вы таким образом его собираете…

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

Это возникает в большинстве проектов занимающих больше нескольких человеко-лет.
И развивающихся в процессе своей жизни.

В общем предлагаю развить и закончить вашу мысль.
Поскольку в таком виде это скорее как антипаттерн для средних и больших проектов (а таковыми считаются, насколько я помню, проекты более 3-5 человеко-лет для средних и свыше 10-15 для больших)
В общем предлагаю развить и закончить вашу мысль.

На момент написания статьи я даже не знал, что её кто-то прочитает, поэтому не вкладывался. Если найдет вдохновение, то подкреплю фактами.

Поскольку в таком виде это скорее как антипаттерн для средних и больших проектов

В моем проекте объем примерно 20 лет * 100 чел. Мне доступно примерно 2 Гб кода, примерно 20 млн строк кода. Сколько кода всего в системе сказать не могу. Такие дела
«Любую проблему можно решить ещё одним уровнем абстракции. Кроме излишней абстракции.».

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

Спасибо за предложение, мне вполне хватает исходников, и на работе и дома.

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

Не я против, Вы придираетесь. Чем сильнее придираетесь, тем лучше поймете мысль. Хардкод исключительно для демонстрации
>Концепция Domain driven design. DDD.

Кстати, не все так хорошо с DDD.
Цитатата из https://habrahabr.ru/post/313110/
«DDD Работает хорошо в устоявшихся бизнес-процессах»

Я-то поднял проблему изменяющихся бизнес-процессов. С устоявшимися все понятно и так.
В ООП эта проблема не актуальна — общий метод легко переопределяется в новых классах, если это требуется. Проверить, что нужные методы переопределены для нового типа в соответствии ТЗ при ревью не сложно — достаточно посмотреть на сигнатуру класса. Инструменты по документированию создают удобные для этого отчеты.
В императивном подходе эта проблема есть..., но это не самая большая проблема императивного программирования.
>общий метод легко переопределяется в новых классах, если это требуется

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

В моем же варианте кода (пусть не самом лучшем) все места сразу «зазвонят», когда попадется новый тип.
Диаграмы классив очень помогают разобраться в унаследованном коде. В ООП еще можно при начале работы с чужим кодом провести рефакторинг (если тесты есть), после которого становится ясно, куда новый класс пихать.
Вообще, программистам не следует делать то, что компилятор и инструмены сделают лучше.
Специфика больших проектов в том, что есть зоопарк инструментов, используются старые технологии вперемешку с новыми, никто не видит полной картины и тд и тп. Зачастую непонятен стек вызова. Бывает такое: Нажимаешь на кнопку, она дает ошибку. Начинаешь разбираться, открываешь проект. В проекте кнопки нет. Через день понятно, что она рисуется через WinAPI. Далее она подгружает dll специальным загрузчиком с сервера. Там вызывается COM объект. COM открывает форму, в которой все компоненты получают данные через HTTPS от объектов на сервере. Объекты на сервере вызывают движок ActiveScript, который берет скрипты из базы данных, которые сами берут данные из хранимых процедур, которые возвращают refcursor. На третий день нашел где ошибка, поправил одну строчку.

Как это можно загрузить в диаграмму — непонятно.

Перечисляемые типы вроде как для этого и созданы? И всякие велосипеды для их эмуляции там, где они "by design" не предусмотрены? И это, вроде как, для всех, кто хоть как-то работает со структурами данных обычная практика? Или я неправильно все понял?

>И это, вроде как, для всех, кто хоть как-то работает со структурами данных обычная практика?

Выдавать ошибку, когда встретился незнакомый тип, «by design» не обеспечивает. По крайней мере на уровне языков такого не встречал.

Так, а в чем проблема получить тип в месте использования, как возвращаемое значение какого-нибудь статического метода какого-нибудь абстрактного класса со встроенной обработкой ошибок в одном месте? А если поддерживаемые модулем типы (при слабой связанности) отправлять, при этом, в качестве аргумента, чтобы отлавливать любые нестыковки? Повторюсь: перечисляемые типы ведь для этого и созданы?

Способов сделать много, я как раз планировал в продолжение написать как сделать, что бы программист не заботился о списках в коде. Что-то типа
if  Is_in_list(type_id, "список типов, ТЗ от 01.01.2016") then
do something;
end if;


В функции is_in_list() выдывать исключение и мгновенно оповещать разработчика или аналитика, даже если исключение возникло у другого пользователя. Так даже пользователю не надо будет звонить в поддержку, что бы разобраться. Поддержке или программисту придет сообщение

«При обработке 'список типов, ТЗ от 01.01.2016' возникло исключение в таком-то куске кода. Требуется либо добавить новый тип в список, либо внести изменения в код для особой обработки этого типа»

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

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

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

При этом все они являются регламентированными
>но это же довольно банальное умозаключение

да банально, не спорю,

Есть анекдот от Мамонова про старого гитариста.Выходит молодой гитарист и начинает играть и левой рукой и правой и вверх тормашками и за спиной и над головой. Выходит старый именитый гитарист, сел на стульчик и начинает на трех аккордах играть. Его спрашивают — «чего ж ты такой примитив гонишь? вон видишь молодой как скачет». «Вы понимаете», спокойно отвечает старый — «он еще ищет, а я уже нашел».
Выдавать ошибку, когда встретился незнакомый тип, «by design» не обеспечивает. По крайней мере на уровне языков такого не встречал.

Паттерн-матчинг в любом уважающем себя функциональном языке.


type Contract = Contract1 | Contract2 | Contract3

let f contract = 
    match contract with
    | Contract1 -> printfn "Found contract 1"
    | Contract2 -> printfn "Found contract 2"

Contract1 |> f //Found contract 1
Contract2 |> f //Found contract 2
Contract3 |> f //Run-time exception: The match cases were incomplete

(https://dotnetfiddle.net/Eup962)


Причем на самом деле, диагностика делается еще на этапе статического анализа.

И всякие велосипеды для их эмуляции там, где они «by design» не предусмотрены?


Мне тут подсказали, что есть так называемое контрактное программирование, где подобная семантика (else raise) встроена в предусловия и постусловия, которые срабатывают перед выполнением процедуры и после её выполнения.

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


У меня на прошлой работе был как раз такой пример — когда на каждый тип договора аналитик писал ТЗ с табличкой что куда сохранять и как, тестировщик писал автотесты, а мы имплементили согласно ТЗ. В итоге у нас было очень мало багов… и очень длинный цикл разработки. В то время как заказчик хотел "сделайте как у типа 123, но с такими-то изменениями". И пусть даже там будут баги, зато эти десятки новых типов будут добавлены за разумное время.

Было бы интересно узнать ваш опыт.

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

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

Что касается ошибок, то debug mode это обычная практика.
Так же как и логи разных уровней.
Вы, похоже, действительно имеете очень мало практического опыта.
Вам уже предложили рабочее решение этого вопроса.

Извините, но предложенный шаблон не решает проблему, которую я поднял без кода, который я привел.

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

Она и не будет решаться без кода.

Поскольку в самой постановке задачи ваш некий новый тип имеет поведение отличное от других.

Вы можете это запрограммировать и сделать конфигурируемым… Но в сложных проектах это потребует прикладников внедренцев, как в тех же 1С, SAP.

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

Хорошо, когда вы знаете что дописывать. В моем проекте из-за большого объема заранее неизвестно что надо дописывать.
Это не соответствует действительности.

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

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

Имеется 2 Гб кода, котрый находится в постоянном развитии. На некоторые модули по 10 человек очереди. Тут нужны навыки человека дождя. :):):)
Я вас огорчу.

Редко поведение вашей прикладной модели отличается от уже реализованных настолько, что требуется изменить все 2 Гб кода (возможно удивлю, но небольшой проект на node.js может подтянуть в себя 0.5 Гб зависимостей в коде).
Поэтому в рамках вашей задачи по наращиванию системы не новым, а модификацей уже существующего функционала вам редко потребуется взаимодействовать с кодом более 10-20 тысяч строк.
В противном случае вам лучше произвести декомпозицию задачи.

Что касается очереди…
То современные системы контроля версий позволяют вести параллельную разработку над одним модулем (git/mercurial).

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

В проекте на 100 человек, если он ещё не развалился, уже есть знающие люди.

Пользуйтесь внутренней экспертизой.
Минимум 3-5 человек адекватного уровня и опыта у вас должны быть
Я в общем сам знающий и хорошо умею считать. Поэтому я не верю слепо всему, что говорят авторитеты и не боюсь, когда мне не верят. :)
Sign up to leave a comment.

Articles