Sytuacja problemowa
W trakcie kariery programistycznej nabyłem wiele doświadczeń z projektów w jakich brałem udział.
Wśród praktyk jakie wyciągnąłem było wynoszenie stałych do interfejsu.
Przydawało się to zwłaszcza do tworzenia zmiennych dla endpointów – wszystko było w jednym pliku, nie było Magic String a dodatkowo łatwość w wyłapaniu duplikatów.
public interface Endpoints { String FILE_REPORT_URL = "/file/report"; String FILE_SCAN_URL = "/file/scan"; String FILE_SCAN_UPLOAD_URL = "/file/scan/upload_url"; String FILE_RESCAN_URL = "/file/rescan"; String FILE_DOWNLOAD_URL = "/file/download"; String FILE_FEED_URL = "/file/feed"; String FILE_SEARCH_URL = "/file/search"; }
W dodatku wszystkie zmienne w interfejsie są publiczne, statyczne, finalne – co tylko uprzyjemnia ich wykorzystywanie.
@GetMapping(Endpoints.FILE_DOWNLOAD_URL) public PfgDocument downloadFile(File file) { return new PfgDocument(file); }
Czystość, łatwość, przejrzystość, więc czemu jest źle?
Jedna życzliwa osoba, uświadomiła mnie, że mam złą praktykę, postanowiłem dociec co w tym złego robiąc research. Największe problemy jakie wynikają ze stosowania tej praktyki to:
- Interfejsy powinny określać zachowanie. Interfejs ma definiować umowę między interfejsem a klasami implementującymi. Constant Interface nie realizuje tego założenia OOP.
- Interfejsy powinny określać typy. W Javie większość głównych typów powinna być reprezentowana przez interfejsy. Constant Interface z założenia nie opisuje typu.
- Zanieczyszczenie przestrzeni nazw – stałe z interfejsu pojawiają się w przestrzeni nazw wszystkich klas implementujących go oraz ich podklas.
Rozwiązanie?
@Koziolek666 zaleca wykorzystać enum:
enum Endpoint { FILE_REPORT_URL("/file/report"), FILE_SCAN_URL("/file/scan"), FILE_SCAN_UPLOAD_URL("/file/scan/upload_url"), FILE_RESCAN_URL("/file/rescan"), FILE_DOWNLOAD_URL("/file/download"), FILE_FEED_URL("/file/feed"), FILE_SEARCH_URL("/file/search"); Endpoint(String location) { this.location = location; } final String location; }
Dodatkową korzyścią jest sprawdzenie w trakcie kompilacji czy dany endpoint jest enumem, więc zapobiega to powstaniu sytuacji, gdzie możliwe jest podawanie niekontrolowanych wartości:
Connection connectToEndpoint(String endpoint){ //... uzyskanie połączenia } // i dalej connectToEndpoint("Ala ma kota, a kot ma stringozę");
Wnioski
Constant Interface Anti-Pattern ma realne uzasadnienie leżące u podstaw OOP. Dobrze jest stosować się do właściwych zasad i w nowych projektach wykluczyć jego stosowanie tego antywzorca.
Pozostaje pytanie, czy jeśli antywzorzec jest już w naszym projekcie, to warto rozpocząć jego wyeliminowanie? Refaktoryzacja w kierunku wyeliminowania antywzorca nie jest trudna i nie powinna stanowić problemu, więc warto rozpatrzeć jej wykonanie.
Jednak sam wzorzec nie jest niebezpieczny. Zazwyczaj nikt nie implementuje Constant Interface w klasach, więc unikamy tym zaśmiecania API. Najbardziej dokuczliwy jest tutaj kunszt wytwarzania oprogramowania, który przez takie antywzorce jest niedotrzymany 😉.