Это пятая и последняя статья серии, посвящённая использованию класса Optional при обработке объектов с динамической структурой. В первой статье было рассказано о способах избежания NullPointerException в ситуациях, когда вы не можете или не хотите использовать Optional. Вторая статья посвящена описанию методов класса Optional в том виде, как он появился в Java 8. Третья — методам, добавленным в класс в Java 9. В четвертой статье я представил класс, который расширяет возможности класса Optional на случай, когда при неудаче при обработке данных в методе нам необходимо обрабатывать информацию об этой неудаче.
В этой статье мы рассмотрим вопрос, стоит ли Optional использовать во всех тех местах, где могут появиться нулевые значения. Я приведу также мнение Brian Goetz, архитектора языка Java v Oracle об этом классе, и конечно, исполню данное в прошлой статье обещание — поощрю каждого читателя, прочитавшего все статьи серии, ценным подарком.
А ведь всё могло быть не так
Перед тем как мы перейдём к рассмотрению полезных с практической точки зрения вопросов использования Optional в разных ролях внутри наших классов, я хотел бы сделать одно маленькое, но с моей точки зрения необходимое, историческое отступление.
Когда проблема расширения Java элементами ФП (Функционального Программирования) назрела, спектр возможных решений представлялся очень широким. В максимальном варианте можно было бы попробовать расширить Java реализацией важнейших парадигм ФП, опробованные к тому времени в таких языках программирования как LISP, ML, Haskell, OCaml, F#, Erlang, Clojure и Scala.
Критики максималистского подхода предупреждали, что реализации некоторых парадигм ФП могут препятствовать ранее принятые при реализации языка архитектурные решения. Кроме того, они опасались, что введение некоторых слишком сложных для освоения парадигм приведёт к их слабому использованию. А это, в свою очередь, грозило тем, что и весь комплекс нововведений будет проигнорирован большинством пользователей Java.
Так или иначе, в конце концов победил подход, провозглашённый как «прагматический». В Java 8 появились только основные элементы ФП в виде потоков, функций, лямбд и Optional.
В рамках победившего подхода Optional в официальной документации не претендовал на роль одной из реализации парадигмы монады из ФП. (Не претендовать на роль, не значит не играть её, смотрите детали здесь. Вместо этого класс Optional позиционировался в официальной «разъясняющей» статье как контейнер для потенциально нулевых объектов.
А где его ещё применить?
А раз так, то некоторые пользователи задались естественным вопросом: Получается, везде где в классе может появиться null, надо теперь использовать Optional?
Упомянутая «разъясняющая» статья Oracle однозначно говорит — Нет!
А именно (в моём переводе):
«Назначение класса Optional не состоит в том, чтобы заменять собой каждую ссылку на потенциально нулевой объект. Целью является возможность разработки более понятных API так, чтобы, просто по сигнатуре метода определить, что возвращается Optional. Это заставляет вас активно анализировать Optional, чтобы разобраться с его значением"Пусть так. Но где, на каких «местах» или «ролях» в классе, можно было бы использовать Optional?
Optional как значение поля?
Давайте разберёмся конкретно и начнем с переменной (поля) класса. Посмотрим упомянутую статью. Там приводится вот такой код:
public class Computer {
private Optional<Soundcard> soundcard;
public Optional<Soundcard> getSoundcard() { ... }
...
}
Ага! Значит использовать Optional в качестве переменной класса можно?
Можно то можно, но вот что надо помнить:
- Класс Optional не реализует интерфей Serializeble. Это означает, что переменная не будет правильно сохранена или считана с носителя с помощью используемых это свойство фреймворков.
- Optional это так называемый value-based объект. Это подразумевает в частности особые правила сравнения инстанций объектов между собой, о чём можно в деталях почитать здесь. Это означает в свою очередь, что некоторые фреймворки, использующие внутри механизмы Reflection, могут неправильно обработать такие поля.
Таким образом, прежде чем использовать Optional как хранитель значения поля в вашем классе, убедитесь, что потенциальным пользователям вашего класса не придётся его сериализировать или обрабатывать с помощью не готовых к этому фраймворков.
Поэтому некоторые авторы советуют не связываться с этими потенциальными головными болями и хранить в полях вместо Optional сами объекты, помня при программировании класса, что они могут быть нулевыми. И только «выпуская» объект наружу в качестве возвращаемого значения помещать его в «футляр» Optional.
Использование Optional в setters
Ну а как с методами задания потенциально нулевых полей класса? Когда стоит предлагать в вашем API метод с сигнатурой типа setSomeObject(Optional<SomeObject> opt) а когда нет?
Очевидный случай — когда вы строите фасад вокруг существующего класса у которого есть метод setSomeObject(SomeObject obj) и obj может быть нулевым. В этом случае вы по крайней мере в покрывающем «фасадном» методе явно уведомляется пользователя о возможности использования нулевого объекта.
В оставшихся случаях почти всегда лучше создавать нормальный setter-метод с ограничением (и возможно проверкой), что передаваемый параметр не равен null. На этапе разработки такое ограничение можно проверять с помощью assert. Проверять ли его в продакштн и если да, то как — это отдельный интересный вопрос, выходящий за рамки нашего обсуждения.
Таким образом, вслед за многими авторами я призываю вас не предполагать и не допускать передачи null в setter-ах.
Как же так, воскликнет внимательный читатель. Во второй статье серии автор сам предлагал использовать вот такой интерфейс:
public interface IBoilerInput2 {
void setAvailability(@Nullable CupOfWater water, boolean powerAvailable);
}
с параметром water, который может принимать значение null, а теперь говорит что это плохо? Да. Именно так. Чтобы сконцентрировать ваше внимание на основном аспекте я в учебном примере позволил себе это. Но при разработке реальных API, которые могут использовать другие разработчики, такого надо избегать. Но как?
Давайте рассмотрим ситуацию внимательнее. Передача null в качестве значения параметра означает как правило изменение конфигурации объекта, отключение части его возможностей либо переключение его поведения в новый режим. Подберите соответствующий термин, описывающий такое переключение.
Упомянутый выше интерфейс преобразился бы тогда примерно так:
public interface IBoilerInputX {
void setWater(@Nonnull CupOfWater water);
void setWaterDisabled();
void setPowerAvailable( boolean powerAvailable);
}
Новый метод setWaterDisabled явно указывает на предстоящее изменение поведения прибора в отличие от предыдущего варианта в IBoilerInput2, где это достигалось вызовом параметра с нулевым значением.
Использование Optional в группе параметров
В некоторых случаях, однако, некоторую группу параметров нельзя по концептуальным или техническим соображениям разбить на отдельные пары из setter и переключателей и вам просто необходимо передать всю группу параметров в одном вызове. Очень часто такое случается с конструкторами класса. При этом некоторые параметры группы могут отсутствовать, быть неготовыми и т.д. — т.е. иметь значение null. В этом случае использование футляра Optional много лучше, чем передача потенциально нулевых параметров напрямую. Вероятность того, что какой-то по ошибке занулившийся в предыдущих вычислениях параметр попадёт внутрь вашего класса и вызовет потом NPE или неверное поведение, в этом случае существенно уменьшается.
Итоговые рекомендации
Подведем предварительные итоги в виде прагматических рекомендаций:
- Используйте Optional в setter или в методах с одним параметром (только) если строите фасад для метода явно предполагающего передачу параметра, который может принимать значение null.
- В большинстве случаев, если параметр может принимать нулевое значение, передавайте его через setter с ограничением на не-нулевое значение и сопутствующим методом переключения режима работы объекта. Внутри переключателя зануляйте значение соответствующего поля.
- Если ваш метод должен иметь много параметров, часть из которых могут принимать нулевые значения, передавайте такие параметры внутри футляра Optional. Это заставит клиента, вызывающего ваш метод явно разобраться с ситуацией перед «вкладыванием» объекта в футляр.
«Неполноценные» возвращаемые значения
В первой и второй статьях этой серии я долго убеждал читателей в том, что потенциально нулевые возвращаемые значения надо обязательно перед выпуском наружу упаковывать в футляр — Optional. Это всегда хорошо? К сожалению, где свет, там и тень.
Сложные объекты, особенно в энтерпрайзных системах представляют собой нередко агрегации или иерархии более простых объектов, которые доступны снаружи с помощью вызова getter-методов. То, что объект возвращает «пустой» объект (неважно, упакованный в футляр или нет) часто не означает, что внутри агрегата или иерархии такой объект отсутствует. В реальных системах это часто означает, что объект в каком-то смысле «неполноценен» либо конфигурация содержащего его агрегат-объекта не позволяет его использовать во внешнем мире. С этой точки зрения возращение Optional.empty() оправдано. Однако, если мы захотим с помощью внешнего framework (без использования Reflection или манипулирования байкодом) сохранить объект на внешнем носителе либо передать по сети, «неполноценность» объекта либо неподходящая конфигурация содержащего его агрегата не должны быть нам помехой. Получается, getter возвращающий футляр Optional нам в этом случае использовать нельзя. Как же так? За что боролись?
На самом деле всё стало только лучше. Да, нам придётся реализовывать специальные методы для персистирования частей большого агрегата. Но при этом их лучше собрать в один отдельный интерфейс, а методы реализации бизнес-логики в другой. Тем самым мы сможем разделить бизнес-аспект и технический аспект использования одного и того же объекта.
Optional вместо «магических» чисел
Аналогом null из мира объектов в мире чисел служат «магические» числа. Т.е. такие значения параметра, с которыми связана радикально иная семантика, чем с «нормальными» числами.
Даже стандартные библиотеки Java грешат этим. Документация к конструктору класса java.net.Socket:
public Socket(String host,
int port,
InetAddress localAddr,
int localPort)
throws IOException
говорит нам про параметр localPort, что если он задан ненулевым, система будет его использовать. А если значение параметра 0 — она поищет вам свободный порт.
Налицо мы имеем радикально разное поведение метода при ненулевом и нулевом значении параметра.
Чтобы в будущем можно было избегать подобных ситуаций, в Java 8 были введены классы OptionalInt и OptionalDouble. Используйте эти классы, если ваш метод должен возвращать некое число в случае успеха, но не-успех также возможен.
А вместо заключения — думайте сами!
Я посвятил пять статей описанию вариантов использования методов класса Optional. Думаю в Java не так много классов, которые вызывают столько вопросов и споров.
А кстати, уважаемые читатели, как вы думаете, сколько строк кода (не считая комментариев и вместе с ними) содержит этот класс?
Некоторые из статей этой серии породили весьма острые и интересные дискуссии. Но это даже не капля в море, а молекула капли по сравнению с дискуссиями и вопросами, появившемся в англоязычном секторе Интерната после появления Optional в Java 8. Одной из самых жарких и интересных дискуссий про Optional явилась вот эта: Should Java 8 getters return optional type?
В дискуссии принял участие и Brian Goetz, занимающий в Oracle позицию «Java Language Architect». Увидев, как Java-программисты используют его детище, вот что он написал в форуме (перевод мой):
Конечно, люди будут делать то, что хотят. Но у нас было четкое намерение при добавлении этой функциональности, и мы не ставили цель создать механизм наподобие Maybe или Some в других языках. Хотя многим этого возможно и хотелось. Наша цель заключалось в том, чтобы дать компактный механизм для определения типов возвращаемых значений с четким выделением ситуации «никакого результата», как альтернативу использованию нулевого значения, что в подавляющем большинстве случаев может вызвать ошибки.Другими словами, создатели языка не всегда расширяют его так как хотелось бы некоторым (продвинутым) пользователям. Пользователи не всегда используют новые возможности языка как задумывали их создатели. И создатели иногда жалеют о допущенных ошибках, которые уже не поправить.
Например, вы, вероятно, никогда не должны использовать его для чего-то, что возвращает массив результатов или список результатов; вместо этого возвратите пустой массив или список. Вы почти никогда не будете использовать его как поле чего-либо или параметр метода.
Я думаю, что его регулярное использование в качестве возвращаемого значения для геттеров определенно будет чрезмерным.
В Optional нет ничего плохого, из за чего его следует избегать. Это просто не то, что многие хотели бы видеть на его месте. И поэтому мы были весьма обеспокоены риском чрезмерного использования.
Обращение: НИКОГДА не вызывайте Optional.get, если вы не можете доказать, что он (в этом контексте) всегда не равен нулю. Вместо этого используйте один из безопасных методов, например orElse или ifPresent. В ретроспективе можно сказать, мы должны были бы вместо get предоставить что-то вроде getOrElseThrowNoSuchElementException, что сделало бы его использование более ясным. Но это — Lesson learned.
Так что, дорогие читатели, думайте сами, решайте сами, как применять Optional в ваших проектах. А мне остаётся надеяться, что эта серия статей и обещанный подарок — постер про Optional, помогут вам в этом.
Обратите внимание на названия групп методов класса слева. Они помогут вам быстрее найти нужный метод.
Девятка в кружке говорит о том, что метод доступен начиная с Java 9.
Последняя колонка освещает семантику параметров.
Семантика типов входных параметров (слева) и результатов (справа) позволит вам быстро вспомнить, что делает метод в случае если нулевого и не-нулевого значения параметра.
Например, для метода Optional.of():
(x, O)=>(O(x),?)
не-нулевой параметр x отобразится в Optional от него, что отображено как O(x),
а при попытке использовать нулевой входной параметр (обозначенный как O), вы получите Exception, обозначенное символом Йены: ?.
Комментарии (20)
visirok Автор
26.03.2018 22:47Спасибо за интересные соображения.
И всё-таки… класс Optional очень сильно отличается от очень многих других классов в Java. Тут начинается магия программирования. Аналогия с физикой. В уравнении Шрёдингера всего 8 символов, а про него написано много-много статей и книг. В классе Optional всего сто строк кода, а про него написаны уже сотни статей. Может тысячи.
И эта магия меняет менталитет. Я сам вначале его использовал очень осторожно, потом преувеличенно, а сейчас на основании описанных в статье рекомендаций.
Хотя разумеется, эти рекомендации надо адаптировать к условиям каждого конкретного проекта. Наприме, Вы абсолютно правы, в старый код вряд ли следует внедрять Optional.
mad_nazgul
27.03.2018 07:45М-да… законы Мерфи в действии. :-)
visirok Автор
27.03.2018 10:54Не разовьёте свою мысль? Что вы имеете в виду?
mad_nazgul
27.03.2018 11:45«Всё, что может пойти не так, пойдет не так.»
О чем и говорит цитата Brian Goetz.
Иначе говоря
«Хотели как лучше, а получилось как всегда»
:-)
visirok Автор
27.03.2018 12:06Понятно. Хотя в нашем случае ситуация несколько другая. Мэрфи писал скоре про отдельные проекты.
Проблема создателей продуктов для массового пользователя состоит в том, что трудно оценить- будут ли продукт использовать. И если да — то так ли, как задумывали авторы.
А среди миллионов программистов всегда найдутся довольные, недовольные и «Левши», которые сумеют подковать блоху. Хоть она после этого и перестанет прыгать.
О том, как они отбирают и оценивают новые фичи, Brain Goetz относительно недавно рассказал в InfoQ: www.infoq.com/news/2018/02/data-classes-for-javamad_nazgul
27.03.2018 13:26Отчасти согласен с вами.
Но проблема, не в том, что некоторые могут не правильно использовать.
А в том, что нужно прикладывать усилия, чтобы правильно использовали.
Т.е. разъяснять, писать гайдлайны и т.д.
Что для массового продукта не совсем правильно.
alexshvarts
28.03.2018 10:31Цикл статей интересный, подкинул пищи для размышлений.
Возможно, Вы хотели написать "объект" в предложении "… передаваемый МЕТОД никогда не будет равен null."?visirok Автор
28.03.2018 10:34Спасибо дважды. Моя цель в том и состояла, чтобы подкинуть пиши для собственных размышлений. Ну и разумеется за замеченную опечатку. Я её исправил.
kalaider
Не со всеми примерами данного цикла статей я согласен. А вот с Brian Goetz (судя по приведенному высказыванию в переводе автора) — напротив.
Map
-ы,List
-ы и им подобные. Ни в коем случае нельзя возвращатьOptional
на запрос значения по ключу/индексу, если пользователь явно не попросил вернуть именноOptional
.Что касается значений по умолчанию, то использовать или нет здесь
Optional
— весьма спорный вопрос. Классический вариант сnull
, 0 или -1 чаще оказывается нагладнее и правильнее. Да,Optional
"безопаснее", т.е. он подстегивает программиста проверять, действительно ли значение есть, а также избавляет от магических чисел, но какой ценой… Ценой подсовывания вместо реального объекта обертки над ним! А это, знаете ли, протекание реализации в контракт метода, причем не самое хорошее. Возможность возврата или установкиnull
должна быть оговорена в контракте метода, но именно оговорена (задокументирована, зааннотирована), а не обернута вOptional
или еще что-то.Что касается
NullPointerException
(NPE в народе), то это вполне штатная ситуация времени разработки. Я считаю, что проверка наnull
входных значений (сеттеров, методов, конструкторов) всегда должна присутствовать в явном или неявном виде. Для этого даже стандартных helper-метод есть. Бояться словить или бросить NPE не надо. Просто условия их возникновения или не возникновения, как ранее было сказано, должны присутствовать в контракте метода. И не в видеOptional
, поскольку это сбивает с толку, заставляет оборачивать аргументы вOptional
насильно, а также приводит к полной неразберихе, когда в старых кусках кода значения по умолчанию обозначается черезnull
, а в новых — уже гордо черезOptional
. Кроме того, планирование интерфейсов усложняется. И тут может закрасться мысль вообще все оборачивать в Optional...Вот в каких случаях использование
Optional
действительно целесообразно и оправдано:Optional
будет выглядеть элегантнееnull
илиxxxOrDefault
. Про коллекции см. выше.Socket
. Однако переусердствовать тоже не стоит. В прозрачные сеттеры и прозрачные конструкторы лучше все-таки по старинке передаватьnull
. А логику переключения состояния, если какой-то объект устанавливается вnull
/Optional.empty()
— по старинке обрабатывать отдельным методом или прописать поведение в контракте (что не всегда приемлемо).Других возможностей его применения я не вижу. Возможно, пока. Кстати, что очень спорно,
Optional
можно применить в коллекциях, которые не перевариваютnull
. Но это из разряда "приятный бонус" и к практике, вероятно, имеют мало отношения.lgorSL
В джаве особых возможностей и нет.
В других языках, где это органично вписано в язык, испольуется очень широко. Kotlin: прямо в системе типов есть типы, которым нельзя присваивать null (кстати, обёртки там нет, никакого снижения производительности). Rust: нет нулевых указателей, но зато есть оптимизация, что Option с указателем — не обёртка, а просто указатель. Scala: Option реализована как обёртка, обладает кучей возможностей и интегрирована в стандартную библиотеку. Например, у всех коллекций есть методы и для возвращения обычных значений и для Option, можно использовать что хочется в зависимости от ситуации.
kalaider
В том-то и дело, что в java это совершенно новая вещь, которая ни разу не интегрирована со стандартной библиотекой и тем более не имеет поддержки на уровне языка (боже упаси). Это, в общем-то, и вынуждает нас идти на некие уступки и не использовать повально
Optional
везде, где этотолько можно, возможно, было бы удобно.visirok Автор
А какой интеграции со стандартной библиотекой Вам недостаёт?
kalaider
Кроме стримов он пока нигде не используется.Не использовался. До Java 9. С Java 9 он начал появляться в новых APIшках. Местами в старых. ServiceLoader, например, где он как раз и уместен — те самые "источники данных", о которых я упоминал ранее. Но в целомOptional
сейчас есть только в стримах и в фишках вокруг них.visirok Автор
А вот поддержка на уровне языка (в виде расширения синтаксиса) может и была бы интересна. Я мог бы себе представить что-то вроде Елвис-оператора специально для Optional. (Это идея не новая, например предложено здесь.)
Кроме того, методы типа or(...) позволяют (фактически) строить workflow примерно как BPM. Другими словами, ветвления потоков управления и данных можно было бы поддерживать на уровне синтаксиса. Но для этого больше подходят двухмерные текстовые структуры либо графика. А это уже слишком большой прыжок от нынешних реалий.
kalaider
Я бы так не сказал. Посмотрите на C#. Во что он превратился. В него тащут все, что считают полезным, и это печально. Сложно сохранять парадигму языка, постоянно навешивая новые языковые конструкции, которые порой только ухудшают восприятие кода. Сокращая количество часов на ввод кода мы увеличиваем в несколько раз количество часов на последующий анализ этого кода нами самими и/или другими разработчиками. Получается какой-то зоопарк конструкций, где еще и совершенно разные по смыслу вещи могут обозначаются одинаково/слишком похоже в разных контекстах. Хотя не секрет, что не для всего это верно.
Но это в целом. Что же касается оператора Элвиса, не считаю конкретно его необходимым на уровне языка, даже при том, что он весьма удобен. И хотя он и удобен, мы теряем контроль над потоком управления, получаем дополнительную сложность в отладке, дополнительный оверхед при чтении таких конструкций, особенно в случае длинных цепочек вида
a?.b.c?.d?:e
. Повторюсь, не стоит тащить все, что удобно, в язык, тем более такой консервативный язык, как java.visirok Автор
Оператор Елвиса либо if..else, да ещё со скобками. Одно из двух необходимо применять. В простых ситуациях оператор Елвиса удобне, с моей точки зрения.
Но решение уже принято. Нового синтаксиса в связи с Optional в Java не добавили.
visirok Автор
Позволю себе с Вами не согласиться.
Про Kotlin и его не-нулевые типы. Насколько я понял, создатели Kotlin пытались за счёт введённой программистом информации проследить импользование переменной и уже на этапе компиляции найти места возникновения NPE. Дело безусловно очень хорошее. И вообще Kotlin содержит много полезных штук. И тем не менее — Optional — это не только и не столько предохранение от NPE, что я пытался показать на многочисленных приближенных к реальности примерах во второй и третьей статьях серии. Да и результаты опроса в конце первой статьи указывают, что многие программисты Java ценят полный спектр методов Optional.
Сравнение с Rust наверное не очень корректное, поскольку у Java и Rust разные целевые группы и базовые парадигмы.
Scala я знаю только в теории. Как я понимаю, именно оттуда взяты многие концепции для Optional в Java. А какие практически интересные возможности Option и его окрестностей в Scala представляются Вам остро недостающими в Java Optional?
lgorSL
Если что, я последнее время на джаве не писал. Могу ошибаться. Итак, ссылочки:
Часть неудобства при использовании связана с самим языком (тут уж ничего не поделать, не менять же весь язык ради одного класса). Например, метод
orElse(new SomeObject())
в скале принимает лениво вычисляемое значение, в нём можно написать какую-нибудь логику типа создания нового элемента. Или, например, есть приятный синтаксисfor (a <- option1; b <- option2) yield a+b
, благодаря которому удобно работать с несколькими Option одновременно.Часть неудобств вызвана именно реализацией класса (вернее, отсутствием некоторой функциональности). Почему в скале Option Serializeble, а в джаве — нет? И ещё — в скале Option довольно сильно похожа на маленькую коллекцию — есть приятные методы типа
foreach, exists, forall, contains
. С их помощью код становится лаконичнее. (В джаве похожая функциональность есть у стрима, в который можно превратить Optional, но мне такой способ кажется немного странным, тем более превращать его придётся вручную).Ещё часть неудобств вызвана стандартной коллекцией. Например, я не могу в джаве от HashMap получить Option. Конечно, это всё мелочи, можно написать что-нибудь типа Optional.of(hashMap.getOrDefault(key, null)), но получится куча некрасивого кода.
В итоге получается, что код с использованием Option в скале выглядит коротким и зачастую более простым, чем с проверками на null, а в джаве — наоборот. И чтобы Option хорошо вписался в язык, придётся внести кучу изменений, которых делать, скорее всего, не будут.
visirok Автор
Во первых — просто дать ссылки на исходники классов — очень хорошая идея. Даже жалко, что я сам не догадался. Респект, как говорят немцы.
Во вторых — сравнивать подходы непросто, не опробовав их интенсивно в реальных условиях. Да, Option в Scala содержит больше методов. Но так ли уж это хорошо?
Тут просматривается интересная диалектика. До появления Optional в Java лучшим его заменителем (т.е. способом решать проблемы, которые потом решились с помощью Optional) считался список из нуля или строго одного элемента. Об этом я писал в первой статье серии. Это решение неудолетворительное, поскольку фактически мы вводим новое понятие специального списка, но технически используем полноценный список. Многие методы из List, например, никогда не применимы к усечённому списку для представления потенциально нулевых обьектов.
В Scala Martin Odersky взял и расширил понятие футляра или контейнера некоторыми методами характерными для списков и итераторов. Тем самым почти придя к ситуации в Java до появления Optional. Вот такая диалектика.
Другими словами, мне кажется, что Option в Scala из чисто догматических соображений перегружен методами, которые технически работают, но концептуально чужды идее футляра или контейнера, в котором может содержаться не более чем один обьект.
PqDn
Применяю Optional для замены множественных if