#swift #exception
Вопрос:
Я ищу элегантный способ объединения guard
блоков с do-try-catch
семантикой в Swift и до сих пор не был удовлетворен своими собственными усилиями.
В качестве фона я получаю некоторые необходимые данные от функции, которая создает исключение и может возвращать необязательное значение. В качестве конкретного примера предположим, что я ищу самый последний элемент из списка задач, хранящегося в файловой системе. Это может быть законно nil
(ничего в списке), или это может вызвать исключение (какая-то ошибка ввода-вывода при доступе к файловой системе), поэтому оба случая различны.
Однако для остальной части метода нас волнует только то, есть ли предмет для работы — так что это звучит как идеальный случай для guard
:
func myCoolMethod() {
guard let todoItem = try? fetchItemFromList() else {
// No item to act on, nothing to do
return
}
// rest of function acts on todoItem
...
}
Это действительно нормально — до тех пор, пока защитный блок не начнет отказывать, когда вы ожидаете, что он будет успешным, и вы действительно хотели бы (если ничего другого) зарегистрировать сообщение об ошибке, чтобы попытаться провести расследование. Таким образом, try?
необходимо заменить a try
, чтобы ошибка была зафиксирована, и теперь нам нужно будет завернуть guard
ее в do
блок:
func myCoolMethod() {
do {
guard let todoItem = try fetchItemFromList() else {
// No item to act on, nothing to do
return
}
// rest of function acts on todoItem
...
}
catch {
// Now we know there was an error of some sort (can specialize the
// catch block if we need to be more precise)
// log error or similar, then...
return
}
}
В этом есть две проблемы, которые я вижу:
do
Блок должен охватывать весь метод (из-за области примененияtodoItem
). Это означает, что логика счастливого пути имеет отступ (чтоguard
особенно помогает избежать); но, кроме того, это означает, что любые ошибки из этой логики будут пойманыcatch
блоком, возможно, случайно. Я бы предпочел, чтобы этотdo
блок был ограничен только областьюguard
.- Там дублируется блок «что делать, если у нас нет товара». В этом тривиальном примере это просто a
return
, но мне все еще немного больно иметь два отдельных блока, которые должны делать в целом одно и то же (где один просто добавляет немного дополнительного контекста с доступной ошибкой).
Я хотел бы подойти к этому как можно ближе:
func myCoolMethod() {
// `do guard` is made up but would enter the else block either if an exception
// is thrown, or the pattern match/conditional fails
do guard let todoItem = try fetchItemFromList() else {
if let error = error {
// Optional `error` is only bound if the guard failed due to an exception
// here we can log it etc.
}
// This part is common for both the case where an exception was thrown,
// and the case where the method successfully returned `nil`, so we can
// implement the common logic for handling "nothing to act on"
return
}
// rest of function acts on todoItem
...
}
Каковы мои варианты здесь?
Ответ №1:
Во-первых, ищите ошибки и разбирайтесь с ними. Затем ищите существование и разбирайтесь с этим:
func myCoolMethod() {
// Making this `let` to be stricter. `var` would remove need for `= nil` later
let todoItem: Item?
// First check for errors
do {
todoItem = try fetchItemFromList()
} catch {
todoItem = nil // Need to set this in either case
// Error handling
}
// And then check for values
guard let todoItem = todoItem else {
// No item to act on (missing or error), nothing to do
return
}
// rest of function acts on todoItem
print(todoItem)
}
Или разделите проблемы на функции. Когда все усложняется, делайте более целенаправленные функции:
func myCoolMethod() {
// And then check for values
guard let todoItem = fetchItemAndHandleErrors() else {
// No item to act on (missing or error), nothing to do
return
}
// rest of function acts on todoItem
print(todoItem)
}
func fetchItemAndHandleErrors() -> Item? {
// First check for errors
do {
return try fetchItemFromList()
} catch {
// Error handling
return nil
}
}
Комментарии:
1. Первый блок кода не компилируется, второе объявление
todoItem
конфликтует с первым. Вы имели в виду использовать разные имена переменных?2. @MartinR Странно, это компилируется для меня.
todoItem
намеренно отбрасывает тень на первую. Какую ошибку вы получили?3. «Определение конфликтует с предыдущим значением» в
guard let todoItem = ...
строке.4. @MartinR это странно. Вот моя суть, которую я использую в приложении командной строки. gist.github.com/rnapier/6f14610ce1897f538feaaf779ab90878 Я знаю, что было исправлено исправление ошибки «по собственному определению», которое только что было добавлено в бета-версию 12.5 3 (которую я использую), но я думал, что оно применимо только к функциям и свойствам.
5. Это бы все объяснило, я сейчас использую 12.4.