Design Pattern

Object Calisthenics em PHP – Parte 2

Este artigo é uma continuação do anterior. Para relembrarmos, abaixo a lista dos assuntos e em negrito os que serão abordados nesse artigo:

  1. Um nível de indentação por método
  2. Não use ELSE
  3. Envolva seus tipos primitivos
  4. Envolva suas collections em classes
  5. Uma chamada de método por linha
  6. Não abrevie
  7. Mantenha as classes pequenas
  8. Não tenha classes com mais de duas variáveis de instância
PHP Básico
Curso de PHP Básico
CONHEÇA O CURSO

3. Envolva seus tipos primitivos

Podemos definir esse exercício para os tipos escalares em PHP que são: int, float, bool e string. “Envolver” vem de um significado da programação orientada a objetos, que quer dizer, colocar o tipo “envolta” de uma classe, a fim de trazer mais resultados e funcionalidades do que um tipo comum/escalar.

Essa técnica vêm de uma aplicação do DDD (Domain-Driven Design) chamada de Value Object, onde temos um objeto-valor pequeno, que irá cuidar de um tipo de dado específico. Como o PHP é fracamente tipado, a melhor aplicação será em passagens de parâmetros de métodos ou funções. Veja o código abaixo:

class Customer
{
    protected $name;
    protected $birthday;

    public function __construct(string $name, string $birthday)
    {
        // Validar aqui???
        $this->name = $name;
        $this->birthday = $birthday;
    }
}

Ambos parâmetros são validáveis e não é legal validarmos no construtor da classe, pelo fato de não podermos reaproveitar as validações. Já o fato de forçarmos os atributos como string na entrada, algo errado pode acontecer se o desenvolvedor que está usando a classe não souber, por exemplo, qual padrão de data utilizado para a entrada $birthday, o que pode gerar um problema lá na frente, possivelmente no banco de dados.

Abaixo um exemplo de bom e outro de mau uso da classe:

// Programador que conhece a classe
$customer = new Customer('John Doe', '1983-02-10');

// Programador que não conhece a classe
// pode gerar um 0000-00-00 no Database
$customer = new Customer('John Doe', '10/02/1983'); 

Poderíamos então usar duas classes que farão envolvimento no tipo string, por exemplo:

  • CustomerName: que cuidará de validação de nome de cliente, pode verificar tamanho, fazer trim, e até limpeza.
  • CustomerBithday: Mais importante que o nome, ela vai validar o formato da data ou até mesmo formatar a entrada como, por exemplo, converter 10/02/1983 para 1983-02-10, evitando assim um problema de inconsistência, lembrando que não precisa ser necessariamente uma classe e, seguindo o princípio de inversão de dependência, podemos facilmente trabalhar com interfaces seguindo estratégias.

Como ficaria após a implementação dessas classes:

class Customer
{
    protected $name;
    protected $birthday;

    public function __construct(CustomerName $name, CustomerBirthday $birthday)
    {
        $this->name = $name;
        $this->birthday = $birthday;
    }
}

Usando:

// Programador que conhece a classe
$customer = new Customer(
     new CustomerName('John Doe'), 
     new CustomerBirthday('1983-02-10')
);

// A data será formatada internamente
$customer = new Customer(
    new CustomerName('John Doe'), 
    new CustomerBirthday('10/02/1983')
);

Um possível problema dessa abordagem é que ela adiciona complexidade à base de código. Na tradução dos Object Calisthenics, é colocado que todos os tipos primitivos devem ser envolvidos em classes, porém, sabemos que em PHP isso pode se tornar improdutivo e desnecessário, portanto, analise o quanto aquela entrada ou tipo pode sofrer mudança, se precisa de validação, normalização etc, só aplique-o se tiver uma real justificativa.

4. Envolva suas collections em classes

Semelhante ao exercício anterior, devemos envolver nossas coleções. Isso significa que trabalhar com um CustomerList é melhor do que com um array, neste caso, o uso dará uma melhor flexibilidade para o tratamento da coleção.

Abaixo uma usabilidade com e outra sem coleção em classe:

// A lógica fica fora, o que pode trazer problemas futuros
foreach ($customers as $customer) {
    if ($customer->isGoldAccount()) {
        $customer->addBonus(new Money('R$ 50,00'));
    }
}

Nesse exemplo queremos adicionar um bônus aos clientes do tipo gold. $customers é um array e por isso para modificar a coleção, precisamos iterá-la com um foreach e ainda internamente verificar se o tipo do cliente é gold.

Se usarmos uma classe que envolve a coleção, ou seja, uma CustomerCollection ela poderá ter 2 métodos:

  • Para filtragem de tipos de clientes: filterGoldAccounts.
  • Para adicionar aos clientes o bônus: addBonus.

A usabilidade ficaria assim:

$customersCollection = new CustomersCollection; // Classe com Lazy Loading

// Filtramos os clientes de conta Gold
$goldCustomers = $customersCollection->filterGoldAccounts();

// Adicionamos pela collection o bonus de R$ 50,00
// filtrado pela classe de coleção
$goldCustomers->addBonus(new Money('R$ 50,00'));

