Как вы справляетесь с огромными "если"-условиями?
-
08-06-2019 - |
Вопрос
Это то, что не дает мне покоя на каждом языке, который я использовал, у меня есть оператор 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)
{
Решение
Разделите условие на несколько логических значений, а затем используйте основное логическое значение в качестве условия.
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)
{
// ...
}
}
Убедитесь, что вы указываете своим переменным имена, которые на самом деле указывают на намерение, а не на функцию.Это в значительной степени поможет разработчику поддерживать ваш код в рабочем состоянии...это мог бы быть ТЫ!
Другие советы
Я удивлен, что никто до сих пор этого не получил.Существует рефакторинг специально для такого типа проблем:
http://www.refactoring.com/catalog/decomposeConditional.html
Здесь необходимо решить два вопроса:читабельность и понятность
Решение "удобочитаемости" - это проблема стиля, и как таковое оно открыто для интерпретации.Мое предпочтение заключается в следующем:
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.
Я думаю, это формально известно как шаблон рефакторинга "Decompose Conditional".Такие инструменты, как Resharper или Refactor Pro!можно ли упростить выполнение такого рода рефакторинга!
Во всех случаях ключом к получению читаемого и понятного кода является использование реалистичных имен переменных.Хотя я понимаю, что это надуманный пример, "var1", "var2" и т.д. Являются нет допустимые имена переменных.У них должно быть название, отражающее основной характер данных, которые они представляют.
Я часто разбиваю их на составляющие логические переменные:
bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...
Во-первых, я бы удалил все == true
детали, которые сделали бы его на 50% короче ;)
Когда у меня тяжелое состояние, я ищу причины.Иногда я вижу, что мне следует использовать полиморфизм, иногда мне нужно добавить какой-нибудь объект состояния.По сути, это подразумевает, что необходим рефакторинг (запах кода).
Иногда я использую Законы Де-Моргана немного упростить логические выражения.
Проверьте Схемы реализации автор: Кент Бек.Есть определенный шаблон, о котором я думаю, который может помочь в этой ситуации...это называется "Стражи порядка".Вместо того чтобы иметь массу условий, вы можете разбить их на защитные, что дает понять, какие неблагоприятные условия в методе.
Так, например, если у вас есть метод, который что-то делает, но есть определенные условия, при которых он не должен что-то делать, а не:
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
}
Он немного более подробный, но гораздо более читабельный, особенно когда у вас начинаются странные вложения, guard может помочь (в сочетании с методами извлечения).
Кстати, я ОЧЕНЬ рекомендую эту книгу.
Я видел много людей и редакторов, которые либо делали отступы для каждого условия в вашем операторе if с помощью одной вкладки, либо сопоставляли его с открытым параметром:
if (var1 == true
&& var2 == true
&& var3 == true
) {
/* do something.. */
}
Обычно я помещаю close paren в ту же строку, что и последнее условие:
if (var1 == true
&& var2 == true
&& var3 == true) {
/* do something.. */
}
Но я не думаю, что здесь все так чисто.
Совет Стива Макконелла, от Код Завершен:Используйте многомерную таблицу.Каждая переменная служит индексом таблицы, а оператор if превращается в поиск по таблице.Например, if (size == 3 && weight > 70) преобразует в решение записи таблицы[size][weight_group]
Попробуйте взглянуть на Функторы и предикаты.Проект Apache Commons обладает отличным набором объектов, позволяющих инкапсулировать условную логику в объекты.Пример их использования доступен на сайте O'reilly здесь.Выдержка из примера кода:
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 );
}
};
Ну, во-первых, почему бы и нет:
if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
...
Кроме того, очень сложно рефакторировать примеры абстрактного кода.Если бы вы показали конкретный пример, было бы легче определить лучший шаблон, соответствующий задаче.
Это не лучше, но то, что я делал в прошлом:(Следующий метод предотвращает логическое тестирование с коротким замыканием, все тесты выполняются, даже если первый имеет значение false.Не рекомендуемый шаблон, если вы не знаете, что вам нужно всегда выполнять весь код перед возвратом - Спасибо ptomato за то, что заметили мою ошибку!)
логическое значение ok = cond1;
ok &= cond2;
ok &= cond3;
ok &= cond4;
ок &= кондиция5;
ok &= cond6;
Что является тем же самым, что и: (это не одно и то же, см. Примечание выше!)
ok = (условие 1 && условие 2 && условие 3 && условие 4 && условие 5 && условие 6);
Я прибегаю к отдельным логическим значениям:
Bool cond1 == (var1 && var2);
Bool cond2 == (var3 && var4);
if ( cond1 && cond2 ) {}
Как упоминали другие, я бы проанализировал ваши условия, чтобы понять, есть ли способ передать их на аутсорсинг другим методам для повышения удобочитаемости.
В рефлексивных языках, таких как PHP, вы можете использовать переменные-variables:
$vars = array('var1', 'var2', ... etc.);
foreach ($vars as $v)
if ($$v == true) {
// do something
break;
}
Мне нравится разбивать их по уровням, поэтому я бы отформатировал ваш пример следующим образом:
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?).Если это так, то, скорее всего, это самый чистый подход.
Макдауэлл,
Вы правы в том, что при использовании единственного оператора '&' вычисляются обе стороны выражения.Однако при использовании оператора '&&' (по крайней мере, в C #) первое выражение, возвращающее значение false, является последним вычисленным выражением.Это делает размещение evaulation перед оператором FOR таким же хорошим, как и любой другой способ сделать это.
@tweakt
Это не лучше, но то, что я делал в прошлом:
логическое значение ok = cond1;ok &= cond2;ok &= cond3;ok &= cond4;ок &= кондиция5;ok &= cond6;
Что является тем же самым, что и:
ok = (условие 1 && условие 2 && условие 3 && условие 4 && условие 5 && условие 6);
На самом деле, эти две вещи не совпадают в большинстве языков.Второе выражение обычно перестает вычисляться, как только одно из условий становится ложным, что может значительно повысить производительность, если оценка условий является дорогостоящей.
Для удобства чтения я лично предпочитаю предложение Майка Стоуна, приведенное выше.Его легко подробно прокомментировать, и он сохраняет все вычислительные преимущества, связанные с возможностью раннего запуска.Вы также можете использовать тот же метод встроенным в функцию, если это приведет к путанице в организации вашего кода и отдалит условную оценку от вашей другой функции.Это немного дрянновато, но вы всегда можете сделать что-то вроде:
do {
if (!cond1)
break;
if (!cond2)
break;
if (!cond3)
break;
...
DoSomething();
} while (false);
значение while (false) какое-то дрянное.Я хотел бы, чтобы в языках был оператор определения области действия под названием "once" или что-то в этом роде, от чего вы могли бы легко отказаться.
Если бы я делал это на Perl, вот как я мог бы выполнять проверки.
{
last unless $var1;
last unless $var2;
last unless $var3;
last unless $var4;
last unless $var5;
last unless $var6;
... # Place Code Here
}
Если вы планируете использовать это поверх подпрограммы, замените каждый экземпляр last
с return
;
Мне нравится разбивать каждое условие на описательные переменные.
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) {
if (var2 == true) {
if (var3 == true) {
...
}
}
}
Тогда вы также сможете реагировать на случаи, когда что-то не соответствует действительности.Например, если вы проверяете входные данные, вы могли бы дать пользователю совет, как их правильно отформатировать, или что-то еще.