Architektura oprogramowaniaInżynieria oprogramowaniaWzorce projektowe

Constant Interface Anti-Pattern – częsty błąd programistów w tym mój

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 😉.

Show Buttons
Hide Buttons