Ir para o conteúdo principal
Background Image

Princípio Tell, Don't Ask na prática

··5 minutos·
Rafael Issao
Autor
Rafael Issao
Apaixonado por tecnologia, programação e inovação. Adoro compartilhar conhecimento e aprender coisas novas todos os dias.
Tabela de conteúdos

Deixar um código coeso e com baixo acoplamento é difícil.

Mas um dos princípios que pode nos ajudar a tornar nosso código melhor é o “Tell, Don’t Ask”.

Vamos aprender um pouco sobre este princípio e ver como aplicar em um código de exemplo.

Definição
#

“Tell, Don’t Ask” pode ser entendido como

“Diga o que devo fazer, e não me peça por informações

fonte:
fonte: https://martinfowler.com/bliki/TellDontAsk.html

A ideia é que lógicas e os dados não devem ficar separadas, mas juntas.

No mundo da programação orientada à objetos, isso significa que as classe são responsáveis pelas lógicas e os seu dados.

Mas como aplicar?

Vamos ver um exemplo com um projeto simples

Exemplo
#

Imagine a seguinte situação:

package br.com.youready.article.d_2024_11_18.image2;

public enum ManchesterProtocolClassification {
    IMMEDIATE,
    VERY_URGENT,
    URGENT,
    STANDARD,
    NON_URGENT;
}
package br.com.youready.article.d_2024_11_18.image2;

import java.time.Instant;
import lombok.Getter;

@Getter
public class Patient {
    private final String name;
    private final ManchesterProtocolClassification classification;
    private final Instant arrivalTime;

    public Patient(String name, ManchesterProtocolClassification classification) {
        this.name = name;
        this.classification = classification;
        this.arrivalTime = Instant.now();
    }

    public Instant getArrivalTime() {
        return arrivalTime;
    }

    public ManchesterProtocolClassification getClassification() {
        return classification;
    }
}
package br.com.youready.article.d_2024_11_18.image2;

import java.time.Duration;
import java.time.Instant;

public class TriageService {
    public boolean isWaitingTimeExceeded(Patient patient) {
        Duration waitingTime = Duration.between(patient.getArrivalTime(), Instant.now());

        Duration maxWaitTime =
                switch (patient.getClassification()) {
                    case IMMEDIATE -> Duration.ofMinutes(0);
                    case VERY_URGENT -> Duration.ofMinutes(10);
                    case URGENT -> Duration.ofMinutes(60);
                    case STANDARD -> Duration.ofHours(2);
                    case NON_URGENT -> Duration.ofHours(4);
                };

        return waitingTime.compareTo(maxWaitTime) > 0;
    }
}

TriageService é um serviço responsável verificar se o tempo de espera do paciente ultrapassou de acordo com a classificação do paciente.

Patient é a classe que possui o nome e o instante de chegada do paciente.

ManchesterProtocolClassification é um enum com as possíveis classificações.

É um código com uma estrutura típica que vemos em muitos projetos. Mas pode ser melhorada aplicando o Tell, Don’t Ask.

Vamos focar no método isWaitingTimeExceeded.

Onde está o problema
#

Veja que o método isWaitingTimeExceeded depende de dois dados do paciente:

  • patient.getArrivalTime()
  • patient.getClassification()

Ou seja, a lógica e os dados estão separados:

  • Lógica: TriageService#isWaitingTimeExceeded
  • Dados: Objeto patient

Se formos aplicar o “Tell, Don’t Ask” nesta situação, seria:

Não peça informações para o objeto patient mas diga o que ele deve fazer.

Ou seja, devemos perguntar para o objeto patient se o tempo de espera excedeu:

package br.com.youready.article.d_2024_11_18.image3;

public class TriageService {
    public boolean isWaitingTimeExceeded(Patient patient) {
        // Aplicamos o tell, dont ask!
        return patient.isWaitingTimeExceeded();
    }
}
package br.com.youready.article.d_2024_11_18.image3;

import java.time.Duration;
import java.time.Instant;
import lombok.Getter;

@Getter
public class Patient {
    private final String name;
    private final ManchesterProtocolClassification classification;
    private final Instant arrivalTime;

    public Patient(String name, ManchesterProtocolClassification classification) {
        this.name = name;
        this.classification = classification;
        this.arrivalTime = Instant.now();
    }

    public Instant getArrivalTime() {
        return arrivalTime;
    }

    public ManchesterProtocolClassification getClassification() {
        return classification;
    }

