Blog Formação DEV

S de SOLID: O Poder da Responsabilidade Única no Código

Descubra como o Princípio da Responsabilidade Única pode simplificar a estrutura do seu código, tornando-o mais organizado, fácil de testar e pronto para evoluir sem complicações!
S de SOLID: O Poder da Responsabilidade Única no Código
Texto de: Carlos Alexandre

Introdução

Neste artigo, vamos explorar o primeiro princípio da sigla SOLID: o Single Responsibility Principle (Princípio da Responsabilidade Única), essencial para escrever códigos mais limpos, coesos e sustentáveis a longo prazo.

O Princípio da Responsabilidade Única (SRP) propõe que cada parte do seu sistema — seja classe, função ou componente — tenha apenas uma função bem definida. Isso reduz o acoplamento e melhora a clareza e a manutenção do código.

Ao entender e aplicar esse princípio corretamente, você estará dando o primeiro passo para uma arquitetura mais robusta e escalável. Vamos ver na prática como identificar violações do SRP e refatorar seu código de forma clara e eficiente.

⚠️ Consequências de não separar responsabilidades no código

Quando misturamos várias responsabilidades em uma mesma estrutura, o código se torna mais difícil de entender, testar e manter — além de mais suscetível a erros.

Algumas das principais consequências incluem:

  • Dificuldade de manutenção: alterar uma funcionalidade pode afetar outras que não têm relação direta, gerando efeitos colaterais indesejados.
  • Baixa testabilidade: testar unidades de código com múltiplas responsabilidades exige mais mocks, setups e complexidade.
  • Pouca reutilização: componentes genéricos tornam-se específicos demais e difíceis de reaproveitar em outros contextos.
  • Menor clareza de propósito: quando um módulo faz muitas coisas, fica difícil entender sua real função só de olhar para o código.

Esses problemas acumulam-se com o tempo, tornando o sistema rígido, frágil e difícil de evoluir — exatamente o oposto do que buscamos com boas práticas de arquitetura.

🧩 Exemplo ruim (violação do SRP):

class UserService {
  createUser(userData) {
    // Validação dos dados
    if (!userData.email.includes('@')) {
      throw new Error('E-mail inválido');
    }

    // Salvando no banco
    database.save(userData);

    // Enviando e-mail de boas-vindas
    emailService.sendWelcomeEmail(userData.email);
  }
} 

Nesse exemplo, a classe UserService está fazendo três coisas distintas: validando dados, lidando com persistência e enviando e-mails. Ou seja, três motivos diferentes para mudar.

✅ Exemplo com SRP aplicado:

// Responsável apenas por validar os dados do usuário
class UserValidator {
  static validate(userData) {
    // Verifica se o e-mail fornecido contém o caractere '@'
    if (!userData.email.includes('@')) {
      throw new Error('E-mail inválido');
    }
  }
}

// Responsável apenas por persistir os dados do usuário
class UserRepository {
  save(userData) {
    // Salva os dados do usuário no banco de dados
    database.save(userData);
  }
}

// Responsável apenas por enviar o e-mail de boas-vindas
class EmailNotifier {
  sendWelcomeEmail(email) {
    // Usa um serviço de e-mail para enviar mensagem de boas-vindas
    emailService.sendWelcomeEmail(email);
  }
}

// Coordena o processo de criação do usuário usando as classes acima
class UserService {
  constructor() {
    // Instancia os componentes responsáveis por salvar e notificar
    this.repository = new UserRepository();
    this.notifier = new EmailNotifier();
  }

  createUser(userData) {
    // 1. Valida os dados do usuário
    UserValidator.validate(userData);

    // 2. Salva os dados no banco de dados
    this.repository.save(userData);

    // 3. Envia o e-mail de boas-vindas
    this.notifier.sendWelcomeEmail(userData.email);
  }
}
 

Agora cada classe tem uma responsabilidade clara e pode ser modificada, testada ou reutilizada de forma independente.

📌 Como identificar violações do SRP no seu código

