Здравствуйте, Poopy Joe, Вы писали:
PJ>Они зависимые, даже если их описывать на ассемблере. Нельзя получить любой из них по своему выбору, можно только вывести один из другого, причем есть всего два пути.
Это понятно. Просто на ассемблере придётся писать руками вообще всё, на шарпе — чуть меньше, а F#, как я понимаю, обходится без бойлерплейта.
PJ>Вообще никакой разницы. Возможет только для PaidOrder
Я не понимаю что такое "вообще никакой разницы". Вы только что мне привели в пример простыню кода, которая всё ещё ничего не делает, кроме как описывает статические бизнес-правила.
Не умаляю полезности этого, но по соотношению "логика на строчку кода" этот пока не выигрывает у любого другого варианта.
PJ>Это поможет не мешать мух с котлетами, например.
Хотелось бы практических аргументов — вроде того, что сократится объём кода.
S>>Как это в каком? Появились типы ShippedPaidOrder и ShippedUnpaidOrder. А как иначе мы выразим требование "заказ перед выдачей должен быть оплачен" при помощи типов?
PJ>А ты про них. Ну да, их стало два, ну в результате у тебя получился ровно один Delivered.
У меня и до этого был один Delivered.
PJ>Может у нас разное понимание экспоненты? Ты это так любишь употреблять но порабы доказать уже математически.
PJ>Имеем,
PJ>PJ> PaidOrder ShippedPaidOrder -------------------------
PJ> / \ / \
PJ>NewOrder -> IncompleteOrder -> ValidatedOrder ShippableOrder Delivered
PJ> \ UnpaidOrder / \ ShippedUnpaidOrder -> DeliveredWithPaiment /
PJ>
PJ>Покажи мне тут экспоненту?
Ну, вы продолжаете хитрить на ровном месте. Каким образом вы склеили PaidShippableOrder с UnpaidShippableOrder?
Если бы вы этого не сделали, то стало бы видно, как появление признака "Paid/Unpaid" удваивает количество классов. Пока что у нас получилось 2 (количество стадий оплаты) * 3 (количество стадий поставки) классов.
С каждым
дополнительным признаком, который может влиять на поведение заказа, пространство типов будет умножаться на количество значений этого признака.
Итого, для N признаков мы и имеем минимум 2*2*2*...*2 N раз. Функцию F(N) = С^N мы и называем экспонентой.
Иногда рост может сдержаться за счёт того, что признаки не являются независимыми — порой мы можем выкинуть патологическое сочетание. Но так везёт не всегда. Вот вы сначала хотели схитрить и потребовать доставку только после оплаты, диагонализировав матрицу состояний. Ну, вот не везде доступна такая роскошь.
PJ>И, второй вопрос, как ты это обычно выражешь по-своему?
Обычно это выражается ровно в тех правилах, которые описывают переход состояний. В том методе, который пытается изменить статус отгрузки, проверяется наличие пре-реквизитов отгрузки. И ему всё равно, сколько ещё есть признаков и какие там у них значения. В терминах типов, этот метод полиморфен — он принимает и PaidShippableOrder, и UnpaidShippableOrder, и любой другой ShippableOrder.
PJ>А код он в C# не в типах живет? И как ты проверишь эту корретность когда поменяешь модель?
Код, конечно, живёт в типах. Но вопрос, в том числе, и в том, сколько будет этих типов — сколько будет бойлерплейта вокруг реально значимого кода.
PJ>Ну откуда мне знать твою бизнес-задачу?! Я лишь заметил, что нет необходимости все валить в кучу, создавая God class с кучей методов.
Конечно нету. Разумную декомпозицию никто не отменял.
S>>Количество бойлерплейта радикально зависит от того, какой стиль кода выбран.
PJ>Да не особо. Тебе надо будет сделать все те же проверки, только в другом месте, и компилятор не будет тебе помогать поддерживать инвариант. Или ты под радикально другим стилем подразумеваешь спагетти-код, который вообще ничего не проверят и тупо валится при любом неверном параметре? Ну такой код будет короче, спору нет.
S>> Пока что у меня впечатление, что только лишь F# имеет (если имеет) достаточно мощную систему типов, чтобы описывать бизнес-правила с нужной точностью.
PJ>F# код моджно декомпилировать в C# код.
Ну, это хорошо. А компилятор С# будет проверять всё то же, что и F#? Ну там — полноту паттерн-матчинга? Или можно декомпилировать F# в C#, потом внести небольшое изменение, и C# всё отлично откомпилирует, а F# бы дал по рукам?
Так-то можно и в IL декомпилировать, и применить там пару трюков, которые не матчатся на валидный C#.
PJ>За исключением некоторых фич компайлера, типы там обычные дотнетовские.
Почитаем.
PJ>Несомненно на F# все это сильно проще, короче и лучше. Ну так и используй F#, если цель не страдать, а получить код который не надо отлаживать.
Но если, по какой-то причине, надо использовать C#, то это не является шоустоппером.
Ну, опять же — можно всё то же самое и на ассемблере написать. Шоустоппером ничто не является. Вопрос в том, будет ли ассемблер проверять инварианты, которые проверяет F#.
PJ>Задача агрегата поддерживать инвариант агрегата. Вот эта логика находится в нем. Бизнес-логика находится в другом слое, я выше описывал. Я выше ссылку на книжку давал, там это все подробно описано, советую найти и прочитать.
Почитаю, почитаю.