// Por fim persistimos
$goldCustomers->persists();

Assim, temos coleções que são específicas e inteligentes o suficiente para melhorar a usabilidade e evitar erros de programação. No PHP temos um conjunto de classes padrão para lidar com listas, a SPL(Standard PHP Library) que tem uma sessão dedicada a iteradores.

5. Uma chamada de método por linha

Devemos sempre fazer uma chamada de método por linha, não se aplicando à bibliotecas que usam do padrão Method Chaining ou DSL(Domain Specific Language).

Para seguir esse exercício não devemos, por exemplo, ao desenvolver um conjunto de Models, relacioná-los com métodos em cadeia, isso pode ser uma péssima ideia, segue um exemplo:

$customer->getById(55988)
         ->getPurchase(18376)
         ->getProducts()
         ->filterById(234);

Queremos resgatar um produto do pedido de um cliente, pode-se parecer muito prático, porém, alguns problemas poderão ocorrer e é muito difícil testar um bloco desses. Como saber se getPurchase encontrou o pedido? E se não encontrou, o que acontece? Nesse caso, vem outra problemática: e se o pedido não contém itens ainda? E temos um retorno null, certamente teremos um erro de método não encontrado.

Por isso, para garantir que tudo ocorreu certo, podemos seguir a Lei de Demeter, ela diz que devemos somente conversar com classes próximas, então, criamos um método para conversar e filtrar o que precisamos, ao invés de percorrer pelos Models que estão distantes. Não focaremos na implementação, porém, o conceito de uso abaixo pode ilustrar essa aproximação:

// Resgatando o model isoladamente
$customer = $customerModel->getById(55988);

// Aproximação: método que pertence a Customer
// sua implementação cuidará de retornos nulls
$product = $customer->getPuchasedProduct(18376, 234);

O principal objetivo desse exercício é não sair percorrendo por objetos retornados em chamadas de métodos, usar chamadas de vários métodos em linha pode gerar muitos problemas de manutenção, dificuldade de entendimento e testes mal escritos. Costuma-se dizer que gera um código que “cheira mal”.

Conclusão

Esses exercícios são mais aprofundados e devem ser estudados com calma, não devemos seguí-los somente por que parece ser o certo, devemos entender a motivação por trás deles, lembrando que nenhuma dessas técnicas são balas de prata e vão servir para toda modelagem, o bom senso é a melhor direção.

Até o próximo artigo da série!

PHP Intermediário
Curso de PHP Intermediário
CONHEÇA O CURSO

Refatorando código PHP para Strategy Pattern

Quando falamos de Design Patterns, fica muito nebuloso entender o que podemos fazer com eles, pois o que muitos desenvolvedores não compreendem é que os padrões resolvem problemas de código ou de design já identificados ou simplesmente melhoram a manutenibilidade do projeto. Para que isso aconteça primeiro precisamos passar pelo problema, conhecer o que o padrão resolve e enfim aplicar uma refatoração.

Vamos falar neste artigo sobre o Strategy, que é um padrão comportamental, muito usado para quando temos regras de uma determinada atividade que podem conter muita lógica, ele organiza e separa o uso dessas lógicas, padronizando a usabilidade das classes de forma que novas implementações possam ser adicionadas no futuro sem muita mudança no uso de uma determinada classe de ação.

Para ilustrá-lo usaremos a refatoração, veremos o problema e aplicaremos o padrão resolvendo uma problemática específica. Para que fique menos complexo tudo será demonstrado em código e comentários, assim acredito que chegaremos a um entendimento melhor.

Problemática

Temos um sistema de gestão de logística e precisamos comunicar aos cliente quando o produto chegou na distribuidora, quando está a caminho da entrega e por fim o momento em que ele foi recebido pelo cliente.

Para tratar das mensagens que são enviadas aos clientes, foi criada uma classe Message da qual comunica por e-mail os clientes, veja abaixo a representação:

class Message
{
    // ...
    public function send(string $message)
    {
        // implementação de mensagem por email
    }
}

// ...
$message->send('Seu produto está a caminho da entrega');

Com o evoluir da aplicação foi preciso implementar um novo modelo de mensagem, o SMS, pois alguns clientes não possuíam cadastro online, e o sistema só possui cadastro simples como: o nome, telefone e endereço.

Resolvemos então criar duas classes, seguindo o principio de SOLID, single responsibility, a classe EmailMessage e a SMSMessage, pensando em futuras implementações fizemos a classe MessageManager retornar o objeto de acordo com o tipo de mensagem necessária.

class MessageManager
{
    // ...
    public function getSender()
    {
        if ($this->customer->fromOnline() && $this->customer->hasEmail()) {
            return new EmailMessage;
        }
        if ($this->customer->hasEmail() === false) {
            return new SMSMessage;
        }
        // ...
    }
}

// ...
$sender = $messageManager->getSender();
$sender->send('Seu produto está a caminho da entrega');

