практика программирования переключения - PullRequest
2 голосов
/ 07 августа 2009
enum SQLErrorCode{
      OK = 0,
      PARTIAL_OK = 1,
      SOMEWHAT_OK = 2,
      NOT_OK = 3,
};

Код 1:

int error = getErrorCode();
if((error == SQLErrorCode.PARTIAL_OK) ||
  (error == SQLErrorCode.SOMEWHAT_OK) ||
  (error == SQLErrorCode.NOT_OK) ||
  (error < 0))
   callFunction1();
else
    callFunction2();

Код 2:

switch(error){
       case SQLErrorCode.PARTIAL_OK: 
                                    callFunction1();
                                    break;
        case SQLErrorCode.SOMEWHAT_OK:
                                    callFunction1();
                                    break;
        case SQLErrorCode.NOT_OK: 
                                    callFunction1();
                                    break;
        default:
                                    callFunction2();
                                    break;
}

Какой метод мне выбрать? Что касается производительности, то не должно быть большой разницы. Как обработать ошибку <0 в случае переключения. </p>

EDIT: Решение Джоэла:

switch(error) {
     case SQLErrorCode.PARTIAL_OK: 
     case SQLErrorCode.SOMEWHAT_OK:
     case SQLErrorCode.NOT_OK: 
         callFunction1();
         break;
     case SQLErrorCode.OK:
         callFunction2();
         break;
     default:     // error < 0 is handled
         callFunction1();
         break;
}

Q. ошибка <0 обрабатывается. Если мне придется обработать другие числа по ошибке, которые не относятся ни к одному из случаев, включая default

Ответы [ 7 ]

12 голосов
/ 07 августа 2009

Без выражения предпочтения, которое лучше, вот еще одна возможность:

switch(error){
    case SQLErrorCode.PARTIAL_OK: 
    case SQLErrorCode.SOMEWHAT_OK:
    case SQLErrorCode.NOT_OK: 
                                callFunction1();
                                break;
    default:
                                callFunction2();
                                break;
}
6 голосов
/ 07 августа 2009

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

Для сравнения приведу количество разных случаев до 10:

enum SQLErrorCode{
    CODE0 = 0,
    CODE1 = 1,
    CODE2 = 2,
    CODE3 = 3,
    CODE4 = 4,
    CODE5 = 5,
    CODE6 = 6,
    CODE7 = 7,
    CODE8 = 8,
    CODE9 = 9
};

enum SQLErrorCode getErrorCode();

void run()
{
    int error = getErrorCode();
#ifdef CASE1
    if((error == CODE0) ||       
       (error == CODE1) ||
       (error == CODE2) ||
       (error == CODE3) ||
       (error == CODE4) ||
       (error == CODE5) ||
       (error == CODE6) ||
       (error == CODE7) ||
       (error == CODE8) ||
       (error == CODE9) ||
       (error < 0))
        callFunction1();
    else
        callFunction2();
#endif
#ifdef CASE2
    switch(error)
    {
        case CODE0:
            callFunction1();
            break;
    case CODE1:
        callFunction1();
        break;
    case CODE2:
        callFunction1();
        break;
    case CODE3:
        callFunction1();
        break;
    case CODE4:
        callFunction1();
        break;
    case CODE5:
        callFunction1();
        break;
    case CODE6:
        callFunction1();
        break;
    case CODE7:
        callFunction1();
        break;
    case CODE8:
        callFunction1();
        break;
    case CODE9:
        callFunction1();
        break;
    default:
        callFunction2();
        break;
}
#endif

}

Теперь рассмотрим сборку, сгенерированную в первом и втором случаях при сборке в Linux с использованием GCC.

Если вы посмотрите на сборку, вы увидите существенную разницу (для более крупных операторов): серия || s (или if / else, если вы сделаете это таким образом) - это серия ветвей, взятых один за раз. switch превращается в большую таблицу: она требует больше кода, но может означать, что она может быть обработана за один переход.