Entender o Princípio da Responsabilidade Única na teoria é uma coisa. Mas saber identificar quando ele está sendo violado no seu código é o que realmente faz diferença no dia a dia.

Aqui vão alguns sinais de alerta que indicam que uma classe, componente ou função pode estar assumindo responsabilidades demais:

🚨 Sinais comuns de violação do SRP:

  • Nome genérico ou confuso: classes com nomes como Manager, Helper, Utils ou Service muitas vezes escondem múltiplas responsabilidades.
  • Muitos métodos com propósitos diferentes: se um mesmo arquivo está validando dados, lidando com banco, enviando e-mails e formatando dados, algo está errado.
  • Múltiplos motivos para modificar: pergunte-se: "Se o sistema mudar devido a uma nova regra de validação, de envio de e-mail ou de banco de dados, terei que alterar esse código?" — Se a resposta for sim para mais de um motivo, o SRP está sendo violado.
  • Dificuldade para testar isoladamente: se, para testar um comportamento específico, você precisa configurar mocks e dependências de partes não relacionadas, há um acoplamento excessivo.
  • Componentes React com muita lógica misturada: hooks, chamadas de API, manipulação de estado, formatação de dados e renderização no mesmo componente são sinais claros de violação.

💡 Dica prática:

Pegue uma classe ou componente e tente responder:

"Qual é a única coisa que essa estrutura precisa fazer?"

Se você não conseguir responder com uma única frase clara, provavelmente ela tem mais de uma responsabilidade.

🧠 Reescrevendo código legado aplicando SRP

Refatorar código legado pode parecer desafiador, mas aplicar o SRP é uma das abordagens mais seguras para começar. O objetivo é identificar responsabilidades misturadas e separá-las em estruturas menores, cada uma com um único propósito.

Vamos ver um exemplo prático de como transformar um código confuso em algo mais modular e sustentável.

🔴 Código legado (com múltiplas responsabilidades):

class OrderService {
  processOrder(order) {
    // Validar pedido
    if (!order.items.length) {
      throw new Error('Pedido sem itens');
    }

    // Calcular valor total
    const total = order.items.reduce((sum, item) => sum + item.price, 0);

    // Salvar no banco
    database.save(order);

    // Enviar confirmação por e-mail
    emailService.sendConfirmation(order.email, total);
  }
} 

✅ Refatorado com SRP:

// Responsável apenas por validar o pedido
class OrderValidator {
  static validate(order) {
    if (!order.items.length) {
      throw new Error('Pedido sem itens');
    }
  }
}

// Responsável apenas por calcular o total do pedido
class OrderCalculator {
  static calculateTotal(order) {
    return order.items.reduce((sum, item) => sum + item.price, 0);
  }
}

// Responsável apenas por salvar o pedido no banco de dados
class OrderRepository {
  save(order) {
    database.save(order);
  }
}

// Responsável apenas por enviar a confirmação por e-mail
class EmailNotifier {
  sendConfirmation(email, total) {
    emailService.sendConfirmation(email, total);
  }
}

// Responsável por orquestrar o fluxo: validar, calcular, salvar e notificar
class OrderService {
  constructor() {
    this.repository = new OrderRepository();  
    this.notifier = new EmailNotifier();      
  }

  processOrder(order) {
    OrderValidator.validate(order);                            // Validação
    const total = OrderCalculator.calculateTotal(order);       // Cálculo do total
    this.repository.save(order);                               // Persistência
    this.notifier.sendConfirmation(order.email, total);        // Notificação
  }
} 

Ao aplicar o SRP, ganhamos um código mais limpo, fácil de manter e testar. Cada classe faz apenas uma coisa — e faz bem. Isso facilita futuras alterações, reduz o acoplamento e aumenta a confiança da equipe ao evoluir o sistema.

Refatorar não é só sobre melhorar código — é sobre melhorar comunicação entre pessoas e manter o sistema saudável no longo prazo.

Se você está lidando com código legado, comece identificando responsabilidades duplicadas ou misturadas. Separar essas responsabilidades é um passo simples, mas poderoso, rumo a um código mais sustentável.

🧱 SRP em componentes front-end

