Рубрики
Без рубрики

Ошибки как значения: освободите себя от неожиданных исключений во время выполнения

Введение, когда я пытаюсь продать людей в функциональном программировании, я скажу что -то вроде «Представьте себе мир без исключений нулевого указателя». Это немного вводит в заблуждение, так как я на самом деле имею в виду силу типов звуков. Тем не менее, это предполагается в функциональном программировании, которое вообще не имеет исключений времени выполнения. Tagged с JavaScript, Python, функциональным, исключением.

Введение

Когда я пытаюсь продать людей в функциональном программировании, я скажу что -то вроде «Представьте себе мир без исключений нулевого указателя». Это немного вводит в заблуждение, так как я на самом деле имею в виду силу типов звуков.

Тем не менее, это предполагается в функциональном программировании, которое вообще не имеет исключений времени выполнения. Вместо этого функции, которые могут сбой, вернутся, если они работают или нет. Ссылаясь на это, люди иногда говорят «ошибки как значения» вместо ошибок, являющихся исключением времени выполнения, которая имеет ошибку внутри нее. Эта система убеждений-это то, что я хочу охватить, а не типы звуков, так как многие используют динамические языки, поэтому вера более эффективна в этих областях без типов.

Это довольно инопланетная точка зрения, и трудно визуализировать, как вы программируете таким образом, если вы никогда не подвергались этому. Это особенно верно, если вы используете не FP-языки (исключая Go и Lua), которые могут выглядеть странно, если вы начнете возвращать значения.

Это немного нюансировано, поэтому я хотел осветить эту основную концепцию, чтобы люди четко понимали, что вы можете жить в мире программирования без неожиданных исключений во время выполнения. Ключевое слово там: «неожиданно». Вы можете выполнить эти ошибки возврата из функций, а не намеренно повышать ошибки. Необязательно, использование типов звуков приведет к 100% кода, а не в решении исключений истощения ресурсов.

Преимущество для вас? Ваш код более предсказуем, вы можете с большей уверенностью выпустить производство, и вы можете предоставить больше функций, быстрее.

Вы делаете это, рассматривая ошибки как значения; Точно так же, как вы возвращаете строку или количество дискриминированного союза из функции, так же как и вы можете вернуть ошибку вместо того, чтобы бросать/поднимать ее.

Зачем рассматривать ошибки как значения?

Ваш код имеет 4 преимущества, делающих это таким образом.

2 результата кода против 3

Все функции имеют только 2 возможных результата: они работают или нет. Это в отличие от 3, где он работает, это не так, или вызывает неожиданную ошибку (в отличие от преднамеренного или поднять )

2 результата программы по сравнению с экспоненциально большим

Когда вы начинаете объединять эти функции в программу, ваша программа сейчас либо работает, либо нет. Именно здесь исключения во время выполнения начинают проявлять 2 ужасных вещах экспоненциально. Сначала они начинают происходить в неожиданных областях вашего кода, что делает его трудным, если не невозможным на динамических языках, чтобы точно отслеживать, где вам нужно положить попытку/уловы. Второй – даже в сильно напечатанных, вы все равно можете получить неверные нулевые указатели, и теперь ваша программа имеет 3 возможных результатов, которые она работает, он не работает или неожиданно терпит неудачу. Типичный подход динамического языка здесь состоит в том, чтобы просто использовать силу динамических языков: быстро запустите код, чтобы выяснить все неожиданные пути, найти их, а затем исправить их.

Технически не правильно сказать «2 результата», так как вы можете получить тип союза, который имеет множество возможных состояний; Я просто имею в виду, что ваша программа всегда возвращает «работала» или «какое -то отклонение».

Немного меньше, чтобы проверить

Ваш код легче проверить на истинном счастливом и несчастном пути. Там нет «неожиданного пути». Вы все еще получите логические ошибки, испытываете проблемы с параллелизмом и закончились системные ресурсы.

Ясное намерение

Ваше намерение кода более четко, особенно на динамических языках, у которых нет типов, чтобы помочь.

Что не так со исключениями выполнения?

За пределами Парень, который изобрел их, сказав, что это была дорогостоящая ошибка, Они устраняют всю уверенность, что ваш код работает в 100% случаев, они уходят от времени, и они поощряют создание сложности.

