Это то, что меня беспокоит на каждом языке, который я использовал, у меня есть оператор 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)
{





Во-первых, я бы удалил все части == true, что сделало бы его на 50% короче;)
Когда у меня большое состояние, я ищу причины. Иногда я вижу, что нужно использовать полиморфизм, иногда мне нужно добавить какой-то объект состояния. По сути, это означает, что нужен рефакторинг (запах кода).
Иногда я использую Законы Де-Моргана, чтобы немного упростить логические выражения.
Я прибегаю к отдельным логическим значениям:
Bool cond1 == (var1 && var2);
Bool cond2 == (var3 && var4);
if ( cond1 && cond2 ) {}
Я видел много людей и редакторов, которые либо делали отступ для каждого условия в вашем операторе if одной табуляцией, либо сопоставляли его с открытым пареном:
if (var1 == true
&& var2 == true
&& var3 == true
) {
/* do something.. */
}
Я обычно помещаю закрывающую скобку в ту же строку, что и последнее условие:
if (var1 == true
&& var2 == true
&& var3 == true) {
/* do something.. */
}
Но я не думаю, что это так чисто.
Я часто разделяю их на логические переменные компонентов:
bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...
Разделите условие на несколько логических значений, а затем используйте главное логическое значение в качестве условия.
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)
{
// ...
}
}
Убедитесь, что вы дали своим переменным имя, которое на самом деле указывает на намерение, а не на функцию. Это очень поможет разработчику поддерживать ваш код ... это можете быть ВЫ!
Ну, во-первых, почему бы и нет:
if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
...
Кроме того, очень сложно реорганизовать абстрактные примеры кода. Если бы вы показали конкретный пример, было бы легче найти более подходящий шаблон для решения проблемы.
Не лучше, но то, что я делал в прошлом: (Следующий метод предотвращает короткое замыкание логического тестирования, все тесты запускаются, даже если первый ложный. Не рекомендуется, если вы не знаете, что вам нужно всегда выполнять весь код перед возвратом - спасибо ptomato за обнаружение моей ошибки!)
boolean ok = cond1;
ok &= cond2;
ok &= cond3;
ok &= cond4;
ok &= cond5;
ok &= cond6;
Это то же самое, что: (не то же самое, см. Примечание выше!)
ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
Это не то же самое, если оператор && закорачивает.
Вау. Я должен был это знать. Мой первый фейспалм по одному из моих собственных ответов ;-)
Мне нравится разбивать их по уровням, поэтому я бы отформатировал ваш пример следующим образом:
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)){
Посмотрите Шаблоны реализации Кента Бека. Я думаю, что есть особый паттерн, который может помочь в этой ситуации ... он называется «Стражи». Вместо того, чтобы иметь множество условий, вы можете превратить их в охрану, которая проясняет, какие неблагоприятные условия в методе.
Так, например, если у вас есть метод, который что-то делает, но есть определенные условия, при которых он не должен что-то делать, а не:
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
}
Он немного более подробный, но гораздо более читабельный, особенно когда у вас появляется странное вложение, сторож может помочь (в сочетании с методами извлечения).
Кстати, я НАСТОЯТЕЛЬНО рекомендую эту книгу.
"Быстрый возврат" убивает и антипаттерн "стрелка" :)
Я не согласен с тем, что эти гвардейцы упрощают чтение. Было бы лучше прокомментировать «сложное» состояние.
Я удивлен, что этого еще никто не получил. Специально для этого типа проблем есть рефакторинг:
http://www.refactoring.com/catalog/decomposeConditional.html
Мне не нравится Decompose Conditional, потому что он загрязняет структуру кода одноразовыми функциями, которые нельзя использовать повторно. Я бы предпочел большой оператор IF с комментариями для каждой «группы» связанных проверок.
Здесь необходимо решить две проблемы: читаемость и понятность.
Решение «читабельности» - это проблема стиля, и как таковая она открыта для интерпретации. Я предпочитаю следующее:
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» и т. д. Являются допустимыми именами переменных нет. У них должно быть имя, которое отражает основную природу данных, которые они представляют.
Если вы занимаетесь программированием на 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?). Если да, то это, вероятно, самый чистый подход.
Если вы сделаете это:
if (var1 == true) {
if (var2 == true) {
if (var3 == true) {
...
}
}
}
Затем вы также можете реагировать на случаи, когда что-то не так. Например, если вы проверяете ввод, вы можете дать пользователю совет, как его правильно отформатировать, или что-то еще.
Это, пожалуй, худшее решение этого вопроса.
Макдауэлл,
Вы правы, когда используете единственный оператор '&', который оценивают обе стороны выражения. Однако при использовании оператора '&&' (по крайней мере, в C#) первое выражение, которое возвращает false, является последним вычисленным выражением. Это делает вывод вычисления перед оператором FOR так же хорошо, как и любой другой способ сделать это.
Как уже упоминали другие, я бы проанализировал ваши условия, чтобы увидеть, есть ли способ передать их другим методам для повышения читабельности.
@tweakt
It's no better, but what I've done in the past:
boolean ok = cond1; ok &= cond2; ok &= cond3; ok &= cond4; ok &= cond5; ok &= cond6;
Which is the same as:
ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
На самом деле, в большинстве языков эти две вещи не совпадают. Второе выражение обычно перестает оцениваться, как только одно из условий ложно, что может значительно улучшить производительность, если оценка условий является дорогостоящей.
Для удобочитаемости я лично предпочитаю предложение Майка Стоуна, приведенное выше. Легко дать подробные комментарии и сохранить все вычислительные преимущества возможности раннего выхода. Вы также можете использовать ту же технику в функции, если это запутает организацию вашего кода, так как условное вычисление переместится подальше от другой вашей функции. Это немного банально, но вы всегда можете сделать что-нибудь вроде:
do {
if (!cond1)
break;
if (!cond2)
break;
if (!cond3)
break;
...
DoSomething();
} while (false);
в то время как (ложь) немного глупо. Я бы хотел, чтобы у языков был оператор области видимости под названием «once» или что-то такое, от чего можно было бы легко избавиться.
Попробуйте взглянуть на Функторы и Предикаты. В проекте 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 );
}
};
Отлично! Один, который нужно скачать и поиграть с methinks.
Мне нравится разбивать каждое условие на описательные переменные.
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
}
Какой смысл создавать отдельные буллы. Кроме того, вы сравниваете каждый varX с true и назначаете его логическому элементу isVarXValid, который по сути является просто от руки для isVar1Valid = var, который является избыточным. У вас уже есть bools для начала, так почему бы не просто if (var1 && var2 && var3 && var4)
Совет Стива Макконелла от Код завершен: Используйте многомерный стол. Каждая переменная служит индексом для таблицы, а оператор if превращается в поиск по таблице. Например, если (размер == 3 && вес> 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;
В рефлексивных языках, таких как PHP, вы можете использовать переменные-переменные:
$vars = array('var1', 'var2', ... etc.);
foreach ($vars as $v)
if ($$v == true) {
// do something
break;
}
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)
)
{
}
Нет ничего плохого в горизонтальном пространстве (перевод строки), вертикальном выравнивании или явной оценке выражений, определяющих скобки, - все это УЛУЧШАЕТ удобочитаемость и ясность.
Просто, легко и эффективно.