Mas como nada é perfeito, uma nova forma de comunicação entrou no projeto, os desenvolvedores precisam agora de um novo modo de envio, este agora via push notification, ou seja, a notificação será via aplicativo de celular, da forma atual nos forçou a ter essa implementação:

class MessageManager
{
    // ...
    public function getSender()
    {
        if ($this->customer->fromOnline() && $this->customer->hasEmail()) {
            return new EmailMessage;
        }
        if ($this->customer->hasEmail() === false) {
            return new SMSMessage;
        }
        if ($this->customer->hasCellPhone()) {
            return new PushMessage;
        }
        // ...
    }
}

// ...

Uma solução

Claramente não estamos seguindo corretamente a responsabilidade única, a classe MessageManager está com muitas regras de mensagem que se mesclam entre si, observamos que as regras de cada classe de mensagem deve estar dentro delas mesmas e o manager só deve invocar o objeto pedido no momento, pois ainda que surjam novas formas de mensagens, seria muito doloroso dar manutenção e testar esse método getSender().

Com o Strategy transformamos cada regra em sua classe, que deve ser passada pelo desenvolvedor, elas seguirão uma interface:

interface MessageSenderInterface
{
    // ...
    public function send(string $message) : bool;
    public function isValidSender() : bool;
}

// ...

Essa interface fará o contrato das estratégias para que todas sigam esses métodos, implementaremos uma das classes de mensagens adicionando Strategy no nome original:

class EmailMessageStrategy implements MessageSenderInterface
{
    // ...
    public function send(string $message) : bool
    {
        // Valida de acordo com a regra deste tipo de mensagem
        if ($this->isValidSender()) {
            return $this->sendMessage($message);
        }
        return false;
    }

    public function isValidSender() : bool
    {
        if ($this->customer->fromOnline() && $this->customer->hasEmail()) {
            return true;
        }
        return false;
    }
}
// ...

Isso deve ser feito também para as demais classes de mensagem, essa prática isola as regras/validações nas classes Strategy de mensagens. Agora faremos o MessageStrategy, que invocará o método da classe em que lhe for informado. Abaixo a implementação do padrão:

class MessageStrategy implements MessageSenderInterface
{
    // ...
    public function __construct(MessageSenderInterface $sender)
    {
        // Guardamos o objeto Message com suas regras e implementações
        $this->sender = $sender;
    }

    public function send(string $message) : bool
    {
        // Retorna a validação de acordo com a regra do objeto
        return $this->sender->send($message);
    }

    public function isValidSender() : bool
    {
        return $this->sender->isValidSender();
    }
}

// ...
// Ao criar o objeto MessageStrategy devemos informar qual o tipo de envio
$message = new MessageStrategy(new EmailMessageStrategy);

// O método invoca o send original da classes anteriormente adicionada
$message->send('Seu produto está a caminho da entrega');

Pronto! Agora a implementação é injetada no MessageStrategy podendo haver outras implementações sem precisar alterar a classe MessageStrategy e, o melhor: as regras específicas para cada modelo de mensagem estarão em suas respectivas classes.

Conclusão

A vantagem em usar esse padrão está em facilitar a organização das classes, centralizar a usabilidade e além disso também dar poder ao desenvolvedor de adicionar camadas na estratégia principal que será replicada a todas as outras estratégias que são injetadas.

Espero que tenham gostado do artigo, a ideia foi ilustrar uma forma de implementar o padrão Strategy de forma simples, lembrando que existem várias maneiras de implementá-lo e os exemplos apresentados acima são meramente uma prova de conceitos.

Até a próxima!

Container de injeção de dependência (DI Container)

No artigo Entendendo Injeção de Dependência vimos sobre o que é injeção de dependência, seu funcionamento e como se dá a sua aplicação.

Injetar dependências pode se tornar uma tarefa tediosa quando se têm muitas classes envolvidas. Antes de injetar uma dependência ela precisa ser instanciada. Portanto, não cuidamos apenas da “injeção”, precisamos também ter o conhecimento de quais objetos ela precisa para funcionar.

Um container de injeção de dependência (DI Container) gerencia e automatiza as instanciações. Dizemos pra ele como um objeto deve ser criado (essa é a parte que nos toca, o nosso conhecimento sobre ele) e então sempre que o precisarmos, basta que usemos o container para obtê-lo.

Esse artigo utilizará PHP como linguagem base para os exemplos, no entanto, há de se destacar, o conceito é agnóstico à linguagem. Se PHP não é a sua “praia”, não tem problema, você pode pesquisar por “dependency injection container C#” ou por qualquer outra linguagem que encontrará importantes referências e implementações.

PHP Básico
Curso de PHP Básico
CONHEÇA O CURSO

No cenário dos frameworks PHP, os mais utilizados pelo mercado (Symfony, Laravel etc) implementam, cada um, o seu próprio container e, assim o fazem, pois seria impraticável manter os objetos “conversando” pelo detrimento da enorme quantidade de instanciações repetidas que precisariam ser feitas no ciclo de uma simples requisição. Esses frameworks possuem centenas de classes e não ter por onde resolver as dependências e reutilizá-las sob demanda, é impensável.