Давайте просто покажем вам несколько основных примеров, которые иллюстрируют проблему. Я сбиваю в «Все исключения времени выполнения» с нулевыми указателями здесь, так как это происходит гораздо больше на динамичных языках, чем сильно напечатанные.

Вот базовый Python AWS Lambda:

def handler(event):
  if event['methd'] == 'GET':
    return true
  return False

С этой функцией неверно неверно, что вызовет исключение:

  1. обработчик В AWS Lambda для Python требуется 2 параметра; Мы предоставили только 1: событие Анкет JavaScript не обеспечивает соблюдение функции, поэтому вы можете безопасно игнорировать 2 -й параметр, контекст, там; Не так в Python. Это может работать в модульных тестах, но не при развертывании в AWS и вызов.
  2. событие является JSON (Python Dictionary), который из балансировщика нагрузки приложения. У него будет Метод Это Get, Post и т. Д., Некоторые Заголовки и, возможно, Queryparameters и тело . Тем не менее, мы сформулировали ошибку Метод без “O”; Метод Таким образом, это не удастся во время выполнения, когда лямбда будет вызван после исправления первой ошибки.
  3. Python Boolean’s – это капитал “t” Верно и капитал “f” Ложный Анкет Наш Ложный Внизу правильно, но наш строчный Верно неправильно и потерпит неудачу … когда это действительно успешно.

Вы не знаете об этих проблемах, в Python, если только вы не используете дополнительные типинг Python 3, у вас есть какой -то Линтер, чтобы найти эти типы общих проблем или, как большинство динамических языков, «вы запускаете код». Единый тест может пропустить ошибку Arity. Это обычная практика в динамических языках, и по уважительной причине: Быстрые петли обратной связи Анкет

Однако петли обратной связи в конечном итоге заканчиваются; В какой -то момент ваш код должен перейти в производство, где вы не используете его, но компьютер. Хотя это не оправдывает медленный процесс CICD; то есть способность быстро реагировать на проблемы в производстве и исправить их, вы хотите, чтобы какая -то уверенность в том, что тебе не придется Анкет В динамичных языках это часто обильное количество автоматизированного и ручного тестирования, чтобы выяснить некоторые из этих проблем выше.

Таким образом, мы не знаем о проблемах, пока не запустим код, используем дополнительные нестандартные инструменты для расширения нашего языка, а также множество автоматизированных и ручных тестов. Мы не просто относимся к языкам и связанным с этим времени, таким как Python, JavaScript, Lua, Elixir и Ruby. Мы также имеем в виду языки, которые имеют сильную набор, но все же могут привести к исключениям из нулевого указателя, таким как Java, Kotlin, Go, C#, F#и TypeScript, чтобы назвать несколько. Системы набора текста в этих языках не приводят к гарантиям во время выполнения.

Эти проблемы имеют значение, потому что, несмотря на использование этих дополнений и тестов, мы все еще можем иметь возникающие ошибки во время производства, где мы не хотим, чтобы ошибочные ошибки возникали. Это приводит к незапланированным реакциям, незапланированным вопросам пользовательского интерфейса и только общее время простоя для клиентов со стрессом для инженеров и их товарищей по команде.

Стратегии смягчения

В настоящее время существует 5 стратегий смягчения последствий, используемых в различных степени, чтобы избежать неожиданных исключений во время выполнения в производственных системах для не FP-языков.

Линтерс

На динамических и напечатанных языках используются линтеры. Они используются перед запуском или составлением кода. Они различаются в целом, но, как правило, все форматируют код, помогают найти общие ошибки и помогают руководству о лучших практиках языка. Для напечатанных языков эти инструменты работают вместе с компилятором, предоставляя вам дополнительные проверки качества, которые компилятор не предоставляет изначально. Примеры включают Pylint Для Python, Eslint Для JavaScript, Ветеринар для хода и PMD Первоначально для Java. Это может предотвратить много исключений времени выполнения.

Попробуйте/поймать блоки

2 -й – блоки Try/поймать. В динамичных языках они расположены вокруг областей, которые с большей вероятностью будут бросать, и на сильно напечатанных языках, вокруг областей, которые вы должны сделать.

// JavaScript
try {
  const result = await getDataFromTechnicalDebtFilledAPI()
} catch (error) {
  console.log("API broke again, surprise surprise:", error)
}

