loading...
PHP Rio

Programação procedural orientada a classes - Parte 2

cviniciussdias profile image Vinicius Dias Updated on ・5 min read

Design de código

Uma vez eu ouvi uma frase mas não me lembro o autor: "Não existe código sem design. Existe código com design ruim". Inclusive adaptei esta frase para "arquitetura" na primeira parte desta publicação.

Antes de falarmos sobre as péssimas decisões de design que tomamos, vamos falar sobre o que é design de código.

Uma analogia interessante foi feita pelo Junior Grossi em um workshop maravilhoso de qualidade de código que fiz com ele. Foi mais ou menos assim (não com essas exatas palavras):

"Podemos fazer uma analogia da arquitetura de software com a arquitetura de uma casa. Onde fica cada cômodo, como se encaixam juntos, etc. É uma visão de bem alto nível. Já o design de código pode ser considerado análogo aos móveis de uma casa. É uma visão de mais baixo nível sobre nossas decisões sobre o código".

Então, basicamente (beem basicamente), arquitetura diz respeito a como você vai separar sua aplicação em camadas, por exemplo, onde cada camada pode ter sua responsabilidade bem definida. Já o design de código diz respeito a como você vai organizar suas classes e métodos em cada uma das camadas. Quais padrões você vai seguir, como cada classe vai se comportar sozinha ou em conjunto com suas respetivas dependências, etc.

Escolhas corriqueiras

Agora que já entendemos basicamente o que é Design de Código, vamos falar sobre escolhas que fazemos frequentemente e que talvez não sejam as mais interessantes sob a ótica da programação orientada a objetos, ou melhor, sob a ótica do design orientado a objetos.

Como já foi discutido no post anterior, é muito comum termos um modelo anêmico: Classes que apenas possuem getters e setters, ou seja, meros sacos de dados. Para quem já estudou C sabe que no fundo isso não é uma classe, mas sim uma struct.

O fato de termos modelos anêmicos onde classes que deveriam ser do nosso domínio na verdade acabam sem nenhuma regra de negócio, geralmente faz com que nós transfiramos essas regras para lugares "errados" (entre aspas porque quem sou eu pra dizer o que é certo ou errado?).

Que lugares errados seriam esses?

Helpers ou Utils

Quem nunca viu uma classe chamada StringHelper ou DateUtils? São classes que claramente estão perdidas em nosso sistema. O que seria, no mundo real, um(a) StringHelper? Se não existe no mundo real, tem certeza que deveria existir no código?

Reflitão

Esse tipo de classe é uma herança clara da programação procedural em nosso código onde estamos separando o comportamento dos seus dados, quando a orientação a objetos tem todas as ferramentas necessárias para nós os juntarmos.

Possível solução

Se você possui uma classe StringHelper, talvez ela esteja fazendo validações bem genéricas. Nesse caso, nada mais justo do que transformá-la em uma classe bem genérica, como String. Nessa classe você pode ter comportamentos e verificações pertinentes a uma string em seu código.

Mas na maioria das vezes as verificações nesse tipo de classe são específicas de regras de negócio. Ex.: Garantir que nome possua pelo menos 10 letras.

Se é uma regra de negócio, deveria estar na classe de negócio. Talvez na classe Pessoa. Talvez numa classe mais específica, como Nome. Depende do seu sistema.

Services inúteis

O livro do Eric Evans sobre DDD é certamente uma obra que revolucionou o mundo do desenvolvimento. Eu particularmente ainda não o li, mas li obras inspiradas por este livro, então sei que: Neste livro foram definidos alguns conceitos sobre classes chamadas de Services.

Uma das possíveis "categorias" desse tipo de classe é Application Service. Este tipo de classe tem como propósito receber informações de fora do domínio do sistema (da web, linha de comando, etc) e orquestrar as chamadas a regras de domínio.

Um exemplo clássico:

<?php

class AddUser
{
    private UserRepository $repository;
    private PasswordHasher $hasher;

    public function __cosntruct(UserRepository $repository, PasswordHasher $hasher)
    {
        $this->repository = $repository;
        $this->hasher = $hasher;
    }

    public function execute(AddUserDto $data): void
    {
        $hashedPassword = $this->hasher
            ->hash($data->password());
        $user = new User($dados->email(), $hashedPassword);

        $this->repository->add($user);
    }
}

