scalabook

Форк
0
/
functional-error-handling.md 
259 строк · 13.6 Кб

Функциональная обработка ошибок

Функциональное программирование похоже на написание ряда алгебраических уравнений, и поскольку алгебра не имеет null

значений или исключений, эти функции не используются в ФП. Это поднимает интересный вопрос: что делать в ситуациях, когда в коде ООП обычно используются null
значение или исключение?

Решение Scala заключается в использовании конструкций, подобных классам Option

/Some
/None
.

Примечание:

  • классы Some
    и None
    являются подклассами Option
  • вместо того чтобы многократно повторять "Option
    /Some
    /None
    ", следующий текст обычно просто ссылается на "Option
    " или на "классы Option
    "

Первый пример

Хотя этот первый пример не имеет дело с null

значениями, это хороший способ познакомиться с классами Option
.

Представим, что нужно написать метод, который упрощает преобразование строк в целочисленные значения. И нужен элегантный способ обработки исключения, которое возникает, когда метод получает строку типа "Hello"

вместо "1"
. Первое предположение о таком методе может выглядеть следующим образом:

def makeInt(s: String): Int =
try
Integer.parseInt(s.trim)
catch
case e: Exception => 0

Если преобразование работает, метод возвращает правильное значение Int

, но в случае сбоя метод возвращает 0
. Для некоторых целей это может быть хорошо, но не совсем точно. Например, метод мог получить "0"
, но мог также получить "foo"
, "bar"
или бесконечное количество других строк, которые выдадут исключение. Это реальная проблема: как определить, когда метод действительно получил "0"
, а когда получил что-то еще? При таком подходе нет способа узнать правильный ответ наверняка.

Использование Option/Some/None

Распространенным решением этой проблемы в Scala является использование классов, известных как Option

, Some
и None
. Классы Some
и None
являются подклассами Option
, поэтому решение работает следующим образом:

  • объявляется, что makeInt
    возвращает тип Option
  • если makeInt
    получает строку, которую он может преобразовать в Int
    , ответ помещается внутрь Some
  • если makeInt
    получает строку, которую не может преобразовать, то возвращает None

Вот доработанная версия makeInt

:

def makeInt(s: String): Option[Int] =
try
Some(Integer.parseInt(s.trim))
catch
case e: Exception => None

Этот код можно прочитать следующим образом: "Когда данная строка преобразуется в целое число, верните значение Int

, заключенное в Some
, например Some(1)
. Когда строка не может быть преобразована в целое число и генерируется исключение, метод возвращает значение None
."

Эти примеры показывают, как работает makeInt

:

val a = makeInt("1")
// a: Option[Int] = Some(value = 1)
val b = makeInt("one")
// b: Option[Int] = None

Как показано, строка "1"

приводится к Some(1)
, а строка "one"
- к None
. В этом суть альтернативного подхода к обработке ошибок. Данная техника используется для того, чтобы методы могли возвращать значения вместо исключений. В других ситуациях значения Option
также используются для замены null
значений.

Примечание:

  • этот подход используется во всех классах библиотеки Scala, а также в сторонних библиотеках Scala
  • ключевым моментом примера является то, что функциональные методы не генерируют исключения; вместо этого они возвращают такие значения, как Option

Потребитель makeInt

Теперь представим, что мы являемся потребителем метода makeInt

. Известно, что он возвращает подкласс Option[Int]
, поэтому возникает вопрос: как работать с этими возвращаемыми типами?

Есть два распространенных ответа, в зависимости от потребностей:

  • использование match
    выражений
  • использование for
    выражений

Использование match выражений

Одним из возможных решений является использование выражения match

:

makeInt(x) match
case Some(i) => println(i)
case None => println("That didn’t work.")

В этом примере, если x

можно преобразовать в Int
, вычисляется выражение в правой части первого предложения case
; если x
не может быть преобразован в Int
, оценивается выражение в правой части второго предложения case
.

Использование for выражений

Другим распространенным решением является использование выражения for

, то есть комбинации for
/yield
. Например, представим, что необходимо преобразовать три строки в целочисленные значения, а затем сложить их. Решение задачи с использованием выражения for
:

val y = for
a <- makeInt(stringA)
b <- makeInt(stringB)
c <- makeInt(stringC)
yield
a + b + c

После выполнения этого выражения y

может принять одно из двух значений:

  • если все три строки конвертируются в значения Int
    , y
    будет равно Some[Int]
    , т. е. целым числом, обернутым внутри Some
  • если какая-либо из трех строк не может быть преобразована в Int
    , y
    равен None

Это можно проверить на примере:

val stringA = "1"
val stringB = "2"
val stringC = "3"
val y = for
a <- makeInt(stringA)
b <- makeInt(stringB)
c <- makeInt(stringC)
yield
a + b + c
// y: Option[Int] = Some(value = 6)

Чтобы увидеть негативный кейс, достаточно изменить любую из строк на что-то, что нельзя преобразовать в целое число. В этом случае y

равно None
:

y: Option[Int] = None

Восприятие Option, как контейнера

Для лучшего восприятия Option

, его можно представить как контейнер:

  • Some
    представляет собой контейнер с одним элементом
  • None
    не является контейнером, в нем ничего нет

Если предпочтительнее думать об Option

как о ящике, то None
подобен пустому ящику. Что-то в нём могло быть, но нет.

Использование Option для замены null

Возвращаясь к значениям null

, место, где null
значение может незаметно проникнуть в код, — класс, подобный этому:

class Address(
var street1: String,
var street2: String,
var city: String,
var state: String,
var zip: String
)

Хотя каждый адрес имеет значение street1

, значение street2
не является обязательным. В результате полю street2
можно присвоить значение null
:

val santa = Address(
"1 Main Street",
null,
"North Pole",
"Alaska",
"99705"
)

Исторически сложилось так, что в этой ситуации разработчики использовали пустые строки и значения null

, оба из которых "взламывают" решения основной проблемы: street2
- необязательное поле. В Scala и других современных языках правильное решение состоит в том, чтобы заранее объявить, что street2
является необязательным:

class Address(
var street1: String,
var street2: Option[String],
var city: String,
var state: String,
var zip: String
)

Теперь можно написать более точный код:

val santa = Address(
"1 Main Street",
None,
"North Pole",
"Alaska",
"99705"
)

или так:

val santa = Address(
"123 Main Street",
Some("Apt. 2B"),
"Talkeetna",
"Alaska",
"99676"
)

Option — не единственное решение

В этом разделе основное внимание уделялось Option

классам, но у Scala есть несколько других альтернатив.

Например, три класса, известные как Try

/Success
/Failure
, работают также, но (а) эти классы в основном используются, когда код может генерировать исключения, и (б) когда желательно использовать класс Failure
, потому что он дает доступ к сообщению об исключении. Например, классы Try
обычно используются при написании методов, которые взаимодействуют с файлами, базами данных или интернет-службами, поскольку эти функции могут легко создавать исключения.

Краткое ревью

  • функциональные программисты не используют null
    значения
  • основной заменой null
    значениям является использование классов Option
  • функциональные методы не выдают исключений; вместо этого они возвращают такие значения, как Option
    , Try
    или Either
  • распространенными способами работы со значениями Option
    являются выражения match
    и for
  • Option
    можно рассматривать как контейнеры с одним элементом (Some
    ) и без элементов (None
    )
  • Option
    также можно использовать для дополнительных параметров конструктора или метода

Ссылки:

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.