Um container nada mais é do que um “mapa” das dependências que o projeto usa, em termos práticos, é uma classe que armazena o conhecimento sobre seus objetos e suas dependências.

Uma implementação genérica de um container (para que possamos assimilar melhor):

<?php
declare(strict_types=1);

use Closure;

final class Container
{
    private $instances = [];

    public function set(string $id, Closure $closure) : void
    {
        $this->instances[$id] = $closure;
    }

    public function get($id) : object
    {
        return $this->instances[$id]($this);
    }
}

O método set() armazena no array $instances a identificação/nome de uma dependência e a lógica por trás da sua instanciação.

Por exemplo:

$container = new Container();

$container->set('db', function() {
    return new DatabaseAdapter('mysql:dbname=test;host=127.0.0.1', 'root', '');
});

O método get() é utilizado para resolver e retornar a instância do objeto. Observe que a instanciação não se dá no set() e sim sob demanda, na hora que precisamos daquele objeto, ou seja, na hora que usamos get().

Observe essa linha:

return $this->instances[$id]($this);

Está executando a função anônima que definimos (a que resolve a dependência) e está passando para ela como único parâmetro a instância da classe Container ($this dentro daquele contexto refere-se à instância da classe em operação). Quando a função anônima é executada temos como retorno um novo objeto.

Lembra o que a nossa função anônima retorna?

$container->set('db', function() {
    return new DatabaseAdapter('mysql:dbname=test;host=127.0.0.1', 'root', '');
});

Pois bem, saindo um pouco dessas nuances relacionadas à implementação, na prática temos:

<?php

$container = new Container();

$container->set('db', function() {
    return new DatabaseAdapter('mysql:dbname=test;host=127.0.0.1', 'root', '');
});

// Imprime a instância de um objeto do tipo 'DatabaseAdapter'
var_dump($container->get('db'));

Vamos aumentar o nosso leque de objetos e suas dependências?

<?php

// Container
$container = new Container();

// Objeto que recupera configurações salvas em algum tipo de arquivo.
$container->set('config', function() {
    return new Config();
});

// Veja que agora *db* têm como dependência um objeto da classe Config
// e o utiliza para obter os dados de acesso ao BD.
$container->set('db', function($container) {
    $config = $container->get('config')->getConfig('db');

    return new DatabaseAdapter($config['dsn'], $config['user'], $config['password']);
});

// A classe UserRepository precisa de uma instância de *db*
// para fazer consultas ao banco de dados.
$container->set('user.repository', function($container) {
    return new UserRepository($container->get('db'));
});

Veja que temos uma cadeia de objetos interdependentes. Imagine agora a situação de termos três diferentes controladores, sendo instanciados em momentos diferentes no ciclo de execução da aplicação e todos eles necessitando da instância de UserRepository para recuperar informações sobre um usuário?

Com o container definido tudo o que teríamos que fazer:

// ... 

$indexController = new IndexController(
    $container->get('user.repository')
);

$userController = new UserController(
    $container->get('user.repository')
);

$registerController = new RegisterController(
    $container->get('user.repository')
);

Observe que estamos passando para o construtor dos controladores uma instância de user.repository. O container lidará de nos retornar o objeto que queremos injetar a partir dessa identificação.

Um container pode implementar ainda mais comportamentos. Por exemplo, você deve ter percebido que a execução de $container->get('user.repository') vai sempre instanciar um novo objeto. Se executarmos 100 vezes, serão 100 novos objetos criados.

No entanto, algumas dependências são definitivas o suficiente para que não haja a necessidade de sempre instanciarmos um novo objeto delas. Nesses casos podemos ter um novo método no container para definir uma dependência compartilhada (singleton), onde uma única instância é gerada e retornada durante todo o ciclo de execução da aplicação.

O objetivo primário desse artigo não é se preocupar tanto com a implementação, mas com o conceito. No entanto, é importante que desenvolvamos alguns “protótipos” para uma melhor assimilação.

Vejamos então a implementação do nosso container com o novo método singleton():

<?php
declare(strict_types=1);

use Closure;

final class Container
{
    private $instances = [];

    public function set(string $id, Closure $closure) : void
    {
        $this->instances[$id] = $closure;
    }

    public function get($id) : object
    {
        return $this->instances[$id]($this);
    }

    public function singleton(string $id, Closure $closure) : void
    {
        $this->instances[$id] = function() use($closure) {
            static $resolvedInstance;

            if(null !== $resolvedInstance) {
                $resolvedInstance = $closure($this);
            }

            return $resolvedInstance;
        };
    }
}

Nesse método a lógica de resolução tem uma camada a mais, nela verificamos:

  1. Essa dependência já foi resolvida (devidamente instanciada) anteriormente?
    1.1. Não? Então assim o faremos.
    1.2. Já foi? Então vamos retorná-la do “cache” da variável estática (tipo de variável que não perde o valor mesmo quando o nível de execução do programa deixa o escopo).