Нет никакого руководства, что такое «скорее»; Вы просто идете со своей кишкой. Процессы разработчика различаются. На таких языках, как Go и Lua, это фактически возвращаемые значения из функций, и у вас есть выбор, как в уловке, если вы обрабатываете с ним или сдаетесь и позволите программе сбой.

-- Lua
status, dataOrError = pcall(getData, 1)
if status == false then
    print("failed:", dataOrError)
end

В Erlang/Elixir, где философия должна «позволить этому разбиться», у вас все еще есть возможность справиться с ошибкой или принять какую -то другую стратегию смягчения.

# Elixir
case result do
  {:ok, data} ->
    transform_data(data)
  _ ->
    log_result_failed()

Они могут справиться с наиболее известными, и некоторые неизвестные исключения времени выполнения, но никогда не поймают все, поскольку вам придется разместить/поймать все возможные ошибки. Это немного легче сделать в ходе, и немного легче игнорировать это в Erlang/Elixir.

Типы

Типы обычно используются как часть языка, чтобы помочь компилятору и/или времени выполнения понять, что означает программист. Типы имеют смысл, например, добавление 2 чисел вместе, тогда программа будет компилировать.

// JavaScript
const add = (a:number, b:number):number =>
    a + b

Если вы попытаетесь добавить номер в Корова Компилятор не будет компилировать код и скажет вам, где ошибка.

add(1, "cow") // <-- won't compile

Тем не менее, типы не только для логики, они также решают простые вещи, такие как ошибки, такие как наш пример Python выше, убедитесь, что вы обрабатываете потенциальные ошибки в своем коде, и не делаете опасных вещей, таких как добавление чисел, чтобы плавать, или предполагать Массив всегда имеет значение внутри него.

Типы поставляются с 2 затратами, и они воспринимаются по -разному в зависимости от инженера и языка. Вы должны намеренно набирать вещи, а не полагать, как в динамических языках. Будь то усилия или нет, зависит от инженера. Во -вторых, компилятор должен компилировать программу против запуска ее, как на динамических языках, и это может глубоко врезаться в цикл быстрого цикла обратной связи.

Также Не все типы созданы одинаково Анкет Большинство языков являются строгими, но все же позволяют возникнуть неожиданные ошибки во время выполнения. Некоторые языки звуки, что означает, что он не будет компилироваться, если не обрабатываются ошибки. Это все еще не делает их невосприимчивыми от исключений времени выполнения. В случае ELM вы все равно можете исчерпать память браузеров, и приложение ELM будет сбой. В Rescrict/OCAML у вас все еще не хватает времени или исчерпывать CPU/Памятную крышку AWS Lambda.

Это также может позволить неверно просачиваться, например, обеспечение того, чтобы число находилось в пределах определенного диапазона или число, даже даже где могут помочь зависимые типы.

Итог: Типы помогают удалить большую часть потенциальных исключений во время выполнения, часто быстро, без необходимости запуска кода, а некоторые могут его гарантировать. Разработка, время компиляции и в случае TypeScript или Python 3 с использованием набор или mypy Затраты на техническое обслуживание типа недооцениваются на вашу страховку.

Тестирование

После написания большей части кода или перед использованием Тестовая разработка , комбинация Единица, свойство и функциональные тесты написаны и запускаются автоматическим образом. Также используются тесты вручную, в том числе «просто запуск приложения». Все это объединилось вместе, гарантируя, что не возникают неожиданных исключений во время выполнения, либо, если они это делают, они обрабатываются. Как и Линтерс и пытаться/поймать блоки, они обрабатывают все возможности, которые вы учитывали, но не все.

# python
assert add(1, 2) == 3
assert_throws add_cow(1) 

Пусть это разбивается

Сначала используется (из моих ограниченных знаний) в Аполлон , а затем популяризирован Erlang Вместо того, чтобы избежать сбоев с большим количеством работы и до сих пор пропускают их, многие разработчики сегодня просто принимают аварии. В Erlang/ Эликсир и Акка Фреймворк, обычно создавать легкий процесс, который является единственной задачей, – это наблюдать за детьми. Дочерний процесс – это то, что запускает фактический код. Если дочерний процесс разбивается, родитель просто порождает еще один. Эта философия перешла от программного обеспечения к аппаратному обеспечению в одноразовом аппаратном движении, и теперь это просто предполагается, что если программное обеспечение будет сбиться с паш, вы просто появляетесь в совершенно новом сервере.