(Кстати, мы говорим о C здесь, верно? Не C #? Код, который вы не будете компилировать: в C перечислители не используют имя перечисления в качестве префикса. Так что PARTIAL_OK без SQLErrorCode. ) * * тысяча двадцать-один

Код 1 : cc -DCASE1 -s switch.s switch.c

        .file   "1241256.c"
        .text
.globl run
        .type   run, @function
run:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        call    getErrorCode
        movl    %eax, -4(%ebp)
        cmpl    $0, -4(%ebp)
        je      .L2
        cmpl    $1, -4(%ebp)
        je      .L2
        cmpl    $2, -4(%ebp)
        je      .L2
        cmpl    $3, -4(%ebp)
        je      .L2
        cmpl    $4, -4(%ebp)
        je      .L2
        cmpl    $5, -4(%ebp)
        je      .L2
        cmpl    $6, -4(%ebp)
        je      .L2
        cmpl    $7, -4(%ebp)
        je      .L2
        cmpl    $8, -4(%ebp)
        je      .L2
        cmpl    $9, -4(%ebp)
        je      .L2
        cmpl    $0, -4(%ebp)
        jns     .L13
.L2:
        call    callFunction1
        jmp     .L15
.L13:
        call    callFunction2
.L15:
        leave
        ret
        .size   run, .-run
        .ident  "GCC: (GNU) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)"
        .section        .note.GNU-stack,"",@progbits

Код 2 : cc -DCASE2 -s switch.s switch.c

        .text
.globl run
        .type   run, @function
run:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        call    getErrorCode
        movl    %eax, -4(%ebp)
        cmpl    $9, -4(%ebp)
        ja      .L2
        movl    -4(%ebp), %eax
        sall    $2, %eax
        movl    .L13(%eax), %eax
        jmp     *%eax
        .section        .rodata
        .align 4
        .align 4
.L13:
        .long   .L3
        .long   .L4
        .long   .L5
        .long   .L6
        .long   .L7
        .long   .L8
        .long   .L9
        .long   .L10
        .long   .L11
        .long   .L12
        .text
.L3:
        call    callFunction1
        jmp     .L15
.L4:
        call    callFunction1
        jmp     .L15
.L5:
        call    callFunction1
        jmp     .L15
.L6:
        call    callFunction1
        jmp     .L15
.L7:
        call    callFunction1
        jmp     .L15
.L8:
        call    callFunction1
        jmp     .L15
.L9:
        call    callFunction1
        jmp     .L15
.L10:
        call    callFunction1
        jmp     .L15
.L11:
        call    callFunction1
        jmp     .L15
.L12:
        call    callFunction1
        jmp     .L15
.L2:
        call    callFunction2
.L15:
        leave
        ret
        .size   run, .-run
        .ident  "GCC: (GNU) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)"
        .section        .note.GNU-stack,"",@progbits
4 голосов
/ 07 августа 2009

Почему бы и нет ...

switch(error) {
    case SQLErrorCode.PARTIAL_OK: 
    case SQLErrorCode.SOMEWHAT_OK:
    case SQLErrorCode.NOT_OK: 
         callFunction1();
         break;
    case SQLErrorCode.OK:
         callFunction2();
         break;
    default:
         if (error < 0)
              callFunction1();
         else
              callFunction2();
         break;
}

Легче писать, чем ваш переключатель, и легче читать, чем ваш if. Тем не менее, он по-прежнему обрабатывает ошибку <0. </p>

EDIT:

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

2 голосов
/ 07 августа 2009

Вы также можете написать функцию, которая будет определять, что ошибка ОК или нет, код ошибки ОК:

bool isOK(int code)
{
  return code == SQLErrorCode.OK;
}

и ваш код может стать:

if (isOk(getErrorCode()))
{
  callFunction2;
}
else
{
  callFunction1;
}
2 голосов
/ 07 августа 2009

Предполагая, getErrorCode() возвращает одно из ваших перечисленных значений или что-то меньше 0, как насчет

int error = getErrorCode();
if (error == SQLErrorCode.OK)
  callFunction2(); // Good path
else
  callFunction1(); // Error / not good enough path

Очевидно, что если вашему коду нужно callFunction2() на error > 3, то это не сработает.

1 голос
/ 07 августа 2009

Всякий раз, когда у вас есть несколько способов получить один и тот же эффект, вы вызываете путаницу. Поскольку в версии коммутатора у вас есть разные случаи для SqlErrorCode.PARTIAL_OK и SqlErrorCode.SOMEWHAT_OK, это означает, что они имеют разную обработку. Требуется некоторое исследование, чтобы увидеть, что происходит (и это не полностью совместимо с вашей обработкой оператора if, что, вероятно, означает, что вас смущает).

В этом случае я бы использовал оператор if, поскольку идея состоит в том, чтобы использовать ту или иную функцию.

1 голос
/ 07 августа 2009

Я некоторое время не трогал С, но разве он не провалился? Таким образом, вы можете написать второй патрон как таковой ...

switch(error){
   case SQLErrorCode.PARTIAL_OK: 
    case SQLErrorCode.SOMEWHAT_OK:
    case SQLErrorCode.NOT_OK: 
                                callFunction1();
                                break;
    default:
                                callFunction2();
                                break;

}

...