Aplicar o Princípio da Responsabilidade Única (SRP) em componentes front-end, como em React, ajuda a tornar o código mais modular e fácil de manter. Em aplicações modernas, onde os componentes desempenham um papel fundamental, dividir responsabilidades pode ser um grande diferencial para a escalabilidade e manutenção do projeto.

🔴 Exemplo violando o SRP (React):

import { useState, useEffect } from 'react';

export function UserProfile() {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Função assíncrona para buscar dados
  async function fetchUserData(){
      try {
        // Responsabilidade 1: Buscar dados usando async/await
        const response = await fetch('/api/user'); // Executa a requisição
        if (!response.ok) { // Verifica se a resposta foi bem-sucedida (status 200-299)
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json(); // Converte a resposta para JSON

        setUser(data);
        setIsLoading(false);

      } catch (err) {
        // Responsabilidade 1 (continuação): Tratar erros da busca
        console.error("Erro ao buscar usuário:", err);
        setError(err);
        setIsLoading(false);
      }
    };

    fetchUserData(); // Chama a função assíncrona dentro do useEffect

  }, []); // Array de dependências vazio: executa apenas na montagem

  // Responsabilidade 2: Gerenciar estados de loading/erro e renderizar baseado neles
  if (isLoading) {
    return <p>Carregando...</p>;
  }

  if (error) {
    return <p>Erro ao carregar usuário: {error.message}</p>;
  }

  if (!user) {
      return null; // Ou outra UI para usuário não encontrado/carregado sem erro
  }

  // Responsabilidade 3: Renderizar a UI e lidar com interações simples
  function handleSendMessage(){
      alert('Mensagem enviada!');
      // Em uma aplicação real, isso seria mais complexo
  };

  return (
    <div> 
      <h1>{user.name}</h1>
      <button onClick={handleSendMessage}>Enviar mensagem</button>  );
} 

✅ Aplicando SRP (React):

Dividimos as responsabilidades em três partes principais:

  1. Hook Customizado (useUserData): Encapsula a lógica de busca de dados e o gerenciamento do estado relacionado (loading, erro, dados). Sua única responsabilidade é fornecer os dados do usuário e seu estado atual para quem o usar.
// src/hooks/useUserData.js

import { useState, useEffect } from 'react';

// Hook customizado: Responsabilidade única de buscar dados do usuário e gerenciar seu estado (loading, error, data)
export function useUserData() {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('/api/user'); // Lógica de fetch
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json(); // Lógica de processamento da resposta
        setUser(data);
      } catch (err) {
        console.error("Erro ao buscar usuário:", err); // Lógica de tratamento de erro
        setError(err);
      } finally {
        setIsLoading(false); // Lógica de gerenciamento de estado (loading)
      }
    };

    fetchData();
  }, []); // Este hook só tem um motivo para mudar: se a lógica de como buscar ou processar dados do usuário mudar.

  // Retorna os dados e o estado da operação
  return { user, isLoading, error };
}
  1. Componente de Apresentação (UserProfileDisplay): Sua responsabilidade única é renderizar a interface visual baseada nas props que recebe. Ele não sabe de onde vêm os dados, nem como lidar com estados de loading/erro, nem o que acontece exatamente quando o botão é clicado (apenas chama a função que recebeu via prop).
// src/components/UserProfileDisplay.js

// Componente de exibição (presentational component): Responsabilidade única de renderizar a UI
export function UserProfileDisplay({ user, onSendMessage }) {
  // Assume que 'user' contém os dados necessários e está pronto para ser exibido
  return (
    <div>
      <h1>{user.name}</h1>
      {/* A lógica exata do que acontece ao clicar é passada via prop */}
      <button onClick={onSendMessage}>Enviar mensagem</button>
    </div>
  );
} 
  1. Componente Contêiner/Orquestrador (UserProfileContainer): Sua responsabilidade única é juntar as peças. Ele utiliza o hook para obter os dados e o estado, gerencia o fluxo (o que mostrar em loading, erro, ou sucesso) e renderiza o componente de apresentação, passando os dados e as funções de callback necessárias.
