Когда нужно попытаться устранить оператор switch?

Я столкнулся с оператором switch в кодовой базе, над которой я работаю, и я пытаюсь понять, как заменить ее чем-то лучше, поскольку switch считаются запахом кода. Однако, прочитав несколько сообщений о stackoverflow о вместо switch Я не могу представить эффективный способ замены этого конкретного оператора switch.

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

В моем случае код (слегка запутанный естественным образом), с которым я борюсь, выглядит следующим образом:

private MyType DoSomething(IDataRecord reader)
{
    var p = new MyType
                {
                   Id = (int)reader[idIndex],
                   Name = (string)reader[nameIndex]
                }

    switch ((string) reader[discountTypeIndex])
    {
        case "A":
            p.DiscountType = DiscountType.Discountable;
            break;
        case "B":
            p.DiscountType = DiscountType.Loss;
            break;
        case "O":
            p.DiscountType = DiscountType.Other;
            break;
    }

    return p;
}

Может ли кто-нибудь предложить способ устранить этот переключатель? Или это подходящее использование переключателя? И если это так, есть ли другие подходящие применения для операторов switch? Мне бы очень хотелось узнать, где они подходят, поэтому я не трачу слишком много времени, пытаясь устранить каждое выражение переключателя, с которым я сталкиваюсь, потому что в некоторых случаях они считаются запахом.

Обновление: По предложению Michael я немного искал дублирование этой логики и обнаружил, что кто-то создал логику в другой класс, который фактически сделал всю инструкцию switch избыточным. Поэтому в контексте этого конкретного кода кода оператор switch не нужен. Однако мой вопрос касается скорее соответствия операторов switch в коде и всегда ли мы должны их заменять, когда они найдены, поэтому в этом случае я склонен принять ответ, что этот оператор switch подходит.

Ответ 1

Это подходящее использование для статута переключателя, поскольку оно делает выбор читаемым и легко добавлять или вычитать его.

См. эту ссылку.

Ответ 2

Операторы Switch (особенно длинные) считаются плохими, а не потому, что они являются операторами switch, а потому, что их присутствие указывает на необходимость реорганизации.

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

Тем не менее, в следующей статье приведены некоторые рекомендации по использованию операторов switch:

http://elegantcode.com/2009/01/10/refactoring-a-switch-statement/

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

private DiscountType GetDiscountType(string discount)
{
    switch (discount)
    {
        case "A": return DiscountType.Discountable;
        case "B": return DiscountType.Loss;
        case "O": return DiscountType.Other;
    }
}

Ответ 3

Я думаю, что изменение кода ради изменения кода - это не лучшее использование одного времени. Изменение кода, чтобы сделать его [более читаемым, более быстрым, эффективным и т.д. И т.д.] Имеет смысл. Не меняйте его просто потому, что кто-то говорит, что вы делаете что-то "вонючее".

-Rick

Ответ 4

Этот оператор switch прекрасен. У вас, ребята, нет других ошибок? lol

Однако есть одна вещь, которую я заметил... Вы не должны использовать индексные ординалы для индексатора объектов IReader [].... что делать, если изменения столбца меняются? Попробуйте использовать имена полей, то есть читатель [ "id" ] и читатель [ "name" ]

Ответ 5

По-моему, это не переключает утверждения, которые являются запахом, это то, что внутри них. Этот оператор switch подходит для меня, пока он не начнет добавлять еще несколько случаев. Тогда, возможно, стоит создать таблицу поиска:

private static Dictionary<string, DiscountType> DiscountTypeLookup = 
  new Dictionary<string, DiscountType>(StringComparer.Ordinal)
    {
      {"A", DiscountType.Discountable},
      {"B", DiscountType.Loss},
      {"O", DiscountType.Other},
    };

В зависимости от вашей точки зрения это может быть более или менее читаемым.

Когда все начинает становиться вонючим, если содержимое вашего дела больше, чем строка или две.

Ответ 6

Роберт Харви и Талджо предоставили отличные ответы - то, что у вас здесь, - это сопоставление от символьного кода до перечислимого значения. Это лучше всего выражать в виде сопоставления, где детали отображения предоставляются в одном месте, либо на карте (как предлагает Talljoe), либо в функции, которая использует оператор switch (как было предложено Робертом Харви).

Оба эти метода, вероятно, прекрасны в этом случае, но я хотел бы обратить ваше внимание на принципиальную схему проекта, которая может быть полезной здесь или в других подобных случаях. Принцип открытого/закрытого:

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

Я думаю, что это хорошая статья о шаблоне реестра - посмотрите, как реестр содержит сопоставление от некоторого ключа до некоторой ценности? Таким образом, это похоже на ваше сопоставление, выраженное как оператор switch. Конечно, в вашем случае вы не будете регистрировать объекты, которые реализуют общий интерфейс, но вы должны получить суть:

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

