Przy tworzeniu oprogramowania niemal zawsze trzeba coś zmieniać. Może to być spowodowane sprawami wewnętrznymi, takimi jak nasze (bądź teamu) widzimisie w stosunku do projektu oraz tymi zewnętrznymi. A to klient/konsument będzie chciał coś zmienić, a to regulacje prawne ulegną przemianie i będzie trzeba dostosowywać aplikację do obowiązujących przepisów, albo znajdzie się jeszcze coś innego.
Czasami jednak nie będziemy mogli nawet zmodyfikować naszej cześci aplikacji od tak, modyfikując istniejący kawałek kodu, bo:
- Wymagało by to większego pokładu czasu oraz wysiłku niż stworzenie komponentu od nowa.
- Złamalibyśmy drugą zasadę SOLID open/closed principle, która jasno mówi, że kod powinien być zablokowany na modyfikację, a otwarty na rozszerzenia.
- Z naszego systemu korzystają inni programiści, lub inne osoby.
- Zrobilibyśmy takie spaghetti w kodzie, że przyszłe pokolenia programistów nie dały by sobie z nim rady.
Co zatem kiedy stworzymy daną funkcjonalność, która z obecnego punktu widzenia jest przestarzała, nie powinno się z niej korzystać, bo np. jest mniej wydajna, lub narażona na jakieś złamanie bezpieczeństwa?
Usuńmy ją?
Usuniecie to jedno z możliwych rozwiązań. Ale wyobraź sobie, że tworzysz bibliotekę, powiedzmy najbardziej popularny system do płatności na świecie. Z twojego rozwiązania korzystają tysiące aplikacji. Co się stanie kiedy usuniesz publiczną metodę z której korzysta większość z nich?
W nowo powstających aplikacjach nic, pobiorą najnowszą wersję biblioteki i użyją zgodnie z przeznaczeniem, ale co się stanie gdy stare systemy ją zaktualizują do najnowszej wersji? Jeden z odbiorców twojej biblioteki używa przestarzałej metody w 3 tysiącach różnych miejsc, a teraz to wszystko nie działa! Ktoś dzisiaj nie będzie spał po nocach. Oczywiście to przykład nad wyrost, taki deweloper sam sobie jest winny.
Ale gdy usuniesz cokolwiek, ten programista będzie niezadowolony, pomyśli, że Twoja biblioteka jest niestabilna i nieprzemyślana. Nawet nie dałeś mu żadnych wskazówek, ani informacji o swoich zmianach. Po prostu dzisiaj działało, a następnego dnia już nie.
Podmieńmy ją
To może złamać zasadę OCP, ponieważ modyfikujesz coś istniejącego. Jednak, gdy usuniesz starą metodę i podmienisz na nową o tych samych parametrach i nazwach to rzeczywiście odbiorcy tego nie zauważą, a ich program się kompiluje. I tu leży problem, ty coś zmieniłeś, może dodałeś jakąś funkcję, zedytowałeś dokumentację, w każdym razie działa to inaczej, ale nikt z zewnątrz nie jest tego świadomy.
Jak ja używam jakiejś technologii to lubię ją znać, chcę wiedzieć co robi (niekoniecznie jak to robi, chociaż mnie akurat takie rzeczy też interesują). A jak coś co znam i z czego korzystam się zmieni to moja aplikacja może zachować się inaczej. Raczej nie będę z tego zadowolony. Bedzie mi nawet ciężko znaleźć przyczynę, bo wina nie leży po mojej stronie.
Atrybut Obsolete
Platforma .NET daje nam możliwość określania jakiejś cześci kodu (w przykładzie użyję metody) atrybutem ObsoleteAttribute. Słowo obsolete tłumacząc oznacza przestarzały, a atrybut ten należy do przestrzeni nazw System. Proces polega na dodaniu atrybutu, który wstawia się nad definicją metody. Wygląda to tak:
[Obsolete] public static string Method() { return "Obsolete method"; }
Atrybut ten informuje osobę korzystającą z metody, że korzysta z metody przestarzałej. Informacja ta widnieje w środowisku uruchomieniowym IDE przez różne oznaczenia:
oraz w komunikatach typu warning:
Platforma oznaczy element jako deprecated, co również oznacza przestarzały. Oprócz metod, atrybut obsolete można przypinać do innych elementów, np. zdarzeń, delegatów, klas, struktur, pól, indekserów, enumów, czy właściwości.
Warto, żeby te zostawione metody przynajmniej “jakoś” działały. Pamiętaj, obsolete oznacza, że to co jest oznaczone jest przestarzałe, ale z jakiegoś powodu zostało w kodzie. Twórca stworzył nową wersję, z której teraz powinieneś korzystać, ale dał Ci jeszcze czas na zmianę, nie powodując u Ciebie pewnych problemów.
Teraz należy korzystać z czegoś innego
Istnieje możliwość dodania parametru do atrybutu, który jest zwykłym stringiem. Zazwyczaj służy on do informacji z jakiego zastępnika powinieneś korzystać.
[Obsolete("Use now NewMethod instead this")] public static string Method() { return "Obsolete method"; }
Komunikat wygląda teraz tak:
Można również opisywać dlaczego ten parametr jest przestarzały, ale w moim odczuciu do tego jest dokumentacja lub artykuł na temat nowej wersji. Poza tym często to co dzieje się w środku nie jest wypuszczane na zewnątrz.
Bardzo istotna zmiana
Ale w naszym projekcie wystąpiła zmiana kluczowa. Każda osoba korzystająca z biblioteki musi zacząć korzystać z nowej metody.
Może się tak zdarzyć, np. jeżeli poprzednia wersja w znaczący sposób powoduje niewłaściwe skutki, np. są dziury w zabezpieczeniu. W tym przypadku również możesz skorzystać z atrybutu Obselete z drugim parametrem. Parametr ten powoduje, że zamiast warninga przy próbie korzystania z przestarzałego kodu dostanie się error.
[Obsolete("Use now NewMethod instead this", true)] public static string Method() { return "Obsolete method"; }
Otrzymamy komunikat:
i nie uda nam się uruchomić programu do czasu, kiedy nie poprawimy metody na aktualną wersję.
Zaletą tego rozwiązania jest to, że ostrzegamy naszych odbiorców. Obsolete jest komunikatem od nas do nich.
Kiedy warto stosować ten atrybut?
Ja przeważnie widzę go w różnych technologiach, szczególnie w Xamarinie, który sam w sobie korzysta z zewnętrznych technologii takich jak np. Android SDK. Jeżeli tam jest coś przestarzałe, to powinno być i w porcie do Xamarina, ale powinno działać (z jakimiś defektami).
Jeżeli robisz projekt sam i nikt z niego nie korzysta raczej nie ma sensu używać atrybutu. Pewnie projekt jest mały i w miarę go znasz, wiesz co zmieniasz, i jak to na niego wpłynie. Jeżeli robisz jednak apkę z myślą o byciu zewnętrznym dostawcą warto stworzyć architekturę dostosowującą się do zmian. Tutaj z pomocą może przyjść atrybutu Obsolete jako ostrzeżenie, albo nawet zakaz stosowania danych kawałków kodu.
Podsumowanie
Atrybut Obsolete informuje programistę, że korzysta z przestarzałej struktury kodu. Poza informacją może on uniemożliwić korzystania z tych fragmentów kodu, jednak nadal pełni rolę informatora. Odbiorcy programiście, który zobaczy taki komunikat zaświeci się żółta (lub czerwona) lampeczka. Poczyta o zmianach jakie zaszły w systemie, pozna zagrożenia wynikające z dalszego korzystania z tej części kodu i zdecyduje, czy ją zmienić. Pod tym wszystkim kryje się również komunikat od twórcy “Dostarczony system się zmienił, bądź tego świadomy i w miarę możliwość zmodyfikuj swój produkt”.
Jednak Obsolete jest stworzony do ustalonych celów, nie należy rzucać nim na prawo i lewo, bo oznacza to tylko, że Twój system jest nieprzemyślany. Czasami warto też zastanowić się czy nie warto poświecić zasady otwarte zamknięte na rzecz modyfikacji czegoś istniejącego, bo może to po prostu się opłacić. Wszystko zależy od konkretnej sytuacji.