Примеры включают Docker Контейнеры, работающие на Elastic Compute Service (ECS) или Elastic Kubernetes Service (EKS) Для Amazon автоматически назначен Dyno’s на Хероку , или простые функции, работающие в AWS Lambda / Функции Azure Анкет В этих ситуациях можно запустить целые приложения, и если даже 1 имеет неожиданное исключение времени выполнения по какой -либо причине, этот контейнер Docker выключен, а новый контейнер Docker развернулся. Для Lambda это примерно то же самое; Ваша функция работает, и это терпит неудачу, тот, кто слушает ответ от Lambda, получает уведомление о том, что он разбился. Как Docker, так и Lambda позволяют вам одновременно порождать тысячи из них, быстро, с уверенностью обрабатываются все ошибки, и вы можете контролировать, как часто и сколько, как много, развернуты на их месте в случае ошибки.

Это не препятствует возникновению ошибок, и никоим образом не помогает разработчикам пользовательского интерфейса создавать веб -браузер или мобильные приложения. Это, однако, ограничивает их радиус взрыва и помогает вашему применению как масштаба, быть устойчивым, а иногда и самовосстановления.

Решение: Ошибки возврата из функций, не намеренно бросайте/поднимайте

Единственный способ убедиться, что у вашего языка нет ошибок времени выполнения – это не использовать исключения. Вместо этого верните их из функций.

На большинстве динамических языков даже ошибки имеют тип времени выполнения, например Ошибка в JavaScript и Исключение в Python. Вы можете создать их, не нарушая и не останавливая свою программу, осмотреть их и даже вернуть их из функций.

Большинству разработчиков, не являющихся FP, удобны как в блоках Try/Catch, так и в некоторых случаях бросают/поднимают их или пользовательские в своем коде.

# Python
def blow_up():
  raise Exception("b00m")
// JavaScript
const blowUp () => {
  throw new Error("b00m")
}

Тем не менее, вы почти никогда не видели, как они хранятся в переменных и использовали позже:

# Python
def show_error():
  my_boom = Exception("b00m")
  print("my_boom:", my_boom)
const blowUp () => {
  const myBoom = new Error("b00m")
  console.log("myBoom:", myBoom)
}

Для обычного разработчика Python/JavaScript это довольно чужды. Зачем вам ошибку? Весь смысл состоит в том, чтобы позволить Вся программа Знайте, что что -то пошло не так, и вы делаете это, принимая эту ошибку и бросая/поднимая ее, не создавая ее и не висят на нее некоторое время.

Голанг метод

Тем не менее, именно так работает, и Луа может быть почти таким же. Вот пример GO:

file, err := os.Open("filename.ext")
if err != nil {
  return nil, err
}

3 вещи, на которые стоит обратить внимание здесь.

Во -первых, обратите внимание, как Операционные системы. Open возвращает 2 значения против 1; A Файл сначала, а затем ошибка второй. GO позволяет вам возвращать несколько значений из функций, поэтому у них есть соглашение, которое вы сначала выполняете свои данные, а ошибка в последнюю очередь. Вы не знаете, что вы получите, вы просто настраиваете переменные для обоих, если функция может потерпеть неудачу.

Во -вторых, обратите внимание, как код сначала проверяет, чтобы увидеть, если err не является нулевым значением. Если это не ноль, то это означает, что это фактическая ошибка, и, таким образом, что -то пошло не так. Вместо того, чтобы запускать дальнейший код, он остановится здесь.

В -третьих, обратите внимание, как это возвращается. Это первое останавливает весь дополнительный код в этом блоке от запуска, а во -вторых, следует той же конвенции «функция может сначала разорвать данные», а погрешность. Поскольку у нас нет данных, мы возвращаем нуль и просто пересылаем исходную ошибку резервного копирования цепочки.

Это соглашение не используется повсюду; Есть некоторые функции, которые являются чистыми и не могут потерпеть неудачу, или некоторые, которые могут потерпеть неудачу, такие как написание в кеш, но это нормально, если это не удастся. В этих случаях вы просто регистрируете его.

Метод Python Golang

