Как вы справляетесь с огромными if-условиями?
Это то, что меня беспокоит на каждом языке, который я использовал, у меня есть оператор if, но в условной части так много проверок, что мне приходится разбивать его на несколько строк, использовать вложенный оператор if или просто признать, что он уродлив, и двигаться дальше с моей жизнью.
Есть ли какие-то другие методы, которые вы нашли, которые могут быть полезны мне и кому-либо еще, столкнувшемуся с той же проблемой?
Пример, все в одной строке:
if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true)
{
Пример, многострочный:
if (var1 = true && var2 = true && var2 = true
&& var3 = true && var4 = true && var5 = true
&& var6 = true)
{
Вложенный пример:
if (var1 = true && var2 = true && var2 = true && var3 = true)
{
if (var4 = true && var5 = true && var6 = true)
{
Ответов (21)21
Разделите условие на несколько логических значений, а затем используйте главное логическое значение в качестве условия.
bool isOpaque = object.Alpha == 1.0f;
bool isDrawable = object.CanDraw && object.Layer == currentLayer;
bool isHidden = hideList.Find(object);
bool isVisible = isOpaque && isDrawable && ! isHidden;
if(isVisible)
{
// ...
}
Еще лучше:
public bool IsVisible {
get
{
bool isOpaque = object.Alpha == 1.0f;
bool isDrawable = object.CanDraw && object.Layer == currentLayer;
bool isHidden = hideList.Find(object);
return isOpaque && isDrawable && ! isHidden;
}
}
void Draw()
{
if(IsVisible)
{
// ...
}
}
Убедитесь, что вы дали своим переменным имя, которое на самом деле указывает на намерение, а не на функцию. Это очень поможет разработчику поддерживать ваш код ... это можете быть ВЫ!
Мне нравится разбивать каждое условие на описательные переменные.
bool isVar1Valid, isVar2Valid, isVar3Valid, isVar4Valid;
isVar1Valid = ( var1 == 1 )
isVar2Valid = ( var2.Count >= 2 )
isVar3Valid = ( var3 != null )
isVar4Valid = ( var4 != null && var4.IsEmpty() == false )
if ( isVar1Valid && isVar2Valid && isVar3Valid && isVar4Valid ) {
//do code
}
if ( (condition_A)
&& (condition_B)
&& (condition_C)
&& (condition_D)
&& (condition_E)
&& (condition_F)
)
{
...
}
в отличие от
if (condition_A) {
if (condition_B) {
if (condition_C) {
if (condition_D) {
if (condition_E) {
if (condition_F) {
...
}
}
}
}
}
}
а также
if ( ( (condition_A)
&& (condition_B)
)
|| ( (condition_C)
&& (condition_D)
)
|| ( (condition_E)
&& (condition_F)
)
)
{
do_this_same_thing();
}
в отличие от
if (condition_A && condition_B) {
do_this_same_thing();
}
if (condition_C && (condition_D) {
do_this_same_thing();
}
if (condition_E && condition_F) {
do_this_same_thing();
}
Большинство инструментов статического анализа для изучения кода будут жаловаться, если в нескольких условных выражениях не используются явные скобки, определяющие анализ выражений, вместо того, чтобы полагаться на правила приоритета операторов и меньшее количество скобок.
Вертикальное выравнивание на одном уровне отступа открытых / закрывающих фигурных скобок {}, открывающих закрывающих круглых скобок (), условных выражений с круглыми скобками и операторов слева - очень полезная практика, которая значительно УЛУЧШАЕТ читаемость и ясность кода, а не заглушает все которые могут быть втиснуты в одну строку без вертикального выравнивания, пробелов или скобок
Правила приоритета операторов сложны, например, && имеет более высокий приоритет, чем ||, но | имеет приоритет перед &&
Так, ...
if (expr_A & expr_B || expr_C | expr_D & expr_E || expr_E && expr_F & expr_G || expr_H {
}
- это действительно простое множественное условное выражение, которое простые люди могут неправильно прочитать и оценить.
if ( ( (expr_A)
& (expr_B)
)
|| ( (expr_C)
| ( (expr_D)
& (expr_E)
)
)
|| ( (expr_E)
&& ( (expr_F)
& (expr_G)
)
)
|| (expr_H)
)
{
}
Нет ничего плохого в горизонтальном пространстве (перевод строки), вертикальном выравнивании или явной оценке выражений, определяющих скобки, - все это УЛУЧШАЕТ удобочитаемость и ясность.
Здесь необходимо решить две проблемы: читаемость и понятность.
Решение «читабельности» - это проблема стиля, и как таковая она открыта для интерпретации. Я предпочитаю следующее:
if (var1 == true && // Explanation of the check
var2 == true && // Explanation of the check
var3 == true && // Explanation of the check
var4 == true && // Explanation of the check
var5 == true && // Explanation of the check
var6 == true) // Explanation of the check
{ }
или это:
if (var1 && // Explanation of the check
var2 && // Explanation of the check
var3 && // Explanation of the check
var4 && // Explanation of the check
var5 && // Explanation of the check
var6) // Explanation of the check
{ }
Тем не менее, такую сложную проверку может быть довольно сложно мысленно проанализировать при сканировании кода (особенно если вы не являетесь первоначальным автором). Подумайте о создании вспомогательного метода, чтобы отвлечься от некоторых сложностей:
/// <Summary>
/// Tests whether all the conditions are appropriately met
/// </Summary>
private bool AreAllConditionsMet (
bool var1,
bool var2,
bool var3,
bool var4,
bool var5,
bool var6)
{
return (
var1 && // Explanation of the check
var2 && // Explanation of the check
var3 && // Explanation of the check
var4 && // Explanation of the check
var5 && // Explanation of the check
var6); // Explanation of the check
}
private void SomeMethod()
{
// Do some stuff (including declare the required variables)
if (AreAllConditionsMet (var1, var2, var3, var4, var5, var6))
{
// Do something
}
}
Теперь при визуальном сканировании метода SomeMethod фактическая сложность тестовой логики скрыта, но семантическое значение сохраняется для понимания людьми на высоком уровне. Если разработчику действительно необходимо разобраться в деталях, можно изучить метод AreAllConditionsMet.
Я думаю, это формально известно как шаблон рефакторинга «Разобрать условный». Такие инструменты, как Resharper или Refactor Pro! может облегчить выполнение такого рода рефакторинга!
Во всех случаях ключом к читаемому и понятному коду является использование реалистичных имен переменных. Хотя я понимаю , что это надуманный пример, «var1», «var2», и т.д., не приемлемые имена переменных. У них должно быть имя, которое отражает основную природу данных, которые они представляют.
Макдауэлл,
Вы правы, когда используете единственный оператор '&', который оценивают обе стороны выражения. Однако при использовании оператора '&&' (по крайней мере, в C#) первое выражение, которое возвращает false, является последним вычисленным выражением. Это делает вывод вычисления перед оператором FOR так же хорошо, как и любой другой способ сделать это.
Ознакомьтесь с шаблонами реализации Кента Бека. Я думаю, что есть особый паттерн, который может помочь в этой ситуации ... он называется «Стражи». Вместо того, чтобы иметь множество условий, вы можете превратить их в охрану, которая проясняет, какие неблагоприятные условия в методе.
Так, например, если у вас есть метод, который что-то делает, но есть определенные условия, при которых он не должен что-то делать, а не:
public void doSomething() {
if (condition1 && condition2 && condition3 && condition4) {
// do something
}
}
Вы можете изменить его на:
public void doSomething() {
if (!condition1) {
return;
}
if (!condition2) {
return;
}
if (!condition3) {
return;
}
if (!condition4) {
return;
}
// do something
}
Он немного более подробный, но гораздо более читабельный, особенно когда у вас появляется странное вложение, сторож может помочь (в сочетании с методами извлечения).
Кстати, я НАСТОЯТЕЛЬНО рекомендую эту книгу.
Я видел много людей и редакторов, которые либо делали отступ для каждого условия в вашем операторе if одной вкладкой, либо сопоставляли его с открытым пареном:
if (var1 == true
&& var2 == true
&& var3 == true
) {
/* do something.. */
}
Я обычно помещаю закрывающую скобку в ту же строку, что и последнее условие:
if (var1 == true
&& var2 == true
&& var3 == true) {
/* do something.. */
}
Но я не думаю, что это так чисто.
Я удивлен, что этого еще никто не получил. Специально для этого типа проблем есть рефакторинг:
http://www.refactoring.com/catalog/decomposeConditional.html
@tweakt
Не лучше, но то, что я делал в прошлом:
логическое ok = cond1; ок & = cond2; ок & = cond3; ок & = cond4; ок & = cond5; ок & = cond6;
Это то же самое, что:
ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
На самом деле, в большинстве языков эти две вещи не совпадают. Второе выражение обычно перестает оцениваться, как только одно из условий ложно, что может значительно улучшить производительность, если оценка условий является дорогостоящей.
Для удобочитаемости я лично предпочитаю предложение Майка Стоуна, приведенное выше. Легко дать подробные комментарии и сохранить все вычислительные преимущества возможности раннего выхода. Вы также можете использовать ту же технику в функции, если это запутает организацию вашего кода, так как условное вычисление переместится подальше от другой вашей функции. Это немного банально, но вы всегда можете сделать что-нибудь вроде:
do {
if (!cond1)
break;
if (!cond2)
break;
if (!cond3)
break;
...
DoSomething();
} while (false);
в то время как (ложь) немного глупо. Я бы хотел, чтобы у языков был оператор области видимости под названием «once» или что-то такое, от чего можно было бы легко избавиться.
Ну, во-первых, почему бы и нет:
if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
...
Кроме того, очень сложно реорганизовать абстрактные примеры кода. Если бы вы показали конкретный пример, было бы легче найти более подходящий шаблон для решения проблемы.
Не лучше, но то, что я делал в прошлом: (Следующий метод предотвращает короткое замыкание логического тестирования, все тесты запускаются, даже если первое ложно. Не рекомендуемый шаблон, если вы не знаете, что вам нужно всегда выполнять все код перед возвратом - Спасибо ptomato за обнаружение моей ошибки!)
логическое ok = cond1;
ок & = cond2;
ок & = cond3;
ок & = cond4;
ок & = cond5;
ок & = cond6;
Это то же самое, что: (не то же самое, см. Примечание выше!)
ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
Во-первых, я бы удалил все == true
части, что сделало бы его на 50% короче;)
Когда у меня большое состояние, я ищу причины. Иногда я вижу, что нужно использовать полиморфизм, иногда мне нужно добавить какой-то объект состояния. По сути, это означает, что нужен рефакторинг (запах кода).
Иногда я использую законы Де-Моргана, чтобы немного упростить логические выражения.
Мне нравится разбивать их по уровням, поэтому я бы отформатировал вам пример следующим образом:
if (var1 = true
&& var2 = true
&& var2 = true
&& var3 = true
&& var4 = true
&& var5 = true
&& var6 = true){
Это удобно, когда у вас больше вложений, как это (очевидно, что реальные условия были бы более интересными, чем "= true" для всего):
if ((var1 = true && var2 = true)
&& ((var2 = true && var3 = true)
&& (var4 = true && var5 = true))
&& (var6 = true)){
Если вам довелось программировать на Python, это удобно со встроенной all()
функцией, применяемой к списку ваших переменных (здесь я просто буду использовать логические литералы):
>>> L = [True, True, True, False, True]
>>> all(L) # True, only if all elements of L are True.
False
>>> any(L) # True, if any elements of L are True.
True
Есть ли на вашем языке соответствующая функция (C#? Java?). Если да, то это, вероятно, самый чистый подход.
Попробуйте взглянуть на Функторы и Предикаты. В проекте Apache Commons есть отличный набор объектов, позволяющий инкапсулировать условную логику в объекты. Пример их использования доступен на Oreilly здесь . Отрывок из примера кода:
import org.apache.commons.collections.ClosureUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.functors.NOPClosure;
Map predicateMap = new HashMap();
predicateMap.put( isHonorRoll, addToHonorRoll );
predicateMap.put( isProblem, flagForAttention );
predicateMap.put( null, ClosureUtils.nopClosure() );
Closure processStudents =
ClosureUtils.switchClosure( predicateMap );
CollectionUtils.forAllDo( allStudents, processStudents );
Теперь подробности всех этих предикатов isHonorRoll и замыканий, используемых для их оценки:
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
// Anonymous Predicate that decides if a student
// has made the honor roll.
Predicate isHonorRoll = new Predicate() {
public boolean evaluate(Object object) {
Student s = (Student) object;
return( ( s.getGrade().equals( "A" ) ) ||
( s.getGrade().equals( "B" ) &&
s.getAttendance() == PERFECT ) );
}
};
// Anonymous Predicate that decides if a student
// has a problem.
Predicate isProblem = new Predicate() {
public boolean evaluate(Object object) {
Student s = (Student) object;
return ( ( s.getGrade().equals( "D" ) ||
s.getGrade().equals( "F" ) ) ||
s.getStatus() == SUSPENDED );
}
};
// Anonymous Closure that adds a student to the
// honor roll
Closure addToHonorRoll = new Closure() {
public void execute(Object object) {
Student s = (Student) object;
// Add an award to student record
s.addAward( "honor roll", 2005 );
Database.saveStudent( s );
}
};
// Anonymous Closure flags a student for attention
Closure flagForAttention = new Closure() {
public void execute(Object object) {
Student s = (Student) object;
// Flag student for special attention
s.addNote( "talk to student", 2005 );
s.addNote( "meeting with parents", 2005 );
Database.saveStudent( s );
}
};
Совет Стива Макконелла из Code Complete : используйте многомерную таблицу. Каждая переменная служит индексом для таблицы, а оператор if превращается в поиск по таблице. Например, если (size == 3 && weight> 70) переводится в решение о входе в таблицу [размер] [группа_веса]
Если бы я делал это на Perl, я мог бы запускать проверки именно так.
{
last unless $var1;
last unless $var2;
last unless $var3;
last unless $var4;
last unless $var5;
last unless $var6;
... # Place Code Here
}
Если вы планируете использовать это по подпрограмме заменить каждый экземпляр last
с return
;