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

Почему утверждение "если" считается злым?

Я только что пришел из Простая конференция по дизайну и тестированию. В одном из сеансов мы говорили о злых словах на языках программирования. Кори Хейнс, который предложил тему, был убежден, что утверждение if является абсолютным злом. Его альтернативой было создание функций с предикатами . Не могли бы вы объяснить мне, почему if является злом.

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

4b9b3361

Ответ 1

Условные предложения иногда приводят к коду, которым сложнее управлять. Это включает в себя не только оператор if, но даже чаще оператор switch, который обычно включает больше ветвей, чем соответствующий if.

Есть случаи, когда вполне разумно использовать, if

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

// this is a good "if" use-case
int Min(int a, int b)
{
    if (a < b) 
       return a;
    else
       return b;
}

// or, if you prefer the ternary operator
int Min(int a, int b)
{
    return (a < b) ? a : b;
}

Разветвление над "кодом типа" - это запах кода

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

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

Рассматривать:

// this is called branching on a "type code",
// and screams for refactoring
void RunVehicle(Vehicle vehicle)
{
    // how the hell do I even test this?
    if (vehicle.Type == CAR)
        Drive(vehicle);
    else if (vehicle.Type == PLANE)
        Fly(vehicle);
    else
        Sail(vehicle);
}

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

// adding a new vehicle is gonna be a piece of cake
interface IVehicle
{
    void Run();
}

// your method now doesn't care about which vehicle 
// it got as a parameter
void RunVehicle(IVehicle vehicle)
{
    vehicle.Run();
}

И теперь вы можете легко проверить, работает ли ваш метод RunVehicle следующим образом:

// you can now create test (mock) implementations
// since you're passing it as an interface
var mock = new Mock<IVehicle>();

// run the client method
something.RunVehicle(mock.Object);

// check if Run() was invoked
mock.Verify(m => m.Run(), Times.Once());

Шаблоны, которые отличаются только в том if условия могут быть повторно использованы

Что касается аргумента о замене if на "предикат" в вашем вопросе, Хейнс, вероятно, хотел упомянуть, что иногда в вашем коде существуют похожие шаблоны, которые отличаются только своими условными выражениями. Условные выражения появляются вместе с if s, но вся идея состоит в том, чтобы извлечь повторяющийся шаблон в отдельный метод, оставив выражение в качестве параметра. Это то, что LINQ уже делает, обычно в результате получается более чистый код по сравнению с альтернативным foreach:

Рассмотрим эти два очень похожих метода:

// average male age
public double AverageMaleAge(List<Person> people)
{
    double sum = 0.0;
    int count = 0;
    foreach (var person in people)
    {
       if (person.Gender == Gender.Male)
       {
           sum += person.Age;
           count++;
       }
    }
    return sum / count; // not checking for zero div. for simplicity
}

// average female age
public double AverageFemaleAge(List<Person> people)
{
    double sum = 0.0;
    int count = 0;
    foreach (var person in people)
    {
       if (person.Gender == Gender.Female) // <-- only the expression
       {                                   //     is different
           sum += person.Age;
           count++;
       }
    }
    return sum / count;
}

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

// average age for all people matched by the predicate
public double AverageAge(List<Person> people, Predicate<Person> match)
{
    double sum = 0.0;
    int count = 0;
    foreach (var person in people)
    {
       if (match(person))       // <-- the decision to match
       {                        //     is now delegated to callers
           sum += person.Age;
           count++;
       }
    }
    return sum / count;
}

var males = AverageAge(people, p => p.Gender == Gender.Male);
var females = AverageAge(people, p => p.Gender == Gender.Female);

А поскольку в LINQ уже есть множество таких удобных методов расширения, вам даже не нужно писать свои собственные методы:

// replace everything we've written above with these two lines
var males = list.Where(p => p.Gender == Gender.Male).Average(p => p.Age);
var females = list.Where(p => p.Gender == Gender.Female).Average(p => p.Age);

В этой последней версии LINQ оператор if полностью "исчез", хотя:

  1. если честно, проблема была не в самом if, а во всем шаблоне кода (просто потому, что он был продублирован), и
  2. if все еще действительно существует, но он написан внутри метода расширения LINQ Where, который был протестирован и закрыт для модификации. Иметь меньше собственного кода - это всегда хорошо: меньше вещей для тестирования, меньше ошибок, а код проще отслеживать, анализировать и поддерживать.

Рефакторинг, когда вы чувствуете запах кода, но не перегружайте

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

Ответ 2

Есть еще один смысл, в котором if может быть злым: когда он приходит вместо полиморфизма.

например.

 if (animal.isFrog()) croak(animal)
 else if (animal.isDog()) bark(animal)
 else if (animal.isLion()) roar(animal)

вместо

 animal.emitSound()

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

Ответ 3

Хорошая цитата из кода Complete:

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

IOW, держите его простым. Если читаемость вашего приложения будет улучшена с использованием предиката в определенной области, используйте его. В противном случае используйте "if" и двигайтесь дальше.

Ответ 4

Я думаю, это зависит от того, что вы делаете, чтобы быть честным.

Если у вас простой оператор if..else, зачем использовать predicate?

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

Этот парень, похоже, был немного педантичен по своему вкусу. Замена всех if на Predicates - просто безумный разговор.

Ответ 5

