SOLID e o princípio do não faça mais do que não deve
Sejam muito bem vindos a mais um artigo. Hoje trataremos sobre SOLID e seu primeiro princípio, e a como ele nos ajuda a desenvolver um código melhor. Antes de tudo, vamos ver uma das definições do que seria o SOLID, de acordo com Robert C. Martin:
Os princípios SOLID nos dizem como organizar as funções e estruturas de dados em classes e como essas classes devem ser interconectadas.
Em resumo, esses princípios nos mostram como organizar e comunicar melhor nosso código. Agora que já vimos sobre a definição, vamos estudar a fundo o primeiro princípio, sim, estou falando do Single Responsibility Principle (SRP), em português: Princípio da Responsabilidade Única.
Problema
E para começarmos a entender o que esse princípio busca resolver, vou contar uma pequena história.
Digamos que você foi contratado como desenvolvedor backend, para trabalhar em determinada empresa. Passado algum tempo, começam a aparecer atividades de frontend. De repente, atividades de banco, de infraestrutura, de segurança, etc.
Quando você perceber, já está fazendo tudo, mesmo sendo contratado para uma determinada tarefa. Nesse momento, os problemas começam a aparecer: você fica atarefado, suas entregas não saem com o tempo e qualidade esperados, os sistemas ficam instáveis e o caos está instalado.
Vimos na história acima, que o aglomerado de responsabilidades para uma única pessoa, é um problema, e em nosso código não seria diferente.
Vamos ver um problema que elucida justamente o que o SRP busca resolver. Vamos construir uma funcionalidade de criar postagens nos correios. As informações necessárias para a postagem estão em uma fila. Após criar a postagem, deverá ser enviado determinado e-mail.
<?php
// Todo o código abaixo é somente um exemplo didático, ocultei boa parte da implementação para não ficar muito complexo.
class PostCreator
{
public function create()
{
try {
// Comunicação com a fila
$client = new SqsClient([
'profile' => 'default',
'region' => 'us-west-2',
'version' => '2012-11-05'
]);
// Resumi toda a implementação de listagem de dados para uma única linha
$messages = $result->get('Messages');
// Percorrendo os itens da fila
foreach ($messages as $message) {
// Lógica falsa de comunicação com o correios
$correios = new Correios();
$response = $correios->createPost($message);
// Lógica falsa de email
$email = new Email();
$email->send($message);
}
} catch (AwsException $e) {
error_log($e->getMessage());
}
}
}
Agora que temos o código pronto, vamos analisar os possíveis problemas que ele tem. No caso, o código se comunica com a fila, pega os dados, percorre esses dados, se comunica com a API dos Correios, cria uma postagem e envia um email. São muitas ações para uma classe que tem como função, criar uma postagem.
Princípio da Responsabilidade Única
Já vimos o problema, muitas responsabilidades. Agora vamos conhecer a definição do SRP e como ele vai ajudar a resolver nosso problema.
Um módulo deve ter uma, e apenas uma, razão para mudar.
Atualmente, nossa classe PostCreator deveria ser responsável unicamente por criar uma postagem, ou seja, a razão para ela mudar deve estar relacionado unicamente a esse contexto. Infelizmente, nossa classe não faz somente isso, existe uma série de ações que ela faz além disso, sobrecarregando e causando vários motivos para alteração.
Levando tudo isso em conta, vamos refatorar nosso código, dividindo um pouco essas responsabilidades:
<?php
class PostHandler
{
public function handler()
{
$queue = SQSQueueReader();
$post = new PostCreator();
$email = new SenderEmail();
$messages = $queue->getMessage();
foreach ($messages as $message) {
$response = $post->create($message);
$email->send($message);
}
}
}
class SQSQueueReader
{
public function getMessage()
{
try {
$client = new SqsClient([
'profile' => 'default',
'region' => 'us-west-2',
'version' => '2012-11-05'
]);
// Resumi toda a implementação de listagem de dados para uma única linha
return $result->get('Messages');
} catch (AwsException $e) {
error_log($e->getMessage());
}
}
}
class PostCreator
{
public function create($message)
{
$correios = new Correios();
return $correios->createPost($message);
}
}
class SenderEmail
{
public function sender($body)
{
$email = new Email();
$email->send($body);
}
}
A refatoração ficou um pouco grande, mas agora temos classes com responsabilidades bem definidas, por exemplo, nossa classe de criação de postagem, faz única e exclusivamente a criação da postagem, temos uma classe somente para mandar email, uma somente para listar os dados da fila e outra que irá fazer toda a manipulação necessária.
Em outras palavras, nós conseguimos dividir o nosso código, agora temos partes separadas que são e devem ser responsáveis por uma única coisa, uma das vantagens ganhas, é que com essa divisão, além da leitura do código melhorar, podemos substituir partes do nosso código, sem necessariamente afetar outras partes do sistema.
SRP e o Comportamento
Galera, na refatoração acima, dividi as responsabilidades de modo que podemos considerar que cada método ficou em uma classe, isso não quer dizer necessariamente que todas as suas classes devem ter apenas um método, muito pelo contrário, suas classes podem ter quantos métodos quiser, o que importa realmente é o comportamento/finalidade se manter único.
Isso mesmo, o SRP trata de comportamento, meu código A tem determinado comportamento, meu código B tem outro e assim por diante. Para deixar isso mais claro, veremos um código onde o comportamento principal é o de cadastrar um usuário, mas também faremos uso de recursos de outras classes:
<?php
class Creator
{
private $conn;
private $validation;
public function __construct()
{
$this->conn = new Connection();
$this->validation = new Validation();
}
public function create($data)
{
$this->createDataAreValid($data);
}
public function createDataAreValid($data)
{
if ($this->validation->validate($data) {
// faz algo
}
$this->createIfEmailIsUnique($data);
}
public function createIfEmailIsUnique($data)
{
if ($this->conn->checkEmail($data['email')) {
// faz algo
}
$this->conn->save($data);
}
}
O exemplo acima é bem didático, podemos ver que mantemos o comportamento da nossa classe, fazemos uso de algumas validações e verificações, porém, deixamos esses recursos fora do escopo da nossa classe, fizemos uso de simples chamadas para essas ações. No código acima, seguimos o SRP tranquilamente.
Resumo
Pessoal, eu quero que vocês entendam que o comportamento, a responsabilidade, de determinada classe/ação, deve ser único, seja um cadastro, uma validação ou qualquer outra coisa. E toda modificação que for necessária, será feita para o contexto a qual a classe está definida. Mesmo que nosso fluxo dependa ou faça uso de outras ações, essas ações devem ser simples chamadas, nós não podemos adicionar responsabilidades desnecessárias ao nosso código.
Agora que já vimos o problema e aprendemos a como solucioná-lo, nos resta praticar bastante para dominar esse princípio. Pode parecer um pouco difícil no começo, todavia, a prática leva a perfeição.