Я часто сталкиваюсь с кодом, который должен выполнять множество проверок и в конечном итоге получает отступ как минимум на пять или шесть уровней, прежде чем что-либо делать. Мне интересно, какие существуют альтернативы.
Ниже я опубликовал пример того, о чем я говорю (это не настоящий производственный код, а просто то, что я придумал в голову).
public String myFunc(SomeClass input)
{
Object output = null;
if (input != null)
{
SomeClass2 obj2 = input.getSomeClass2();
if (obj2 != null)
{
SomeClass3 obj3 = obj2.getSomeClass3();
if (obj3 != null && !BAD_OBJECT.equals(obj3.getSomeProperty()))
{
SomeClass4 = obj3.getSomeClass4();
if (obj4 != null)
{
int myVal = obj4.getSomeValue();
if (BAD_VALUE != myVal)
{
String message = this.getMessage(myVal);
if (MIN_VALUE <= message.length() &&
message.length() <= MAX_VALUE)
{
//now actually do stuff!
message = result_of_stuff_actually_done;
}
}
}
}
}
}
return output;
}





Для получения справки см. Код стрелки сглаживания.
- Replace conditions with guard clauses.
- Decompose conditional blocks into seperate functions.
- Convert negative checks into positive checks.
Вернуться раньше:
if (input == null) {
return output;
}
Вы можете избавиться от некоторой вложенности, используя защитные предложения.
public String myFunc(SomeClass input)
{
Object output = null;
if (input == null) return "";
SomeClass2 obj2 = input.getSomeClass2();
if (obj2 == null) return "";
SomeClass3 obj3 = obj2.getSomeClass3();
if (obj3 == null || BAD_OBJECT.equals(obj3.getSomeProperty()))
{
return "";
}
SomeClass4 = obj3.getSomeClass4();
if (obj4 == null) return "";
int myVal = obj4.getSomeValue();
if (BAD_VALUE == myVal) return "";
String message = this.getMessage(myVal);
if (MIN_VALUE <= message.length() &&
message.length() <= MAX_VALUE)
{
//now actually do stuff!
message = result_of_stuff_actually_done;
}
return output;
}
Однако измените все операторы return "";, которые я использовал для иллюстрации точки, на операторы, которые вызывают описательную разновидность Exception.
Да, вы можете удалить отступы следующим образом:
Обычно проверки выполняются последовательно и сравнивают с ошибкой, а не с успехом. Он удаляет вложенность и упрощает отслеживание (IMO).
public String myFunc(SomeClass input)
{
Object output = null;
if (input == null)
{
return null;
}
SomeClass2 obj2 = input.getSomeClass2();
if (obj2 == null)
{
return null;
}
SomeClass3 obj3 = obj2.getSomeClass3();
if (obj3 == null || BAD_OBJECT.equals(obj3.getSomeProperty()))
{
return null;
}
SomeClass4 = obj3.getSomeClass4();
if (obj4 == null)
{
return null;
}
int myVal = obj4.getSomeValue();
if (BAD_VALUE == myVal)
{
return null;
}
String message = this.getMessage(myVal);
if (MIN_VALUE <= message.length() &&
message.length() <= MAX_VALUE)
{
//now actually do stuff!
message = result_of_stuff_actually_done;
}
return output;
}
Вы предполагаете, что я знал, что такое закон Де Моргана :) На самом деле, я оставил его таким, каким он был исключительно из-за личных предпочтений. В моей голове было больше смысла читать это таким образом.
Если вам не нужно останавливать процесс, не вставляйте.
Например, вы можете:
if (input == null && input.getSomeClass2() == null && ...)
return null;
// Do what you want.
Предполагая, что вы используете такой язык, как Java, который упорядочивает условные выражения.
В качестве альтернативы вы можете:
if (input == null && input.getSomeClass2() == null)
return null;
SomeClass2 obj2 = input.getSomeClass2();
if (obj2 == null)
return null;
...
// Do what you want.
Для более сложных случаев.
Идея состоит в том, чтобы вернуться из метода, если вам не нужно обрабатывать. Встраивание в большой вложенный if практически невозможно прочитать.
если это просто проблема с удобочитаемостью, вы можете сделать ее более понятной, переместив вложение на другой метод. Дополнительно преобразуйте его в стиль защиты, если хотите.
public String myFunc(SomeClass input)
{
Object output = null;
if (inputIsValid(input))
{
//now actually do stuff!
message = result_of_stuff_actually_done;
}
return output;
}
private bool inputIsValid(SomeClass input)
{
// *****************************************
// convert these to guard style if you like
// *****************************************
if (input != null)
{
SomeClass2 obj2 = input.getSomeClass2();
if (obj2 != null)
{
SomeClass3 obj3 = obj2.getSomeClass3();
if (obj3 != null && !BAD_OBJECT.equals(obj3.getSomeProperty()))
{
SomeClass4 = obj3.getSomeClass4();
if (obj4 != null)
{
int myVal = obj4.getSomeValue();
if (BAD_VALUE != myVal)
{
String message = this.getMessage(myVal);
if (MIN_VALUE <= message.length() &&
message.length() <= MAX_VALUE)
{
return true;
}
}
}
}
}
}
return false;
}
И, пожалуйста, никогда не пишите так (если вы не поддерживаете свой собственный код)
Мне приходилось поддерживать такой код, и он такой же ужасный, как фильм Charles_Bronsonn (хотя некоторым нравятся эти фильмы)
Этот вид кода обычно исходит из процедурных языков, таких как C (процедурный C: P) В любом случае.
Это стало причиной того, что ObjectOrientedProgrammng стало мейнстримом. Он позволяет создавать объекты и добавлять к ним состояние. Создавайте операции с этим состоянием. Они не только собственники.
Я знаю, что вы придумали этот сценарий, но в большинстве случаев все эти условия - бизнес правила!!. В большинстве случаев эти правила МЕНЯЮТСЯ, и если первоначальный разработчик больше не работает (или прошло уже несколько месяцев), не будет реального способа изменить этот код. Правила неудобно читать. И от этого возникает много боли.
1.) Сохраняйте состояние объекта ВНУТРИ объекта, используя переменные-члены частный (атрибуты AKA, свойства, переменные экземпляров и т. д.)
2.) Сделайте методы закрытыми (для этого предназначен этот уровень доступа), чтобы никто не мог вызвать их по ошибке и поместить программу в область NullPointerException.
3.) Создайте методы, определяющие состояние. Это то, что они называют самодокументирующийся код
Так что вместо
// validates the user has amount
if ( amount > other && that != var || startsAligned() != false ) {
}
Создать метод
if ( isValidAmount() ) {
}
private boolean isValidAmount() {
return ( amount > other && that != var || startsAligned() != false );
}
Я знаю, что это выглядит многословно, но позволяет человеку читать код. Компилятор не заботится о читабельности.
Итак, как бы это выглядело при таком подходе?
Нравится.
// these are business rules
// then it should be clear that those rules are
// and what they do.
// internal state of the object.
private SomeClass2 obj2;
private SomeClass3 obj3;
private SomeClass4 obj4;
//public String myFunc( SomeClass input ) {
public String myComplicatedValidation( SomeClass input ) {
this.input = input;
if ( isValidInput() &&
isRuleTwoReady() &&
isRuleTreeDifferentOf( BAD_OBJECT ) &&
isRuleFourDifferentOf( BAD_VALUE ) &&
isMessageLengthInRenge( MIN_VALUE , MAX_VALUE ) ) {
message = resultOfStuffActuallyDone();
}
}
// These method names are self explaining what they do.
private final boolean isValidInput() {
return this.input != null;
}
private final boolean isRuleTwoReady() {
obj2 = input.getSomeClass2();
return obj2 != null ;
}
private final boolean isRuleTreeDifferentOf( Object badObject ) {
obj3 = obj2.getSomeClass3();
return obj3 != null && !badObject.equals( obj3.getSomeProperty() );
}
private final boolean isRuleFourDifferentOf( int badValue ) {
obj4 = obj3.getSomeClass4();
return obj4 != null && obj4.getSomeValue() != badValue;
}
private final boolean isMessageLengthInRenge( int min, int max ) {
String message = getMessage( obj4.getSomeValue() );
int length = message.length();
return length >= min && length <= max;
}
Я знаю, похоже, больше кода. Но подумайте об этом. Правила почти удобочитаемы
if ( isValidInput() &&
isRuleTwoReady() &&
isRuleTreeDifferentOf( BAD_OBJECT ) &&
isRuleFourDifferentOf( BAD_VALUE ) &&
isMessageLengthInRenge( MIN_VALUE , MAX_VALUE ) ) {
message = resultOfStuffActuallyDone();
}
Можно почти прочитать как
if is valid input
and rule two is ready
and rule three is not BAD OBJECT
and rule four is no BAD_VALUE
and the message length is in range
И если правила будут небольшими, кодировщик может очень легко понять их и не бояться что-то затормозить.
Подробнее об этом можно прочитать по адресу: http://www.refactoring.com/.
Почему вы остановились на тесте if? Его также можно превратить в скорейшее возвращение, применив закон Де Моргана, чтобы перевернуть смысл составного теста.