Voltando ao contexto do nosso exemplo das classes controladoras que recebem a injeção do objeto UserRepository, podemos agora otimizar a resolução dessa dependência usando o método singleton() ao invés do set():

$container->singleton('user.repository', function($container) {
    return new UserRepository($container->get('db'));
});

Agora, na injeção dessa dependência nas classes dos controladores teremos sempre o mesmo objeto sendo compartilhado entre as diferentes instâncias:

// ... 

$indexController = new IndexController(
    $container->get('user.repository')
);

$userController = new UserController(
    $container->get('user.repository')
);

$registerController = new RegisterController(
    $container->get('user.repository')
);

É comum referências a DI Container (Dependency Injection Container) ou a IoC Container (Inversion of Control Container). São a mesma coisa: containers de injeção de dependência. A diferença é que um IoC Container precisa conseguir (inclusive) resolver as dependências a partir do mapeamento de interfaces (ele as resolve a partir de abstrações em detrimento às implementações concretas). É possível programar um IoC Container para resolver determinado objeto se uma determinada interface for requerida.

O container do Laravel Framework é um bom caso de uso. Ele é bastante encorpado e consegue resolver de diferentes formas. Ele é considerado um IoC Container, mas nada de errado se o referirmos como sendo um DI Container.

Veja esse trecho do core do Laravel Framework:

// The database manager is used to resolve various connections, since multiple
// connections might be managed. It also implements the connection resolver
// interface which may be used by other components requiring connections.
$this->app->singleton('db', function ($app) {
    return new DatabaseManager($app, $app['db.factory']);
});

Ele possui diversos services providers que configuram dezenas de dependências. E o container é compartilhado e utilizado por quase todas as classes do Framework.

(Para visualizar o Código-fonte do IoC Container do Laravel, clique aqui).

Laravel 5.1 - Framework PHP
Curso de Laravel 5.1 - Framework PHP
CONHEÇA O CURSO

Outras importantes implementações de containers para PHP:

Recomendação de leitura:

Novo artigo da série, sobre resolução automática de dependências. Nele , vamos incrementar o container criado aqui nesse artigo. Portanto, recomendo a leitura:

Até a próxima!

PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

Por que a arquitetura do meu software deu errado?

Atualmente, muitos desenvolvedores estão dando um pouco mais de relevância para algo que não era tão levado em consideração há pouco tempo atrás: a arquitetura do código. Muitas profissionais de TI estão dando cada vez mais valor para a qualidade do código que é produzido, ao invés de simplesmente se preocuparem em “fazer funcionar”. E isso é excelente! =)

Agora, também é notório que muitas pessoas estão aplicando a arquitetura no código que elas constroem de maneira equivocada, quer seja porque aquela arquitetura não era muito adequada à situação, ou porque houve um overhead arquitetural, ou por falta de preocupação com outros fatores além da arquitetura utilizada no código ou até mesmo pela simples falta de domínio. E qual é o resultado disso? Softwares ruins, difíceis de testar e até mesmo com baixa legibilidade, indo completamente contra tudo que arquitetura de software tenta alcançar.

Por que será que esse tipo de coisa acaba acontecendo? Vamos ver algumas possíveis razões.

Python - Banco de dados com DB API
Curso de Python - Banco de dados com DB API
CONHEÇA O CURSO
Esquecimento de que produzir um software não é simplesmente escrever código

Alguns desenvolvedores dão muita atenção a somente… codificar! Porém, produzir um software não consiste somente em produzir código. Muitos se esquecem que existe a parte de testes de software, a parte de configuração do ambiente de execução do software, a parte de validação do funcionamento do software, entre vários outros fatores. Se você vai, por exemplo, construir uma aplicação web, você ou outra pessoa deverão em algum momento configurar servidores de aplicação, firewalls, permissões de acesso e outras coisas. Tudo isso, no final, impacta até mesmo na maneira como você irá realizar a codificação da sua aplicação. O mesmo ocorre com relação aos testes: se você quiser criar um projeto que possa ser testado de verdade, você precisará se preocupar com a maneira como você produz seu código, para que seja possível realizar os testes até mesmo nos menores níveis (como os testes unitários). Também podemos considerar conceitos como granularidade e até mesmo complexidade de algoritmos.

Outro exemplo: você precisa criar um software de missão crítica que irá realizar intercâmbio de informações com o meio externo. Será que aqui, ao invés de utilizar o padrão JSON para fazer este intercâmbio de informações, não caberia o bom e velho SOAP? Perceba que até mesmo esse tipo de situação pode impactar na maneira como você vai montar a arquitetura de seu software, impactando de maneira direta até mesmo na codificação.

Aplicação incorreta de conceitos