Há pontos de melhoria nesse código (sempre há), mas basicamente: Ele recebe os dados de algum mecanismo de entrega através de um DTO (objeto de transferência de dados, que serve basicamente para transferir dados entre camadas no sistema), e passa para o domínio que sabe cifrar a senha, armazenar o usuário no repositório, etc.

Problema comum

Essa definição de Application Service em algum momento foi tomada por alguém como "Classe obrigatória para adicionar indireção desnecessária no sistema".

Na prática é muito comum vermos código onde um Controller chama um método de uma classe com Service no nome, e esse método não faz nada além de chamar um método de outra classe. Essa chamada extra de um método é conhecida como indireção, de forma resumida.

Algo parecido com isso:

<?php

class UserService
{
    private UserRepository $repository;

    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }

    public function add(User $user): void
    {
        $this->repository->add($user);
    }

    // todos os outros métodos públicos que também existem em UserRepository
}

Agora me responda: O que ganhamos ao ter a classe UserService? Ganhamos apenas mais indireções em nosso sistema. Mais um lugar que nossa IDE vai nos fazer passar até chegar no código que de fato executa algo.

Além de parecerem ser classes conhecidas como God classes, já que aparentam fazer tudo no sistema, são inúteis na prática.

Esse é mais um exemplo de programação procedural sendo aplicada, onde temos apenas funções sem nenhum significado pro negócio agrupadas em uma classe que simplesmente chama outras funções.

Outra característica de programação procedural nesse caso é que muito comumente temos métodos não tão relacionados na mesma classe Service.

É comum um UserService ter um método com uma chamada para salvar um usuário e outro método para enviar um e-mail para ele. Responsabilidades que deveriam estar em classes separadas.

No canal Dev Eficiente, do Aberto Souza, tem um vídeo falando um pouco mais sobre o problema deste tipo de classe, mas sob uma outra ótica. Vale a pena conferir:

Camada Model do MVC

Um ponto que tem levantado bastante discussão em comunidades que participo é: O que é a camada Model no MVC?

Discutir sobre MVC é assunto pra outro post, mas o que tenho visto como consenso é: Camada que faz a persistência dos dados.

E a regra de negócio? Uma galera tem colocado nos Controllers. E aí? Se essa lógica precisar ser reaproveitada, como fazemos? Como chamamos um Controller dentro de outro? Complicado, né?

Em nossos Controllers é muito comum ver aquele código super famoso da programação procedural cheio de IFs e verificações em tipos primitivos, sem nenhum significado para o negócio.

Padrões arquiteturais

Para "resolver" esse problema, diversos padrões arquiteturais existem, e eu falei um pouquinho (bem pouquinho) sobre arquitetura nesse meu outro post:

Conclusão

Por mais que a gente estude muito sobre ferramentas, frameworks, hypes e modinhas, os princípios da programação orientada a objetos mudam muito pouco e merecem MUITA atenção.

Estude conceitos, princípios, padrões... Tudo isso vai poder ser aplicado naquele framework que foi lançado semana passada e você está doido pra usar pra fazer um CRUD. Acredite.

Infelizmente eu ainda vejo muito código utilizando estas "técnicas" procedurais utilizando classes, e pessoas acreditando que isso é desenvolver utilizando a programação orientada a objetos.

Você costuma ver código assim no seu dia a dia? Compartilha aqui nos comentários algum caso que você tenha visto recentemente.

Posted on by:

cviniciussdias profile

Vinicius Dias

@cviniciussdias

Sempre tento aplicar a regra do bom escoteiro no código: "Sempre deixe o local de acampamento (código) mais limpo do que você encontrou.".

PHP Rio

O PHPRio é o grupo de todas as pessoas que participam da comunidade PHP no estado do Rio de Janeiro. Todos contribuem, de diversas formas, para a organização de nossos eventos e divulgação do PHP em terras fluminenses.

Discussion

pic
Editor guide
 

Eu não conhecia o conceito de indireção, isso ajuda muito a identificar classes que são só wrappers que não adicionam nada.

Sobre os helpers genéricos, geralmente isso é algo que anda acompanhado com código estático, pois são funções agrupadas em classes. Escrevi um post sobre código estático e acoplamento e o exemplo que uso é uma dessas classes genéricas cheia de funções (sem estado).

