Другие блоки увеличивают сложность кода? [закрыто]


18

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

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    else
    {
        return "No you can't";
    }
}

Многие люди, с которыми я встречался, ReSharper, и этот парень (чей комментарий напомнил мне, что я некоторое время просил об этом) рекомендуют рефакторинг кода, чтобы удалить elseблок, оставив этот:

(Я не могу вспомнить, что сказал большинство, иначе я бы не спросил)

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    return "No you can't";
}

Вопрос: Есть ли увеличение сложности, вызванное отсутствием elseблока?

У меня сложилось впечатление, что elseболее прямо заявляет о своем намерении, заявляя, что код в обоих блоках напрямую связан.

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

Возьмите этот вариант моего упрощенного примера (игнорируя тот факт, что orоператор, поскольку это намеренно упрощенный пример):

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

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

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

Конечно, есть и другие способы определения конкретного примера, которые предотвращают эту ситуацию, но это всего лишь пример.

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

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


В действительности, когда есть оператор «if» с «return», его можно рассматривать как «защитный блок» в> 50% всех случаев. Поэтому я думаю, что ваше заблуждение заключается в том, что «защитный блок» является исключительным случаем, а ваш пример - обычным случаем.
Док Браун

2
@AssortedTrailmix: я согласен, что такой случай может быть более читабельным при написании elseпредложения (на мой вкус, он будет еще более читабельным, если оставить ненужные скобки. Но я думаю, что пример не очень хорошо выбран, потому что в в вышеприведенном случае я бы написал return sky.Color == Color.Blue(без if / else). Пример с другим типом возврата, отличным от bool, вероятно, сделает это более понятным
Док Браун

1
как указано в комментариях выше, предлагаемый пример слишком упрощен, предлагая умозрительные ответы. Что касается части вопроса, которая начинается с «Кто-то может теперь добавить новый блок if ...» , он задавался и отвечал много раз прежде, см., Например, Подходы к проверке нескольких условий? и несколько вопросов, связанных с этим.
Комнат

1
Я не вижу, что еще блоки имеют отношение к принципу СУХОЙ. DRY имеет больше общего с абстракцией и ссылками на часто используемый код в форме функций, методов и объектов, чем то, что вы просите. Ваш вопрос связан с читабельностью кода и, возможно, условностями или идиомами.
Шашанк Гупта

2
Голосование возобновить. Хорошие ответы на этот вопрос не особенно основаны на мнении. Если приведенный пример в вопросе неадекватен, было бы разумно улучшить его путем редактирования, а не голосовать за закрытие по причине, которая на самом деле не соответствует действительности.
Майкл Шоу

Ответы:


27

На мой взгляд, явный блок else предпочтительнее. Когда я вижу это:

if (sky.Color != Blue) {
   ...
}  else {
   ...
}

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

Когда я вижу это:

if (sky.Color != Blue) {
   ...
} 
return false;

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

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

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

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

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

Вскоре после первоначальной версии этого ответа в моем проекте был обнаружен подобный код:

Foobar merge(Foobar left, Foobar right) {
   if(!left.isEmpty()) {
      if(!right.isEmpty()) {
          Foobar merged = // construct merged Foobar
          // missing return merged statement
      }
      return left;
   }
   return right;
}

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


Это подводит итог моим мыслям об этом, я рад, что я не единственный, кто так думает. Я работаю с программистами, инструментами и IDE, предпочитая удаление elseблока (это может быть неприятно, когда я вынужден делать это просто для того, чтобы придерживаться соглашений для кодовой базы). Мне тоже интересно увидеть контрапункт здесь.
Селали Адобор

1
Я варьируюсь на этом. Иногда явное иное не приносит вам особой пользы, поскольку вряд ли вызывает тонкие логические ошибки, о которых упоминает ОП, - это просто шум. Но, в основном, я согласен, и иногда я делаю что-то вроде того, } // elseгде остальные обычно идут как компромисс между ними.
Теластин

3
@Telastyn, но что ты получаешь от пропуска остальных? Я согласен, что во многих ситуациях выгода очень мала, но я думаю, что даже за небольшую выгоду это стоит делать. Конечно, мне кажется странным помещать что-то в комментарий, когда это может быть код.
Уинстон Эверт

2
Я думаю, что у вас такое же заблуждение, что и у ОП - «если» с «возвращением» в основном можно рассматривать как защитный блок, так что вы обсуждаете здесь исключительный случай, в то время как более частый случай защитных блоков ИМХО более читабелен без «еще».
Док Браун

... так что то, что вы написали, не так, но ИМХО не стоит многих голосов, которые вы получили.
Док Браун

23

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

Многие ifзаявления по сути являются охранниками. Они предотвращают разыменование нулевых указателей и других «не делайте этого!» ошибки. И они приводят к быстрому прекращению текущей функции. Например:

if (obj === NULL) {
    return NULL;
}
// further processing that now can assume obj attributes are set

Теперь может показаться, что здесь elseбыло бы очень разумно:

if (obj === NULL) {
    return NULL;
}
else if (obj.color != Blue) {
   // handle case that object is not blue
}

И действительно, если это все, что вы делаете, это не намного больше отступ, чем в примере выше. Но это редко все, что вы делаете с реальными многоуровневыми структурами данных (условие, которое возникает постоянно при обработке распространенных форматов, таких как JSON, HTML и XML). Поэтому, если вы хотите изменить текст всех дочерних элементов данного узла HTML, скажите:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
    return NULL;
}
for (c in p.children) {
    c.text = c.text.toUpperCase();
}
return p;

Охранники не увеличивают отступ кода. С elseпредложением вся фактическая работа начинает перемещаться вправо:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
else {
    p = elements[0]
    if ((p.children === NULL) or (p.children.length == 0)) {
        return NULL;
    }
    else {
        for (c in p.children) {
            c.text = c.text.toUpperCase();
        }
        return p;
    }
}

Это начинает иметь значительное движение вправо, и это довольно тривиальный пример, только с двумя условиями охраны. Каждый дополнительный охранник добавляет еще один уровень отступа. Реальный код, который я написал, обрабатывая XML или потребляя подробные потоки JSON, может легко составлять 3, 4 или более сторожевых условий. Если вы всегда используете else, вы в конечном итоге с отступом 4, 5 или 6 уровней. Может быть, больше. И это определенно способствует ощущению сложности кода и увеличению времени, затрачиваемого на понимание того, что соответствует чему. Быстрый, плоский стиль раннего выхода устраняет некоторые из этих «лишних структур» и кажется более простым.

Приложение Комментарии к этому ответу заставили меня понять, что, возможно, не было ясно, что обработка NULL / empty не единственная причина для условных выражений защиты. Не почти! Хотя этот простой пример фокусируется на обработке пустых / пустых значений и не содержит других причин, поиск глубоких структур, таких как XML и AST, например, для «релевантного» контента часто содержит длинный ряд специальных тестов для отсеивания нерелевантных узлов и неинтересных случаев. Серия тестов «если этот узел не релевантен, возврат» вызовет тот же тип правостороннего дрейфа, что и NULL / пустые охранники. И на практике последующие тесты релевантности часто сочетаются с NULL / пустыми защитными элементами для соответственно более глубоких искомых структур данных - двойной удар для смещения вправо.


2
Это подробный и правильный ответ, но я собираюсь добавить это к вопросу (сначала он ускользнул от моего сознания); Существует «исключение», когда я всегда нахожу elseблок излишним, когда использую ifблок, чтобы применить ограничение, которое должно быть логически выполнено для всего следующего кода, такого как проверка на нуль (или любые другие средства защиты).
Селали Адобор

@AssortedTrailmix Я полагаю, что если вы исключите случаи, когда вы можете регулярно делать досрочный выход в thenпредложении (например, последовательные защитные операторы), то не будет особого смысла избегать конкретного elseпредложения. Действительно, это уменьшило бы ясность и потенциально могло бы быть опасным (из-за невозможности просто указать альтернативную структуру, которая есть / должна быть там).
Джонатан Юнис

Я думаю, что разумно использовать защитные условия для выброса, как только вы ушли с счастливого пути. Однако для случая обработки XML / JSON я также обнаружил, что если вы сначала проверяете входные данные по схеме, вы получаете более качественные сообщения об ошибках и более чистый код.
Уинстон Эверт

Это идеальное объяснение случаев, когда еще нужно избегать.
Крис Шиффхауэр

2
Я бы обернул API, чтобы вместо этого не выдавать глупые значения NULL.
Уинстон Эверт

4

Когда я вижу это:

if(something){
    return lamp;
}
return table;

Я вижу общую идиому . Распространенный паттерн , который в моей голове звучит так:

if(something){
    return lamp;
}
else return table;

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

Я не нуждаюсь в явном else, моя голова «помещает это туда» для меня, потому что он распознал образец в целом . Я понимаю значение всего распространенного паттерна, поэтому мне не нужны мелкие детали, чтобы прояснить его.

Например, прочитайте следующий текст:

введите описание изображения здесь

Вы читали это?

Я люблю Париж весной

Если это так, вы не правы. Прочитай текст еще раз. Что на самом деле написано

Я люблю Париж в в весеннее время

В тексте есть два слова "the". Так почему ты пропустил один из двух?

Это потому, что ваш мозг увидел общий паттерн и немедленно перевел его в известное значение. Ему не нужно было читать всю деталь по деталям, чтобы выяснить смысл, потому что он уже распознал шаблон , увидев его. Вот почему он проигнорировал лишнее «то ».