Python также поддерживает возвращение нескольких значений. Это означает, что вы можете отразить, как работает Go, и ваш код Python будет выглядеть так же, как Go.

def open_file(filename):
    try:
        f = open(filename, "r").read()
        return f, None
    except Exception as e:
        return None, e

А теперь, чтобы использовать его, вы просто отражаете тот же стиль:

file, err = open_file("demofile.txt")
if err is not None:
    return None, err
print("file:", file)

Python 3 результат

В Python 3 есть тип, называемый Союз Анкет Он делает то, что говорит и объединяет, или объединяет вместе два или более типа в один. Используя союз, вместо возврата нескольких значений из функции и необходимости проверять, какой из них на самом деле не нулевой, вы можете просто вернуть 1 значение. Есть кроличья дыра методов в Как Вы используете это значение, поэтому мы просто сосредоточимся на обновлении нашего кода выше, чтобы вернуть это единственное значение.

def open_file(filename:str) -> Optional[str, Exception]:
    ...

Теперь, когда вы используете его, вы либо получите строку, либо исключение обратно в качестве единого значения.

Обещание/будущее

В то время как типы союзов Python 3 помогают обеспечить соблюдение концепции «либо или» возвращения, часто легче иметь единый Тип стоимости возвращается. Для функций, которые могут потерпеть неудачу, это чрезвычайно полезно, потому что это ситуация, когда существует только 2 возможных результата: либо это сработало, либо это не так. Этот тип может затем обрабатывать обе ситуации в общем интерфейсе.

Вот как работают обещания или будущее. JavaScript имеет их встроенные, и Python & Lua есть библиотеки, которые поддерживают их использование.

fs.readFile(filename)
.then(data => console.log("file data:", data))
.catch(error => console.log("error:", error))

Есть несколько преимуществ этого подхода. На динамических языках это небольшое преимущество, потому что это заставляет вас использовать больше функций и императивный код для проверки значений. Это снижает риск, вы случайно что -то ошибетесь или сделаете императивный код, который случайно вызывает исключение времени выполнения.

Кроме того, если вы всегда возвращаете Обещать , тогда ваши функции никогда не принесут ошибки, потому что они всегда возвращают значение. Если они обнимают обещания вместо Асинхрон/жду Синтаксис, тогда у них есть встроенный удар, поэтому все исключения во время выполнения обрабатываются автоматически.

Наконец, независимо от того, какой тип вы возвращаете в обещание, каждая функция в вашей программе знает, как работать с обещанием в общем интерфейсе Тогда для значения и поймать Для ошибки с возможностью изменить то, что возвращается, если это необходимо.

Стоимость развития

Теперь, когда вы знаете, как вернуть ошибки как значения из функций вместо того, чтобы бросать их, давайте поговорим о затратах на разработку этого подхода и о том, что он влияет на ваш рабочий процесс.

Возвращение против броска

Это огромное изменение для разработчиков, привыкших к бросанию исключений, или, как минимум, обрабатывают их, часто по типу. Вместо бросать или поднимать , они будут использовать возвращаться . Вместо того, чтобы соответствовать типам в лоте/кроме блоков, они будут соответствовать шаблону или просто использовать поймать метод Вместо того, чтобы утверждать, что функция выбрасывает некоторую ошибку в модульном тесте, они вместо этого будут утверждать на возвращаемых значениях. После того, как вы отклоняетесь от языковых норм, результаты Google для примеров общего языка/проблемы, скорее всего, не в этом стиле ошибки возврата.

Это имеет довольно огромные затраты на языки, которые изначально не поддерживают этот стиль развития, такой как Java. Такие языки, как JavaScript и Python, в основном поддерживают все стили программирования, поэтому более прощают. Такие языки, как Go, Lua и другие языки функционального программирования, охватывают это, так что там должно быть естественно.

Как правило, это личное или командное решение о деталях реализации и, возможно, выбором библиотеки на языках, которые не являются изначально поддерживают этот стиль. Стоит потратить время на то, чтобы попробовать реализации, чтобы все были на борту.

Методы отладки

Как у вас отладка может измениться. Например, в Python вы потеряете трассировку стека, используя этот метод, если вы специально не вызовуте методы печати по самой ошибке. Позволяя ему взорваться, когда обычно автоматически печатает это на экране, который ожидается в том, как вы справляетесь с неожиданными.

