Когда использовать опцию
Я изучаю Scala в течение некоторого времени и не могу четко понять использование опции. Это помогает мне избегать нулевых проверок, когда я связываю функции (согласно docs). Это ясно для меня:)
Далее я вижу, что Опция может быть своего рода индикатором для разработчика, где здесь возможно нулевое значение, и это нужно обработать. Это правда? Если да, я должен использовать Опцию, если это возможно? Например
class Racer {
val car = new Car()
}
У меня есть класс Racer, и я уверен, что поле автомобиля не может быть нулевым (поскольку оно является константой и получает значение при создании экземпляра Racer). Здесь нет необходимости в опции.
class Racer {
var car = new Car()
}
Здесь я делаю так, чтобы автомобиль мог измениться. И возможно, кому-то назначить нулевой автомобиль. Должен ли я использовать опцию здесь? Если да, то я замечаю, что все поля моего класса являются кандидатами на опцию. И мой код выглядит следующим образом:
class Racer {
var car: Option[Car] = None
var currentRace: Option[Race] = None
var team: Option[Team] = None
...
}
Хорошо ли это? Для меня это похоже на использование опции.
def foo(): Result = {
if( something )
new Result()
else
null
}
У меня есть метод, который может возвращать null. Должен ли я возвращать вариант? Должен ли я всегда делать это, если возможно, чтобы метод возвращал null?
Любые мысли об этом были бы полезны. Спасибо заранее!
Мой вопрос аналогичен Почему опция, но я думаю, что это не то же самое. Это больше о том, когда, а не почему.:)
Ответы
Ответ 1
Вам следует избегать null
как можно больше в Scala. Он действительно существует только для взаимодействия с Java. Таким образом, вместо null
используйте Option
, когда возможно, что функция или метод может возвращать "нет значения" или когда логически допустимо для переменной-члена иметь "нет значения".
Что касается вашего примера Racer
: Действительно ли Racer
действительно, если он не имеет car
, currentRace
и team
? Если нет, то вы не должны использовать эти параметры переменных-членов. Не просто используйте их, потому что теоретически можно назначить их null
; сделайте это, только если логически вы считаете его допустимым объектом Racer
, если какая-либо из этих переменных-членов не имеет значения.
Другими словами, лучше сделать вид, будто null
не существует. Использование кода null
в Scala является запахом кода.
def foo(): Option[Result] = if (something) Some(new Result()) else None
Обратите внимание, что Option
имеет много полезных методов для работы.
val opt = foo()
// You can use pattern matching
opt match {
case Some(result) => println("The result is: " + result)
case None => println("There was no result!")
}
// Or use for example foreach
opt foreach { result => println("The result is: " + result) }
Кроме того, если вы хотите запрограммировать в функциональном стиле, вы должны как можно больше избегать изменчивых данных, что означает: избегать использования var
, использовать val
вместо этого, использовать неизменяемые коллекции и создавать свои собственные классы насколько это возможно.
Ответ 2
Когда речь заходит о функциональном программировании в Scala, Option
гораздо предпочтительнее, чем null
, поскольку он безопасен по типу и отлично сочетается с другими конструкциями в функциональной парадигме.
В частности, вы можете легко написать идиоматический код, используя функции высокого порядка на Option
. Этот Scala вариант Cheat Sheet является полезным для чтения на эту тему.
Ответ 3
В то время как Option
может сделать определение класса более подробным, альтернативой является не знать, когда вам нужно проверить, определена ли переменная. Я думаю, что в вашем примере, если ваш класс был неизменным (все поля были val
s), вам не понадобилось бы столько Option
s.
Для вашего метода foo
, если вы возвращаете значение null, вам необходимо задокументировать его, и клиент должен прочитать эту документацию. Затем клиент напишет кучу кода, например:
val result = foo(x)
if (result != null) {
println(result)
}
Если вместо этого вы определяете foo
для возврата Option[Result]
, система типа заставляет клиента обрабатывать возможность результата undefined. Они также обладают всеми возможностями классов коллекций.
result foreach println
Дополнительным преимуществом использования метода collection с Option
вместо тестирования для null
является то, что если ваш метод расширен до многоэлементной коллекции (например, List
), вам может и не понадобиться вообще измените свой код. Например, изначально вы можете предположить, что ваш Racer
может иметь только одну команду, поэтому вы определяете val crew: Option[Crew]
, и вы можете проверить, является ли команда старше 30 с помощью crew.forall(_.age > 30).getOrElse(false)
. Теперь, если вы изменили определение на val crew: List[Crew]
, ваш старый код все равно будет компилироваться, и теперь вы проверите, будут ли все члены вашего экипажа старше 30 лет.
Иногда вам нужно иметь дело с null
, потому что библиотеки, которые вы используете, могут вернуть его. Например, когда вы получаете ресурс, вы можете получить результат null
. К счастью, легко защищать результаты, чтобы они были преобразованы в опцию.
val resource = Option(this.getClass.getResource("foo"))
Если getResource
возвращено null
, тогда resource
равно None
, а в противном случае - Some[URL]
. Круто!
К сожалению, Option
, вероятно, будет иметь некоторые накладные расходы, потому что это связано с дополнительным созданием объекта. Однако эта служебная нагрузка минимальна по сравнению с исключением нулевого указателя!
Ответ 4
Идея Options
состоит в том, чтобы избавиться от null
, поэтому да, вы должны использовать их вместо возврата "null
или значение". Это также почему, если вы хотите моделировать необязательные поля (связь 0..1 с другими объектами), использование Option
- это, безусловно, хорошо.
Небольшой недостаток заключается в том, что он объявляет объявление класса с большим количеством необязательного поля немного подробным, как вы отметили.
Еще одна вещь, в scala, вам предлагается использовать "неизменяемые объекты", поэтому в вашем примере поля должны быть немного val
s.;)