То же самое с вышеупомянутой идиомой. Программисты , которые читали много кода привыкли видеть эту картину , из if(something) {return x;} return y;. Им не нужен опыт, elseчтобы понять его смысл, потому что их мозг «помещает это для них». Они распознают его как шаблон с известным значением: «то, что после закрывающей скобки будет работать только как неявное elseиз предыдущего if».

Так что, на мой взгляд, это elseне нужно. Но просто используйте то, что кажется более читабельным.


2

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

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

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

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

Давайте посмотрим на пример, теперь он должен возвращать true только в том случае, если небо синее, а влажность высокая, влажность не высокая или небо красное. Но тогда вы ошибаетесь в одной скобке и помещаете ее в неправильную else:

bool CanLeaveWithoutUmbrella() {
    if(sky.Color == Color.Blue)
    {
        if (sky.Humidity == Humidity.High) 
        {
            return true;
        } 
    else if (sky.Humidity != Humidity.High)
    {
        return true;
    } else if (sky.Color == Color.Red) 
    {
            return true;
    }
    } else 
    {
       return false;
    }
}

Мы видели все подобные глупые ошибки в рабочем коде, и их может быть очень трудно обнаружить.

Я бы предложил следующее:

Я считаю, что это легче читать, потому что я сразу вижу, что по умолчанию false.

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

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

  • Если случай такой же простой, как и представленный, мой ответ будет состоять в том, чтобы вообще пропустить любые if elseпункты.

    возврат (sky.Color == Color.Blue);

  • Если дело немного сложнее, с различными предложениями, я бы ответил:

    bool CanLeaveWithoutUmbrella () {

    if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
        return true;
    } else if (sky.Humidity != Humidity.High) {
        return true;
    } else if (sky.Color == Color.Red) {
        return true;
    }
    
    return false;
    

    }

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

    double CanLeaveWithoutUmbrella() {
    
        double risk = 0.0;
    
        if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
            risk = 1.0;
        } else if (sky.Humidity != Humidity.High) {
            risk = 0.5;
        } else if (sky.Color == Color.Red) {
            risk = 0.8;
        }
    
        Log("value of risk:" + risk);
        return risk;
    

    }

  • Если мы говорим не о методе, который возвращает что-то, а о блоке if-else, который устанавливает некоторую переменную или вызывает метод, тогда да, я бы включил последний elseпункт.

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


Я чувствую , что вы делаете ту же ошибку , другие сделали и морфинг на примере немного слишком много, таким образом , рассматривая новый вопрос, но мне нужно , чтобы занять второе место и изучить процесс получения на ваш пример, так что сейчас это выглядит действительны для меня. И примечание стороны, это не связано с принципом открытия / закрытия . Сфера слишком узкая, чтобы применять эту идиому. Но интересно отметить, что его применение на более высоком уровне будет устранять весь этот аспект проблемы (устраняя необходимость в любой модификации функции).
Селали Адобор

1
Но если мы не сможем изменить этот слишком упрощенный пример, добавив предложения, ни изменить его текст, ни внести в него какие-либо изменения, то вам следует учитывать, что этот вопрос относится только к тем точным строкам кода, и он больше не является общим вопросом. В этом случае вы совершенно правы, это не добавляет сложности (и не удаляет ее) к предложению else, поскольку с самого начала не было сложности. Однако вы несправедливы, потому что именно вы предлагаете идею о том, что могут быть добавлены новые предложения.
FranMowinckel

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

Держитесь за правки, у вас что-то хорошее, я
набираю

1

Хотя описанный случай if-else имеет большую сложность, в большинстве (но не во всех) практических ситуациях он не имеет большого значения.

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

Вот почему

Присутствие «else» создало неявный 3-й случай, который должен был быть оценен - ​​ни «if», ни «else» не были взяты. Вы можете думать (как я был в то время) WTF?

Объяснение пошло так ...

С логической точки зрения, есть только два варианта - «если» и «еще». Тем не менее, мы сертифицировали объектный файл, который генерировал компилятор. Поэтому, когда было «другое», необходимо было провести анализ, чтобы подтвердить, что сгенерированный код ветвления был правильным и что загадочный 3-й путь не появился.

Сравните это со случаем, когда есть только «если» и нет «еще». В этом случае есть только два варианта - путь «если» и путь «не если». Два случая для оценки менее сложны, чем три.

Надеюсь это поможет.


в объектном файле, ни один из случаев не будет отображаться как идентичные JMP?
Уинстон Эверт

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

