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
Знаете ли някаква незадължителна практика, която причинява терсер код? Или да доведе до по-малко проблеми?
Уведомете ни в коментарите.