Re[27]: Что вы предлагаете на замену эксепшенов?
От: IT Россия linq2db.com
Дата: 30.11.05 03:51
Оценка: 264 (19) +1
Здравствуйте, Сергей Губанов, Вы писали:

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>>
Если нам не помогут, то мы тоже никого не пощадим.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.