(Я думаю, SE съел мой комментарий ...) Это очень интересный ответ. Хотя я бы сказал, что случай, который он демонстрирует, является несколько нишевым, он показывает, что некоторые инструменты могут найти разницу между ними (Даже самая распространенная метрика кода, основанная на потоке, который я вижу, цикломатическая сложность, не будет измерять разницу.
Селали Adobor

1

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

def value(key):
    if key == FROB:
        return FOO
    elif key == NIK:
        return BAR
    [...]
    else:
        return BAZ

Это довольно ясно - это эквивалент caseпоиска оператора или словаря, версия более высокого уровня return foo == bar:

KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ

def value(key):
    return KEY_VALUES.get(key, default=DEFAULT_VALUE)

Это более понятно, потому что, хотя количество токенов больше (32 против 24 для исходного кода с двумя парами ключ / значение), семантика функции теперь явная: это просто поиск.

(Это имеет последствия для рефакторинга - если вы хотите получить побочный эффект, если у key == NIKвас есть три варианта:

  1. Вернитесь к стилю if/ elif/ elseи вставьте одну строчку в key == NIKкейс.
  2. Сохраните результат поиска в значение и добавьте ifоператор valueдля особого случая перед возвратом.
  3. Поместите ifзаявление в звонящего.

Теперь мы понимаем, почему функция поиска является мощным упрощением: третий вариант, очевидно, является самым простым решением, поскольку помимо того, что он даетvalue меньшую разницу, чем первый, он единственный, который обеспечивает выполнение только одного действия. )

Возвращаясь к коду OP, я думаю, что это может служить руководством для сложности elseоператоров: код с явным elseоператором объективно более сложен, но более важно то, делает ли он семантику кода ясной и простой. И на это нужно отвечать в каждом конкретном случае, поскольку каждый фрагмент кода имеет свое значение.


Мне нравится, что ваша таблица поиска переписана для простого случая переключения. И я знаю, что это любимая идиома Python. На практике, однако, я часто нахожу, что многофакторные условные выражения ( caseили switchоператоры) менее однородны. Несколько вариантов - это просто возврат значений, но один или два случая требуют дополнительной обработки. И когда вы обнаружите, что стратегия поиска не подходит, вам нужно переписать в совершенно ином if elif... elseсинтаксисе.
Джонатан Юнис

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

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

1

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

Ваш пример выглядит как выражение для меня. В языках, подобных C, троичный оператор ?:очень похож на ifоператор, но это выражение, и часть else не может быть пропущена. Имеет смысл, что ветвь else не может быть опущена в выражении, потому что выражение всегда должно иметь значение.

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

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue)
               ? true
               : false;
}

Так как это логическое выражение, оно действительно должно быть упрощено вплоть до

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

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


+1, ОП ИМХО обсуждает патологический случай, который лучше написать так, как вы показали выше. Гораздо более частый случай «если» с «возвращением» , по моему опыту, случай «охранник».
Док Браун

Я не знаю, почему это происходит, но люди продолжают игнорировать самый первый абзац вопроса. На самом деле, вы все используете точную строку кода, которую я прямо говорю не использовать. I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Селали Адобор

И, перечитывая свой последний вопрос, вы, похоже, неправильно поняли его смысл.
Селали Адобор

Вы использовали пример, который практически просил упростить. Я прочитал и обратил внимание на ваш первый абзац, поэтому мой первый кодовый блок вообще есть, вместо того, чтобы перейти непосредственно к наилучшему способу выполнения этого конкретного примера. Если вы считаете, что я (или кто-то еще) неправильно понял цель вашего вопроса, возможно, вам следует отредактировать его, чтобы прояснить свое намерение.
Майкл Шоу

Я бы отредактировал его, но я бы не знал, как сделать это более понятным, поскольку я использую точную строку кода, которую вы делаете, и говорите «не делайте этого». Есть бесконечные способы переписать код и убрать вопрос, я не могу охватить их все, но многие люди поняли мое утверждение и уважали его ...
Селали Адобор

1

Еще одна вещь, о которой следует подумать в этом аргументе, - это привычки, которые продвигает каждый подход.

  • if / else переопределяет образец кода в терминах ветвей. Как вы говорите, иногда это явно хорошо. На самом деле есть два возможных пути выполнения, и мы хотим это подчеркнуть.

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

  • Конечно, есть случай 3 или более (n), где мы обычно рекомендуем отказаться от цепочки if / else и использовать оператор case / switch или полиморфизм.

Руководящий принцип, который я имею в виду, заключается в том, что метод или функция должны иметь только одну цель. Любой код с оператором if / else или switch предназначен только для ветвления. Поэтому он должен быть абсурдно коротким (4 строки) и делегироваться только на правильный метод или давать очевидный результат (как в вашем примере). Когда ваш код такой короткий, трудно пропустить эти множественные возвраты :) Во многом как «goto считать вредным», любое утверждение ветвления может быть использовано неправильно, поэтому обычно стоит добавить некоторые критические соображения в операторы else. Как вы упомянули, просто наличие блока else обеспечивает место для скопления кода. Вы и ваша команда должны решить, как вы к этому относитесь.

