TDA: Tell, don’t ask

Dzisiaj na tapet weźmiemy TDA, czyli: Tell, don’t ask, kolejną regułę clean code. Zasada ta mówi nam o tym, abyśmy zlecali zadania, zamiast pytać obiekt o dane, a następnie nimi manipulować. Sprawdź ten wpis, jeśli chcesz pisać czytelniejszy kod.

Mów, nie pytaj…

Zajmiemy się dzisiaj inną regułą z zakresu clean code, a mianowicie TDA, które rozwija się do: „tell, don’t ask”, co na polski daje nam „mów, nie pytaj”. Swoją drogą, czy Was też bawią tłumaczenia niektórych pojęć IT na język polski? 

Zasada TDA przypomina nam, abyśmy stosowali się do reguł programowania obiektowego i opakowywali dane oraz zachowania w odpowiednie funkcje. Zamiast więc tworzyć metody, których użyjemy do pobrania pewnych danych, a następnie przeprocesujemy te dane poza samą metodą, stwórzmy funkcję, której zlecimy to zadanie w całości. Taki zabieg powoduje, że zachowania, bądź logika przenoszone są do obiektu, który przechowuje właściwe dane. Lekko poruszyłem ten temat podczas porównywania koncepcji modeli bogatych oraz anemicznych, więc jeśli jeszcze nie widziałeś/widziałaś tego wpisu, to zachęcam do jego sprawdzenia.

Kiedy łamiemy TDA?

Dobra, to teraz przykład praktyczny, jak można złamać TDA. Wyobraźmy sobie, że chcemy dostarczyć system, który przelicza wiek psa na wiek ludzki. Mamy serwis, który przyjmuje, jako parametr id pieska zapisanego w bazie danych, a następnie zwraca jego wiek przeliczony na lata ludzkie.

class Dog {
  constructor(
    readonly dogId: DogId,
    readonly name: string,
    readonly birthDate: Date
  ) {}
}

class DogsService {
  constructor(
    private readonly dogRepository: DogRepository
  ) {}

  async calculateHumanAge({ dogId, unit }: CalculateDogHumanAgeDto): Promise<Age> {
    const { birthDate } = await this.dogRepository.findOrFail(dogId);

    const age = periodTillNow(birthDate, unit);

    return Age.create(
      age * 7,
      unit
    );
  }
}

W powyższym przykładzie TDA złamaliśmy, ponieważ wyciągamy wiek psa, a następnie przeliczamy go na ludzki w serwisie. W myśl zasady tell, don’t ask, powinniśmy owe wyliczenie przeprowadzić w obiekcie psa.

Przykład TDA

Powyższy kod możemy poprawić w bardzo łatwy sposób. Wystarczy przenieść logikę wyliczającą do obiektu Dog i… po sprawie.

class Dog {
  constructor(
    readonly dogId: DogId,
    readonly name: string,
    readonly birthDate: Date
  ) {}

  calculateHumanAge(unit: AgeUnit): Age {
    return Age.create(
      periodTillNow(dog.birthDate, unit),
      unit
    );
  }
}

class DogsService {
  constructor(
    private readonly dogRepository: DogRepository
  ) {}

  async calculateHumanAge({ dogId, unit }: CalculateDogHumanAgeDto): Promise<Age> {
    const dog = await this.dogRepository.findOrFail(dogId);

    return dog.calculateHumanAge(unit);
  }
}

Warto także dodać, że wadliwy kod (ten niespełniający TDA) posiadał także tzw. zapach kodu, o nazwie feature envy, czyli zazdrość o implementację. O samych zapachach kodu powiem więcej już wkrótce. Niemniej jednak problem jest ten sam, mianowicie logika obliczania wieku powinna być w klasie Dog, a nie w serwisie.

Czy TDA oznacza brak getterów?

Powtarzam to przy każdej zasadzie czy podejściu, nie popadajmy ze skrajności w skrajność. Powinniśmy stosować się do TDA tam, gdzie jest to konieczne i daje to wymierne rezultaty. Stosowanie TDA nie oznacza jednak, że musimy całkowicie zrezygnować z getterów. Jedną z fundamentalnych zasad programowania obiektowego jest zachowanie balansu pomiędzy połączeniem zachowań i informacji o obiekcie. Staraj się więc stosować TDA, ale nie rób tego za wszelką cenę. Dodam jeszcze tylko, że wiele osób jest sceptycznie nastawionych do tej reguły, ponieważ widzieli oni, jak łatwo z TDA można przesadzić i usunąć ze swojego systemu wszystkie gettery. 

Podsumowanie

  • TDA, to przydatna zasada z zakresu clean code.
  • Tell, don’t ask zmusza nas, abyśmy tworzyli metody zorientowane na zachowania, zamiast używać getterów.
  • Pamiętaj, aby nie przesadzić. Gettery też są potrzebne – zachowaj odpowiedni balans pomiędzy zachowaniami, a zapytaniami.
Autor wpisu

blog@orbisbit.com

Komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Sprawdź również
  • CQRS – Command Query Responsibility Segregation

    Command Query Responsibility Segregation czyli CQRS. Jest to wzorzec projektowy, który rozdziela zadania odczytu i zapisu do osobnych modeli. Sprawdź ten wpis, aby dowiedzieć się kiedy i jak z niego skorzystać.

    Zobacz wpis

  • GRASP – kolejny zbiór zasad Clean Code do zapamiętania

    Pewnie większość z Was słyszała o zasadach SOLID. Są one bardzo rozpowszechnione i dosyć często stosowane, ale czy słyszeliście o GRASP? General Responsibility Assignment Software Patterns, to kolejna dawka zasad czystego kodu do zapamiętania.

    Zobacz wpis

  • Wzorzec strategia (strategy pattern)

    Jeżeli masz dość if-ologii w swoim kodzie, to konieczne sprawdź czym jest czynnościowy wzorzec projektowy strategia. Pozwala on mądrze obsługiwać różne scenariusze w procesie i jednocześnie być fancy pod względem zasad SOLID.

    Zobacz wpis