Здравствуйте, Сергей Губанов, Вы писали:
IT>>Сейчас чего-нибудь изобразим.
СГ>Написали Вы много,
А ты думал ты в сказку попал. Хватит в солдатиков в песочнице играться, welcome to the real world.
СГ>но принципиальной разницы с предыдущим примером нет.
А вот это мы сейчас и узнаем
Непонятно почему ты пропустил целый лэйер, но да бог с ним. Даже и с ним твой код рабочим назвать нельзя. Точнее он будет работать, но только в чистом, глубоком вакууме, т.к. увлёкшись бесконечными if'ами и &'ми ты совсем забыл сделать Rollback транзакции в случае ошибки. Ошибки двух методов StealSomeXXX ты тоже почему-то решил не обрабатывать. Отговорки типа ну я мол только идею продемонстрировал не принимаются. Впрочем, как раз её ты и "продемонстрировал" — сам понатыкал сосен и сам же в них запутался.
А теперь давай перепишем твой код на C# так чтобы он по идее был рабочим и можно было хоть что-то хоть как-то сравнивать. На строчки скупиться не будем (я же в своём примере не экономил). Я буду писать в своём стиле, но придерживаясь твоей идее. Любые замечания по существу принимаются.
Итак, первый слой — UI. Мой код из предыдущего примера.
void WithdrawalAndGetBalance()
{
try
{
decimal newBalance = AccountManager.WithdrawalAndGetBalance("12345", 100m);
Console.WriteLine("New balance is: {0}", bewBalance);
}
catch (Exception ex)
{
Console.WriteLine("Oops: " + ex.Message);
}
}
Твой вариант (по твоей идее).
void WithdrawalAndGetBalance()
{
decimal newBalance;
Errors err = null;
if (AccountManager.WithdrawalAndGetBalance("12345", 100m, out newBalance, ref list))
Console.WriteLine("New balance is: {0}", bewBalance);
else
{
if (err != null)
Console.WriteLine("Oops: " + err.Message);
else
Console.WriteLine("Just oops :xz:");
}
}
Счёт:
lines: 7:12
if/else/&: 0:4
AccountManager. Мой пример.
public static decimal WithdrawalAndGetBalance(string accountNumber, decimal amount)
{
Validate(accountNumber);
Validate(amount);
return new AccountService().WithdrawalAndGetBalance(accountNumber, amount);
}
Твой вариант.
public static bool WithdrawalAndGetBalance(string accountNumber, decimal amount, out decimal newBalance, ref Errors err)
{
return Validate(accountNumber, ref err) &&
Validate(amount, ref err) &&
new AccountService().WithdrawalAndGetBalance(accountNumber, amount, out newBalance, ref err);
}
Здесь я для наглядности перенёс вызовы на разные строчки, иначе текст не помещался на экран. В общем, далее слишком длинные строки я буду переносить.
lines: 4:3
if/else/&: 0:3
params: 2:4 (output: 0:2)
Но это всё цветочки. Вот они ягодки.
AccountService. Мой пример.
public decimal WithdrawalAndGetBalance(string accountNumber, decimal amount)
{
Validate(accountNumber);
Validate(amount);
using (AccountDataAccessor dataAccessor = new AccountDataAccessor())
{
dataAccessor.BeginTransaction();
decimal balance = dataAccessor.GetBalance(accountNumber);
if (balance < amount)
throw new BalanceException("You are freaking bankrupt!");
balance -= amount;
dataAccessor.SetNewBalance(accountNumber, balance);
if (balance > 1000000m)
dataAccessor.StealSomeForMe(accountNumber, 1.15m);
balance -= 1.15m;
if (balance > 10000000m)
dataAccessor.StealSomeForHomelessPeople(accountNumber, 0.15m);
balance -= 0.15m;
dataAccessor.ChargeForService(accountNumber, 10m);
balance -= 10m;
dataAccessor.CommitTransaction();
return balance;
}
}
Твой вариант.
delegate bool Do();
public bool WithdrawalAndGetBalance(string accountNumber, decimal amount, out decimal newBalance, ref Errors err)
{
newBalance = 0m;
if (Validate(accountNumber, err) && Validate(amount, ref err))
{
AccountDataAccessor dataAccessor = new AccountDataAccessor();
if (dataAccessor.Open(ref err))
{
if (dataAccessor.BeginTransaction(ref err))
{
if (new Do(delegate()
{
if (dataAccessor.GetBalance(accountNumber, out newBalance, ref err))
{
if (newBalance < amount)
{
err := Errors.NewList(Errors.NewError("You are freaking bankrupt!"), err); // Вот такое вот "кидание исключения"
return false;
}
else
{
newBalance -= amount;
if (dataAccessor.SetNewBalance(accountNumber, newBalance, ref err))
{
if (balance > 1000000m)
{
if (dataAccessor.StealSomeForMe(accountNumber, 1.15m, ref err) == false)
return false;
balance -= 1.15m;
}
if (balance > 10000000m)
{
if (dataAccessor.StealSomeForHomelessPeople(accountNumber, 0.15m, ref err) == false)
return false
balance -= 0.15m;
}
if (dataAccessor.ChargeForService(accountNumber, 10m, ref err))
{
balance -= 10m;
return true;
}
}
}
}
}())
{
if (dataAccessor.CommitTransaction(ref err))
{
dataAccessor.Close();
return true;
}
else
{
dataAccessor.Close();
return false;
}
}
dataAccessor.RollbaсkTransaction();
}
dataAccessor.Close();
}
return false;
}
}
lines: 35:71
if/else/&: 3:16 (9 уровней вложенности)
params: 2:4 (output: 0:2)
Нормально, да? Мало того что я на него потратил примерно на порядок больше времени, так ещё после того как я его закончил мне захотелось плакать
Итого имеем. Увеличение количество строк кода в два раза. Усложнение логики за счёт ветвления кода в 5 раз. В принципе, это и есть реальные, даже несколько заниженные цифры. Код начинает ветвиться и разрастаться с ужасающей быстротой, так же быстро теряется наглядность и появляются повторяющиеся фрагменты типа dataAccessor.Close() в примере. Уж извини, старался держаться в рамках твоей идеи. Для того, чтобы это хоть как-то контролировать в реальной жизни (а не в песочнице), некоторые проверки начинают пропускаться или просто забываться делаться с соответствующими последствиями. При внесении изменений в такой, уже казалось бы отлаженный код, внести новый баг — это обычное дело. Чтобы этого избежать, нужно тщательнейшим образом разобраться в этом бреде и делать всё максимально аккуратно, а это время, много времени.
Код с исключениями, как нетрудно заметить, имеет линейную структуру, прост и нагляден. Сделать ошибку в нём трудно, вносить изменения просто.
Некоторые, особенно одарённые товарищи, додумываются вообще до просто таки гениальной идеи. Если мы уже всё равно передаём структуру err, то почему бы в неё не затолкать какой-нибудь код, который можно было бы проверить на клиенте и сделать что-нибудь этакое. Т.е. коды результата уже начинают использоваться не только по своему прямому назначению, но и для других, внебрачных целей. Понять потом логику таких программ можно только "поднявшись" до уровня таких гениев.
Но это ещё не всё.
AccountDataAccessor. Мой код.
public decimal GetBalance(string accountNumber)
{
return (decimal)ExecuteScalarSp("GetBalance", accountNumber);
}
public void SetNewBalance(string accountNumber, decimal amount)
{
ExecuteNonQuerySp("SetBalance", accountNumber, amount);
}
public void StealSomeForMe(string accountNumber, decimal amount)
{
ExecuteNonQuerySp("StealSomeForMe", accountNumber, amount);
}
public void StealSomeForHomelessPeople(string accountNumber, decimal amount)
{
ExecuteNonQuerySp("StealSomeForHomelessPeople", accountNumber, amount);
}
public void ChargeForService(string accountNumber, decimal amount)
{
ExecuteNonQuerySp("ChargeForService", accountNumber, amount);
}
Твой вариант.
public bool GetBalance(string accountNumber, out newBalance, ref Errors err)
{
// oops!!! А ExecuteScalarSp параметра Errors не поддерживает.
// Ну дятлы её какие-то писали, не додумались до такой классной идее.
//
///////////return (decimal)ExecuteScalarSp("GetBalance", accountNumber);
}
// остальные упсы поскипаны.
В случае со "стандартной" ExecuteScalarSp, чтобы сохранить лицо, тебе скорее всего придётся написать длиннючий switch на все возможные коды возврата и подобрать к ним более менее вменяемые сообщения.
Да, кстати, использование ref и out параметров считается признаком плохого дизайна. FxCop на это заявляет "Consider a design that does not require that 'parameter_name' be an out parameter".
... << RSDN@Home 1.2.0 alpha rev. 0>>