На что инструмент действительно жалуется, так это тот факт, что у вас есть возврат / разрыв / бросок внутри блока if. Итак, что касается этого, вы написали пункт охраны, но облажались. Вы можете сделать инструмент счастливым или «развлечь» его предложение, не принимая его.


Я никогда не задумывался над тем, что инструмент может рассматривать условие if как защитное предложение, как только оно вписывается в шаблон для одного, это, вероятно, так, и он объясняет причины для одной «группы сторонников» в связи с его отсутствием
Селали Адобор

@AssortedTrailmix Правильно, вы думаете о elseсемантике, инструменты анализа кода обычно ищут посторонние инструкции, потому что они часто подчеркивают неправильное понимание потока управления. elseЕще через ifкоторый возвращает впустую биты. if(1 == 1)часто вызывает одно и то же предупреждение.
Стив Джексон

1

Я думаю, что ответ зависит от этого. Ваш пример кода (как вы косвенно указываете) просто возвращает оценку условия и лучше всего представляется, как вы, очевидно, знаете, в виде одного выражения.

Чтобы определить, будет ли добавление условия else пояснять или затенять код, вам необходимо определить, что представляет IF / ELSE. Это (как в вашем примере) выражение? Если так, то это выражение должно быть извлечено и использовано. Это условие охраны? Если так, то ELSE является ненужным и вводящим в заблуждение, поскольку это делает две ветви кажущимися эквивалентными. Это пример процедурного полиморфизма? Извлеките это. Это предварительные условия? Тогда это может быть важно.

Прежде чем вы решите включить или исключить ELSE, решите, что он представляет, и вам сообщат, должен ли он присутствовать или нет.


0

Ответ немного субъективен. elseБлок может помочь читаемость, установив , что один блок кода выполняется исключительно к другому блоку кода, без необходимости , чтобы прочитать обоих блоков. Это может быть полезно, если, скажем, оба блока относительно длинные. Есть случаи, когда наличие ifs без elses может быть более читабельным. Сравните следующий код с количеством if/elseблоков:

if(a == b) {
    if(c <= d) {
        if(e != f) {
            a.doSomeStuff();
            c.makeSomeThingsWith(b);
            f.undo(d, e);
            return 9;
        } else {
            e++;
            return 3;
        }
    } else {
        return 1;
    }
} else {
    return 0;
}

с:

if(a != b) {
    return 0;
}

if(c > d) {
    return 1;
}

if(e != f) {
    e++;
    return 3;
}

a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;

Второй блок кода превращает вложенные ifоператоры в охранные предложения. Это уменьшает отступы и делает некоторые условия возврата более понятными («если a != b, тогда мы вернемся 0!»)


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

Мы могли бы написать первый блок кода вот так, чтобы сделать его более читабельным:

if(a != b) {
    return 0;
} else if(c > d) {
    return 1;
} else if(e != f) {
    e++;
    return 3;
} else {
    a.doSomeStuff();
    c.makeSomeThingsWith(b);
    f.undo(d, e);
    return 9;
}

Этот код эквивалентен обоим вышеупомянутым блокам, но он представляет некоторые проблемы. Предложение elseздесь соединяет эти операторы if вместе. В приведенной выше версии статьи об охране пункты охраны не зависят друг от друга. Добавление нового означает просто написание нового ifмежду последней ifи остальной логикой. Здесь это тоже можно сделать, лишь с небольшим увеличением когнитивной нагрузки. Разница, однако, заключается в том, что перед новым предложением защиты должна появиться некоторая дополнительная логика. Когда заявления были независимыми, мы могли сделать:

...
if(e != f) {
    e++;
    return 3;
}

g.doSomeLogicWith(a);

if(!g.isGood()) {
    return 4;
}

a.doSomeStuff();
...

С подключенным if/else цепью это сделать не так просто. На самом деле, самый простой путь - это разорвать цепочку, чтобы она выглядела скорее как версия предложения гвардии.

Но на самом деле это имеет смысл. Использование if/elseслабо подразумевает связь между частями. Мы могли бы предположить , что a != bкакое - то образом связаны с c > dв if/elseцепи , версии. Это предположение будет вводить в заблуждение, как мы можем видеть из версии пункта охраны, что на самом деле они не связаны между собой. Это означает, что мы можем делать больше с независимыми предложениями, например, переупорядочивать их (возможно, для оптимизации производительности запросов или для улучшения логического потока). Мы также можем когнитивно игнорировать их, как только мы их пройдем. В if/elseцепочке я менее уверен, что отношения эквивалентности между aиb не всплывет позже.