Обычно динамические языковые программисты запускают код и Ожидайте Неожиданные исключения времени выполнения. Вся методология состоит в том, чтобы запустить код, исправление, тестирование, повторить быстрые итерации. Теперь, вместо результата, который вы ожидали, или исключение с трассировкой стека, которой вы не были, вы вместо этого смотрите на возвратные значения функции, зарегистрированные на экране. Поскольку это скорее функциональный менталитет программирования, вы ищете выходные значения функций, а не значения переменных или трассировки стека.

Вы все еще можете использовать печатные операторы и по -прежнему использовать точки разрыва отладки. Вы просто потратите меньше времени, пробираясь по трассам стека, чтобы найти, где произошли ошибки. Вместо этого ошибки должны сказать вам, какую функцию и модуль они произошли и почему. Что еще более важно, у вас будет код, обрабатывая эти ошибки; Ожидаемый код обращается с неожиданностью. Когда вы запускаете программу, и она не работает, но не приводит к тому, что вы ожидали, здесь есть некоторые наклонности к тому, как это определить. Если побочные эффекты, у вас будет либо больше журналов, либо больше возвратных значений, которые указывают, был ли побочный эффект успешным или нет, либо, по крайней мере, контекст, чтобы помочь понять, что может произойти. Если просто вернуть значения, вы узнаете, как массировать ваши данные, чтобы включить этот контекст «был успешной программой или нет» в выходное значение.

Тестирование

Хотя это и не совсем на 100%, почти все ваши тесты должны быть в форме:

  1. Функция принимает вход
  2. Функция возвращает значение
  3. Вы утверждаете, что значение соответствует тому, что вы ожидаете от этого ввода
file_result = open_file("test.txt")
assert file_result.is_successful() == True

Вы все еще можете использовать заглушки и издевательства, но их должно быть намного меньше. Не будет ни одного «утверждать, что этот блок кода в конечном итоге выбрасывает какой -то тип ошибки». Теперь, когда ошибки являются возвращаемыми значениями, как и обычные данные, вы просто утверждаете тип данных. Для классовых архитектур это может показаться довольно чуждым, так как большинство классов будут иметь методы/функции, которые не возвращают значения, имеют много побочных эффектов, и вы не можете легко проверить их таким образом. Этот стиль разработки не способствует объектно -ориентированному программированию, что является одной из причин, почему у Go нет занятий.

Строгие или типы звуков

Если вы используете звук или даже строгие типы, меньше необходимости проверить выходные выходы в модульных тестах. Скорее, вы должны использовать больше тестов на свойство/fuzz, чтобы убедиться, что вы всегда получаете результат успеха (данные, которые вы ожидаете), и ошибки для плохих входов. Это гарантирует, что типы выполняют свою работу.

Единственная реальная разница – вы утверждаете выводы по сравнению с попытками попробовать/поймать все прогоны теста свойства.

Пусть это разбивается или нет?

Это большой, и опять же, должно быть командным решением. У облачных провайдеров, таких как AWS, исключения являются нормальным и ожидаемым договором между реактивными архитектурами. Короче говоря, ожидается, что код вернет значение или сбой. Нарушение этого контракта – это лучшие практики облака. AWS был построен таким образом, потому что индустрия разработки программного обеспечения построена таким образом. Не все следует за Go или Erlang или Haskell с различной ошибкой, обрабатывающей философию. Я рассказываю о различных стратегиях, используя приведенные выше, вы можете использовать, например, Lambda и Step -функции ( Видео | слайды )

Триггеры AWS Lambda часто обрабатывают свои собственные поиски. Например, если вы используете очередь сообщений, такую как SQS, и Lambda должен обрабатывать каждое сообщение, но сбой AWS автоматически повторно повторно повторно. Это не случайно, а скорее прекрасная особенность AWS. Тем не менее, это может летать перед лицом наилучшей практики, которую предлагает эта статья: не бросайте ошибки. Если вы не бросаете ошибки, но у вас есть ошибка, как вы скажете AWS, что у вас есть ошибка, если вы не бросаете ее?