// src/containers/UserProfileContainer.js

import useUserData from '../hooks/useUserData'; // Importa o hook
import UserProfileDisplay from '../components/UserProfileDisplay'; // Importa o componente de apresentação

// Componente Contêiner/Orquestrador: Responsabilidade única de orquestrar o uso do hook e do componente de apresentação
function UserProfileContainer() {
  // Usa o hook para obter dados e estados
  const { user, isLoading, error } = useUserData();

  // Gerencia os estados e decide o que renderizar
  if (isLoading) {
    return <p>Carregando...</p>; // Responsabilidade de gerenciar o estado de loading
  }

  if (error) {
    return <p>Erro ao carregar usuário: {error.message}</p>; // Responsabilidade de gerenciar o estado de erro
  }

  if (!user) {
      return null; // Lida com o caso de dados não encontrados após carregar sem erro
  }

  // Define a lógica de interação (pode ser movida se complexa, mas aqui é simples)
  const handleSendMessage = () => {
    alert('Mensagem enviada!');
    // Esta lógica só tem um motivo para mudar: se a forma como mensagens são enviadas mudar.
  };

  // Renderiza o componente de apresentação, passando os dados e o handler
  return <UserProfileDisplay user={user} onSendMessage={handleSendMessage} />;
  // Este componente só tem um motivo para mudar: se a forma como as outras peças (hook, display) são conectadas mudar.
} 

💡 Benefícios de Aplicar SRP:

  • Facilidade para Testar: Cada parte (hook, componente de exibição, contêiner) tem um foco único, tornando cada unidade muito mais fácil de testar isoladamente (ex: testar o hook mockando a API, testar o display com props mockadas).
  • Maior Reusabilidade: O hook useUserData e o componente UserProfileDisplay são genéricos o suficiente para serem usados em outros lugares.
  • Leitura e Manutenção Claras: Cada arquivo/unidade de código tem um propósito bem definido.
  • Separação de Interesses: Lógica de dados, apresentação e orquestração ficam separadas.
  • Menos Conflitos em Equipes: Divisão clara de responsabilidades reduz a chance de conflitos no controle de versão.

Aplicar SRP no front-end ajuda a manter os componentes pequenos, reutilizáveis e fáceis de manter. Em times, isso reduz conflitos e melhora a colaboração. Se um componente está difícil de entender ou testar, provavelmente ele está assumindo mais de uma responsabilidade — e esse é o sinal para aplicar SRP.

✨ Conclusão

O Princípio da Responsabilidade Única (SRP) é um alicerce para um código de qualidade. Ao garantir que cada unidade — seja classe, função ou componente — tenha apenas um único motivo para mudar, destravamos uma série de benefícios essenciais:

  • Manutenção Simplificada: Alterações localizadas, menos efeitos colaterais.
  • Testabilidade Aprimorada: Unidades isoladas são fáceis de testar.
  • Maior Reusabilidade: Componentes focados são mais adaptáveis.
  • Clareza e Legibilidade: O propósito do código se torna evidente.

Vimos como identificar as violações e a importância de refatorar, tanto em classes tradicionais quanto em componentes front-end React. Aplicar o SRP, mesmo incrementalmente, reduz o acoplamento e aumenta a confiança ao evoluir o sistema.

Dominar o SRP é dar um passo fundamental para escrever código mais organizado, robusto e preparado para os desafios futuros. É um investimento direto na saúde do seu projeto e na produtividade da sua equipe.

Sobre o autor
Cod3r

Cod3r

Com mais de 400 mil alunos, a Cod3r é uma das principais escolas de tecnologia do País. Um de seus produtos mais importantes é a Formação DEV, com objetivo de preparar os profissionais para o mercado.

Ótimo! Inscreveu-se com sucesso.

Bem-vindo de volta! Registou-se com sucesso.

Assinou com sucesso o Blog Formação DEV .

Sucesso! Verifique o seu e-mail para obter o link mágico para se inscrever.

As suas informações de pagamento foram atualizadas.

Seu pagamento não foi atualizado.