Отношения, подразумеваемые посредством else, также очевидны в глубоко вложенном примере кода, но по-другому. Операторы elseотделены от условных, к которым они прикреплены, и они связаны в обратном порядке с условными (первое elseприсоединено к последнему if, последнее elseприсоединено к первому if). Это создает когнитивный диссонанс, когда мы вынуждены возвращаться назад по коду, пытаясь выровнять отступы в нашем мозгу. Это все равно что пытаться сопоставить кучу скобок. Будет еще хуже, если отступ будет неправильным. Дополнительные скобки тоже не помогают.

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

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

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

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

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

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color == Color.Blue)
    {
        return true;
    }

    if(sky.Color == Color.Black)
    {
        return true;
    }

    return false;
}

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

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


1
Мое редактирование вопроса касается этого случая. Когда ограничение, которое должно быть логически выполнено для всего следующего кода, такого как нулевая проверка (или любые другие средства защиты), я согласен, что пропуск elseблока важен для читабельности.
Селали Адобор

1
Ваша первая версия трудна для понимания, но я не думаю, что это необходимо при использовании if / else. Посмотрите, как я написал бы это: gist.github.com/winstonewert/c9e0955bc22ce44930fb .
Уинстон Эверт

@WinstonEwert Смотрите изменения для ответа на ваши комментарии.
cbojar

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

0

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

void DoOneOfTwoThings(bool which)
{
    if (which)
        DoThingOne();
    else
        DoThingTwo();
}

... более читабельно, чем ...

void DoOneOfTwoThings(bool which)
{
    if (which) {
        DoThingOne();
        return;
    }
    DoThingTwo();
}

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

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input)) {
        ProcessInputOfTypeTwo(input);
        return;
    }
    ProcessInputDefinitelyOfTypeOne(input);

}

... более читабельно, чем ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input))
        ProcessInputOfTypeTwo(input);
    else
        ProcessInputDefinitelyOfTypeOne(input);
}

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


Возможно, лучше выбрать более конкретный пример для второго случая. Моя немедленная реакция на это: «Это не может быть настолько ненормально, если вы все равно продолжите и обработаете это».
Уинстон Эверт

@WinstonEwert ненормальный , возможно, неправильный термин. Но я согласен с Заком, что если у вас есть дефолт (case1) и одно исключение, оно должно быть записано как » if-> Делать исключение и оставить« в качестве стратегии возврата-досрочного возврата . Подумайте о коде, в котором по умолчанию включено больше проверок, и в первую очередь было бы быстрее обработать исключительный случай.
Томас Джанк

Это похоже на случай, с которым я говорил when using an if block to apply a constraint that must be logically satisfied for all following code, such as a null-check (or any other guards). По логике, любая работа ProcessInputOfTypeOneзависит от того факта, что !InputIsReallyTypeTwo(input)нам нужно уловить это за пределами нашей обычной обработки. Обычно ProcessInputOfTypeTwo(input);это было throw ICan'tProccessThatExceptionбы так, что сходство стало бы более очевидным.
Селали Адобор

@WinstonEwert Возможно, я бы лучше сформулировал это, да. Я думал о ситуации, которая часто возникает при ручном кодировании токенизаторов: в CSS, например, последовательность символов " u+" обычно вводит токен "диапазон юникода", но если следующий символ не является шестнадцатеричной цифрой, тогда нужно сделать резервную копию и обработать 'u' вместо идентификатора. Таким образом, основной цикл сканера отправляет «сканировать токен диапазона Юникода» несколько неточно, и он начинается с «... если мы находимся в этом необычном особом случае, сделайте резервную копию, а затем вызовите подпрограмму сканирования идентификатора».
Звол

@AssortedTrailmix Даже если пример, о котором я думал, был не из устаревшей кодовой базы, в которой исключения не могут быть использованы, это не условие ошибки , просто код, который вызывает ProcessInputOfTypeOneнеточную, но быструю проверку, которая иногда вместо этого ловит тип два
Звол

0

Да, сложность возрастает, поскольку предложение else является избыточным . Таким образом, лучше оставить этот код более скудным и подлым. Это соответствует тому же принципу, что и исключение избыточных thisквалификаторов (Java, C ++, C #), исключение скобок в returnоператорах и т. Д.

Хороший программист думает "что я могу добавить?" в то время как великий программист думает «что я могу удалить?».


1
Когда я вижу упущение elseблока, если оно объясняется в одной строке (что не часто), это часто с такого рода рассуждениями, поэтому +1 для представления аргумента «по умолчанию» я вижу, но я не посчитайте это достаточно сильным рассуждением. A good programmer things "what can I add?" while a great programmer things "what can I remove?".Кажется, это обычная поговорка о том, что многие люди растягиваются без тщательного рассмотрения, и я лично считаю, что это один из таких случаев.
Селали Адобор

Да, читая твой вопрос, я сразу почувствовал, что ты уже принял решение, но хотел подтверждения. Этот ответ, очевидно, не тот, который вы хотели, но иногда важно учитывать мнения, с которыми вы не согласны. Может быть, что-то тоже есть?
Виджет

-1

Я заметил, передумал по этому делу.

Когда мне задали этот вопрос около года назад, я бы ответил примерно так:

»Должен быть только один entryи один exitметод« И с учетом этого я бы предложил изменить код на:

bool CanLeaveWithoutUmbrella()
{
    bool result

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    return result;
}

С точки зрения коммуникации понятно , что нужно делать. Ifчто-то имеет место, манипулируйте результатом одним способом , else манипулируйте им другим способом .

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

bool CanLeaveWithoutUmbrella()
{
    bool result=false

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    return result;
}

Тогда возникает вопрос: зачем вообще использовать временную переменную? Следующий шаг рефакторизации приводит к:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue)
    {
        return true;
    }
    return false;
}

