Rozważ sytuację, w której mam trzy (lub więcej) sposoby wykonywania obliczeń, z których każdy może zawieść z wyjątkiem. Aby spróbować każdego obliczenia, aż znajdziemy taki, który się powiedzie, wykonałem następujące czynności:
double val;
try { val = calc1(); }
catch (Calc1Exception e1)
{
try { val = calc2(); }
catch (Calc2Exception e2)
{
try { val = calc3(); }
catch (Calc3Exception e3)
{
throw new NoCalcsWorkedException();
}
}
}
Czy jest jakiś przyjęty wzorzec, który pozwala osiągnąć to w lepszy sposób? Oczywiście mógłbym zawinąć każde obliczenie w metodę pomocniczą, która zwraca wartość null w przypadku niepowodzenia, a następnie po prostu użyć ??
operatora, ale czy istnieje sposób na zrobienie tego bardziej ogólnie (tj. Bez konieczności pisania metody pomocniczej dla każdej metody, której chcę użyć )? Myślałem o napisaniu metody statycznej przy użyciu typów generycznych, która zawija dowolną metodę w try / catch i zwraca wartość null w przypadku niepowodzenia, ale nie jestem pewien, jak bym to zrobił. Jakieś pomysły?
źródło
String
doDate
usingSimpleDateFormat.parse
i muszę wypróbować kilka różnych formatów w kolejności, przechodząc do następnego, gdy rzuca wyjątek.Odpowiedzi:
O ile to możliwe, nie używaj wyjątków dla przepływu kontroli lub nietypowych okoliczności.
Ale żeby odpowiedzieć bezpośrednio na twoje pytanie (zakładając, że wszystkie typy wyjątków są takie same):
źródło
Calc1Exception
,Calc2Exception
iCalc3Exception
mają wspólną klasę bazową.continue
w bloku catch ibreak
po bloku catch, aby pętla kończyła się, gdy obliczenia działają (dzięki Lirikowi za ten bit)break
Oświadczenie następującecalc();
wtry
(i żadnecontinue
oświadczenie) może być trochę bardziej idiomatyczne.Aby zaoferować alternatywę „poza polem”, a co powiesz na funkcję rekurencyjną ...
UWAGA: w żadnym wypadku nie twierdzę, że jest to najlepszy sposób wykonania pracy, po prostu inny sposób
źródło
return DoCalc(c++)
jest równoważnereturn DoCalc(c)
- wartość po zwiększeniu nie zostanie przekazana głębiej. Aby to działało (i wprowadzało więcej niejasności), mogłoby być bardziej podobnereturn DoCalc((c++,c))
.Możesz spłaszczyć zagnieżdżenie, wprowadzając je do metody takiej jak ta:
Ale podejrzewam, że prawdziwym problemem projektowym jest istnienie trzech różnych metod, które zasadniczo robią to samo (z perspektywy dzwoniącego), ale rzucają różne, niepowiązane wyjątki.
Zakłada się, że te trzy wyjątki nie są ze sobą powiązane. Jeśli wszystkie mają wspólną klasę bazową, lepiej byłoby użyć pętli z jednym blokiem catch, jak zasugerowała Ani.
źródło
Staraj się nie kontrolować logiki opartej na wyjątkach; należy również pamiętać, że wyjątki powinny być rzucane tylko w wyjątkowych przypadkach. Obliczenia w większości przypadków nie powinny zgłaszać wyjątków, chyba że uzyskują dostęp do zasobów zewnętrznych lub analizują ciągi lub coś takiego. W każdym razie w najgorszym przypadku postępuj zgodnie ze stylem TryMethod (takim jak TryParse ()), aby hermetyzować logikę wyjątków i sprawić, by przepływ sterowania był utrzymywany i czysty:
Inna odmiana wykorzystująca wzorzec Try i łącząca listę metod zamiast zagnieżdżonych, jeśli:
a jeśli foreach jest trochę skomplikowane, możesz to wyjaśnić:
źródło
Utwórz listę delegatów do funkcji obliczeniowych, a następnie korzystaj z pętli while, aby przechodzić między nimi:
To powinno dać ci ogólne pojęcie, jak to zrobić (tj. Nie jest to dokładne rozwiązanie).
źródło
Exception
wtedy łapać . ;)Exception
.To wygląda na pracę dla ... MONADS! W szczególności monada Może. Zacznij od monady Może, jak opisano tutaj . Następnie dodaj kilka metod rozszerzających. Napisałem te metody rozszerzające specjalnie dla problemu, jak go opisałeś. Zaletą monad jest to, że możesz napisać dokładne metody rozszerzenia potrzebne w twojej sytuacji.
Użyj go w ten sposób:
Jeśli często wykonujesz tego rodzaju obliczenia, być może monada powinna zmniejszyć ilość gotowego kodu, który musisz napisać, jednocześnie zwiększając czytelność twojego kodu.
źródło
Maybe
, nie czyni tego monadycznym rozwiązaniem; wykorzystuje zero monadycznych właściwości,Maybe
więc równie dobrze może po prostu użyćnull
. Poza tym stosowane „monadicly” to byłby odwrotny zMaybe
. Prawdziwe rozwiązanie monadyczne wymagałoby użycia monady stanu, która zachowuje pierwszą nie-wyjątkową wartość jako swój stan, ale byłoby to przesadą, gdyby działała normalna ocena łańcuchowa.Inna wersja metody try . Ten pozwala na wyjątki wpisane, ponieważ istnieje typ wyjątku dla każdego obliczenia:
źródło
&&
zamiast tego między każdym warunkiem.W Perlu możesz to zrobić
foo() or bar()
, co zostanie wykonane,bar()
jeśli sięfoo()
nie powiedzie. W C # nie widzimy tej konstrukcji „if fail, then”, ale istnieje operator, którego możemy użyć do tego celu: operator null-coalesce??
, który jest kontynuowany tylko wtedy, gdy pierwsza część ma wartość null.Jeśli możesz zmienić podpis swoich obliczeń i jeśli zawiniesz ich wyjątki (jak pokazano w poprzednich postach) lub przepisasz je, aby
null
zamiast tego zwracały, twój łańcuch kodu staje się coraz krótszy i nadal łatwy do odczytania:Kiedyś następujące zamienniki dla swoich funkcji, co prowadzi do wartości
40.40
wval
.Zdaję sobie sprawę, że to rozwiązanie nie zawsze będzie miało zastosowanie, ale zadałeś bardzo ciekawe pytanie i uważam, mimo że wątek jest stosunkowo stary, że jest to wzorzec, który warto rozważyć, kiedy można to naprawić.
źródło
Biorąc pod uwagę, że metody obliczeniowe mają ten sam podpis bezparametrowy, można zarejestrować je na liście, iterować po tej liście i wykonywać metody. Prawdopodobnie byłoby jeszcze lepiej, gdybyś użył
Func<double>
znaczenia „funkcja zwracająca wynik typudouble
”.źródło
Możesz użyć Task / ContinueWith i sprawdzić wyjątek. Oto fajna metoda rozszerzenia, dzięki której jest ładna:
źródło
Jeśli rzeczywisty typ zgłoszonego wyjątku nie ma znaczenia, możesz po prostu użyć bloku catch bez typu:
źródło
if(succeeded) { break; }
po złapaniu.I używaj go w ten sposób:
źródło
Wydaje się, że intencją PO było znalezienie dobrego schematu rozwiązania jego problemu i rozwiązania obecnego problemu, z którym w tym momencie się borykał.
Widziałem wiele dobrych wzorców unikających zagnieżdżonych bloków try catch , opublikowanych w tym kanale, ale nie znalazłem rozwiązania problemu, który jest cytowany powyżej. Oto rozwiązanie:
Jak wspomniał powyżej OP, chciał stworzyć obiekt opakowujący, który powraca
null
w przypadku niepowodzenia . Nazwałbym to kapsułą ( kapsuła bezpieczna dla wyjątków ).Ale co, jeśli chcesz utworzyć bezpieczny pod dla wyniku typu odwołania zwracanego przez funkcje / metody CalcN ().
Możesz więc zauważyć, że nie ma potrzeby „pisania metody pomocniczej dla każdej metody, której chcesz użyć” .
Te dwa rodzaje strąków (dla
ValueTypeResult
S iReferenceTypeResult
S) są na tyle .Oto kod
SafePod
. Nie jest to jednak pojemnik. Zamiast tego tworzy bezpieczne opakowanie delegata dla obuValueTypeResult
s iReferenceTypeResult
s.W ten sposób można wykorzystać operator koalescencji zerowej w
??
połączeniu z siłą pierwszorzędnych podmiotów obywatelskichdelegate
.źródło
Masz rację co do zawijania wszystkich obliczeń, ale powinieneś zawijać je zgodnie z zasadą powiedz-nie-pytaj.
Operacje są proste i mogą niezależnie zmieniać ich zachowanie. I nie ma znaczenia, dlaczego upadają. Jako dowód możesz zaimplementować calc1DefaultingToCalc2 jako:
źródło
Wygląda na to, że obliczenia zawierają więcej ważnych informacji do zwrócenia niż same obliczenia. Być może bardziej sensowne byłoby dla nich wykonanie własnej obsługi wyjątków i zwrócenie klasy „wyników”, która zawiera informacje o błędach, wartościach itp. Pomyśl tak, jak klasa AsyncResult postępuje według wzorca asynchronicznego. Następnie możesz ocenić rzeczywisty wynik obliczeń. Możesz to zracjonalizować, myśląc w kategoriach, że jeśli obliczenia się nie powiodą, są tak samo informacyjne, jak gdyby przeszły. Dlatego wyjątek to informacja, a nie „błąd”.
Oczywiście bardziej się zastanawiam nad ogólną architekturą, której używasz do wykonywania tych obliczeń.
źródło
while (!calcResult.HasValue) nextCalcResult()
, zamiast listy Calc1, Calc2, Calc3 itd.A co ze śledzeniem działań, które wykonujesz ...
źródło
track
w tym przypadku) i wiem dokładnie, który segment w moim kodzie spowodował awarię bloku. Może powinieneś był opracować, aby poinformować członków, takich jak DeCaf, żetrack
wiadomość jest wysyłana do niestandardowej procedury obsługi błędów, która umożliwia debugowanie kodu. Wygląda na to, że nie rozumiał twojej logiki.