Design de software

Separação de responsabilidades: cirurgia em componente de 800 linhas

Você provavelmente já viu código com tudo dentro de um único arquivo de view de 800 linhas.

4 min de leitura
Imagem de capa de Separação de responsabilidades: cirurgia em componente de 800 linhas

Você provavelmente já viu código com tudo dentro de um único arquivo de view de 800 linhas.

No código da view que desenha a tela há chamadas de API; ao lado, lógica para calcular descontos; abaixo, funções para formatar datas; e, espalhados pelo arquivo, envios de eventos de analytics. Para corrigir qualquer coisa, é preciso ler as 800 linhas inteiras.

O princípio violado por esse tipo de arquivo é a separação de responsabilidades, Separation of Concerns em inglês. Este é o último capítulo da série sobre princípios de desenvolvimento.

A separação de responsabilidades (SoC) determina que o programa seja dividido em unidades com responsabilidades diferentes, para que cada parte trate de apenas uma responsabilidade.

“Responsabilidade” é o tipo de tarefa com que o programa precisa se preocupar. Como exibir a tela, de onde obter os dados e como aplicar as regras de negócio são responsabilidades diferentes.

O termo foi criado pelo cientista da computação Dijkstra, que o expressou assim em um texto de 1974.

Pensar concentrando-se em um único aspecto por vez. Essa é a única técnica eficaz de pensamento que conheço.

A mente humana não consegue lidar com várias responsabilidades ao mesmo tempo. Por isso, cada trecho de código deve conter apenas uma.


Um caso conhecido: HTML, CSS e JS

Se você já trabalhou com desenvolvimento web, então já usa separação de responsabilidades todos os dias.

Tecnologia Responsabilidade
HTML Estrutura — o que existe
CSS Apresentação — como aparece
JavaScript Comportamento — como reage

Se, como antigamente, enchermos as tags HTML com os atributos style e onclick, até mudar a cor de um botão exige procurar no HTML. Ao dividir em três arquivos, o designer olha apenas o CSS, e quem cuida da marcação, apenas o HTML.

A estrutura controller-service-repository do backend e a separação entre view, view model e camada de dados no iOS seguem o mesmo princípio. MVC e arquiteturas em camadas são, no fim, padrões que formalizam a separação de responsabilidades.


Operando aquela view de 800 linhas

Vamos separar por responsabilidade a view de 800 linhas que vimos antes.

// Before: Todas as responsabilidades em um arquivo
struct ProductPage: View {
    // Responsabilidade 1: Obter dados (chamadas de rede, carregamento e tratamento de erros 150linhas)
    // Responsabilidade 2: Regras de negócio (cálculo de descontos e estoque 200linhas)
    // Responsabilidade 3: Eventos de analytics (rastreamento de abas 100linhas)
    // Responsabilidade 4: Desenhar a tela (body 350linhas)
}
// After: Cada responsabilidade encontra seu próprio lugar
ProductStore(id:)               // Obter dados
calcDiscount(product, user)     // Regras de negócio (função pura)
Tracker.track("product")        // Eventos de analytics

struct ProductPage: View {      // Apenas desenhar a tela
    @State private var store: ProductStore

    var body: some View {
        let price = calcDiscount(store.product, user)
        ...
    }
}

Depois de separar, as vantagens ficam claras. Se a política de descontos mudar, basta olhar calcDiscount; como essa é uma função pura, independente da tela, ela também é fácil de testar. Se a especificação da API mudar, basta alterar ProductStore. Comparado a ler as 800 linhas inteiras, o escopo da alteração cai para um décimo.

Depois de separar, o escopo da alteração caiu para um décimo
Depois de separar, o escopo da alteração caiu para um décimo

Até onde devemos separar?

A parte mais difícil da separação de responsabilidades é decidir onde traçar a fronteira. Se separarmos demais, teremos um labirinto que exige navegar por dezenas de arquivos.

Meu critério é o motivo da mudança. É a mesma pergunta que vimos ao tratar do SRP de SOLID.

Estes dois códigos mudam pelo mesmo motivo e ao mesmo tempo?

A política de descontos e o layout da tela mudam por motivos diferentes, então devem ser separados. Já o botão e seu handler de toque quase sempre mudam juntos, então ficam juntos. Sob essa perspectiva, o fato de SwiftUI reunir estrutura, estilo e comportamento em um arquivo por view não é uma contradição. As responsabilidades foram redefinidas pela unidade que muda em conjunto, não pelo tipo de tecnologia.

No fim, os seis princípios apontavam para o mesmo lugar
No fim, os seis princípios apontavam para o mesmo lugar

Encerrando a série

Este é o ponto principal que fica da separação de responsabilidades.

  • Faça cada trecho de código tratar de apenas uma responsabilidade. A mente humana não faz multitarefa.
  • Na prática, trace as fronteiras pela “unidade que muda em conjunto”, não pelo tipo de tecnologia.
  • Separar demais cria um labirinto. A separação em si não pode virar o objetivo.

Com isso, termina a série sobre princípios de desenvolvimento: KISS (manter simples), DRY (manter o conhecimento em um só lugar), YAGNI (apenas o necessário agora), SOLID (reduzir o impacto das mudanças), mínimo espanto (comportar-se como esperado) e separação de responsabilidades (uma coisa por vez).

Olhando para trás, os seis princípios apontam para o mesmo lugar. O código é lido por pessoas, não por computadores, e código bom é código fácil de alterar. Mesmo que você esqueça os nomes dos princípios, guarde esta frase.