5 навика, които трябва да избягвате и да получите по-чист код в бъдеще

„Имам колеги, които не знаят, че Optional съществува.“ — „един разработчик на Java в Reddit.“

Използваме Optional от 8 години. Все пак хората имат проблеми с използването на Optional.

Трябва ли да използваме или ofNullable с orElseThrow? Използваме ли ofNullable по грешен начин? Какъв е случаят на използване на Optional flatMap?

Нека се потопим в 5 практики, които трябва да избягвате с Optional.

1. Не валидирайте инварианти с опция

Валидирането не трябва да се извършва с Optional.

Вместо валидиращо изключение, те искат празното Optional. И това не е случаят на използване на този инструмент. Трябва да направите валидирането предварително и да хвърлите изключението.

Оставете Optional просто като индикатор за отсъствие.

1 Optional<Foo> getMeFoo(String fooId) {
2    var fooIdValid = validateFooId(fooId);
3    if(!fooIdValid) { throw new FooIdInvalidException();}
4    
5    return fooService.getFooById(fooID);
}

Не връщате празното Optional в частта за валидиране. Връщате Optional само ако отсъства от fooService.

Ако върнете празно Optional на ред 2, ще имате неяснота.

Ще имате два случая, показани с една стойност. И това със сигурност не е това, което Optional трябваше да направи на първо място.

Ако не искате да продължите без тази стойност, можете да хвърлите изключение. Ето как би изглеждало това:

Foo getMeFoo(String fooId) throws IllegalStateException {
    var fooIdValid = validateFooId(fooId);
    if(!fooIdValid) { throw new FooIdInvalidException();}
       return fooService.getFooById(fooID)
       .orElseThrow(() ->  new IllegalStateException());}
}

Друг пример, който може да видите, е празен Optional при изключения.

Не е добра практика, тъй като използвате празно Optional до swallow грешката. Този код прави тихи грешки и може да ви обърка в бъдеще.

Какво ще кажете за празни уловки? За този сценарий можем да върнем празна опция, тъй като тя ще маркира правилно непроблемния.

0 следният сценарий може да означава две неща —възниква грешка или нулата има смисъл.

С празно Optional получаваме единственото значение отзад и това е отсъстваща стойност.

2. flatMap помага много с вложените Optional

Повечето разработчици използват карти и това е добре в повечето случаи. Но ако трябва да картографирате Optional към друг Optional, ще имате ненужна обвивка.

Какъв е добър кандидат за flatMap?

Ако и входът, и изходът на функцията за картографиране са Optional. Например Optional се преминава през картографи и се сравнява по пътя. В крайна сметка щяхме да получим едно Optional и без обвивка.

Друг случай на използване би бил последователността на множество операции заедно.

Нека имаме множество операции, които изчисляват въз основа на един аргумент. И те зависят един от друг, но резултатите не са задължителни.

Optional<Integer> calcAs(int a) {return Optional.of(a+1);}
jshell> Optional.of(1).flatMap(a -> calcAs(a).flatMap(b -> calcAs(b)))
$6 ==> Optional[3]
jshell> Optional.of(1).map(a -> calcAs(a).map(b -> calcAs(b)))
$7 ==> Optional[Optional[Optional[3]]]

Ето разликата, която ще получите.

3. Не смесвайте orElseThrow с ofNullable

Използването на ofNullable и orElseThrow е безсмислено. Вместо това можете да направите проста нулева проверка.

Както се вижда в предишния пример, вие получавате опция и разрешавате с orElseThrow. Не се връзват ofNullable и orElseThrow.

Този проблем възниква, защото разработчиците използват опция като нулева проверка. По избор ще хвърли NPE, ако стойността е нула. Това не означава, че трябва да го използвате вместо нулева проверка.

Ако можете да избегнете orElseThrow, това също е добра практика.

След това бихте гледали на Optionals като на обикновен инструмент за по-нататъшно картографиране. И избягвайте чертата value!=null по избор. По-голяма стойност се крие в картографирането и функционалните черти, активирани по избор.

getMeFoo("123").map(Foo::getXProp).orElse(DEFAULT_VALUE)

4. Не използвайте Optionals за полета за запис

По избор е предназначен за връщани стойности. Не като обектни полета.

По избор не може да се сериализира, така че използването в записи няма смисъл. Записите са носители на данни, така че не трябва да добавяме несериализирани опции.

Някои биха объркали опцията като изключение в записите. Но това не е така.

Дори и в бъдеще незадължителното ще си остане незадължително. Valhalla няма да позволи използването по избор като поле. Незадължителен в бъдеще ще бъде клас, базиран на стойност, но все пак имайте Незадължителните характеристики.

Само примитивните опции могат да бъдат сериализирани. Но ще изчакаме Valhalla, за да видим как ще работи това. В края на краищата те ще трябва да изпълнят обещанието за „Кодове като клас (по избор), работи като int.“

Можем ли да направим опция за сериализиране?

Николай разглежда „проксито за сериализиране по избор“. Въпреки това, ние трябва да напишем наша собствена сериализация, а не нещо, което Java поддържа от кутията.

Вместо това можете да използвате „Guava’s Optional“. Тази опция може да се сериализира в сравнение с опцията на Java. Въпреки това, хората от Guava отново ще ви „насочат към официалния вариант на Java“.

Въпросът е да се избягват Optional полета в записите. Това е същото като да поставите null като поле на запис. И това не е Optional случай на употреба.

5. Вземете Optional стойност в orElseGet

Как бихте получили Optional в рамките на orElseGet?

Следното не е възможно:

Понастоящем за orElseGet можем да добавим доставчик от типа върната стойност. Така че няма поддръжка за добавяне на Optional доставчици.

Вместо това можем да използваме Optional#or). Този оператор ни позволява да върнем Optional вместо някаква стойност. Въпреки това този оператор е достъпен само за Java 9 и по-нови версии. За по-ниски версии ще трябва да нанесете стойността на Незадължително.

Какво можем да видим от предишните решения?

По-новите версии на Java решават много проблеми за Optional. Java 9 и 10 добавят много нови методи за Optional, така че надграждането ще разреши много от вашите проблеми.

Guava Optional има or метод за по-ниски версии на Java, ако това е, което ви трябва. Въпреки това, внимавайте, че Guava Optional и Java's Optional не са взаимозаменяеми. Без Guava's Optional вместо това ще ви трябва следното:

thisOptional.isPresent() ? thisOptional : secondChoice

Знаете ли някаква незадължителна практика, която причинява терсер код? Или да доведе до по-малко проблеми?

Уведомете ни в коментарите.