Jakiś czas temu z dużego projektu nad którym obecnie pracuję zostały całkowicie usunięte i zaorane biblioteki Enterprise Library. I życie stało się prostsze. Wykorzystywaliśmy je jedynie do logowania i obsługi wyjątków i JAKOŚ trzeba było te funkcjonalności w systemie zachować. O ile w przypadku logowania wybór jest dość naturalny (wypasiony log4net), o tyle z wyjątkami nie było już tak “oczywiście”.
Na szczęście dzięki własnej fasadzie na EHAB, o której pisałem kilka miesięcy temu, nie było konieczności zastępowania wielolinijkowych snippetów w całym kodzie źródłowym. Wystarczyła elegancka podmiana samej implementacji pod fasadą i bez praktycznie żadnych zmian “na zewnątrz” pozbyliśmy się rozpasionych i totalnie “niezarządzalnych” plików konfiguracyjnych EntLiba.
Założenie było dość proste: napisać funkcjonalnie równoważny EHABowi komponent, który nie wymaga nie wiadomo czego, a po prostu DZIAŁA. Konfigurowalna “z zewnątrz” obsługa wyjątków to moim zdaniem rozwiązanie dość dyskusyjne i – raczej średnio praktyczne. Dlatego też “nowe” polityki są definiowane wyłącznie w kodzie. Oto jak zmodyfikowane zostało przedstawione poprzednie rozwiązanie:
Każdy zbiór zasad musi implementować interfejs IExceptionHandlingPolicy:
1: public interface IExceptionHandlingPolicy 2: { 3: bool Handle(Exception exc); 4: }
Na przykład tak:
1: public class DataAccessPolicy : IExceptionHandlingPolicy 2: { 3: #region Singleton 4: 5: public static readonly IExceptionHandlingPolicyInstance = new DataAccessPolicy(); 6: 7: private DataAccessPolicy() 8: { 9: 10: } 11: 12: #endregion 13: 14: private readonly ILog _log = LogManager.GetLogger(typeof(DataAccessPolicy)); 15: 16: public bool Handle(Exception exc) 17: { 18: // log exception information 19: _log.Error(exc); 20: 21: // do not rethrow these exceptions 22: if (exc is NotImportantException) 23: return false; 24: 25: // rethrow all other exceptions 26: return true; 27: } 28: }
Dzięki takiej implementacji nadal mamy możliwość sterowania LOGOWANIEM informacji o wyjątku w KONFIGURACJI – a to dzięki log4net. Tam możemy wpisać czy informacja o danym wyjątku idzie do EventLoga, na konsolę, na maila czy jeszcze gdzieś indziej. I taka konfiguracja jest jak najbardziej OK, w przeciwieństwie do definiowania w zewnętrznym pliku takich reguł jak “czy wyrzucić wyjątek wyżej”.
Powyższa Implementacja – jawne sprawdzanie typu wyjątku i decyzja na tej podstawie – może wydawać się głupia, naiwna i amatorska, ale moim zdaniem… nie ma w tym nic złego. Reguły takie tak czy siak nie powinny się zbytnio rozrastać – w końcu ile różnych typów wyjątków chcemy rozpoznawać na tym etapie na takim poziomie szczegółowości?
Wykorzystanie tak zdefiniowanych strategii wymagało jedynie niewielkiej modyfikacji istniejącej już fasady:
1: public static class HandleExceptions 2: { 3: public static void DataAccess(Action operation) 4: { 5: Handle(operation, DataAccessPolicy.Instance); 6: } 7: 8: public static T DataAccess<T>(Func<T> operation) 9: { 10: return Handle(operation, DataAccessPolicy.Instance); 11: } 12: 13: public static void DataAccess(Exception exception) 14: { 15: Handle(exception, DataAccessPolicy.Instance); 16: } 17: 18: /// <summary> 19: /// Performs a given function handling exceptions that might occur. 20: /// </summary> 21: /// <returns>Result of the operation or a default value for a given type.</returns> 22: private static T Handle<T>(Func<T> operation, IExceptionHandlingPolicy policy) 23: { 24: T ret = default(T); 25: 26: Handle(() => 27: { 28: ret = operation(); 29: }, policy); 30: 31: return ret; 32: } 33: 34: /// <summary> 35: /// Performs a given procedure handling exceptions that might occur. 36: /// </summary> 37: private static void Handle(Action operation, IExceptionHandlingPolicy policy) 38: { 39: try 40: { 41: operation(); 42: } 43: catch (Exception exc) 44: { 45: if (policy.Handle(exc)) 46: throw; 47: } 48: } 49: 50: /// <summary> 51: /// Handles a given exception. 52: /// </summary> 53: private static void Handle(Exception exception, IExceptionHandlingPolicy policy) 54: { 55: if (policy.Handle(exception)) 56: throw exception; 57: } 58: }
Co o tym myślicie? Należy zdawać sobie sprawę z tego, że jawne opakowanie kodu w metodę HandleExceptions(…) występuje sporadycznie w kluczowych miejscach i MOŻE BYĆ traktowane jako aspekt… tyle że zaimplementowany domowym sposobem.
Spoko, kwestia nazewnictwa. jak metoda sie nazywa Handle i zwraca true lub false to raczej bym sie spodziewal innego dzialania niz u Ciebie.
To handle to tak jakby rethrow czy nawet shouldrethrow.
od jakiegos czasu stosuje metode typu: olej wszystkie wyjatki z przedzialu, jedynie nie zaloguj. i zakazdym razem jest to robione w kodzie. nie ma to byc konfigurowalne zewnatrz. zreszta zmiana taka by robilo rethrow zewnatrz moze spodowoac ze aplikacja sie wysypie w miejscu w ktorym nie powinna – to znacyz zwroci wyjatek ktory jest nie oczekiwany i to na przyklad przy metodzie indexOf kiedy zakladamy ze wyjatek moze wystapic.
trzeba tylko uwazac by HandleExceptions nie impelemntowalo zbyt duzo modulow, ale zakladajac ze jest ich od 3 do 10 w rozwiazaniu to sie zabardzo nie rozrosnie, wrazie co grupowanie elementow w VS + partial class i da sie tym zarzadzac :)
Gutek
A może zamiast log4net – http://nlog-project.org?
@dario-g:
W czym nlog jest lepsze od log4net? Nie to zebym oba rozwiazania analizowal i potrafil powiedziec ‘w czym jest lepsze log4net od nlog’ – po prostu jestem ciekaw:). Po zastapieniu EntLiba przez log4net bylem oszolomiony mozliwosciami (wlasciwie dopiero wtedy logowanie nabralo sensu). Czy nlog ma jakis taki killer-feature?