Isso é algo comum se tratando de arquitetura e pode até ser “mal de desenvolvedor”: quando aprendemos algo novo, queremos logo sair aplicando em nossos projetos. O grande problema disso é que é bem complicado conseguirmos aplicar um conceito novo em um projeto que tenha uma certa complexidade e da maneira correta, assim, “logo de cara”. Além disso, geralmente nem avaliamos se a situação com a qual nos deparamos é realmente a ideal para aplicar aquele novo conceito que estamos loucos para mostrar para todo mundo que já “dominamos”, quer seja esse conceito uma nova arquitetura (como DDD, por exemplo) ou mesmo algum design pattern. O resultado disso: um código conceitualmente incorreto, com várias falhas de implementação, de manutenção e testes muito complicados e que ninguém sente orgulho em dizer que participou da respectiva codificação. O mais complicado dessa situação: o prazo do projeto vai fatalmente apertar e no final, para cumprimento do prazo, você estará apelando para “gambiarras” que vão contra a própria arquitetura que você tinha imaginado inicialmente.

Eu já tive esse tipo de experiência na pele… Por várias vezes precisei trabalhar em um projeto em que a impressão era que o responsável simplesmente resolveu aplicar todos os design patterns existentes! No final, o que era para facilitar a manutenção e tornar o código mais legível, causou o efeito completamente contrário. O código tinha um custo muito alto de manutenção e era muito mais complexo do que deveria ser. E por que isso aconteceu? Porque vários conceitos de design patterns que estavam sendo utilizados foram aplicados de maneira completamente equivocada. O efeito foi justamente o contrário do que era esperado. Faltou talvez um pouco de conhecimento e bom senso a quem desenvolveu a arquitetura do projeto inicialmente.

É comum aprendermos conceitos de arquitetura de código e querermos aplicá-los de maneira imediata em nossos projetos. Mas, um mantra que acaba ficando é: domine-o primeiro. Estude-o bastante, aplique-o em projetos menores e de menor relevância inicialmente, para que você possa de fato dominar o que você acabou de aprender. Depois, avalie com frieza se realmente é necessário aplicar o conceito em questão. Não adianta você aplicar uma série de conceitos em um software que não exige um código tão complexo. É como se você estivesse “matando uma mosca com um tiro de bazuca”. Não é porque você acabou de aprender algo que você precisa aplicar a novidade em todos os seus projetos e em todas as situações. Se caso for viável e prudente a aplicação de um conceito em seu código, utilize o ciclo de refactoring para realizar esta alteração! =)

Falta de alinhamento de conhecimento entre a equipe

Isso também, infelizmente, é muito comum… Dificilmente você irá desenvolver um software sozinho: você estará inserido em uma equipe. E isso fatalmente irá causar um desalinhamento de conhecimento entre os componentes da equipe, afinal, é normal sabermos mais que alguém e alguém saber mais que a gente. E aqui está uma grande armadilha.

É importante definir um estilo de arquitetura que esteja congruente com o nível de conhecimento da equipe.

Por exemplo: existe um padrão arquitetural que está ficando cada vez mais popular, que é o DDD (Domain Driven Design). O grande ponto é que, para um desenvolvedor conseguir entender e aplicar corretamente os conceitos previstos pelo DDD, ele tem que ter um certo domínio de, no mínimo, orientação a objetos… A chance de as coisas darem erradas se você tentar codificar um projeto baseado no DDD com uma equipe mais iniciante ou que não conheça muito bem orientação a objetos é muito grande. Neste caso, a atitude mais sensata é utilizar um estilo arquitetural mais simples, como por exemplo uma arquitetura baseada em domínios anêmicos (por mais que esta deturpe alguns conceitos de orientação a objetos, rs).

Isso não quer dizer que você não deve estimular a equipe a aprender novos padrões arquiteturais e expandir os conhecimentos, muito pelo contrário. O grande ponto é que isso não deve ser feito com o projeto que está sendo desenvolvido. Ao invés disso, a criação de grupos de estudos paralelos ao desenvolvimento do projeto é, por exemplo, uma excelente idéia. Pode ser que a equipe não possa aplicar a arquitetura desejada no projeto atual, mas certamente estará preparada e entenderá a necessidade de se aplicar uma arquitetura mais elaborada nos próximos projetos.

Muitas vezes, o simples é melhor

De nada adianta pensarmos em uma arquitetura super mirabolante se ela não puder ser implementada com sucesso, ou se ela se demonstrar complexa demais para a situação a qual ela tenta resolver. Aqui, cabe o velho ditado: “menos é mais”. Código não tem que ser algo complexo de se entender e complicado de lidar. Muito pelo contrário: quando mais simples, melhor vai ser a arquitetura do código, desde que a arquitetura consiga atender os requisitos do projeto.

Aqui, cabe um exemplo muito legal: o do StackOverflow. Esse site que certamente você já visitou e usou se você trabalha com desenvolvimento possui uma arquitetura super simples. E nem por isso ele deixa de atender milhões de usuários espalhados pelo mundo. Não é porque o projeto é enorme que sua arquitetura precisa ser a mais complexa do mundo.

O StackOverflow é construído em cima de .NET, SQL Server e Redis. Para que você tenha idéia, nem mesmo frameworks ORM mirabolantes eles utilizam: eles apelam para o velho e eficiente ADO.NET (em algumas partes com o auxílio do Dapper). Se você é desenvolvedor .NET, sabe que aqui não temos nada maluco ou complexo do ponto de vista técnico. Só que funciona muito bem! A arquitetura é simples, direta e eficiente. Não foi necessário nenhum tipo de complicação para fazer o StackOverflow funcionar! =)