Ответ 7

Я бы не использовал if. if будет менее понятным, чем switch. switch говорит мне, что вы сравниваете одно и то же на всем протяжении.

Просто, чтобы напугать людей, это менее понятно, чем ваш код:

if (string) reader[discountTypeIndex]) == "A")
   p.DiscountType = DiscountType.Discountable;
else if (string) reader[discountTypeIndex]) == "B")
   p.DiscountType = DiscountType.Loss;
else if (string) reader[discountTypeIndex]) == "O")
   p.DiscountType = DiscountType.Other;

Этот switch может быть в порядке, вы можете посмотреть предложение @Talljoe.

Ответ 8

Являются ли переключатели типа скидок, расположенные по всему вашему коду? Будет ли добавление нового типа скидок потребует изменить несколько таких коммутаторов? Если это так, вы должны посмотреть на факторинг выключателя. Если нет, использование переключателя здесь должно быть безопасным.

Если во всей вашей программе распространено много специфических скидок, вы можете захотеть реорганизовать это как:

p.Discount = DiscountFactory.Create(reader[discountTypeIndex]);

Затем объект скидки содержит все атрибуты и методы, связанные с выяснением скидок.

Ответ 9

Да, это похоже на правильное использование оператора switch.

Однако у меня есть еще один вопрос для вас.

Почему вы не включили метку по умолчанию? Выброс исключения на метке по умолчанию гарантирует, что программа будет работать неправильно, если вы добавите новый скидTypeIndex и забудьте изменить код.

Кроме того, если вы хотите сопоставить строковое значение с Enum, вы можете использовать атрибуты и отражение.

Что-то вроде:

public enum DiscountType
{
    None,

    [Description("A")]
    Discountable,

    [Description("B")]
    Loss,

    [Description("O")]
    Other
}

public GetDiscountType(string discountTypeIndex)
{
    foreach(DiscountType type in Enum.GetValues(typeof(DiscountType))
    {
        //Implementing GetDescription should be easy. Search on Google.
        if(string.compare(discountTypeIndex, GetDescription(type))==0)
            return type;
    }

    throw new ArgumentException("DiscountTypeIndex " + discountTypeIndex + " is not valid.");
}

Ответ 10

Вы правы, чтобы заподозрить этот оператор switch: любой оператор switch, зависящий от типа чего-либо, может указывать на отсутствующий полиморфизм (или отсутствующие подклассы).

Словарь TallJoe - хороший подход.

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

public enum DiscountType : int
{
    Unknown = 0,
    Discountable = 1,
    Loss = 2,
    Other = 3
}

затем

p.DiscountType = Enum.Parse(typeof(DiscountType), 
    (string)reader[discountTypeIndex]));

было бы достаточно.

Ответ 11

Я думаю, это зависит от того, создаете ли вы MType множество разных мест или только в этом месте. Если вы создаете MType во многих местах, которые всегда должны переключаться для типа dicsount, есть некоторые другие проверки, то это может быть запахом кода.

Я попытался бы создать создание MTypes в одном месте в вашей программе, возможно, в конструкторе самого MType или в каком-то методе factory, но наличие случайных частей ваших значений назначения программы может привести к тому, что кто-то не сможет зная, как должны быть ценности и делать что-то неправильно.

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

Ответ 12

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

private MyType DoSomething(IDataRecord reader)
{
    var p = new MyType
                {
                   Id = (int)reader[idIndex],
                   Name = (string)reader[nameIndex]
                }

    p.DiscountType = FindDiscountType(reader[discountTypeIndex]);

    return p;
}

private DiscountType FindDiscountType (string key) {
    switch ((string) reader[discountTypeIndex])
    {
        case "A":
            return DiscountType.Discountable;
        case "B":
            return DiscountType.Loss;
        case "O":
            return DiscountType.Other;
    }
    // handle the default case as appropriate
}

Довольно скоро я бы заметил, что FindDiscountType() действительно принадлежит классу DiscountType и перемещает функцию.

Ответ 13

Когда вы разрабатываете язык и, наконец, имеете возможность удалить самый уродливый, самый неинтуитивный синтаксис, подверженный ошибкам, на всем языке.

ЭТО, когда вы пытаетесь удалить оператор switch.

Чтобы быть ясным, я имею в виду синтаксис. Это что-то взято из C/С++, который должен был быть изменен, чтобы соответствовать более современному синтаксису в С#. Я полностью согласен с концепцией предоставления коммутатора, чтобы компилятор мог оптимизировать скачок.