Antes eu achava código estático algo sempre ruim (o que é uma postura bem inflexível), hoje vejo que depende do contexto, pois vejo que factorys estáticas adicionam legibilidade em construtores e tem seu valor para criar entidades e value objects.

Um lugar onde vejo uso de método estático é em value objects, pois dá para adicionar lebilidade com funções que ajudam (quase como helpers) só que dentro de um contexto que não é genérico, por exemplo:

<?php

namespace Domain\ValueObject;

/**
 *
 * @author Raphael da Silva
 *
 */
class Fullname
{

    private $firstName;
    private $lastName;

    public function __construct(
        string $firstName,
        string $lastName
    ){

        $this->firstName = $firstName;
        $this->lastName  = $lastName;

    }

    public static function upperCaseFirstChar(string $name): string
    {

        return ucfirst($name);

    }

    public function getFirstName(): string
    {

        return self::upperCaseFirstChar($this->firstName);

    }

    public function getLastName(): string
    {

        return self::upperCaseFirstChar($this->lastName);

    }

    public function getFullname(): string
    {

        return $this->getFirstName() . ' ' . $this->getLastName();

    }
}

Nesse caso o método estático que usei tem o propósito só de dar mais legibilidade e contexto a função usada pelo value object.

 

Ótima contribuição, Raphael. Mas nesse seu caso eu teria uma classe Name que cuidaria de ter a primeira letra maiúscula. Isso vai de encontro com o estudo de "Obsessão por tipos primitivos". Talvez eu escreva um artigo ou faça um vídeo sobre isso no futuro.

 

Acabei de ver que sua classe já é a representação de um nome. No seu caso, concordo com sua implementação. Só deixaria esse método estático privado, talvez. Mas realmente, acredito que você tenha colocado o método no melhor lugar possível sim. :-D Ótimo exemplo!

Obrigado pelo feedback, Vinicius. Eu já troquei uma ideia com você via e-mail sobre o uso de DTO's no qual eu tinha dúvidas sobre o propósito desse padrão.

Sobre o código estático, eu acabei vendo que não é problemático o uso de métodos estáticos em casos assim, o propósito é aqui foi dar contexto e legebilidade, pois o método em questão torna mais nítido (ou óbvio) o objetivo da função nativa ucfirst nesse contexto de nomes.

Pensei agora que o nome do método poderia até ser mais descritivo ainda (upperCaseFirstCharFromName, por exemplo) para reforçar isso. Essa abordagem fez sentido para mim em value objects como esse, achei válido compartilhar.

Valeu!

 

Salve Vinícius.
Tudo tranquilo?

Esse post foi um tapa na minha cara.
🤣🤣🤣🤣

Sem palavras pra essas explicações e ajuda que está dando.

Queria saber contigo onde consigo material sobre isso?
Mesmo estando na área, sou muito fraco pra descrever um bom padrão, arquitetura.
Sou alucinado por esse tipo de material, mas mesmo lendo alguns, parecem que não entram na mente. 😓

Novamente, muito obrigado pela força.

 

Pow, Fernando, você não sabe como fico feliz com esse feedback.
Obrigado. Bom saber que to ajudando alguém.

Sobre indicações de material: Orientação a objetos em geral. Princípios como Tell Don't Ask, Least Astonishment, SOLID, etc.

Lê o livro Modern PHP do Josh Lockhart e o DDD in PHP do Carlos Buenosvinos. Ambos são ótimos.

Eu também não poderia deixar de recomendar a Alura, visto que sou professor lá.
Aqui tem uma lista completa com os cursos que eu já gravei lá:
cursos.alura.com.br/user/cvinicius...

Abração, e bons estudos.

 

Que tapa na cara foram esses dois últimos posts!
Na minha cabeça conhecia Orientação a Objetos e aplicava corretamente em meus códigos. Quanto mais se aprende, mais se vê o quanto ainda tem a se aprender.
Obrigado por compartilhar conhecimento! Você é foda!

 

Isso é muito verdade, parece que a busca por tentar entender o paradigma não tem fim.

 

Fico feliz que tenha sido útil, Tadeu. Eu que agradeço pelo feedback.