Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Все равно идею не понимаю. Перенести в compile-time логику работы программы... Так ведь, если доводить до предела, можно потребовать в compile-time определение правильной последовательности всех действий программы. А это означает, что эта последовательность должна быть жестко фиксированной и не зависеть от данных — от них зависимость в compile-time не проверишь же...
Попробую проиллюстрировать свою идею. Положим, есть у нас сервер, умеющий что-нибудь считать.
Для того, что бы работать с ним надо:
1. Открыть сессию.
2. Загрузить данные.
3. Вызвать функцию вычисления (их может быть много).
4. Если вычисление прошло успешно, то выгрузить данные.
5. Закрыть сессию.
Как бы выглядел "обчный" порошо... интерфейс сервера и сессии:
interface IServer
{
ISession CreateSession();
}
interface ISession : IDisposable
{
void UploadData(Data);
Data DownloadData();
bool DoFuncA();
bool DoFuncB();
bool DoFuncC();
}
Интерфейс сервера меня устраивает, поработаю над интерфейсом сессии.
interface ISessionUploadData : IDisposable
{
Variant<ISessionDoJob, ISessionUploadData> UploadData(Data);
}
interface ISessionDoJob : IDispoable
{
Variant<ISessionDownloadData, IDisposable> DoFuncA();
Variant<ISessionDownloadData, IDisposable> DoFuncB();
Variant<ISessionDownloadData, IDisposable> DoFuncC();
}
interface ISessionDownloadData : IDisposable
{
Tuple<Data, IDisposable> DownloadData();
}
Все интерфейсы наследуются от IDisposable, что означает, что закрыть сессию можно в любой момент.
Обрати внимание на Variant. По сути это должен быть алгебраический тип, в который можно завернуть несколько вариантов возврата, параллельно с нужными кодами возврата (ошибки и т.п.)
В частности, функция UploadData в случае успеха возвращает ISessionDoJob, что бы продолжать работать с данными, либо снова ISessionUploadData, если данные не загрузились.
Функции вычисления возвращают либо ISessionDownloadData, если вычисление прошло, либо IDisposable, если ошибка.
То есть нет возможности вызвать функцию обработки данных до её загрузки или если загрузка обломалась.
И нет возможности вызвать функцию скачки результата, если данные не были обработаны.
Дальше, положим что мы решили поменять протокол. Сказали, что после загрузки но перед обработкой надо вызвать функцию PreparеData, которая как-нибудь данные готовит. Мы меняем интерфейсы, добавляя ISessionPrepareData "между" ISessionUploadData и ISessionDoJob:
interface ISessionUploadData : IDisposable
{
Variant<ISessionPrepareData, ISessionUploadData> UploadData(Data);
}
interface ISessionPrepareData : IDisposable
{
ISessionDoJob UploadData(Data);
}
interface ISessionDoJob : IDispoable
{
Variant<ISessionDownloadData, IDisposable> DoFuncA();
Variant<ISessionDownloadData, IDisposable> DoFuncB();
Variant<ISessionDownloadData, IDisposable> DoFuncC();
}
interface ISessionDownloadData : IDisposable
{
Tuple<Data, IDisposable> DownloadData();
}
Клиентский код, который мы забыли поправить, тут же перестанет собираться, мы получим по рукам во время сборки продукта и сможем подумать, не погорячились ли мы с новым протоколом, или просто поправить клиента. Теперь понятно, как статически описать и проконтролировать альтернативные варианты workflow протокола?