Стиль программирования: должны ли вы вернуться раньше, если условие охраны не выполняется?
одна вещь, которую я иногда задавался вопросом, - Это лучший стиль из двух, показанных ниже (если есть)? Лучше ли сразу вернуться, если условие охраны не было выполнено, или вы должны делать только другие вещи, если условие охраны и устраивает?
для аргумента предположим, что условие guard является простым тестом, который возвращает логическое значение, например, проверяет, находится ли элемент в коллекции, а не что-то, что может влияет на поток управления, вызывая исключение. Также предположим, что методы / функции достаточно короткие, чтобы не требовать прокрутки редактора.
// 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 ответов
Я предпочитаю первый стиль, за исключением того, что я бы не создавал переменную, когда в ней нет необходимости. Я бы сделал так:
// Style 3
public SomeType aMethod() {
if (!guardCondition()) {
return null;
}
SomeType result = new SomeType();
doStuffToResult(result);
doMoreStuffToResult(result);
return result;
}
за последние несколько лет я понял, что код, написанный в этом стиле, часто слишком сложен и трудно читается/поддерживается, и я переключился на стиль 1.
кто сказал, что старые собаки не могут научиться новым трюкам? ;)
стиль 1-это то, что косвенно рекомендует ядро Linux.
от http://www.kernel.org/doc/Documentation/CodingStyle, Глава 1:
теперь некоторые люди будут утверждать, что наличие 8-символьных отступов делает код перемещается слишком далеко вправо и затрудняет чтение на 80-символьный экран терминала. Ответ на этот вопрос таков:если вам нужно больше чем 3 уровня вмятия, вы привинчены в любом случае, и должны исправить ваш программа.
стиль 2 добавляет уровни отступов, следовательно, он обескуражен.
лично мне нравится стиль 1. Стиль 2 затрудняет сопоставление закрывающих скобок в функциях, которые имеют несколько тестов защиты.
Я не знаю, если гвардии здесь правильное слово. Обычно неудовлетворенный охранник приводит к исключению или утверждению.
Но кроме этого ... --7-->Я бы пошел со стилем 1, потому что он держит код чище, на мой взгляд. У вас есть простой пример с одним условием. Но что происходит со многими условиями и типе 2? Это приводит к большому количеству вложенных if
s или огромные if-условия (с ||
, &&
). Я думаю, что лучше вернуться от метода, как только ты знаешь, что можешь.
Но это конечно очень субъективно ^^
иногда это зависит от языка и того, какие "ресурсы" вы используете (например, открытые дескрипторы файлов).
В C стиль 2 определенно безопаснее и удобнее, потому что функция должна закрывать и/или освобождать любые ресурсы, полученные во время выполнения. Это включает выделенные блоки памяти, дескрипторы файлов, дескрипторы ресурсов операционной системы, таких как потоки или контексты рисования, блокировки мьютексов и многое другое. Задержка return
пока очень конец или иным образом ограничение числа выходов из функции позволяет программисту более легко убедиться, что он/она правильно очищает, помогая предотвратить утечки памяти, обрабатывать утечки, тупик и другие проблемы.
в C++ с помощью RAII - стиль программирования, оба стиля одинаково безопасны, поэтому вы можете выбрать более удобный. Лично я использую стиль 1 с RAII-style C++. C++ без RAII похож на C, поэтому, опять же, стиль 2, вероятно, лучше в этом случай.
в таких языках, как Java с сборкой мусора, среда выполнения помогает сгладить различия между двумя стилями, потому что она очищается после себя. Однако с этими языками также могут быть тонкие проблемы, если вы явно не" закрываете " некоторые типы объектов. Например, если вы создадите новый java.io.FileOutputStream
и не закрыть это перед возвращением, то связанный дескриптор операционной системы останется открытым, пока мусор среды выполнения не соберет FileOutputStream
экземпляр, который выпал из области видимости. Это может означать, что другой процесс или поток, который должен открыть файл для записи, может быть не в состоянии до FileOutputStream
экземпляр собирается.
Если вы копаетесь в .net-Framework с помощью .net-Reflector, вы увидите, что программисты .net используют стиль 1 (или, возможно, стиль 3, уже упомянутый unbeli). Причины этого уже упоминались в ответах выше. и, возможно, еще одна причина-сделать код более читаемым, кратким и понятным. чаще всего этот стиль используется при проверке входных параметров, вам всегда нужно это делать, если вы программируете своего рода frawework/library/dll. первый проверить все входные параметры, чем работать с их.
Мартин Фаулер рефакторинг относится к этому как : "заменить вложенные условные предложения Guard"
операторы If/else также приносят цикломатическую сложность. Поэтому труднее тестировать случаи. Чтобы проверить все блоки if/else, вам может потребоваться ввести множество опций.
где, как будто есть какие-либо предложения guard, вы можете сначала проверить их и разобраться с реальной логикой внутри предложений if/else более ясным образом.
хотя это идет вразрез с лучшими практиками, которым меня учили, я считаю, что гораздо лучше уменьшить вложенность утверждений if, когда у меня есть такое условие. Я думаю, что это намного проще читать, и хотя он выходит в нескольких местах, все еще очень легко отлаживать.
Я бы сказал, что Style1 стал более использоваться, потому что это лучшая практика, если вы объедините его с небольшими методами.
Style2 выглядит лучшим решением, когда у вас есть большие методы. Когда они у тебя будут ... у вас есть общий код, который вы хотите выполнить независимо от того, как вы выходите. Но правильное решение заключается не в том, чтобы заставить одну точку выхода, а сделать методы меньше.
например, если вы хотите извлечь последовательность кода из метода, и этот метод имеет два точки выхода у вас начинают возникать проблемы, это сложно сделать автоматически. Когда у меня есть большой метод, написанный в style1, я обычно преобразую его в style2, затем я извлекаю методы, тогда в каждом из них я должен иметь код Style1.
поэтому Style1 лучше всего, но совместим с небольшими методами. Style2 не так хорошо, но рекомендуется, если у вас есть большие методы, которые вы не хотите, есть время, чтобы разделить.
Я предпочитаю использовать способ #1 сам, логически легче читать, а также логически больше похоже на то, что мы пытаемся сделать. (если что-то плохое случится, выйдите из функции сейчас, не проходите go, не собирайте $200)
кроме того, большую часть времени вы хотели бы вернуть значение, которое не является логически возможным результатом (т. е. -1), чтобы указать пользователю, который вызвал функцию, что функция не смогла выполнить должным образом и принять соответствующие меры. Это лучше поддается и методу №1 тоже.
Я бы сказал: "Это зависит от..."
в ситуациях, когда мне нужно выполнить последовательность очистки с более чем 2 или 3 строками перед выходом из функции/метода, я бы предпочел стиль 2, потому что последовательность очистки должна быть записана и изменена только один раз. Это означает, что ремонтопригодность проще.
во всех остальных случаях я бы предпочел стиль 1.
номер 1, Как правило, простой, ленивый и небрежный способ. Номер 2 четко выражает логику. Другие отметили, что да, это может стать громоздким. Однако эта тенденция имеет важное преимущество. Стиль #1 может скрыть, что ваша функция, вероятно, делает слишком много. Это не наглядно демонстрирует сложность происходящего. Т. е. это мешает коду сказать вам: "Эй, это становится слишком сложным для этой одной функции". Это также делает его немного проще для других разработчиков, которые не знают ваш код, пропустить эти возвраты, разбрызганные здесь и там, на первый взгляд.
Так пусть код говорить. Когда вы видите длинные условия, появляющиеся или вложенные операторы if, это говорит о том, что, возможно, было бы лучше разбить этот материал на несколько функций или что его нужно переписать более элегантно.