Se estiver curioso sobre a arquitetura do StackOverflow, você pode ver maiores detalhes neste link aqui.

Preciosismo do responsável pela arquitetura

Como geralmente não existe um “cargo” específico para arquiteto de software, geralmente o desenvolvedor com mais experiência e conhecimento acaba exercendo este papel. E isso é muito bom, porque ele terá uma oportunidade muito legal para repassar todo seu conhecimento e experiência para o restante da equipe ao definir a arquitetura dos projetos que a mesma vai desempenhar. O grande problema é quando essa pessoa se fecha em seus conhecimentos, se indispondo a ouvir opiniões da equipe ou a aprender coisas novas.

É importante sempre lembrarmos que a área de desenvolvimento de software é muito dinâmica. E isso logicamente ocorre também com arquitetura de software. É fácil percebermos isso: há pouco tempo atrás, era o paradigma procedural quem imperava. Aí, menos tempo atrás ainda, veio o paradigma orientado a objeto. Hoje, temos uma explosão do paradigma funcional, por causa da popularização do JavaScript e de outras linguagens funcionais, como o F#. As coisas estão sempre mudando. Novos conceitos estão sempre surgindo, e continuarão a surgir. Conceitos antigos caem em desuso, e continuarão caindo. Essa mutabilidade é que dá a graça e faz a área de desenvolvimento de software em geral ser tão desafiadora.

Quem assume o papel de arquiteto precisa estar antenado nestas mudanças. Na verdade, qualquer profissional de TI precisa sempre estar a par dessas novidades. Mas, no caso do arquiteto, isso cai mais na situação de que ele precisa ter consciência de que o que ele pensa e fala não é uma verdade absoluta, pois as coisas estão sempre mudando. Preciosismo em arquitetura de software nunca será bem-vindo.

É complicado falar de “certo” e “errado” em arquitetura de software

É importante salientar que este post não tem a menor intenção de ser um “juiz” e dizer que tudo que você faz hoje está “certo” ou “errado” com relação a arquitetura de software. Eu penso que, tratando-se de arquitetura, isso não existe. Têm-se o “melhor” ou “mais aplicável” e o “menos melhor” ou “menos aplicável”. E esse enquadramento pode depender de uma série de fatores externos, como o nível de conhecimento da equipe, plataforma a ser utilizada e até mesmo o tempo disponível para execução do projeto. É aí que entra o conhecimento e até mesmo um certo feeling por parte do responsável por definir a arquitetura do projeto de software em questão. Também é necessário lembrar que, mais importante que aplicar um conceito arquitetural todo rebuscado em um projeto, querendo ou não, o mais importante é entregá-lo. É para isso que existem os ciclos de refactoring.

Os pontos deste post tratam apenas de minha opinião pessoal. Posso estar certo ou errado… Você pode concordar comigo ou não… E é justamente isso que torna tão legal conversar e discutir sobre arquitetura de software. o/

Concorda com os pontos deste post? Discorda? Quer acrescentar alguma coisa? Tem uma opinião diferente sobre algum ponto? Ou ainda tem uma experiência que gostaria de compartilhar? “Bora” conversar aí embaixo nos comentários! 😀

Até o próximo post!

Symfony - Template Engine Twig
Curso de Symfony - Template Engine Twig
CONHEÇA O CURSO

Padrões de projeto: o que são e o que resolvem

Design Patterns (comumente relacionados na literatura de TI como “Padrões de Projeto”) são soluções para problemas comuns que encontramos no desenvolvimento ou manutenção de um software orientado a objetos (não são tão bem aplicáveis em outros paradigmas). Olhando assim parece ser algo realmente bem “requintado”, não é?

Vamos tentar entender sob outra perspectiva: imagine você no futuro, daqui 15 anos, certamente terá passado por N problemas, repetidamente e em diferentes projetos. A tendência natural é que você crie soluções comuns (mas elegantes, assim esperamos, né? :P) para resolvê-los de tal forma que você consiga aplicá-las no futuro em outros projetos. Essas soluções, criadas por você, mesmo que não compartilhadas à literatura (livros ou artigos acadêmicos), podem ser consideradas como sendo Design Patterns, sim, padrões de projeto criados por você para resolver os seus problemas. Nada mais justo, não?

Sorte do dia: Não precisamos esperar uma dezena ou mais de anos para acumularmos experiência (e, consequentemente, perda de cabelo) para que tenhamos desenvolvido eficientes soluções para os nossos softwares.

Isaac Newton certa vez dissera:

Se cheguei até aqui foi porque me apoiei no ombro de gigantes.

Lhe convido a absorver essa ideia que nos foi “presenteada” séculos atrás por esse brilhante cientista (alguns diriam que ele foi um “full stack” por ter sido físico, matemático, astrônomo, filósofo, teólogo, alquimista e coisas mais. :P).

