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 😉.
Szkolenia live dla developerów • praktyczna wiedza, realne case’y, zero lania wody 

