Программирование против перечисления в инструкции switch, это ваш способ сделать?

Посмотрите на фрагмент кода:

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

Мне было интересно, кодируют ли ваши коллеги-разработчики и этот побег...

public enum DrivingState {Neutral, Drive, Parking, Reverse};

public class MyHelper
{
    private DrivingState drivingState = DrivingState.Neutral;

    public void Run()
    {
        switch (this.drivingState)
        {
            case DrivingState.Neutral:
                DoNeutral();
                break;
            case DrivingState.Drive:
                DoDrive();
                break;
            case DrivingState.Parking:
                DoPark();
                break;
            case DrivingState.Reverse:
                DoReverse();
                break;
            default:
                throw new InvalidOperationException(
                    string.Format(CultureInfo.CurrentCulture, 
                    "Drivestate {0} is an unknown state", this.drivingState));
        }
    }
}

В обзорах кода я встречаю много реализаций с только выражением break в escape-умолчанию по умолчанию. Это может быть проблемой с течением времени.

Ответ 1

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

Я бы предпочел просто unit test каждый случай и получил дополнительное утверждение, что есть только четыре возможных случая. Если кто-либо добавит новые значения enum, unit test сломается.

Что-то вроде

[Test]
public void ShouldOnlyHaveFourStates()
{
    Assert.That(Enum.GetValues( typeof( DrivingState) ).Length == 4, "Update unit tests for your new DrivingState!!!");
}

Ответ 2

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

Я прочитал код один раз, и я это понял. Итак, по моему скромному мнению, ваш код является примером хорошего стиля кодирования.

Ответ 3

Есть альтернатива этому, который должен использовать нечто похожее на перечисления Java. Частные вложенные типы допускают "более строгие" перечисления, где единственным "недопустимым" значением, доступным во время компиляции, является null. Вот пример:

using System;

public abstract class DrivingState
{
    public static readonly DrivingState Neutral = new NeutralState();
    public static readonly DrivingState Drive = new DriveState();
    public static readonly DrivingState Parking = new ParkingState();
    public static readonly DrivingState Reverse = new ReverseState();

    // Only nested classes can derive from this
    private DrivingState() {}

    public abstract void Go();

    private class NeutralState : DrivingState
    {
        public override void Go()
        {
            Console.WriteLine("Not going anywhere...");
        }
    }

    private class DriveState : DrivingState
    {
        public override void Go()
        {
            Console.WriteLine("Cruising...");
        }
    }

    private class ParkingState : DrivingState
    {
        public override void Go()
        {
            Console.WriteLine("Can't drive with the handbrake on...");
        }
    }

    private class ReverseState : DrivingState
    {
        public override void Go()
        {
            Console.WriteLine("Watch out behind me!");
        }
    }
}

Ответ 4

Это выглядит довольно разумно для меня. Существуют и другие варианты, такие как Dictionary<DrivingState, Action>, но то, что у вас есть, проще и должно быть достаточным для большинства простых случаев. Всегда предпочитайте простые и читаемые; -p

Ответ 5

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

Так что, возможно, вы не должны работать таким образом, в первую очередь. Как насчет:

interface IDrivingState
{
    void Do();
}

Сохраните текущее состояние (объект, который реализует IDrivingState) в переменной, а затем выполните его следующим образом:

drivingState.Do();

Предположительно, у вас будет какой-то способ перехода состояния в другое состояние - возможно, Do вернет новое состояние.

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

Обновление в ответ на комментарий:

С помощью enum/switch, когда вы добавляете новое значение перечисления, теперь вам нужно найти каждое место в своем коде, где это значение перечисления еще не обработано. Компилятор не знает, как с этим справиться. По-прежнему существует "контракт" между различными частями кода, но он неявный и неявный для проверки компилятором.

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

Ответ 6

Я бы использовал NotSupportedException.

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

Ответ 7

Я бы предложил использовать либо NotImplementedException, либо лучше пользовательское DrivingStateNotImplementedException, если вы хотите генерировать исключения.

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

Это как настоящий автомобиль, процессор решает, что он пропускает включение света, что он делает, бросает исключение и "ломает" все управление или возвращается в известное состояние, которое безопасно, и дает предупреждение драйвер "oi, у меня нет света"

Ответ 8

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

Если вы ошиблись, что у вас есть значение unhandles, вы должны обязательно выбросить исключение, как в примере (или обработать ошибку каким-либо другим способом). Нужно никогда проглатывать условие ошибки, не указывая на то, что что-то не так.

A default случай с a break не пахнет очень хорошо. Я бы удалил это, указав, что коммутатор не обрабатывает все значения и, возможно, добавляет комментарий, объясняющий почему.

Ответ 9

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

Проблема со всеми сложными полиморфными ужасами выше: они заставляют инкапсуляцию в класс или требуют дополнительных классов - это прекрасно, когда есть только метод DrivingState.Drive(), но вся вещь ломается, как только у вас есть DrivingState. Serialize(), который сериализуется в зависимости от DrivingState или любого другого состояния реального мира.

перечисления и переключатели сделаны друг для друга.

Ответ 10

Я программист на C, а не С#, но когда у меня есть что-то вроде этого, у меня есть мой компилятор, чтобы предупредить меня, если не все случаи перечисления обрабатываются в коммутаторе. После установки этого параметра (и установки предупреждений-как-ошибок) я не беспокоюсь о проверках времени выполнения для вещей, которые можно поймать во время компиляции.

Можно ли это сделать на С#?

Ответ 11

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

Это хороший пример того, что можно сделать с простым полиморфизмом красивым, чистым и расширяемым способом. Просто объявите базу DrivingStrategy и наследуйте от нее всю версию управляющей логики. Это не чрезмерная инженерия - если бы у вас было два случая, это было бы, но четыре уже проявили потребность в этом, особенно если каждая версия Do... вызывает другие методы. По крайней мере, мой личный опыт.

Я не согласен с решением Jon Skeet, которое замораживает ряд состояний, если это действительно не требуется.

Ответ 12

Я думаю, что использование типов перечислений и, следовательно, операторов switch для реализации State (также State Design Pattern) является not особенно хорошей идеей. ИМХО это подвержено ошибкам. По мере того как внедряемая машина состояния становится сложной, код будет постепенно менее понятным вашим коллегам-программистам.

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

Кроме того, я хотел бы спросить вас, сколько операций будет применимо к DrivingState вместе с Run()? Если несколько, и если вы собираетесь в основном повторить этот оператор switch несколько раз, это будет кричать сомнительного дизайна, если не сказать больше.