Engenheiros de softwares por décadas desenvolveram padrões de projeto para resolver problemas comuns. Por que não dar uma chance de conhecê-los e, quem sabe, utilizá-los?

Parafraseando o filósofo e professor da USP, Clóvis de Barros Filho, em uma palestra muito especial:

Os caras escreveram as soluções, véio. Eles tiveram que tirar o negócio do zero! Só precisamos entender e aplicar a bagaça!

Ruby on Rails Intermediário
Curso de Ruby on Rails Intermediário
CONHEÇA O CURSO

Como tudo começou

Toda a inspiração por trás do nosso conceito de “padrões de projeto” veio, na realidade, da arquitetura (sim, àquela área de conhecimento que projeta e arquiteta ambientes), de um livro chamado “A Patter Language”, escrito por Christopher Alexander, Sara Ishikawa e Murray Silverstein, foi o que primeiramente lançou a ideia de “linguagem de padrões”. O livro teve como propósito apresentar centenas de padrões sobre como cidades, bairros, casas e ambientes no geral poderiam ser projetados.

O primeiro grande trabalho da área de desenvolvimento de software que absorveu tal ideia coletando e desenvolvendo dezenas de padrões para softwares se deu em 1994 em um livro chamado “Design Patterns: Elements of Reusable Object-Oriented Software”, escrito por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. Originalmente o livro discutiu 23 padrões de projetos. Os autores e o livro causaram tanto impacto que começaram a ser chamados e reconhecidos por Gang of Four (GoF) (gangue dos quatro) e tais padrões começaram a ser intitulados de GoF Patterns (Padrões GoF).

Olhando assim parece até que em uma noite chuvosa a “gangue dos quatro” reunida na garagem de um deles se acometeu de uma sobrenatural inspiração e então gritaram ==”Eureka! Eureka!”== e logo já escreveram os 23 padrões, não é? É, só parece. Os padrões, na realidade, são soluções de problemas retirados de ==códigos reais==.

Categorias dos padrões GoF

Os padrões GoF foram divididos e categorizados de acordo com a natureza do problema que eles resolvem (ou ao menos tentam, uma vez que software, como bem você sabe, é algo com “vida própria” haha):

Padrões de Criação: Tem como objetivo abstrair a instanciação de objetos. Com eles, o sistema vai solicitar um objeto de um determinado tipo e o terá prontinho, sob demanda, sem nem se preocupar com as nuances da criação. Fazendo um paralelo com o mundo real, uma empresa automobilística quando precisa de amortecedores, ela terceiriza (solicita-os) e então os instala em seus carros, sem se preocupar com o todo envolvido na criação desse componente.

Padrões estruturais: Os padrões dessa categoria se preocupam em melhor organizar a estrutura das classes e os relacionamentos entre classes e objetos.

Padrões comportamentais: Os padrões dessa categoria atuam diretamente na delegação de responsabilidades, definindo como os objetos devem se comportar e se comunicar.

Padrões GoF

Abaixo a relação dos padrões GoF.

Nome do padrãoCategoria
Abstract FactoryCriacional
BuilderCriacional
Factory MethodCriacional
PrototypeCriacional
SingletonCriacional
AdapterEstrutural
BridgeEstrutural
CompositeEstrutural
DecoratorEstrutural
FacadeEstrutural
FlyweightEstrutural
ProxyEstrutural
Chain of ResponsibilityComportamental
CommandComportamental
InterpreterComportamental
IteratorComportamental
MediatorComportamental
MementoComportamental
ObserverComportamental
StateComportamental
StrategyComportamental
Template MethodComportamental
VisitorComportamental

Concluindo

Os padrões GoF foram descritos há décadas e, software é quase como um organismo vivo, ele passa por transformações para se adaptar às novas realidades do “ambiente”.

Nem todos os padrões GoF são bem aceitos nas comunidades de desenvolvimento, por exemplo, você já deve ter ouvido de algum colega desenvolvedor que você deveria passar longe de usar um Singleton por ser um “anti pattern”. Não tenho como propósito aqui fazer julgamento de valor mas, nem tudo é “preto no branco”. Conheço grandes softwares open sources (Frameworks de linguagens bem estabelecidas) que usam Singleton em alguma parte de seus códigos e nem por isso deixaram de ser bons.

Abusar no uso de padrões de projeto pode nos levar a caminhos tortuosos. Na realidade, supervalorizar qualquer coisa nessa nossa complexa vida pode ser perigoso, pois tendemos a criar determinamos “vícios” nas resoluções dos nossos problemas. O bom senso é sempre fundamental. E no final, como sempre, nunca teremos todas as respostas. Nossos softwares nunca serão “perfeitos e impecáveis” e nunca estaremos em plenitude satisfeitos com o código que escrevemos. O desenvolvimento intelectual tem que ser constante e a paranóia precisa ser moderada. Então, vai uma cerveja (ou coca) aí? 😛

Em futuros artigos veremos a aplicação de alguns desses padrões.

Ruby on Rails Intermediário
Curso de Ruby on Rails Intermediário
CONHEÇA O CURSO