В серверных средах, которые используют контейнеры, такие как Docker в Упругая контейнерная служба или Упругая служба Kubernetes Ожидается, что если произойдет неожиданное исключение во время выполнения, контейнер заставил бы себя сбой, чтобы серверы могли раскрутить новый здоровый. Опять аварии Ожидается и поощряется здесь.

Один из способов справиться с этим – развертывание. Ржавчина и Библиотека возврата Python Следуйте этой технике. Вы можете сделать все свои чистые вычисления без исключений времени выполнения, но как только вы хотите вернуться в «императивный мир», вы называете развернуть . Это даст вам значение или подведет исключение, если бы была ошибка. Думайте об этом как о переводчике для вашего чистого кода в AWS, который ожидает нечистого кода.

Например, вот какой -то Pure Python -код, который анализирует сообщения SQS от AWS:

def handler(event, _):
  return verify_event(event)
  .bind( lambda _: parse_sqs_message(event) )
  .bind( validate_message )
  .bind( process_message )

Если событие происходит от SQS, успешно проанализировал словарь JSON Event, подтвержденный типом сообщения, который мы ожидали, и мы успешно удалили его из очереди SQS, тогда эта лямбда вернется ОК (правда) Анкет Однако, если какая -либо из этих 4 вещей выйдет из строя, это вернет Ошибка ("Причина") . AWS не знает, что такое Ошибка ("Причина") Конвертированный в словарь JSON – это … он просто предположит, что Lambda успешно обработала сообщение. Что не так. Просто вызов развернуть В конце гарантирует, что это Верно Или это поднимет исключение, если это ошибка. Это имеет небольшой нюанс, чтобы сделать ваш модульный тест на вашу лямбду, чтобы проверить исключение 😜.

Иногда, однако, вы хотите, чтобы способность вручить ответ. Используя API -шлюз или балансировщики нагрузки приложения, где ваш лямбда является API REST, это обычно. Успешный? Прохладный:

{
  "statusCode:" 200
}

Не удалось? Прохладный:

{
  "statusCode": 500
}

В этом случае сопоставление шаблонов является лучшим выбором, когда вы преобразуете (или карту) возвращаемое значение типа профсоюза, такое как результат ответа HTTP. Пример ниже показывает, как это сделать, если лямбда вызывается API Gateway или ALB:

def handler(event, _):
    return verify_event(event)
    .bind( lambda _: do_work() )
    .bind( convert_to_http_response )

Теперь ваш convert_to_http_response Функция будет нести ответственность за преобразование ОК (правда) к {StatusCode: 200} и Ошибка ("Причина") к {StatusCode: 500} Анкет

Здесь вы увидите шаблон, который, хотя все триггеры обычно ожидают обратного ответа (SQS не заботится, API Gatweay/Alb имеют строгие требования, lambda.invoke или функция шага ожидайте json или ничего и т. Д.). Все услуги следуют «если это сбои, это считается неудачной или ложной» мантрой. Несмотря на то, что это в случае, хорошая новость заключается в том, что это почти всегда последняя функция в ваших цепенных функциях в вашей лямбде Итак, вы знаете, где его найти.

Выводы

Возвращение ошибок из функций вместо их бросания помогает обеспечить более предсказуемый код. Более предсказуемый код означает меньше ошибок и большее количество уверенности, развертываемого для PROD с большим количеством функций, доставленных быстрее. Вы можете меньше беспокоиться о страшных неожиданных исключениях во время выполнения и больше беспокоиться о логике тестирования и параллелизма; Действительно сложные проблемы.

Игнорирование неожиданных исключений во время выполнения будет по -прежнему стоить триллионы, как в деньгах, так и в стрессе для себя.

Вы можете избежать их, возвращая ошибки из функций, используя типы, чтобы обеспечить правильное обращение с ними, при этом сохраняя способность конвертировать обратно в случае работы в рамках инфраструктуры, которая ожидает сбоев.

При желании вы можете использовать языки, которые поддерживают эту функциональность, чтобы вы никогда больше не беспокоились о них. Строго напечатали такие языки, как F#, GO и Lua, могут помочь вам облегчить этот стиль после того, как вы освоили на своем языке. Как только вы почувствуете себя комфортно, надежные языки, такие как Elm, Rescrict, Rust и Haskell, могут помочь вам больше никогда не беспокоиться о них. В основном.

Оригинал: “https://dev.to/jesterxl/errors-as-values-2doe”