Próbuję zrozumieć nową obsługę błędów w języku Swift 2. Oto co zrobiłem: najpierw zadeklarowałem wyliczenie błędu:
enum SandwichError: ErrorType {
case NotMe
case DoItYourself
}
A potem zadeklarowałem metodę, która zgłasza błąd (nie ludzie z wyjątkiem. To jest błąd.). Oto ta metoda:
func makeMeSandwich(names: [String: String]) throws -> String {
guard let sandwich = names["sandwich"] else {
throw SandwichError.NotMe
}
return sandwich
}
Problem dotyczy strony dzwoniącej. Oto kod, który wywołuje tę metodę:
let kitchen = ["sandwich": "ready", "breakfeast": "not ready"]
do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
print("Not me error")
} catch SandwichError.DoItYourself {
print("do it error")
}
Po tym do
, jak kompilator linii mówi Errors thrown from here are not handled because the enclosing catch is not exhaustive
. Ale moim zdaniem jest to wyczerpujące, ponieważ w SandwichError
wyliczeniu są tylko dwa przypadki .
W przypadku zwykłych instrukcji switch, swift może zrozumieć, że jest to wyczerpujące, gdy zajmujemy się każdą sprawą.
do
bloki na najwyższym poziomie, które nie są wyczerpujące - jeśli zawiniesz do w funkcję nie rzucającą, wygeneruje błąd.Odpowiedzi:
Istnieją dwa ważne punkty modelu obsługi błędów w Swift 2: wyczerpujący i elastyczny. Razem sprowadzają się do tego, że twoje
do
/catch
stwierdzenie musi wychwycić każdy możliwy błąd, a nie tylko te, o których wiesz, że możesz je rzucić.Zwróć uwagę, że nie deklarujesz, jakie typy błędów funkcja może zgłaszać, tylko czy w ogóle zgłasza. Jest to problem typu zero-one-infinity: jako ktoś definiujący funkcję do wykorzystania przez innych (w tym twoje przyszłe ja), nie chcesz, aby każdy klient twojej funkcji dostosowywał się do każdej zmiany w implementacji twojego funkcji, w tym jakie błędy może zgłosić. Chcesz, aby kod wywołujący twoją funkcję był odporny na takie zmiany.
Ponieważ Twoja funkcja nie może określić, jakie błędy zgłasza (lub może generować w przyszłości),
catch
bloki, które wychwytują błędy, nie wiedzą, jakie typy błędów może generować. Tak więc, oprócz obsługi typów błędów, o których wiesz, musisz obsłużyć te, których nie znasz, za pomocącatch
instrukcji uniwersalnej - w ten sposób, jeśli twoja funkcja zmieni zestaw błędów, które zgłosi w przyszłości, wywołania nadal będą łapać błędy.Ale nie poprzestawajmy na tym. Pomyśl trochę więcej o tej idei odporności. Sposób, w jaki zaprojektowałeś swoją kanapkę, musisz opisać błędy w każdym miejscu, w którym ich używasz. Oznacza to, że za każdym razem, gdy zmieniasz zestaw przypadków błędów, musisz zmieniać każde miejsce, które ich używa ... niezbyt zabawne.
Ideą definiowania własnych typów błędów jest umożliwienie scentralizowania takich rzeczy. Możesz zdefiniować
description
metodę dla swoich błędów:A następnie kod obsługi błędów może poprosić o opisanie typu błędu - teraz każde miejsce, w którym obsługujesz błędy, może używać tego samego kodu i obsługiwać również możliwe przyszłe przypadki błędów.
Utoruje to również drogę typom błędów (lub ich rozszerzeniom) do obsługi innych sposobów zgłaszania błędów - na przykład możesz mieć rozszerzenie typu błędu, które wie, jak przedstawić,
UIAlertController
aby zgłosić błąd użytkownikowi iOS.źródło
error caught in main()
- Więc chociaż wszystko, co powiedziałeś, brzmi rozsądnie, nie mogę odtworzyć tego zachowania.try
wyrażenia wymuszonego w kodzie produkcyjnym, ponieważ może to spowodować błąd w czasie wykonywania i spowodować awarię aplikacjitry!
. Ponadto prawdopodobnie istnieją ważne, „bezpieczne” przypadki użycia dla różnych operacji „wymuszania” w języku Swift (rozpakowywanie, próba itp.), Nawet w przypadku kodu produkcyjnego - jeśli dzięki warunkom wstępnym lub konfiguracji niezawodnie wyeliminujesz możliwość awarii, może bardziej rozsądne jest zwarcie do natychmiastowej awarii niż pisanie kodu obsługi błędów, którego nie można przetestować.SandwichError
klasie ma sens. Jednak podejrzewam, że w przypadku większości błędów logika obsługi błędów nie może być tak hermetyzowana. Dzieje się tak, ponieważ zwykle wymaga to znajomości kontekstu wywołującego (czy przywrócić, spróbować ponownie, zgłosić awarię nadawcy itp.). Innymi słowy, podejrzewam, że i tak najpowszechniejszym wzorcem byłoby dopasowanie do określonych typów błędów.Podejrzewam, że to po prostu nie zostało jeszcze poprawnie wdrożone. Przewodnik szybkiego programowania zdecydowanie wydaje się sugerować, że kompilator może wywnioskować wyczerpujące dopasowania „jak instrukcja przełącznika”. Nie wspomina o potrzebie generała
catch
, aby być wyczerpującym.Zauważysz również, że błąd występuje w
try
wierszu, a nie na końcu bloku, tj. W pewnym momencie kompilator będzie w stanie wskazać, któratry
instrukcja w bloku ma nieobsługiwane typy wyjątków.Dokumentacja jest jednak nieco niejednoznaczna. Przejrzałem wideo „Co nowego w Swift” i nie znalazłem żadnych wskazówek; Będę próbował dalej.
Aktualizacja:
Jesteśmy teraz w wersji Beta 3 bez cienia wnioskowania o typie błędu. Teraz uważam, że gdyby było to kiedykolwiek zaplanowane (i nadal myślę, że tak było w pewnym momencie), dynamiczne wysyłanie rozszerzeń protokołów prawdopodobnie to zabiło.
Aktualizacja Beta 4:
Xcode 7b4 dodał obsługę komentarzy w dokumencie
Throws:
, które „powinny być używane do dokumentowania, jakie błędy mogą być zgłaszane i dlaczego”. Myślę, że to przynajmniej zapewnia pewien mechanizm komunikowania błędów konsumentom API. Kto potrzebuje systemu typów, skoro masz dokumentację!Kolejna aktualizacja:
Po spędzeniu pewnego czasu z nadzieją do automatycznego
ErrorType
wnioskowania, a obecnie pracuje co ograniczenia byłyby z tego modelu, ja zmieniłem zdanie - to jest to, co mam nadzieję, że zamiast narzędzi firmy Apple. Głównie:Jeszcze inna aktualizacja
Uzasadnienie firmy Apple dotyczące obsługi błędów jest teraz dostępne tutaj . Było również kilka interesujących dyskusji na liście mailingowej swift-evolution . Zasadniczo John McCall jest przeciwny błędom typograficznym, ponieważ wierzy, że większość bibliotek i tak będzie zawierała ogólny przypadek błędu, a wpisane błędy raczej nie dodadzą zbyt wiele do kodu poza szablonem (użył terminu „aspiracyjny blef”). Chris Lattner powiedział, że jest otwarty na błędy maszynowe w Swift 3, jeśli może działać z modelem odporności.
źródło
Swift martwi się, że zestawienie sprawy nie obejmuje wszystkich spraw, aby to naprawić, musisz utworzyć domyślną sprawę:
źródło
catch
zestawieniach.func method() throws(YourErrorEnum)
, a nawetthrows(YourEnum.Error1, .Error2, .Error3)
wie, co można wyrzucićByłem również rozczarowany brakiem typu, jaki funkcja może rzucać, ale teraz dostaję to dzięki @rickster i podsumuję to w ten sposób: powiedzmy, że moglibyśmy określić typ, który funkcja rzuca, mielibyśmy coś takiego:
Problem polega na tym, że nawet jeśli nic nie zmienimy w myFunctionThatThrows, jeśli dodamy po prostu przypadek błędu do MyError:
mamy przerąbane, ponieważ nasze polecenie do / try / catch nie jest już wyczerpujące, podobnie jak każde inne miejsce, w którym nazwaliśmy funkcje, które wyrzucają MyError
źródło
catch {}
na dole każdego bloku jest prawdopodobnie gorszy. Mam nadzieję, że kompilator w końcu automatycznie wywnioskuje typy błędów tam, gdzie może, ale nie byłem w stanie potwierdzić.Teraz potwierdź numer:
źródło
Utwórz wyliczenie w ten sposób:
Utwórz metodę taką jak:
Teraz sprawdź, czy błąd występuje, czy nie, i napraw go:
źródło