Подтвердить что ты не робот

Два отдельных "если", одно и то же условие - все в порядке?

Какой из двух, как вы думаете, лучше?

if (the_condition)
{
    variable = sth;
}
else
{
    variable = sth_else;
}

if (the_condition)
{
    variable_2.doSth();
}
else
{
    variable_2.doSthElse();
}

или

if (the_condition)
{
    variable = sth;
    variable_2.doSth();
}
else
{
    variable = sth_else;
    variable_2.doSthElse();
}

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

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

4b9b3361

Ответ 1

# 2

Что делать, если условие изменяется? Тогда вам придется обновить его в русских местах! (И легко пропустить один, вводя тонкие, труднодоступные ошибки.)

Я лично считаю, что # 1 гораздо труднее читать, так как я должен прочитать условие для каждого присваивания, потому что это может быть иначе. С# 2 я знаю, что все в теле if находится в контексте одного условия - вы можете сказать, что условие инкапсулирует единый, цельный многооперационный кусок поведения.

Конечно, если утверждения в теле if не связаны (т.е. не являются частью одного и того же куска поведения), то они должны быть в отдельном if s. Вероятно, это не так: вы настраиваете переменные в зависимости от начального условия (что, скорее всего, одна логическая операция).

Ответ 2

Повторяющиеся условия без причины не являются хорошими, ни для производительности (вероятно, не имеет значения), ни для обслуживания (имеет значение).

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

Ответ 3

Многие веские причины предпочесть второй вариант уже даны, но есть еще один: структура первого варианта не соответствует тому, как думают многие люди. Ваш собственный мозг работает так?

If I go to the store I'll buy milk.
If I go to the store I'll buy eggs.
If I go to the store I'll get cash from the ATM.

Или как это?

If I go to the store I'll buy milk, buy eggs, and get cash from the ATM.

Ответ 4

Компактная версия; Я нахожу это наиболее читаемым, и обычно это будет так, как я подхожу к нему своим кодом... YMMV, конечно.;)

// the safest way; it protects you from unexpected results in cases
// where the evaluation of "the_condition" has side-effects, or where
// it may be changed in the future to have them (as can often happen, 
// for instance, when a class field is changed to a property)
var theCondition = the_condition;

variable1 = theCondition ? sth           : sth_else;
variable2 = theCondition ? sth_different : sth_even_more_different;

// if evaluating "the_condition" has no side-effects, and you can be
// fairly sure it never will, then you can do this, but be careful:
variable1 = the_condition ? sth           : sth_else;
variable2 = the_condition ? sth_different : sth_even_more_different;

Не определен для какого-либо конкретного ответа, помните (в тех случаях, когда результаты могут использоваться несколько раз), хорошая практика назначать результаты соответствующим областям переменных (локальные, если они являются временными или, возможно, объектными или классными (статическими) свойства, когда они более постоянны), которые названы таким образом, чтобы выявить концептуальный характер результатов (например, не переменные1 и переменные2: D).

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

[Спасибо Peter Wone за предложение в комментариях, что точка с несколькими оценками должна быть более явной.]


Также возможно использовать следующее:

        switch (the_condition)
        {
            case true:
                variable1 = sth;
                variable2 = sth_else;
                break;

            case false:
                variable1 = sth_different;
                variable2 = sth_even_more_different;
                break;

        }

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

Как я уже упоминал в своем ответе на комментарий ниже, возможный пример для оператора case будет состоять в следующем:

  • эта проверка находится в конечной машине
  • the_condition представляет состояние FSM
  • the_condition тип FSM, скорее всего, будет изменен на перечислимый тип в какой-то момент
    При отражении:
    •   
    • Мне кажется, что логический концепт, представляющий нечто, отличное от по-настоящему двоичного параметра [yes/no | off/on | true/false] типа , должен быть, независимо, обрабатываться как нечто другое чем двоичное значение, так как:     
      •       
      • Я думаю, что, где это возможно, код должен представлять понятия, а не детали реализации.      
      • Выбор логического значения для представления не двоичной опции будет случайной особенностью вашей реализации, а не концептуальным характером опции.      
      • Это может произойти даже в тех случаях, когда вы, скорее всего, never измените переменную на нумерованный тип.      
      • И это соответствует технико-экономическому обоснованию, так как представление этого выбора с помощью case возможно. [Здесь делается предположение: либо это не в плотном цикле, либо компилятор оптимизирует код, чтобы он не был более интенсивным, чем условный, или что код выполняется приемлемо независимо.]    
        
    Пример/Обоснование:     
    •       
    • Вы пишете настраиваемый FSM, который при определенных условиях должен выбирать элементы из одного из двух внутренних списков для подачи на некоторую изношенную реализацию алгоритма:         
      •           
      • Реализация, которую вы используете, работает только при выборе элементов, по одному из каждого списка, либо спереди назад, либо назад назад.          
      • Логическое имя UseFifoAlgorithm выбирается как свойство, определяющее, нужно ли вытягивать элементы в режиме FIFO или LIFO.          
      • Итак, в этом случае UseFifoAlgorithm представляет выбор спереди-назад, но его отрицание" !UseFifoAlgorithm "концептуально не эквивалентно" Не используйте алгоритм "один-на-назад", чтобы вытащить из списки ". но, скорее," Использовать обратный алгоритм для вытягивания из списков ".          
      • Поскольку отрицание значения не означает то же самое, что отрицание концепции, стоящей за ней, использование отрицания значения как детерминанта в коде не представляет, концептуально, что действительно происходит, и поэтому оно кажется нецелесообразно использовать его.         
      • Поскольку оператор case представляет собой конструкцию, которая наилучшим образом представляет (концептуально) выбор между недвоичными возможностями, и поскольку было показано, что это не (концептуально) двоичный выбор, оператор case является гораздо более подходящий выбор для представления характера сравнения.         
      • Поэтому, предполагая, что код выполняет адекватно задачу, оператор case является допустимым и, следовательно, должен использоваться (возможно, с комментариями выше" истина "и" ложь", определяющий, какой метод выбран), а чем условное.     
       

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

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

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

