Предлагаемый рефакторинг для if-оператора следующей формы?

#c #if-statement #refactoring #theory

#c #if-оператор #рефакторинг #теория

Вопрос:

Я пытаюсь сделать это как можно более общим. Давайте предположим, что в if-операторе я проверяю, является ли некоторое логическое выражение A истинным. Допустим, существуют конкретные случаи, когда A является истинным, A.a и A.b, которые являются взаимоисключающими. То же самое верно для другого логического выражения B. Затем рассмотрим следующий код:

 if (A) {
  if (A.a) {
    somethingSpecificToAa();
    foo();
  }
} else if (B) {
  if (B.a) {
    somethingSpecificToBa();
    foo();
  }
} else {
    foo();
}
  

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

Поскольку foo() выполняется, когда:

  • A.a является истинным
  • B.a верно
  • Ни A, ни B не являются истинными

Я подумал о следующем:

 if (A.a) {
  somethingSpecificToAa();
} else if (B.a) {
  somethingSpecificToBa();
}

if (A.a || B.a || !(A || B)) {
  foo();
}
  

который должен иметь такое же поведение. Это лучший способ сделать это? Обратите внимание, что условие во 2-м операторе if 2-го примера в действительности оказывается чрезвычайно длинным, поэтому мой код по-прежнему выглядит как 1-й пример (я ненавижу разбивать один if на несколько строк.) Я также думал о создании лямбда-выражения, которое возвращает a bool , эквивалентное A.a || B.a || !(A || B) и вместо этого подключает лямбда-выражение ко 2-му оператору if. В качестве альтернативы я мог бы сохранить структуру 1-го примера, но заменить множество строк каждого foo() из них на ( void ) лямбда-выражение, которое делает то же самое, хотя я не уверен, что это устраняет запах.

Я переусердствовал на данный момент, думая о лямбдах? Какой подход лучше всего подходит для поддержания чистого кода?

РЕДАКТИРОВАТЬ: кажется, я сделал его слишком общим. Я имею дело с контейнерами STL, а не с моими собственными классами, и более «точным» примером будет:

 int shirtACleanliness = calculateCleanliness(shirtA);
if (itemsToWash.contains(shirtA)) { //itemsToWash is a std::set
  if (shirtA.cleanliness > shirtACleanliness) {
    itemsToWash.erase(shirtA);
    shirtA.cleanliness = shirtACleanliness;
    itemsToWash.insert(shirtA); //the set is ordered on cleanliness, so this re-inserts in the correct position
    doSomeOtherStuff(shirtA);
  }
} else if (itemsToDry.contains(shirtA)) { //itemsToDry is a std::vector
  if (shirtA.cleanliness > shirtACleanliness) {
    itemsToDry.erase(shirtA);
    shirtA.cleanliness = shirtACleanliness;
    itemsToWash.insert(shirtA);
    doSomeOtherStuff(shirtA);
  }
} else {
  shirtA.cleanliness = shirtACleanliness;
  itemsToWash.insert(shirtA);
  doSomeOtherStuff(shirtA);
}
//am aware aware contains() is not a method for either type
//and std::vector does not have erase() by value, this is just conceptual
  

Комментарии:

1. Если, например A , и B являются указателями (что означает, что у вас действительно есть A->a и B->a ), то вы всегда должны A B сначала проверять и в любом случае.

2. Помните, что если по какой-либо причине вам нужно использовать немного другой foo() в ветке, то вам придется создать дополнительный foo2() . Вместо изменения форматирования if я бы разбил foo() его на более мелкие, управляемые функции / классы, чтобы каждая if подотрасль казалась достаточно маленькой.

3. @Someprogrammerdude Это не указатели, а просто абстрактное представление того, что я пытаюсь сделать. Но да, даже в моем реальном коде я должен проверять A amp;amp; A.a and B amp;amp; B.a , а не просто A.a and B.a .

4. @Tetix foo() согласно моему приведенному выше коду делает то же самое независимо от того, когда он вызывается; Я поместил все данные, относящиеся к конкретной ветке, в somethingSpecificToAa() и somethingSpecificToBa() . Если я не неправильно понял вашу точку зрения?

5. @ampharos Я просто думал о будущем, на всякий случай foo() , если один из сценариев будет отличаться таким образом, который somethingspecificA/B невозможно включить. Затем требуется новый foo2() и т. Д. Вот почему я предложил разбить foo() на более мелкие функции / классы, если это возможно.

Ответ №1:

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

 Element* element=nullptr;

if(vectorA.contains(X)){
   element = vectorA.get(X);
}else if(setB.contains(X)){
   element = setB.get(X);
}

if(element != nullptr amp;amp; element->a){
   something(element);
}

if(element == nullptr || element->a){
    foo();
}
  

Комментарии:

1. На данный момент это самый чистый и простой для понимания подход. К сожалению, если я не ошибаюсь, нет никакого различия между тем, было ли element получено из набора или вектора, мы просто знаем, найдено ли оно в любом из них. Итак, мне не хватает исключительного поведения, которое я различаю с помощью somethingExclusiveToAa() and somethingExclusiveToBa() . Будет ли плохо использовать a bool во 2-м блоке (который мы назначаем в 1-м блоке) для выбора между something(element) и somethingElse (element)?

2. Я думал somethingExclusiveToAa() , и somethingExclusiveToBa() мы просто получали элемент, но если это не так, вы можете удалить if(element != nullptr amp;amp; element->a){ something(element);} и поместить свои данные после get(X); . Похоже, что ваши конкретные случаи затрудняют выражение чего-то более общего. На данный момент я бы сказал, что мы недостаточно знаем, чтобы двигаться вперед.

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

Ответ №2:

Поскольку там есть какой-то общий код, почему бы не разбить его на функцию? У вас может быть что-то вроде

 template<typename T, typename F>
bool doSomething(T constamp; t, F f)
{
    if (t amp;amp; t.a)
    {
        f();
        foo();
    }

    return static_cast<bool>(t);
}
  

И используйте его как

 if (!doSomething(A, amp;somethingSpecificToAa) amp;amp; !doSomething(B, amp;somethingSpecificToBa))
{
    foo();
}
  

Комментарии:

1. Мне это действительно нравится. Может быть, я просто медлю, но как я могу в этом случае определить исключительное поведение для вызова функтора f() ? Должно быть по-другому, смотрим ли мы на A или B.

2. Просто определите общедоступную функцию f() в A и B и замените f(); на t.f() . Это означает, что вам не нужен второй параметр в функции шаблона, предоставленной @Some programmer dude .