Zamień bóla na enuma

Dlaczego zamienić? Moim zdaniem czytelniej i jasno sformułowana myśl i łatwiej zrozumieć. Nie chodzi o prosty przypadek, gdzie zamiana polegałaby na zamianie true/false na MyEnum.True/MyEnum.False – nie nie, to byłoby szaleństwem. Ale może od razu do kodu, bo czas nagli dzisiaj.

Pierwszy przypadek, wszystko działa jak należy:

Jakaś tam klasa filtrów, przyjmuje (@3) identyfikator, nazwę i bit, które oznacza że filtr będzie grupą lub nie (w zasadzie nie wiadomo, czym jest nie-grupa)

Teraz wykorzystanie:

Zasadniczo prosty kod, jest jakaś lista filtrów (@5). Następnie dodajemy filtry; pierwsze id, jakaś nazwa i true, potem id, nazwa i false. Osoba nowa w projekcie, lub nowa w tej części kodu na pierwszy rzut oka nie będzie wiedzieć co to oznacza. Następne dwie linijki są trochę bardziej rozgadane, bo jawnie mówią jak interpretować ostatniego bool’a.
Dalej szukamy tylko tych, gdzie IsGroup jest ustawione na true (@12). Jeszcze dalej wołamy metodę, która robi to samo, ale na podstawie parametru wejściowego (@18). Najczęściej gdy się tworzy takie metody nie zwraca się dużej uwagi na nazwę parametru, bo albo to R# wygenerował, a on robi dobrze, albo jest to jedna linijka i nie muszę się nad tym za bardzo skupiać. Rzeczywiście sama metoda jest prosta, trudno się pomylić do czego służy parametr i gdzie go wykorzystać.
Spójrzcie teraz na klienta tej metody, ma przesłać dwa parametry, listę filtrów (to proste), a potem flagę która oznacza group – group co?! Trzeba zajrzeć do ciała metody, żeby sprawdzić co oznacza ten parametr i jak mam go wykorzystać. Można też poprawić nazwę parametru lub (czuję obrzyd jak to piszę) napisać dokumentacja.

A co gdyby napisać kod w taki sposób:

Na scenę wchodzi tajemniczy FilterType:

Jak widać, tajemniczy bohater to enum, które jawnie definiuje jakiego rodzaju może być filtr:

  • 0 – nie został określony, nikt nie wie, nikt nie słyszał. Jeśli w ten sposób został zainicjalizowany, to najprawdopodobniej nie zostanie nigdy wykorzystany
  • 1- Typ grupowy
  • 2- Typ pojedynczy (aha, teraz to trochę jaśniejsze)

Prosty klient klasy Filtr, może wyglądać jakoś tak:

Na początku ponownie tworzymy listę, tym razem nie trzeba zgadywać co oznacza true/false. Nie trzeba także uciekać się do używania nazwanych parametrów aby rozjaśnić intencje.
Następnie gdy wybieramy z kolekcji filtry (@12), znowu w jasny sposób mówimy o typie, który nas interesuje. I wreszcie na koniec metoda filtrująca (@18) także w jawny sposób definiuje parametry których wymaga do działania.

Co jeszcze dobrego płynie z przyjęcia enuma? Jeśli pojawi się nowy rodzaj, nie trzeba będzie wprowadzać kolejnego bool’a który będzie mówić, czy filtr jest czy nie jest danego rodzaju. Domyślnie filtr jest nie znanego typu, więc wszystkie podczas poszukiwać za grupowym/pojedynczym takie znaleziska się nie pojawią. Nawet gdy nazwa parametru będzie do bani, sam tym parametru sprzeda intencje programisty i użytkownik metody (@18) będzie wiedzieć jakiego rodzaju wartość ma przesłać, aby otrzymać interesująco go wynik.

I jeszcze na koniec: oczywiście że ciężko tworzyć enum’y do każdego wykorzystania bool’a. Równie ciężko jest pisać kod który działa i jest czytelny, bezbłędny czy zoptymalizowany – a jednak każdy się stara.

Miłego wieczoru!

9 thoughts on “Zamień bóla na enuma

  1. Pingback: dotnetomaniak.pl
  2. Zamienianie bóla na enuma ma też sens dla zwracanych typów.
    Mona mieć metodę:
    bool IsStandalone()

    i potem taki bool sobie krąży i coraz mniej rozumiemy co znaczy i jaki jest sens wartości false.

    A można zmienić na:
    FilterType GetFilterType()

  3. Co do pedantyczności kodu i używania enuma zamiast boola to oczywiście racja. Ja dodałbym od siebie jeszcze, że enum oczywiście może bardzo fajnie zastąpić całą kolekcję booli dzięki użyciu flags attribute. Jeśli jednak chodzi o „aż takie” dbanie o czystość kodu to jednak dużo szybciej/lepiej edytuje się collection initializer niż dodaje do listy elementy zaraz po jej utworzeniu. Zaś odstępy między kolejnymi liniami kodu oznaczają, że kod „nie do końca” może być czytelny dla innego programisty, bo metoda prawdopodobnie wykonuje więcej niż jedną „czynność” i może lepiej byłoby to jakoś dzielić i nazywać 😉

    1. Flagi na enum’ach jasna sprawa.
      Używanie inicjalizatora – kwestia gustu.
      Natomiast podział metody Example w klasie DummyUsage – dla mnie za dużo 😉 W prawdziwym projekcie, pewnie można by się zastanawiać czy nie ma tam za dużo kodu. Na potrzeby tego wpisu i ukazania przykładu wywołania przed i po zmianach uważam za wystarczająco czyste.

  4. Kontrowersyjna myśl, dla przypadku filtrów. Nie używać bool nie używać w enumów tylko użyć dziedziczenia

    abstract class Filter
    class StandaloneFilter : Filter
    class GroupFilter : Filter
    ?

      1. zawsze mozna zrobic cos takiego:

        [Flags]
        enum Filter{
        Group= 1,
        Standalone =2,
        Both = 3
        }

        is sprawdzac to poprzez (i to robie z pamieci wiec moze byc zly kod ale powinnno pokazac idea)

        var groupFilters = filters.Where(filter => filter.FilterType & FilterType.Group == 1) ;

Dodaj komentarz