Ответ 5

Я бы сказал, что зависит от вашей бизнес-модели.

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

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

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

Ответ 6

Мои личные предпочтения:

T const variable = foo(v1, v2);
T2 const variable2 = bar(v1, v2);

Измененная переменная const не может быть изменена (ах!), поэтому отладка делается намного проще, потому что вы можете предположить, что она не изменится (иначе компилятор пожаловался бы).

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

Ответ 7

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

variable = sth_else;
variable_2 = sth_even_more_different;

if (the_condition)
{
    variable = sth;
    variable_2 = sth_different;
}

Другая стенография, которая, по мнению некоторых, является плохой практикой. Не используйте слишком много:

variable = the_condition ? sth : sth_else;
variable_2 = the_condition ? sth_different : sth_event_more_different;

Ответ 8

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

Ответ 9

Могу ли я выступать за другой подход?

Кажется, вы назначаете один и тот же набор переменных в каждом if-body. Наборы переменных звучат как минимум POD aggregate для меня:

struct Foo {
    int variable;
    int variable_2;
};

Вариант I

Тогда

const Foo alpha = {sth, sth_different},
          bravo = {sth_else, sth_even_more_different};
...
Foo foo;
if (the_condition)
    foo = alpha;
else
    foo = bravo;

// or something like  const Foo foo=get(the_condition);

Вариант II

Или более константная версия с меньшим изменчивым состоянием и фактической инициализацией:

const Foo alpha = {sth, sth_different},
          bravo = {sth_else, sth_even_more_different};
...
const Foo foo = the_condition ? alpha
                              : bravo;

Вариант III

Или в контрольном, критическом по времени, узком значении кода (но полностью зависит, как сказано, с оценкой), хорошей старой таблицы поиска:

const Foo foos[] = {{sth, sth_different},
                    {sth_else, sth_even_more_different}};
...
const Foo foo = foos[the_condition?0:1];

Вариант IV

В зависимости от вашего фактического прецедента, возможно, более естественным было бы инвертировать вещь:

class Foo {
public:
    Foo (bool condition) : [...] {...}
};

Приложение

Исполнение инициализации

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

class Foo {
    Foo (int var_a, int var_b) : var_a(var_a), var_b(var_b) {}

    const int var_a;
    const int var_b;

private:
    Foo(); // or "= delete" in upcoming standard
};

Производительность

Интересно, что даже старая gcc строки 3.x скомпилирует оба варианта...

#include <iostream>
int pure() {                  
    bool cond;
    std::cin >> cond;        
    int a, b;    
    a = cond ? 0 : 2;
    b = cond ? 1 : 3;        
    std::cout << a << "," << b << std::endl;
}

struct Foo {
    int a, b;
};
int agg() {
    const Foo alpha = { 0, 1 },
              bravo = { 2, 3 };                  
    bool cond;
    std::cin >> cond;        
    const Foo result = cond ? alpha : bravo;        
    std::cout << result.a << "," << result.b << std::endl;
}

... во что-то, что отличается только 2-6 инструкциями по сборке, по сравнению с 80-120 инструкциями. К сожалению, у меня нет современного g++. Но уже с этим действительно устаревшим компилятором, использование агрегатов не пахнет мне (если я найду время, я представим результаты позже).

Ответ 10

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

Комментарий к стилю
Это хорошая идея, чтобы выбраться из привычки писать

if (the_condition)
  variable = sth;
else
  variable = sth_else;

Всегда используйте фигурные скобки. Добавление фигурных скобок не меняет скомпилированный код на iota. Стоимость автора при добавлении этих брекетов минимальна; с умным редактором стоимость может быть несуществующей. Потенциальная экономия в сокращении глупых, трудно преследующих ошибок огромна. Из-за этого существует много, много проектов, которые делают код формы выше незаконным. Всегда используйте фигурные скобки.

Ответ 11

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

// type for pointer to member function
typedef void (*variable_2_type::pmf)();

// type holding value and action to execute:
struct result { 
    T value;
    pmf action;
};

// table of values/actions:
result results[] = { 
    {sth_else, &variable_2_type::doSthElse },
    {sth,      &variable_2_type::doSth }
};

// use the table:
variable = results[condition]. value;
variable_2.*(results[condition].action)();