Так что это ясно в том, что он делает. Я бы даже пошел еще дальше:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) return true;
    return false;
}

Или для слабонервных :

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) { return true; }
    return false;
}

Вы можете прочитать это так: »CanLeaveWithoutUmbrella возвращает bool . Если ничего особенного не происходит, возвращается false «Это первое чтение сверху вниз.

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

У меня сложилось впечатление, что остальное более прямо заявляет о намерениях, заявляя, что код в обоих блоках напрямую связан.

Да, именно так. Но эта информация избыточна . У вас есть случай по умолчанию и одно «исключение», которое покрываетсяif -statement.

Имея дело со сложными структурами, я бы выбрал другую стратегию, исключив ifили switchвообще уродливого брата .


+1 избыточность определенно показывает увеличение не только сложности, но и путаницы. Я знаю, что мне всегда приходилось перепроверять код, написанный таким образом, именно из-за избыточности.
dietbuddha

1
Не могли бы вы удалить дела после дела, начиная с него, So this is clear in what it does...и расширить его? (Предыдущие случаи также имеют сомнительное значение). Я говорю это, потому что это то, что задает вопрос, мы исследуем. Вы изложили свою позицию, опуская блок else, и на самом деле эта позиция нуждается в дополнительном объяснении (и та, которая заставила меня задать этот вопрос). Из вопроса:I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Селали Адобор

@AssortedTrailmix Конечно! Что именно я должен уточнить? Так как ваш первоначальный вопрос был «Могут ли еще блоки увеличивать сложность кода?», И мой ответ: да. В этом случае это может быть опущено.
Томас Джанк

И нет! Я не понимаю, отрицательный голос.
Томас Джанк

-1

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

return true;

Следовательно, если ifблок должен быть введен, вся остальная функция по определению не выполняется. Но если он не введен, поскольку дальнейших ifоператоров нет, вся остальная функция выполняется. Было бы совершенно иначе, если бы returnоператор не находился в ifблоке, в этом случае наличие или отсутствие elseблока оказало бы влияние, но здесь это не оказало бы влияния.

В своем вопросе, изменив свое ifусловие и пропустив else, вы заявили следующее:

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

Они должны читать код немного более внимательно. Мы используем языки высокого уровня и четкую организацию кода, чтобы смягчить эти проблемы, но добавление полностью избыточного elseблока добавляет «шум» и «беспорядок» в код, и нам также не нужно инвертировать или ревертировать наши ifусловия. Если они не могут понять разницу, то они не знают, что делают. Если они могут заметить разницу, то они всегда могут инвертировать исходные ifусловия сами.

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

Это, по сути, то, что здесь происходит, хотя. Вы возвращаетесь из ifблока, так что ifоценка условия false является ограничением, которое должно быть логически выполнено для всего следующего кода.

Есть ли увеличение сложности, вызванное отсутствием блока else?

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


2
«Они должны читать код чуть более внимательно». Стиль кодирования существует, так что программисты могут уделять меньше внимания мелочам и больше внимания тому, что они на самом деле делают. Если ваш стиль заставляет вас обращать внимание на эти детали без необходимости, это плохой стиль. «... совершенно бесполезные линии ...» Они не бесполезны - они позволяют вам сразу увидеть, что такое структура, без необходимости тратить время на рассуждения о том, что произойдет, если эта часть или эта часть будет выполнена.
Майкл Шоу

@MichaelShaw Я думаю, что ответ Авива Кона входит в то, о чем я говорю.
Panzercrisis

-2

В приведенном вами конкретном примере это так.

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

Я думаю, что это не может быть проще, чем это:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

6
Я специально обращаю внимание на тот факт, что этот очень упрощенный пример можно переписать, чтобы удалить ifутверждение. На самом деле я явно не рекомендую дальнейшее упрощение, используя именно тот код, который вы использовали. Кроме того, цикломатическая сложность не увеличивается elseблоком.
Селали Адобор

-4

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

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

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

