Стиль программирования: стоит ли возвращаться раньше, если условие охраны не выполняется? - PullRequest
30 голосов
/ 28 мая 2010

Одна вещь, которую я иногда задавался вопросом: какой стиль лучше из двух, показанных ниже (если есть)? Лучше ли вернуться немедленно, если условие охраны не было выполнено, или вы должны выполнять другие действия, только если условие охраны удовлетворено ?

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

// Style 1
public SomeType aMethod() {
  SomeType result = null;

  if (!guardCondition()) {
    return result;
  }

  doStuffToResult(result);
  doMoreStuffToResult(result);

  return result;
}

// Style 2
public SomeType aMethod() {
  SomeType result = null;

  if (guardCondition()) {
    doStuffToResult(result);
    doMoreStuffToResult(result);
  }

  return result;
}

Ответы [ 12 ]

37 голосов
/ 28 мая 2010

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

// Style 3
public SomeType aMethod() {

  if (!guardCondition()) {
    return null;
  }

  SomeType result = new SomeType();
  doStuffToResult(result);
  doMoreStuffToResult(result);

  return result;
}
27 голосов
/ 28 мая 2010

Будучи обученным Джексону в конце 80-х, моя укоренившаяся философия всегда заключалась в том, что «функция должна иметь одну точку входа и одну точку выхода»; это означало, что я написал код в соответствии со стилем 2.

За последние несколько лет я осознал, что код, написанный в этом стиле, часто слишком сложен и его трудно читать / поддерживать, и я перешел на Стиль 1.

Кто сказал, что старые собаки не могут выучить новые трюки? ;)

14 голосов
/ 28 мая 2010

Стиль 1 - это то, что ядро ​​Linux косвенно рекомендует.

С http://www.kernel.org/doc/Documentation/CodingStyle, глава 1:

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

Стиль 2 добавляет уровни отступов, поэтому он не рекомендуется.

Лично мне нравится стиль 1. Стиль 2 затрудняет сопоставление закрывающих скобок в функциях, которые имеют несколько защитных тестов.

6 голосов
/ 28 мая 2010

Я не знаю, является ли guard правильным словом здесь. Обычно неудовлетворенный охранник приводит к исключению или утверждению.
Но помимо этого я бы выбрал стиль 1 , потому что, на мой взгляд, он делает код чище. У вас есть простой пример только с одним условием. Но что происходит со многими условиями и стилем 2? Это приводит к множеству вложенных if с или огромным if-условиям (с ||, &&). Я думаю, что лучше вернуться из метода, как только вы узнаете, что можете.
Но это, безусловно, очень субъективно ^^

5 голосов
/ 18 ноября 2014

Мартин Фаулер именует этот рефакторинг как: «Заменить вложенные условные выражения на охранные»

Операторы if / else также приводят к цикломатической сложности. Отсюда сложнее тестировать кейсы. Чтобы протестировать все блоки if / else, вам может потребоваться ввести множество опций.

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

5 голосов
/ 28 мая 2010

Если вы покопаетесь в .net-Framework с помощью .net-Reflector, вы увидите, что .net-программисты используют стиль 1 (или, возможно, стиль 3, уже упомянутый unbeli).Причины уже упомянуты в ответах выше.и, возможно, еще одна причина - сделать код лучше читаемым, кратким и понятным.чаще всего этот стиль используется при проверке входных параметров, вы всегда должны делать это, если вы программируете что-то вроде frawework / library / dll.Сначала проверьте все входные параметры, чем работайте с ними.

4 голосов
/ 28 мая 2010

Иногда это зависит от языка и того, какие «ресурсы» вы используете (например, дескрипторы открытых файлов).

В C стиль 2 определенно безопаснее и удобнее, потому что функция должна закрывать и / или освобождать любые ресурсы, полученные во время выполнения. Это включает в себя выделенные блоки памяти, дескрипторы файлов, дескрипторы ресурсов операционной системы, таких как потоки или контексты рисования, блокировки мьютексов и любые другие вещи. Задержка return до самого конца или иное ограничение количества выходов из функции позволяет программисту легче гарантировать, что он / она правильно очищается, помогая предотвратить утечки памяти, обрабатывать утечки, взаимоблокировки и другие проблемы.

В C ++ с использованием RAII стилей программирования оба стиля одинаково безопасны, поэтому вы можете выбрать более удобный. Лично я использую Style 1 с RAII-стилем C ++. C ++ без RAII похож на C, поэтому, опять же, Style 2, вероятно, лучше в этом случае.

В таких языках, как Java с сборкой мусора, среда выполнения помогает сгладить различия между двумя стилями, потому что она убирает за собой. Однако и с этими языками могут возникнуть тонкие проблемы, если вы явно не «закроете» некоторые типы объектов. Например, если вы создаете новый java.io.FileOutputStream и не закрываете перед возвратом, соответствующий дескриптор операционной системы будет оставаться открытым, пока мусор во время выполнения не соберет экземпляр FileOutputStream, выпавший из области , Это может означать, что другой процесс или поток, которому необходимо открыть файл для записи, может быть не в состоянии, пока не будет собран экземпляр FileOutputStream.

3 голосов
/ 28 мая 2010

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

1 голос
/ 19 октября 2011

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

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

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

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

0 голосов
/ 28 мая 2010

Номер 1, как правило, легкий, ленивый и неаккуратный способ. Номер 2 выражает логику чисто. Другие отметили, что да, это может стать громоздким. Эта тенденция, тем не менее, имеет важное преимущество. Стиль № 1 может скрывать, что ваша функция, вероятно, выполняет слишком много. Это не наглядно демонстрирует сложность происходящего. То есть он препятствует тому, чтобы код говорил вам «эй, это становится слишком сложным для этой единственной функции». Это также облегчает задачу других разработчиков, которые не знают вашего кода, пропустить эти возвраты, разбросанные здесь и там, на первый взгляд в любом случае.

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

...