Существует кампания Anti-If, которая началась ранее в этом году. Основная предпосылка заключается в том, что многие вложенные операторы if часто могут быть заменены полиморфизмом.

Мне было бы интересно увидеть пример использования Predicate. Это больше соответствует функциональному программированию?

Ответ 6

if не является злом (я также считаю, что назначение морали для написания кода является asinine...).

г. Хейнс глуп и должен смеяться.

Ответ 7

Как и в библейском стихе о деньгах, если утверждения не являются злыми - ЛЮБОВЬ, если утверждения являются злыми. Программа без утверждений является смешной идеей, и использование их по мере необходимости имеет важное значение. Но программа, которая имеет 100 if-else, если блоки в строке (что, к сожалению, я видел) определенно зла.

Ответ 8

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

Например, комментарии перечислены как запах кода Мартина Фаулера, но я бы не стал серьезно относиться к тем, кто говорит: "Комментарии злы, не используйте их".

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

Ответ 9

Я соглашусь с вами; он ошибался. Вы можете зайти слишком далеко с такими вещами, слишком умными для вашего же блага.

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

Ответ 10

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

Ответ 11

Предикаты поступают из логических/декларативных языков программирования, таких как PROLOG. Для некоторых классов проблем, таких как решение ограничения, они, вероятно, превосходят много вытаскиваемых пошагово, если это делать-то-то-делаю-это дерьмо. Проблемы, которые были бы долгими и сложными для решения на императивных языках, могут выполняться всего несколькими строками в PROLOG.

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

Определенно не полезно везде; Мне бы очень хотелось попробовать написать драйвер устройства с предикатами вместо операторов. Но да, я думаю, что основной момент, вероятно, звучит и стоит хотя бы познакомиться, если не использовать все время.

Ответ 12

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

function void Test(Predicate<int> pr, int num) 
{
    if (pr(num))
    { /* do something */ }
    else
    { /* do something else */ }
}

Конечно, вы можете использовать оператор terniary (?:), но это всего лишь оператор if в маскировке...

Ответ 13

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

Ответ 14

ММО: Я подозреваю, что он пытался спровоцировать дебаты и заставить людей думать о неправильном использовании "если". Никто всерьез не предполагал, что такая фундаментальная конструкция синтаксиса программирования должна была быть полностью устранена, если бы они?

Ответ 15

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

Предполагаемый полиморфизм до if лежит в основе этого. Вместо:

if(animaltype = bird) {
     squawk();
} else if(animaltype = dog) {
     bark();
}

... использование:

animal.makeSound();

Но это предполагает, что у вас есть класс/интерфейс Animal - так что вам говорит if, что вам нужно создать этот интерфейс.

Итак, в реальном мире, какой вид if мы видим, что приводит нас к решению полиморфизма?

if(logging) {
      log.write("Did something");
}

Это действительно раздражает, чтобы увидеть весь ваш код. Как насчет, вместо этого, наличия двух (или более) реализаций Logger?

this.logger = new NullLogger();    // logger.log() does nothing
this.logger = new StdOutLogger();  // logger.log() writes to stdout

Это приводит нас к шаблону стратегии.

Вместо:

if(user.getCreditRisk() > 50) {
     decision = thoroughCreditCheck();
} else if(user.getCreditRisk() > 20) {
     decision = mediumCreditCheck();
} else {
     decision = cursoryCreditCheck();
}

... вы могли бы...

decision = getCreditCheckStrategy(user.getCreditRisk()).decide();

Конечно, getCreditCheckStrategy() может содержать if - и это может быть уместно. Вы подтолкнули его в аккуратное место, где оно принадлежит.

Ответ 16

Я думаю, что если утверждения являются злыми, но если выражения не являются. То, что я подразумеваю под выражением if в этом случае, может быть чем-то вроде тройного оператора С# (условие? TrueExpression: falseExpression). Это не зло, потому что это чистая функция (в математическом смысле). Он оценивает новое значение, но он не влияет ни на что другое. Из-за этого он работает в модели замещения.

Императив Если утверждения являются злыми, потому что они заставляют вас создавать побочные эффекты, когда вам это не нужно. Для того, чтобы оператор If имел смысл, вы должны создавать разные "эффекты" в зависимости от выражения условия. Эти эффекты могут быть такими, как операции ввода-вывода, рендеринг изображений или базы данных, которые меняют вещи вне программы. Или это могут быть операторы присваивания, которые мутируют состояние существующих переменных. Обычно лучше минимизировать эти эффекты и отделить их от реальной логики. Но из-за операторов If мы можем свободно добавлять эти "условно исполняемые эффекты" всюду в коде. Я думаю, что это плохо.

Ответ 17

Хорошо, что в ruby ​​ у нас есть , если;)

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

Ответ 18

Если это не зло! Рассмотрим...

int sum(int a, int b) {
    return a + b;
}

Скучно, а? Теперь с добавленным if...

int sum(int a, int b) {
    if (a == 0 && b == 0) {
        return 0;
    }
    return a + b;
}

... производительность вашего создания кода (измеренная в LOC) удваивается.

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

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

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

int sum(int a, int b) {
    if (a == 0 || b == 0) {
        return 0;
    }
    return a + b;
}

Сколько еще понадобилось бы смены кода, если бы вы не изобрели право с самого начала.

Благодарность будет твоей со всех сторон.

Заключение: никогда не бывает, если есть.

Там вы идете. К.