    // Movemos a lógica para a classe
    public boolean isWaitingTimeExceeded() {
        Duration waitingTime = Duration.between(this.getArrivalTime(), Instant.now());

        Duration maxWaitTime =
                switch (this.getClassification()) {
                    case IMMEDIATE -> Duration.ofMinutes(0);
                    case VERY_URGENT -> Duration.ofMinutes(10);
                    case URGENT -> Duration.ofMinutes(60);
                    case STANDARD -> Duration.ofHours(2);
                    case NON_URGENT -> Duration.ofHours(4);
                };

        return waitingTime.compareTo(maxWaitTime) > 0;
    }
}

Esta versão do código é melhor em alguns pontos:

  • Se eu escrever um teste unitário para verificar a regra de negócio do tempo de espera, este teste depende somente do objeto Patient.
  • Reduzimos o acoplamento entre a classe TriageService e o enum ManchesterProtocolClassification. Isso significa que posso realizar alterações no ManchesterProtocolClassification sem a preocupação de modificar o TriageService.

Show!

Diminuímos o custo para escrever os testes e também diminuímos o custo da alteração!

Vamos continuar aplicando o “Tell, Don’t Ask”!

Eliminando o switch
#

Agora vamos olhar para o método Patient#isWaitingTimeExceeded.

Conseguem eliminar o switch aplicando o “Tell, Don’t Ask”?

Vamos pensar.

A lógica, para funcionar, precisa do tempo de chegada e o valor da classificação. Cada informação está em:

  • tempo de chegada - objeto Patient
  • valor da classificação - valor de cada enum de ManchesterProtocolClassification

Ou seja, já que a propriedade classification do objeto Patient possui o valor do enum, podemos dizer para o classification o que ele deve fazer!

Ficaria assim:

package br.com.youready.article.d_2024_11_18.image4;

import java.time.Duration;

enum ManchesterProtocolClassification {
    IMMEDIATE(Duration.ofMinutes(0)),
    VERY_URGENT(Duration.ofMinutes(10)),
    URGENT(Duration.ofMinutes(60)),
    STANDARD(Duration.ofHours(2)),
    NON_URGENT(Duration.ofHours(4));

    private final Duration maxWaitTime;

    ManchesterProtocolClassification(Duration maxWaitTime) {
        this.maxWaitTime = maxWaitTime;
    }

    public boolean isWaitingTimeExceed(Duration waitingTime) {
        return waitingTime.compareTo(maxWaitTime) > 0;
    }
}
package br.com.youready.article.d_2024_11_18.image4;

import java.time.Duration;
import java.time.Instant;
import lombok.Getter;

@Getter
public class Patient {
    private final String name;
    private final ManchesterProtocolClassification classification;
    private final Instant arrivalTime;

    public Patient(String name, ManchesterProtocolClassification classification) {
        this.name = name;
        this.classification = classification;
        this.arrivalTime = Instant.now();
    }

    public Instant getArrivalTime() {
        return arrivalTime;
    }

    public ManchesterProtocolClassification getClassification() {
        return classification;
    }

    public boolean isWaitingTimeExceeded() {
        Duration waitingTime = Duration.between(this.getArrivalTime(), Instant.now());
        return this.getClassification().isWaitingTimeExceed(waitingTime);
    }
}
package br.com.youready.article.d_2024_11_18.image4;

public class TriageService {
    public boolean isWaitingTimeExceeded(Patient patient) {
        return patient.isWaitingTimeExceeded();
    }
}

Agora sim o código está mais coeso, pois cada lógica está junto com os dados necessários para a sua execução.

E percebam uma coisa interessante: O switch sumiu!

Não precisamos de comparação como if, else e switch para executar a lógica!

Então aqui fica uma dica:

Se tiver um if, else ou switch no seu código e você estiver utilizando dados de um objeto para realizar sua lógica, pode ser um ponto com a possibilidade de aplicar o “Tell, Don’t Ask”.

Conclusão
#

Estamos acostumados ou familiarizados com códigos que pedem informações para outros objetos e módulos.

O princípio “Tell, Don’t Ask” serve para nos lembrar que existe uma outra maneira de escrever seu código.

Abstração e encapsulamento são a chave mas se estiver com dúvida de quando aplicar, a pergunta que você sempre pode fazer é:

Estou pedindo uma informação ou estou dizendo o que deve ser feito?

Se você tiver dúvidas, sugestões ou até correções, sinta-se à vontade para comentar ou falar diretamente comigo!

🥒🥒 Até o próximo artigo! 🥒🥒