Mamy większą aplikację lub grę (nieistotne), która podczas rozruchu ładuje różne dane z plików zewnętrznych – zawartość plików konfiguracyjnych, obrazy itd. Niektóre dane są wymagane (np. obrazy), bez których program nie może działać prawidłowo, a niektóre są potrzebne (np. konfiguracja), choć możliwe jest pominięcie ich ładowania. Podczas zamykania programu, zwalniana jest pamięć i pewne dane są z powrotem zapisywane na dysku (np. konfiguracja).
Kod źródłowy składa się z dziesiątek klas, służących do reprezentowania tych danych. Jakieś listy z obrazami, drzewka obiektów z najróżniejszymi danymi zassanymi z konfigów itd. Niektóre z tych obiektów opakowują po kilka innych, tworząc drzewka.
To, tak naprawdę nie ma znaczenia, ponieważ mięso problemu leży gdzie indziej.
Ale tak przy okazji; ja zawsze takich przypadkach opieram się na automatycznej serializacji i deserialziacji obiektów.
To mnie całkowicie zwalnia z ręcznym wiązaniem obiektów z danymi i łatwo pozwala na rozwijanie modelu danych dla konfiguracji z pełnym wsparciem odczytu konfiguracji z różnych wersji modelu danych obiektów konfiguracji.
Tu jest trochę zabawy, aby zrobić to ogólnie, ale warto.
I teraz meritum – weźmy pod lupę rozruch. Podczas ładowania danych może się zdarzyć, że któregoś potrzebnego pliku nie ma (np. obraz) i leci wyjątek, albo zawartość pliku konfiguracyjnego jest skopana (zmatolona struktura xml)
Przy poprawnej deserializacji jesteś odporny na takie problemy.
Poprawnej, tj takiej, która potrafi odczytać dane z pliku, który nie jest w 100% zgodny z modelem obiektowym.
i trzeba zapytać użytkownika co robić – resetować czy anulować rozruch (ubić program). To samo przy zamykaniu programu – nie da się zapisać konfiguracji, bo odmowa dostępu lub jakikolwiek inny problem.
W jaki sposób kontrolujecie obsługę błędów i dajecie użytkownikowi możliwość wyboru co robić? A dokładniej pytając, w jaki sposób ładujecie dane do obiektów, aby z jednej strony dało się poinformować użytkownika i dopytać co robić jeśli wystąpi błąd, a z drugiej strony, aby kod źródłowy nie wyglądał jak śmietnik?
Samo zagadnienie wydaje się trudne do ogarnięcia, więc dorzucę jeszcze trochę, coby było ciekawiej. ;)
Mamy obiekt, który wewnątrz posiada osadzonych kilka mniejszych. Główny obiekt np. reprezentuje całe drzewo z pliku konfiguracyjnego i ma główną metodę LoadFromFile
. W tej metodzie otwierany jest plik konfiguracyjny, a nastepnie wołane są metody LoadFromTree
każdego osadzonego obiektu, przekazując im konkretną gałąź drzewa konfiguracji. Każdy z tych obiektów ładuje swoje porcje danych.
Meritum – kilka osadzonych obiektów załaduje swoje porcje, ale jeden z nich nie będzie w stanie i walnie wyjątkiem. W jaki sposób obsłużycie ten błąd, tak aby użytkownik dostał komunikat i mógł pominąć ładowanie danej gałęzi lub całkowicie przerwać proces rozruchu?
Ty się zastanów co chcesz zrobić.
Czym innym jest pominięcie brakującej danej podczas odczytu i nie jest to sytuacja krytyczna, a czym innym brak istotnych danych dla aplikacji.
To pierwsze to na pewno nie wyjątek raczej ostrzeżenie do logu.
To drugie, to zawsze wyjątek.
I po trzecie - moim zdaniem, możesz całkowicie pominąć kwestie związane z powiadamianiem i oczekiwaniem decyzji od użytkownika.
Nawet jeśli dostarczysz mechanizm który będzie zbierał feedback od usera to i tak najprawdopodobniej będzie on (feedback, nie mechanizm) nieprawidłowy.
Wg mnie lepiej całkowicie darować sobie moment czekania na decyzję usera. A może i nawet powiadamiania...
Dlaczego?
Ano dlatego, że najpewniej user w przypadku problemu i tak nic z nim nie zrobi lub robi źle.
O ile Ty będziesz userem, to pewnie będziesz w stanie ocenić czy brak jakiejś konfiguracji jest krytyczny i nie można jej pominąć lub nie.
Ty tak, a inni? Nie sądzę.
Poza tym, co to za informacja "nie udało się załadować XYZ czy kontynuować"?
Kontynuujemy, ale nie załadowano - nie wiem - layoutu programu i nic nie widać.
Apka niby działa, ale co z tego?
Lepiej samemu w kodzie zdecydować co jest krytyczne i musi być, a co nie jest nie zostało załadowane i jedziemy z ustawieniami domyślnymi.
Pozwolicie wydostać się wyjątkowi poza główną metodę LoadFromFile
czy obsłużycie go wewnątrz?
Ogólnie chciałbym się dowiedzieć jakie macie techniki pisania kodu realizującego taką kontrolę błędów i interakcję z użytkownikiem. Ja wiem, że najłatwiej byłoby komunikat wyświetlić wewnątrz LoadFromFile
i w razie wciśnięcia przez użytkownika przycisku Abort
po prostu dupnąć Halt
i mieć problem z głowy, ale takie coś raczej nazwałbym potworkiem, a nie dobrą architekturą programu.
To można rozwiązać na wiele rożnych sposobów.
Najprostszy to implementacja interfejsu np. IConfiguration
przez delegację dla każdej klasy, która posiada zapis/odczyt konfiguracji.
Implementacja może być dostarcza różna i w różny sposób - nawet przez kontener IoC.
No, zapraszam do dyskusji.
Tu chyba nie ma za bardzo o czym dyskutować, ponieważ wiele zależy od tego czego oczekujesz i jak to już zaimplementowałeś.
Możemy dyskutować o np. czymś co ja nazywam Generic Persistance Layer, ale...
To jest dość skomplikowane i większość ludzi nie widzi sensu w tym aby mieć jeden ogólny mechanizm na odczyt i zapis konfiguracji. Taki mechanizm który przynosi całkowicie przezroczystą obsługę konfiguracji hierarchicznej, gdzie każde node w konfiguracji może być przechowywany w osobnym pliku, jednym pliku dla wielu node'ów lub w bazie danych.
Mi to było potrzebne, to sobie zrobiłem.
Ale nie będę dyskutował o szczegółach, bo to nie ma większego sensu.