niedziela, 5 marca 2017

Refaktoryzacja - Bool To Enum

Dziś mały przykład refaktoryzacji polegającej na zamianie flag bool na enum'a. Zdecydowałem się na ten krok gdyż nie lubię złożonych warunków if i tam gdzie można staram się je upraszczać. Poniżej klasa zawierająca dwa pola typu bool, którę odpowiadają za to jaka wersja pliku ma zostać wyeksportowana.


public class FileExport
    {
        public bool Origin { get; set; }
        public bool Modified { get; set; }

        public void Export()
        {
            if (Origin && !Modified)
            {
                WriteLine("Exporting origin file version");
            }
            else if (!Origin && Modified)
            {
                WriteLine("Exporting modified file version");
            }
            else if (Origin && Modified)
            {
                WriteLine("Exporting origin&modified file version");
            }
            else
            {
                WriteLine("Nothing to export ....");
            }
        }
    }


Co mi tutaj nie odpowiadało to klauzule if. Może zbyt mocno uprościłem ten przykład ale w kodzie produkcyjnym te klauzule były dość długie i mało czytelne. Zastanawiałem się także co będzie jak nastąpi wprowadzenie kolejnej flagi do warunku?
I tutaj przyszedł pomysł wprowadzenia typu enum, który uwzględniał by już kombinację tych flag i odpowiednio by je opisywał.

Krok #1 - Utworzenie typu enum


[Flags]
public enum VersionToExport
{
    None = 0,
    Origin = 1 << 1,
    Modified = 1 << 2,
    Both = Origin | Modified
}
 Atrybut Flags umożliwia traktowanie takiego typu enum jako pola bitowego, które może być zbiorem kombinacji pól. Kolejne pola otrzymują wartość kolejnych potęg dwójki 0, 2, 4, 8, 16 ...

Krok #2 - Przypisanie wartości


Kolejnym etapem jest "zapalenie" odpowiednich bitów. Ja użyłem takiego rozwiązania:

public VersionToExport ExportVersion {get; set; }

public SetExportVersion()
{
    ExportVersion = Origin ? VersionToExport.Origin : 0;
    ExportVersion |= Modified ? VersionToExport.Modified : 0;
}

To tyle. Opcji None oraz Both nie musimy tutaj przypisywać. None jest domyślną wartością a Both jest z kolei kombinacją dwóch flag Origin i Modified. Zwracam tutaj uwagę na operator |=, który wykonuje operację bitową OR. x |=y jest odpowiednikiem x = x | y

Krok #3 - Użycie


I na koniec przykład jak uległa zmianie klauzula if. Można przy okazji zamienić ją na switch'a (Jakoś tak mam, że przy typach enum częściej używam instrukcji switch).


public void Export()
{
    switch (ExportVersion)
    {
        case VersionToExport.Origin:
            WriteLine("Exporting origin file version");
            break;
        case VersionToExport.Modified:
            WriteLine("Exporting modified file version");
            break;
        case VersionToExport.Both:
            WriteLine("Exporting origin&modified file version");
            break;
        default:
            WriteLine("Nothing to export ....");
            break;
    }
}

W całym tym rozwiązaniu chciałem pozbyć się różnych kombinacji true&false z klauzul if. Dodatkowo enum lepiej opisuje dany warunek przez co kod lepiej się czyta.


2 komentarze:

  1. Hej, również lubię refaktoryzować kod, ale nie przepadam też za switchem w C i pochodnych (redundatny break; na końcu). Zastanawiam się, czy nie lepsze w tym przypadku byłoby zastosowanie dziedziczenia w celu wyeliminowania jeszcze switcha? ;p
    http://stackoverflow.com/questions/126409/ways-to-eliminate-switch-in-code

    OdpowiedzUsuń
    Odpowiedzi
    1. No z tym dziedziczeniem to można by z kolei przesadzić w drugą stronę ("Composition over inheritance"). Całkowicie nie dałoby się wyeliminować switch'a - poleciał by w inne miejsce tworzyć odpowiednie instancje obiektów (wg. zasad clean code tylko do tego powinien służyć). Tym zabiegiem chciałem dokonać małej refaktoryzacji.

      ps. Jesteś pierwszym komentującym na moim blogu. Aż chciałbym zrobić małe "rozdajo" ale póki co brak sponsorów ;)

      Usuń