piątek, 31 marca 2017

#2 Recepta na - pretty if

Druga "recepta na" będzie związana z warunkami if i skracaniem ich zapisu a zarazem poprawy ich czytelności.
Na początek przykład nad którym popracujemy:


public class MyBusinessClass
{
    AuthorizationService authorizationService = new AuthorizationService();

    public void AddOrderToInvoice(Order order, Invoice invoice, UserInfo user)
    {
        if (authorizationService.HasAuthorization(order, user, AuthorizationLevel.Read) && authorizationService.HasAuthorization(invoice, user, AuthorizationLevel.Write))
        {
            //do something
        }
    }
}


Klasa biznesowa, której metoda AddOrderToInvoice musi wykonać sprawdzenie czy użytkownik ma odpowiednie uprawnienia do wykonania tej operacji. Weryfikujemy uprawnienia do odczytu na zamówieniu i uprawnienia do zapisu na fakturze. Od razu zaznaczam, że jest to pseudo kod - tak aby pokazać istotę długich, skomplikowanych warunków if. (W sumie nie taki on pseudo bo spotykam go dość często ...)
Pierwsze co możemy zrobić bez większego wysiłku to oczywiście przerzucenie drugiego warunku do osobnej linii:


public void AddOrderToInvoice(Order order, Invoice invoice, UserInfo user)
{
    if (authorizationService.HasAuthorization(order, user, AuthorizationLevel.Read) 
        && authorizationService.HasAuthorization(invoice, user, AuthorizationLevel.Write))
    {
        //do something
    }
}

Jest o tyle lepiej, że przynajmniej mieści się już na ekranie ;) Ale to jeszcze średnio mnie zadowala. W takich sytuacjach często brakuje biznesowego opisu co dany warunek weryfikuje. A jakby tak wyciągnąć tą weryfikację przed klauzulę if przypisując wyniki do zmiennych, których nazwy dobrze je opisują?


public void AddOrderToInvoice(Order order, Invoice invoice, UserInfo user)
{
    bool hasReadPermissionOnOrder = authorizationService.HasAuthorization(order, user, AuthorizationLevel.Read);
    bool hasWritePermissionOnInvoice = authorizationService.HasAuthorization(invoice, user, AuthorizationLevel.Write);

    if (hasReadPermissionOnOrder && hasWritePermissionOnInvoice)
    {
        //do something
    }
}


Teraz śledząc taki warunek nie musimy czytać całego wywołania metod tylko wprost widzimy co jest sprawdzane. Czy możemy to tak zostawić? ... No pewnie, że nie! Przerzucając od tak wywołania metod do zmiennych pozbyliśmy się jednej bardzo istotnej właściwości operatora &&. Sprawdzenie drugiego warunku (uprawnień do zapisu na fakturze) odbywało się dopiero kiedy pierwszy warunek (uprawnienia do odczytu na zamówieniu) zwracał wartość true. Aktualnie wykonujemy dwa sprawdzenia niezależnie. W przypadku prostych metod, nie sięgających do bazy, zużywających zasobów itd. może nie byłoby to problemem. Ja jednak optuje za jeszcze innym rozwiązaniem. Nazywam to "lazy check" ;) W tym celu wykorzystuje delegat Func<T, TResult>


public void AddOrderToInvoice(Order order, Invoice invoice, UserInfo user)
{
    Func<bool> hasReadPermissionOnOrder = () => authorizationService.HasAuthorization(order, user, AuthorizationLevel.Read);
    Func<bool> hasWritePermissionOnInvoice = () => authorizationService.HasAuthorization(invoice, user, AuthorizationLevel.Write);

    if (hasReadPermissionOnOrder() && hasWritePermissionOnInvoice())
    {
        //do something
    }
}

Wprowadziłem delegat Func<bool> z deklaracją typu "inline". Jego wykonanie nastąpi dopiero w klauzuli if przy czym jeśli hasReadPermissionOnOrder() zwróci wartość false nie nastąpi wykonanie hasWritePermissionOnInvoice() - a o to ostatecznie nam chodziło.

Jeśli macie jakieś swoje "recepty na" klauzule if to podzielcie się nimi w komentarzach. Chętnie dowiem się co sądzicie o tym konkretnym rozwiązaniu.

Brak komentarzy:

Prześlij komentarz