Во-первых, у вас есть вопрос о elseфилиале. Я не против в любом случае, но, как правило, опускаю else. Я понимаю аргумент о явной явности, но мое мнение таково, что ifоператоры должны заключать в себе «необязательные» ветки, например, return trueодну. Я бы не назвал return falseветку «необязательной», так как она действует по умолчанию. В любом случае, я считаю это очень незначительной проблемой, и гораздо менее важной, чем следующие:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        return false;
    }
}

Гораздо более важный вопрос заключается в том , что ваши филиалы делают слишком много! Ветви, как и все, должны быть «максимально простыми, но не более». В этом случае вы использовали ifоператор, который разветвляется между блоками кода (коллекциями операторов), когда все, что вам действительно нужно для разветвления, это выражения (значения). Это понятно, так как а) ваши блоки кода содержат только отдельные операторы и б) они являются одним и тем же оператором ( return). Мы можем использовать троичный оператор, ?:чтобы сузить ветвь только до той части, которая отличается:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue)
            ? true
            : false;
    }
}

Теперь мы достигли очевидной избыточности, с которой наш результат идентичен this.color == Color.Blue. Я знаю, что ваш вопрос просит нас игнорировать это, но я думаю, что очень важно указать, что это процедурный образ мышления первого порядка: вы разбиваете входные значения и создаете некоторые выходные значения. Это хорошо, но, если возможно, гораздо эффективнее мыслить в терминах отношений или преобразований между входом и выходом. В этом случае отношения очевидно, что они одинаковы, но я часто вижу такой сложный процедурный код, который затемняет некоторые простые отношения. Давайте продолжим и сделаем это упрощение:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue);
    }
}

Следующая вещь , которую я бы указать на то , что ваш API содержит двойной негатив: это возможно , CanLeaveWithoutUmbrellaчтобы быть false. Это, конечно, упрощает NeedUmbrellaбыть true, поэтому давайте сделаем это:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool NeedUmbrella()
    {
        return (this.color != Color.Blue);
    }
}

Теперь мы можем начать думать о вашем стиле кодирования. Похоже, вы используете объектно-ориентированный язык, но, используя ifоператоры для перехода между блоками кода, вы в итоге написали процедурный код.

В объектно-ориентированном коде все блоки кода являются методами, и все ветвления выполняются с помощью динамической диспетчеризации , например. используя классы или прототипы. Можно спорить, упрощает ли это вещи, но это должно сделать ваш код более совместимым с остальной частью языка:

class Sky {
    bool NeedUmbrella()
    {
        return true;
    }
}

class BlueSky extends Sky {
    bool NeedUmbrella()
    {
        return false;
    }
}

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


Первый абзац ответа, кажется, находится на пути к ответу на вопрос, но он сразу расходится с точной темой, которую я явно прошу игнорировать для этого очень простого примера . От вопроса: I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.). На самом деле вы используете точную строку кода, которую я упоминаю. Кроме того, хотя эта часть вопроса не по теме, я бы сказал, что вы слишком далеко зашли в своем заключении. Вы радикально изменили код.
Селали Адобор

@AssortedTrailmix Это чисто вопрос стиля. Имеет смысл заботиться о стиле и имеет смысл игнорировать его (сосредоточив внимание только на результатах). Я не думаю, что имеет смысл заботиться о return false;vs else { return false; }, не заботясь о полярности ветвей, динамической диспетчеризации, троичных и т. Д. Если вы пишете процедурный код на языке OO, радикальные изменения необходимы;)
Warbo

В общем, вы можете сказать что-то подобное, но это не применимо, когда вы отвечаете на вопрос с четко определенными параметрами (особенно когда без соблюдения этих параметров вы можете «абстрагироваться» от всего вопроса). Я также сказал бы, что последнее предложение просто неправильно. ООП - это «процедурная абстракция», а не «процедурная облитерация». Я бы сказал, что тот факт, что вы перешли от одного лайнера к бесконечному количеству классов (по одному на каждый цвет), показывает, почему. Кроме того, мне все еще интересно, как динамическая диспетчеризация должна быть связана с if/elseоператором и какова полярность ветвей.
Селали Адобор

Я никогда не говорил, что ОО-решение лучше (на самом деле я предпочитаю функциональное программирование), я просто сказал, что оно больше соответствует языку. Под «полярностью» я понимал, какая вещь «истинная», а какая «ложная» (я переключил ее с помощью «NeedUmbrella»). В ОО-дизайне весь поток управления определяется динамическим распределением. Например, Smalltalk больше ничего не разрешает en.wikipedia.org/wiki/Smalltalk#Control_structures (также см. C2.com/cgi/wiki?HeInventedTheTerm )
Warbo

2
Я бы проголосовал за это, пока не добрался до самого последнего параграфа (о ОО), который вместо этого заработал понижательную оценку! Это именно тот тип бездумного чрезмерного использования конструкций ОО, который генерирует код равиоли , который так же нечитаем, как спагетти.
Звол
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.