Рефакторинг моего кода: условия, основанные на разных переменных - PullRequest
3 голосов
/ 20 ноября 2010

Учитывая:

internal void Configure(ButtonEventArgs args, IBroker broker, FunctionEntry entry)
        {
            int phase = broker.TradingPhase;

            if (args.Button == ItemType.SendAutoButton)
            {                              
                if (phase == 1)
                {
                    entry.SetParameter("ANDealerPrice", -1);
                    entry.SetParameter("ANAutoUpdate", 4);
                }
                else if (phase == 2)
                {
                    entry.SetParameter("ANDealerPrice", -1);
                    entry.SetParameter("ANAutoUpdate", 2);
                }
            }

            if (phase == 1)
            {
                if (broker.IsCashBMK)
                {
                    entry.SetParameter("Value", 100);

                }
                else if (broker.IsCross)
                {

                    entry.SetParameter("Value", 200);

                }
            }
}

Я ищу предложения по рефакторингу кода выше.Как предложил Фаулер: «Заменить условие на Стратегию / полиморфизм», я не могу реализовать эффективный код в этой строке.Поскольку существует несколько условий, основанных на множественных переменных.

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

Спасибо за ваш интерес.

Редактировать: 1) Мое намерение состоит в том, чтобы применить здесь принцип Open-Closed, чтобы, если завтра произойдет изменение в логике, я мог расширить эти условия, введя новый класс.2) Пожалуйста, не обращайте внимания на магические числа, в реальном сценарии у меня есть действительная константа / источник для них.

Ответы [ 5 ]

2 голосов
/ 20 ноября 2010

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

void SetAnDealerPrice(ButtonEventArgs args, IBroker broker,
        FunctionEntry entry) {
    if (args.Button != ItemType.SendAutoButton)
        return;
    int phase = broker.TradingPhase;
    if (phase == 1 || phase == 2)
        entry.SetParameter("ANDealerPrice", -1);
}

void SetAnAutoUpdate(ButtonEventArgs args, IBroker broker,
        FunctionEntry entry) {
    if (args.Button != ItemType.SendAutoButton)
        return;
    switch (broker.TradingPhase) {
    case 1:
        entry.SetParameter("ANAutoUpdate", 4);
        break;
    case 2:
        entry.SetParameter("ANAutoUpdate", 2);
        break;
    }
}

void SetValue(IBroker broker, FunctionEntry entry) {
    if (broker.TradingPhase != 1)
        return;
    entry.SetParameter("Value", broker.IsCashBMK ? 100 : 200);
}

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

2 голосов
/ 20 ноября 2010

На ум приходят два очевидных рефакторинга:

  1. Удалите else: теперь символы if можно упростить.
  2. Переставьте if, чтобы if (phase == ...) всегда было внешним.

Если вы хотите, вы можете изменить порядок блоков if, чтобы объединить блоки if (phase == 1), хотя я думаю, что я бы повторил это if (phase == 1) несколько раз, чтобы подготовиться к следующему шагу.

Эти рефакторинги облегчают применение приведенного ниже.

internal void Configure(ButtonEventArgs args, IBroker broker, FunctionEntry entry) 
{ 
        int phase = broker.TradingPhase; 

        if (phase == 1) 
        {                               
            if (args.Button == ItemType.SendAutoButton)
            { 
                entry.SetParameter("ANDealerPrice", -1); 
                entry.SetParameter("ANAutoUpdate", 4); 
            }
        }
        if (phase == 2) 
        {                               
            if (args.Button == ItemType.SendAutoButton) 
            { 
                entry.SetParameter("ANDealerPrice", -1); 
                entry.SetParameter("ANAutoUpdate", 2); 
            } 
        } 
        if (phase == 1) 
        { 
            if (broker.IsCashBMK) 
            { 
                entry.SetParameter("Value", 100); 

            }
        }
        if (phase == 1)
        { 
            if (broker.IsCross) 
            { 

                entry.SetParameter("Value", 200); 

            } 
        } 

}

Теперь у вас есть длинный список маленьких if-блоков. Это может быть преобразовано в List<MyAction>. Где-то вы должны заполнить этот список, но обходить его довольно просто:

internal void Configure(ButtonEventArgs args, IBroker broker, FunctionEntry entry)  
{
      foreach(var action in MyActions)
      {
           action(args, broker, entry);
      }  
 }
internal void PopulateMyActions()
{
      // Hopefully I have not made a syntax error in this code...
      MyActions.Add( (ButtonEventArgs args, IBroker broker, FunctionEntry entry) =>
         {
            if (broker.TradingPhase == 1) 
            {                               
                if (args.Button == ItemType.SendAutoButton)
                { 
                    entry.SetParameter("ANDealerPrice", -1); 
                    entry.SetParameter("ANAutoUpdate", 4); 
                }
            }
        } );
      // And so on
}

Альтернативой является создание отдельных списков для фазы == 1 и фазы == 2 и исключение посредника из вызова на action:

internal void Configure(ButtonEventArgs args, IBroker broker, FunctionEntry entry)  
{
      int phase = broker.TradingPhase;
      foreach(var action in MyActions[phase])
      {
           action(args, entry);
      }  
 }

internal void PopulateMyActions()
{
      // Hopefully I have not made a syntax error in this code...
      MyActions[1].Add( (ButtonEventArgs args, FunctionEntry entry) =>
         {
            if (args.Button == ItemType.SendAutoButton)
            { 
                entry.SetParameter("ANDealerPrice", -1); 
                entry.SetParameter("ANAutoUpdate", 4); 
            }
        } );
      // And so on
}

Мне кажется, я предпочитаю последнее, поскольку оно делает особую роль phase более явной.

Дополнительный рефакторинг может заменить action(args, entry) на action(args.Button, entry), но я не могу судить, подходит ли это.

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

PS: Код не проверен, так как я сейчас не в компиляторе. Не стесняйтесь редактировать мой ответ, чтобы удалить синтаксические ошибки, добавить объявление MyActions и т. Д.

2 голосов
/ 20 ноября 2010

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

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

Возможно, вы захотите разделить по фазам и применить Demeter.

Там также много волшебных чисел.Если вы переводите из одной системы в другую, сделайте их константами системы, в которую вы переводите, в противном случае переместите их в модель данных.

2 голосов
/ 20 ноября 2010

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

Похоже, у вас должно быть как минимум два метода, один GetBrokerPrice и один SetPriceOptions

1 голос
/ 20 ноября 2010

Мне кажется, что все, что делает эта функция, это устанавливает строковые параметры FunctionEntry.
Я бы сказал, FunctionEntry должен справиться с этой логикой.
Это не должно быть сделано Configure().
Я думаю, что он должен передать ItemType из ButtonEventArgs и IBrkoer в экземпляр FunctionEntry и позволить ему решить эту логику if-else.

Что касается вложенных if-else, меня это не слишком беспокоит.
Бизнес-логика может быть настолько сложной, насколько она хочет, особенно когда ей не хватает единообразия.
Было бы более сложно составить справочную таблицу для этой логики, поскольку в этом случае вам нужна трехмерная таблица: какую кнопку, какую фазу торговли у брокера, а затем какой параметр изменить. Это было бы хлопот, чтобы быть честным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...