SRP ou Single Responsibility Principle é um princípio de design que faz parte do famoso S.O.L.I.D.
Existem muitos exemplos de aplicação e código deste princípio mas que considero muito simples para ser percebido em um projeto real de software dentro das empresas.
E por termos essa dificuldade, quebramos com frequência este princípio.
Neste artigo, trago uma definição do SRP e alguns exemplos reais de projetos na área de desenvolvimento de software de como eles quebraram o princípio.
O que é SRP?#
A definição que gosto de utilizar é:
Deixe junto às coisas que mudam pela mesma razão. Deixe separado as coisas que mudam por razões diferentes.
Por exemplo, imagine um componente em React que tem a seguinte lógica para alugar ou comprar um filme em uma plataforma:
const MovieAction = () => {
const [actionType, setActionType] = useState('rent');
const handleButtonClick = () => {
// Logic to handle button click
console.log(`Action: ${actionType}`);
};
return (
<>
<label>
<input
type="radio"
checked={actionType === 'rent'}
onChange={() => setActionType('rent')}
/>
Rent
</label>
<label>
<input
type="radio"
checked={actionType === 'buy'}
onChange={() => setActionType('buy')}
/>
Buy
</label>
<PrimaryButton
actionType={actionType}
onClick={handleButtonClick}
/>
</>
);
};
// Assuming PrimaryButton is defined elsewhere
const PrimaryButton = ({ actionType, onClick }) => (
<button onClick={onClick}>{actionType === 'rent' ? 'Rent Movie' : 'Buy Movie'}</button>
É um código que podemos considerar como simples.
Imagine que veio um novo requisito: Quando o usuário clicar ‘Alugar’, ele deve ir para uma outra tela para perguntar por quanto tempo será alugado.
E o novo código fica assim:
const MovieAction = () => {
const [actionType, setActionType] = useState('rent');
const navigate = useNavigate();
const handleButtonClick = () => {
// Um novo if surge quando o botão é clicado
if (actionType === 'rent') {
navigate('/alugar-duracao');
} else {
// Realiza a ação de compra
console.log('Buying movie...');
}
};
return (
<>
<label>
<input
type="radio"
checked={actionType === 'rent'}
onChange={() => setActionType('rent')}
/>
Rent
</label>
<label>
<input
type="radio"
checked={actionType === 'buy'}
onChange={() => setActionType('buy')}
/>
Buy
</label>
<PrimaryButton
actionType={actionType}
onClick={handleButtonClick}
/>
</>
);
};
// Assuming PrimaryButton is defined elsewhere
const PrimaryButton = ({ actionType, onClick }) => (
Dentro da função handleButtonClick criamos um if para saber se o usuário está tentando alugar ou comprar um filme.
Ou seja, a mudança está focada no fluxo de ‘alugar um filme’ mas a alteração que fizemos influencia também o fluxo de ‘comprar o filme’ pois os dois fluxos estão no mesmo código.
Deixamos juntos as coisas que mudam por razões diferentes. O ideal nesta situação é:
const MovieAction = () => {
const [actionType, setActionType] = useState('rent');
return (
<>
<RentFlow
isSelected={actionType === 'rent'}
onSelect={() => setActionType('rent')}
/>
<BuyFlow
isSelected={actionType === 'buy'}
onSelect={() => setActionType('buy')}
/>
</>
);
};
// Assuming RentFlow and BuyFlow components are defined elsewhere
const RentFlow = ({ isSelected, onSelect }) => (
<label>
<input type="radio" checked={isSelected} onChange={onSelect} />
Rent
</label>
);
const BuyFlow = ({ isSelected, onSelect }) => (
<label>
<input type="radio" checked={isSelected} onChange={onSelect} />
Buy
</label>
);
Assim, conseguimos separar os componentes visuais por fluxo selecionado e deixar a lógica separada do componente principal.
Este tipo de refatoração é o que o SRP sugere.
Quando conseguimos separar coisas que mudam por razões diferentes, diminuímos o acoplamento entre os elementos de tal forma que uma alteração no primeiro elemento não afeta o segundo elemento.
E quando conseguimos deixar junto as coisas que mudam pela mesma razão aumentamos a coesão de tal forma que lembramos de alterar tudo que for necessário.
Ou seja, quando quebramos o SRP provavelmente estamos aumentando o acoplamento e diminuindo a coesão entre os elementos.
SRP pode ser quebrado mesmo com um bom design#
Um dos pontos interessantes deste princípio é que ele depende da mudança que ocorre no sistema e no contexto em volta dela.
Ou seja, mesmo que o código tenha um bom design, isso não significa que mantemos o SRP.
Imagine um sistema bancário que atende pessoas físicas e oferece o serviço de cartão de crédito e conta corrente.
E para cada serviço existe uma equipe de desenvolvimento:

O código é bem feito e cumpre o SRP: a conta corrente tem um módulo e uma equipe específica e mesma coisa para o Cartão de Crédito.
Agora vem uma nova mudança: precisamos atender agora pessoas jurídicas e oferecer o mesmo serviço.
Como a empresa tem duas equipes, os gestores resolvem mudar a responsabilidade das equipes também: uma equipe para cuidar de pessoa física e outra para cuidar de pessoa jurídica.
E é neste momento que ocorre a quebra do SRP:

Com a nova responsabilidade da equipe, o mesmo módulo terá regras para PF e PJ no mesmo lugar: as coisas que mudam por razões diferentes deveriam estar separadas, mas não estão.
O módulo de Conta Corrente e Cartão Crédito terá modificações das duas equipes.
Esse tipo de reorganização de equipes normalmente traz a quebra do SRP se não houver consideração do projeto ou do código.
Com certeza, surgirão bugs que uma equipe criou e que reflete somente no fluxo de trabalho da outra equipe.
Perceba que a origem da mudança vem da equipe. Então deveríamos refatorar o projeto para refletir a realidade das equipes também.
Quebra do SRP pode ocorrer em uma base de código única#
Às vezes podemos achar que o mesmo software consegue atender dois clientes ou mais do mesmo nicho.
Mas se algo do workflow de um dos clientes for diferente, o mesmo software ou uma base de código pode não ser suficiente.
Quebramos o SRP quando tentamos generalizar uma solução para clientes que parecem ter o mesmo problema.
Este caso aconteceu com o Word da Microsoft.
A ideia era colocar o Microsoft Word para Windows e Mac com a mesma base de código.
Mas, com a execução, o time da Microsoft Word percebeu que o usuário do Mac tem um perfil diferente do usuário do Windows.
E também tinha a questão dos atalhos que eram diferentes em cada uma delas.

A Gestora de Produto então decidiu criar duas bases de código, uma para o Windows e outra para o Mac. Ou seja, deixou de quebrar o SRP.
SRP sempre está relacionado com mudança. Neste caso, o usuário de cada plataforma tinham desejos diferentes. Se mudam por razões diferentes, deixe separado. Essa é a ideia do SRP.

Pode parecer um trabalho redobrado. Mas não é. É um trabalho de qualidade que foi entregue pensando no usuário final.
Você prefere tentar “economizar” tempo e dinheiro com base única mas que os usuários reclamam do seu sistema ou entregar valor para o usuário final?
Como identificar a quebra do SRP?#
Muitos devs focam na quebra do SRP no nível do código, mas percebem que a origem da quebra nunca vem do código. Vem sempre de alguma mudança.
Por exemplo, o primeiro exemplo é baseado na mudança do fluxo de aluguel. O segundo exemplo é baseado na mudança da responsabilidade da equipe. O terceiro exemplo é baseado na mudança do perfil do usuário de cada plataforma.
Ou seja, devemos prestar atenção nas mudanças que realizamos no código.
Para adicionar uma funcionalidade, todas as alterações de código estão próximas ou juntas? Precisa de uma equipe para alterar? Precisa mexer em um módulo extra? Precisa colocar um if para descobrir o contexto e o fluxo?
Tudo isso são sinais de quebra do SRP.
Não é sobre deixar o código sucinto ou limpo. É sobre facilitar e agilizar as mudanças que queremos realizar no nosso sistema.
Identifiquei a quebra do SRP! Devo refatorar?#
A resposta é sempre sim, mas uma pergunta melhor é como refatorar.
Existem refatorações que exigem um esforço maior e saber refatorar em pequenos passos se torna um conhecimento crucial nestas situações.
Sem este conhecimento, provavelmente o código nunca será refatorado para cumprir o SRP.
Se identificar uma quebra do SRP sempre pense em como refatorar em pequenos passos, garantindo o funcionamento com testes automatizados.
A pergunta é sempre “como refatorar?” e não “devo refatorar?”.
Conclusão#
Compartilhei uma visão mais ampla e exemplos concretos que vivenciei da quebra do SRP. Para cada quebra do SRP existe uma solução inteligente e eficiente.
E quanto mais cedo identificarmos a quebra do SRP e refatorar, menos “spaghetti code” teremos no projeto.
Se tiver mais curiosidade sobre o assunto, pode falar comigo!
🥒🥒 Espero que tenham curtido e até o próximo artigo! 🥒🥒