Mostrando postagens com marcador Boas Práticas. Mostrar todas as postagens

O verdadeiro custo do Paliativo



Quem nunca ouviu uma frase semelhante a essa: "Faz aí uma correção que resolve esse problema rapidamente, pois precisamos entregar isso para o cliente e tem que funcionar. Outra hora revisamos esse código com mais calma".

E assim iniciou-se um ciclo viciante (sim, viciante, essa é realmente a palavra) de pequenas inserções de código que visam boas intenções, obviamente, porém com consequências destrutivas no longo prazo. Se você já passou por isso sabe do que estou falando. São essas pequenas atitudes que fazem com que um projeto comece a ter pequenas amostras de código que poderão futuramente nunca ser substituídas.

Afinal, para que serve um paliativo?

Você já se perguntou? Bom eu vou lhe explicar. Um paliativo segue a ideia de um MVP praticamente, desenvolver o mínimo de valor entregável que gere um retorno e que atenda às expectativas do cliente. Só que um paliativo não pode ser chamado de MVP, ele apenas segue a ideia, aí você adiciona um pouco mais de velocidade ao processo (nesse momento você já deve estar no meio do fogo), e também algumas palavras mágicas devem ser proferidas: "Isso será temporário!".

Ei pera aí! Temporário? Eu acho que tem algo errado aí. Algo que deveria ser temporário acaba permanecendo por muito tempo no sistema. Na maioria das vezes esse paliativo recebe correções e até melhorias. Nesse ponto, significa praticamente dar uma armadura a um monstro que já existia. Você agora está fortalecendo esse monstro.

"O cliente não se importa com o código", muito se ouve falar isso. A única pessoa que se importa com ele é você, pois nem o seu gestor está interessado se aquilo é um paliativo, se é de fato uma implementação permanente, se é a melhor aplicação dos padrões de projeto que existe, ninguém se importa, somente você e agora você tem um monstro com uma armadura em seu sistema. Sabe por quanto tempo ele vai viver?

Por que paliativos são um problema?

Simples, pois eles funcionam. E somente por esse atrativo é que são tão requisitados e aceitos. E então esquecidos. Mas então qual é o problema com eles? Especificamente, o problema não está no paliativo. Se um sistema fosse apenas um paliativo, ele em si seria o sistema, logo não seria um paliativo, nem poderia ser chamado disso. O que estou tentando dizer é que nesse contexto o "paliativo" não é um problema, ele é um sistema. 

Quando o problema é o paliativo, você tem um problema no sistema. Um pequenino e indefeso código lá nos confins daquele bolo de classes e arquivos de configuração, no futuro pode resultar em um comportamento que precise de correção ou melhoria. Nesse contexto, nós temos um problema, mas o que é o problema: o paliativo inocente ou a situação do futuro que precisa mexer nesse paliativo? Perceba que aqui estamos falando de questões como planejamento e segurança: quanto estou disposto a me defender de possíveis anomalias conhecidas do meu sistema?

Bom, você tem um problema quando esse paliativo está em um local incomum, não rastreável, nem por olhos humanos, durante a rotina do dia-a-dia, e nem por sistemas de versionamento. Você também tem um problema com paliativos quando eles não seguem boas práticas como escalabilidade, legibilidade, manutenibilidade, responsabilidade, baixo acomplamento, e tantas outras mais. Aliás, um paliativo que segue essas práticas não pode ser chamado de paliativo.

Não sei se ficou claro, mas o fato de um sistema ser algo dinâmico faz com que você tenha que pensar no futuro. Já falei sobre o futuro logo acima. O paliativo somente será um problema se existir um futuro, e ele existe para a maioria dos sistemas. Partindo dessa premissa, talvez o problema realmente não seja o paliativo, mas sim você que chegou ao ponto de precisar de um paliativo.

Algumas táticas para contornar isso

Fugir dos paliativos é um pouco difícil. Você precisa deles e isso é inevitável por diversas razões: um paliativo de curto prazo é a melhor solução para que um projeto seja entregue, uma demanda seja finalizada ou uma correção em ambiente produtivo mantenha o sistema online. Então nesse caso o que fazer? 

Regra nº 1: Rastreabilidade

Primeiro passo é tornar esse paliativo rastreável, ou seja, você quer que esse paliativo dentro de algum tempo vire algo que seja revisado, e feito de forma correta e permanente. Estamos falando agora de código limpo, performático e fácil de entender. Um código que siga os padrões do Projeto e de Projeto (perceba a diferença entre "de projeto" e "do projeto", um papo para outro post). 

Se a sua empresa utiliza algum controle de issue, o melhor é neste exato momento que você está criando o paliativo também criar uma issue explicando como encontrar o paliativo e o que deve ser feito para que posteriormente alguém atenda a esta solicitação, ou mesmo você, e a correção deste paliativo resulte em uma implementação correta do mesmo. Se um Kanban é utilizado em seu projeto, essa é a hora de fazer um ticket ou story aparecer nele, provavelmente em backlog, o local não importa, mas alguma coisa deve aparecer.

Regra nº 2: Código Sujo

Sim, sujo, é isso mesmo que você leu, uma denominação que indica o oposto de código limpo. Uma importante atitude é não fazer com que esse paliativo seja algo excepcionalmente bom, ou seja, se você é um expert em código não utilize das melhores práticas, o palitivo não pode ser considerado um código fantástico que alguém vai olhar e pensar "Meu Deus, eu não vou conseguir fazer melhor que isso". Se esse código envolve telas faça uma tela com HTML puro as pessoas não devem gostar disto, a própria implementação deve ser um alerta para que as coisas sejam melhoradas.

Outra motivação para que esse paliativo seja sujo, é que fazer um código limpo geralmente demanda mais tempo, pois atender as boas práticas na maioria das vezes requer tempo, análise, uma relembrada dos padrões de código, conduta e convenções utilizadas. Se você está utilizando um paliativo, muito provavelmente esteja em um mal momento, algo está pegando fogo, então por que perder tempo com coisas bonitas?

Se você não tem saída e este paliativo deve ser uma implementação muito boa tanto em front quanto em back, performática e fácil de ler, então você ainda deve lembrar que tem a possibilidade de criar uma issue no sistema para rastreamento futuro. Se tempo não for o problema para você, por que está chamado sua implementação de paliativo?

Regra nº 3: Conhecimento

Eu não poderia deixar de falar sobre isso, pois esse com certeza é o assunto-chave para reduzir drasticamente a capacidade de você gerar um paliativo no sistema. Deixei ele por último, pois quero que esse seja o foco. As regras 1 e 2 são contingências, mas a regra 3 é o que realmente fará a diferença na sua vida.

Você já se perguntou por que chegou ao ponto de precisar de um paliativo? Bom, vou tentar listar alguns pensamentos, ou mesmo afirmações, que podem estar passando pela sua cabeça agora:

A plataforma/framework/lib que estou utilizando possui um bug, e agora preciso de um paliativo. Bom, você identificou um bug, será que consegue documentar esse bug e passar para a equipe de suporte desse recurso de terceiro que você precisa? Esse recurso está encapsulado e é acessível através de uma interface do seu sistema, e pode facilmente ser implementado um novo encapsulamento oferencendo a possibilidade de troca ou alternância de recurso? O ideal aqui é reportar o bug, mas também ter o sistema muito bem protegido para que uma troca de lib, por exemplo, não seja um problema. O acoplamento dessa lib no sistema deveria ser uma interface ou classe do sistema, e só. O nome desse padrão de projeto é Facade, mas nada impede você de utilizar o padrão Adapter, também conhecido como Wrapper, caso o recurso seja minúsculo e precise de adaptações para conexão com o seu sistema.

Outra pessoa da minha equipe gerou esse problema, e agora preciso de um paliativo. Ótimo, você identificou um problema no sistema, identificou o gerador, fez o paliativo. Hora de respirar? Não. Tente entender os motivos que levaram essa pessoa do seu time a criar esse problema para você. Muito provavelmente será falta de conhecimento, e agora você tem uma missão em suas mão: melhorar o processo de desenvolvimento do seu time. Você tem o conhecimento necessário para propagar de maneira controlada a informação de ações ou boas práticas para evitar problemas que geram paliativos no sistema. Um time é tão forte quanto seu integrante mais fraco.

Eu gerei esse problema, e agora preciso de um paliativo. Perfeito, você tem nas mãos um feedback perfeito para tomar uma atitude de mudança em sua vida. Melhore suas práticas de desenvolvimento de software, utilize testes unitários, crie anotações para não esquecer de configurações complexas de plataformas mal documentadas, utilize testes automatizados, explore mais as lógicas do seu código, teste em múltiplos ambientes, converse com outras pessoas do time, peça para alguém mais validar o seu código, peça por sugestões de melhorias, peça por sugestões de testes que podem ser feitos, explore mais as plataformas online como StackOverflow, teste como se sua vida dependesse desse código, teste, Teste, TESTE.

Para você desenvolvedor...

Se você leu até aqui acredito que seja alguém como eu, que deseja insamente melhorar, que seja curioso, que seja inquieto com o código fonte. Espero que meu objetivo tenha sido alcançado com você. Minha visão sobre o paliativo, o motivo de eu considerar isso um problema, como atacar o problema, mas principalmente o que fazer pró-ativamente para que um paliativo nunca seja necessário em seu sistema. 

Dev In PF: Gramática, Vocabulário e Pragmática


No dia 06 de abril de 2013 ocorreu o 1º encontro do Dev In PF. Pessoas da área de desenvolvimento de software da cidade de Passo Fundo agora contam com esse grupo que vem com a intenção de proporcionar eventos relacionados a área, fortalecer os laços de amizades e parcerias e estimular o conhecimento. Ver esse grupo nascendo e crescendo é algo espetacular para Passo Fundo e eu estive no 1º encontro apresentando um material "light" sobre boas práticas.



A experiência adquirida no mini-curso da SenaInfo me possibilitou apresentar com calma e confiança. Essa foi a segunda apresentação para um público da área que eu atuo. O público ajudou bastante, pois haviam pessoas que já trabalham na área, o que facilitou a apresentação de boas práticas de programação. Mas de novo esse assunto Vinícius? Novamente eu volto a apresentar boas práticas de programação, pois acredito ser um assunto fundamental e eternamente presente na vida de um profissional do desenvolvimento de software.

Nessa apresentação minimizei a teoria e apliquei mais exemplos, pois isso possibilita prender a atenção do público ao mesmo tempo que facilita o entendimento da mensagem final da apresentação: boas práticas não são uma tarefa de desenvolvimento, mas uma cultura, uma atitude profissional.



Acredito ter acertado no título, no assunto e no público. Dev In PF já possuía outras boas apresentações como TDD, HTML5 e Linguagens e seus ecossistemas, sendo que boas práticas são atitudes empregadas em todas as práticas e linguagens. A apresentação pode ser conferida logo abaixo.


Refatoração na prática


"Pensar é o trabalho mais pesado que existe. 
Talvez seja por isso que tão poucos o exercitem."
(Henry Ford)

Ser pragmático tem seu valor. Sempre me importei com o código, gosto de ser assim, pois conheço o real valor dessa ação, ou melhor, dessa conduta. Ser pragmático envolve muitos conceitos e acima de tudo, prática. Nesse post quero demonstrar uma prática muito conhecida de quem é pragmático que é a refatoração de código.

Refatorar código não é tão simples assim, exige conhecimento da lógica de negócio e pode ter resultados catastróficos se for feito por alguém que não possua uma visão macro do projeto e possa visualizar todas as dependências possíveis (lembrando que mesmo com os mecanismos de busca das IDEs muita informação está em locais inacessíveis para estas).



O código a seguir envolve um caso onde um controller é responsável por receber dados para o envio de um e-mail. Esse controller deve validar os dados de entrada, montar o e-mail e enviá-lo. Vou mostrar o código “cru” e então começarei a refatorá-lo. Passo a passo pretendo descrever conceitos que estão sendo atendidos e outros que possivelmente estão sendo violados. Será utilizada a linguagem Java para isso.

Case 1
import java.util.Date;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class Case1 {

    public void abstractController() throws Exception {
        
        try {
            // dados recebidos de uma fonte qualquer
            String de = "eu@email.com";
            String para = "qualquer@email.com";
            String assunto = "Refactoring na Prática";
            String corpo = "Corpo da Mensagem";
            
            // validacao de email
            Pattern p = Pattern.compile(".+@.+\\.[a-z]+");  
            
            Matcher m1 = p.matcher(de);  
            if (!m1.matches())  
                throw new IllegalArgumentException("Email address is invalid: \"" + de + "\"");
            
            Matcher m2 = p.matcher(para);  
            if (!m2.matches())  
                throw new IllegalArgumentException("Email address is invalid: \"" + para + "\"");
            
            Properties props = new Properties();
            props.put("mail.host", "127.0.0.1");
            Session session = Session.getInstance(props);
            MimeMessage msg = new MimeMessage(session);
            try {
                msg.setFrom(new InternetAddress(de));
                InternetAddress[] to = {new InternetAddress(para)};
                msg.setRecipients(Message.RecipientType.TO, to);
                msg.setSentDate(new Date());
                msg.setSubject(assunto);
                msg.setText(corpo);
                Transport.send(msg);
            } catch (MessagingException e) {
                // omitido
            }
        } catch (Exception e) {
            // omitido
        }
        
    }

}
A classe Case1 é o primeiro caso que quero mostrar. Em seu método abstractController ela executa a lógica de receber os atributos que identificam um e-mail, validar alguns deles, montar o e-mail utilizando a biblioteca javax.mail, então por fim, enviar o e-mail. Um método com muitas responsabilidades.

Isso funciona?

Sim (se você tiver o SMTP configurado em sua máquina como um servidor local). Acredito que nesse ponto se você não for alguém pragmático esse código é algo normal, mas para mim é um monstro. Quando estudei software a primeira coisa que aprendi lá em Algoritmos é que devo pegar um problema e fragmentá-lo, então resolver os problemas menores sendo que ao final terei resolvido o problema todo. O código acima para mim é um problema e que necessita de fragmentação.

Case 2

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class Case2 {
    
    /**
     * Refactoring:
     * - Criacao da classe EmailAddress
     */
    public void abstractController() {
        
        EmailAddress de = new EmailAddress("eu@email.com");
        EmailAddress para = new EmailAddress("qualquer@email.com");
        
        String assunto = "Refactoring na Prática";
        String corpo = "Corpo da Mensagem";
        
        Properties props = new Properties();
        props.put("mail.host", "127.0.0.1");
        Session session = Session.getInstance(props);
        MimeMessage msg = new MimeMessage(session);
        try {
            msg.setFrom(de.toInternetAddress());
            InternetAddress[] to = {para.toInternetAddress()};
            msg.setRecipients(Message.RecipientType.TO, to);
            msg.setSentDate(new Date());
            msg.setSubject(assunto);
            msg.setText(corpo);
            Transport.send(msg);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
}
Nesse ponto do código, uma classe extra foi criada com a intenção de centralizar o e-mail bem como sua validação. A classe EmailAddress tem a responsabilidade de fazer apenas isso, representar um e-mail. Vale lembrar que em um refactoring não se pode alterar o fluxo que a aplicação já possui, pois isso seria tarefa em uma correção ou manutenção. A classe EmailAddress segue o padrão Tiny Type, que são pequenos tipos de objeto que encapsulam pouca lógica.

EmailAddress

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

/**
 * Tiny Type Pattern
 *
 * @author Vinicius Martins Knob
 */
public final class EmailAddress {

    private final InternetAddress _emailAddress;

    /**
     * Cria um EmailAddress a partir de um email
     * @param emailAddress
     * @throws AddressException
     */
    public EmailAddress(String emailAddress) throws AddressException {
        validar(emailAddress);
        _emailAddress = new InternetAddress(emailAddress);
    }

    private void validar(String emailAddress) {
        Pattern p = Pattern.compile(".+@.+\\.[a-z]+");  
        Matcher m = p.matcher(emailAddress);  
        if (!m.matches())  
            throw new IllegalArgumentException("Email address is invalid: \"" + emailAddress + "\"");
    }
    
    @Override
    public String toString() {
        return _emailAddress.getAddress();
    }
    
    public InternetAddress toInternetAddress(){
        return _emailAddress;
    }
    
    /**
     * Retorna uma lista imutavel de EmailAddress a partir de um array de emails.
     * @param emails
     * @return
     * @throws AddressException
     */
    public static List<EmailAddress> asList(String... emails) throws AddressException {
        if (emails.length == 0)
            return Collections.emptyList();
        
        List<EmailAddress> foo = new ArrayList<EmailAddress>();
        for (String bar : emails) {
            foo.add(new EmailAddress(bar.trim()));
        }
        return Collections.unmodifiableList(foo);
    }
}
O que ganho com isso?

Em um projeto grande, ter aquele trecho de código responsável por validar o e-mail espalhado por toda parte seria inadmissível. Uma alternativa encontrada muitas vezes por mim em outros projetos é uma classe com métodos estáticos onde exista um método validarEmail(), não que seja errado, mas quando criei a classe EmailAddress estive pensando em conceitos muito discutidos pela comunidade de software como coesão, responsabilidade única, legibilidade e baixo acoplamento. EmailAddress é uma classe que quando você vê tem plena noção de seus limites e responsabilidades, pois ela é simples e clara.

Case 3
import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class Case3 {

    /**
     * Refactoring:
     * - Criacao da classe Email
     */
    public void abstractController() {

        EmailAddress de = new EmailAddress("eu@email.com");
        EmailAddress para = new EmailAddress("qualquer@email.com");

        Email email = Email.create()
                .from(de)
                .to(para)
                .subject("Refactoring na Prática")
                .body("Corpo da Mensagem")
            .build();

        Properties props = new Properties();
        props.put("mail.host", "127.0.0.1");
        Session session = Session.getInstance(props);
        MimeMessage msg = new MimeMessage(session);
        try {
            msg.setFrom(de.toInternetAddress());
            InternetAddress[] to = {para.toInternetAddress()};
            msg.setRecipients(Message.RecipientType.TO, to);
            msg.setSentDate(new Date());
            msg.setSubject(email.getSubject());
            msg.setText(email.getBody());
            Transport.send(msg);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
}
No caso 3 externalizei uma segunda parte da lógica, a montagem do e-mail. A montagem consiste na coleta dos dados e suas respectivas validações. Para isso criei a classe Email. Ela segue o padrão Builder.

Email

import java.text.MessageFormat;
import java.util.List;

/**
 * 
 * Esta classe segue o Builder Pattern
 *
 * @author Vinicius Martins Knob
 */
public class Email {
    
    private EmailAddress _from;
    private List<EmailAddress> _to;
    private String _subject;
    private String _body;
    
    private Email() {}
    
    public EmailAddress getFrom() {
        return _from;
    }
    public List<EmailAddress> getTo() {
        return _to;
    }
    public String getSubject() {
        return _subject;
    }
    public String getBody() {
        return _body;
    }
    
    @Override
    public String toString() {
        String pattern = "[from: {0}, to: {1}, subject: {2}, body: {3}]";
        Object[] data = {_from, _to.toString(), _subject, _body};
        return MessageFormat.format(pattern, data);
    }
    
    @Override
    public boolean equals(Object obj) {
        return this.toString().equals(obj.toString());
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result * (_from==null?0:_from.hashCode());
        result = 31 * result * (_to==null?0:_to.hashCode());
        result = 31 * result * (_subject==null?0:_subject.hashCode());
        result = 31 * result * (_body==null?0:_body.hashCode());
        return result;
    }
    
    /**
     * 
     * Builder Method - Builder Pattern
     * 
     */
    public static EmailBuilder create() {
         return new EmailBuilder();
    }
    
    /**
     * 
     * Builder Class - Builder Pattern
     * <br>Inner Class
     *
     */
    public static class EmailBuilder{
        private final Email _email = new Email();
        
        private EmailBuilder(){}
        
        public EmailBuilder from(EmailAddress from){
            _email._from = from;
            return this;
        }
        public EmailBuilder to(List<EmailAddress> to){
            _email._to = to;
            return this;
        }
        public EmailBuilder subject(String subject){
            _email._subject = subject;
            return this;
        }
        public EmailBuilder body(String body){
            _email._body = body;
            return this;
        }
        public Email build(){
            return _email;
        }
        
    }
    
}
A classe Email contempla o padrão Builder, padrão aplicado em virtude da quantidade de parâmetros utilizados, e a tendência dessa classe, dependendo do sistema, é aumentar a quantidade desses parâmetros. Cada método poderia conter a validação respectiva ao dado inserido, centralizando toda a lógica apenas em uma classe, para todo sistema. A classe Email faz a coleta e validação de dados de um e-mail, e só isso. Os atributos atendidos pela classe EmailAddress são também atendidos aqui: coesão, responsabilidade única, legibilidade e baixo acoplamento.

Case 4

import java.util.Arrays;

public class Case4 {
    
    /**
     * Refactoring: 
     * - Criacao da classe Sender
     */
    public void abstractController() {
        try {
            EmailAddress de = new EmailAddress("eu@email.com");
            EmailAddress para = new EmailAddress("qualquer@email.com");

            Email email = Email.create()
                    .from(de)
                    .to(Arrays.asList(para))
                    .subject("Refactoring na Prática")
                    .body("Corpo da mensagem")
                .build();

            Sender.send(email);
        } catch (Exception e) {
            // log
        }

    }
}

No caso 4, uma terceira lógica foi movida do controller para uma classe específica, a classe Sender. Essa classe passa a ser responsável pelo envio do e-mail, e somente isso. Ela encapsula boa parte da API de envio de e-mail do java (javax.mail) e possui apenas um método estático, send(email). O parâmetro de send é um objeto do tipo Email, a classe criada no caso 3. O contrutor de Sender lança uma UnsupportedOperationException, pois não existe motivos para instanciar essa classe, ela foi projetada para apenas enviar um e-mail, não armazenando dado algum. O seu método private getArrayAddress efetua uma conversão da lista de EmailAddress para um array de InternetAddress, exigência esta imposta pela API javax.mail.

Sender

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.naming.NamingException;

/**
 * <p>
 * Classe responsavel por enviar um email.
 * </p>
 * <p>
 * Classe nao-instanciavel, seu unico proposito eh enviar um email,
 * funcionalidade essa fornecidade pelo metodo {@link #send(Email)}.
 * </p>
 * <p>
 * <b>OBS:</b> Classe nao projetada para heranca.
 * </p>
 * 
 * @author Vinicius Martins Knob
 * 
 */
public final class Sender {
    
    private Sender() {
        throw new UnsupportedOperationException("classe nao-instanciavel");
    }
    
    public static void send(Email email) throws NamingException {
        Properties p = new Properties();
        p.put("mail.host", "127.0.0.1");
        Session session = Session.getInstance(p);
        MimeMessage msg = new MimeMessage(session);
        try {
            msg.setFrom(email.getFrom().toInternetAddress());
            msg.setRecipients(Message.RecipientType.TO, getArrayAddress(email.getTo()));
            msg.setSentDate(new Date());
            msg.setSubject(email.getSubject());
            msg.setText(email.getBody());
            Transport.send(msg);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
    
    private static InternetAddress[] getArrayAddress(List<EmailAddress> emails){
        List<InternetAddress> internetAddresses = new ArrayList<InternetAddress>();
        for (EmailAddress address : emails) {
            internetAddresses.add(address.toInternetAddress());         
        }
        return internetAddresses.toArray(new InternetAddress[internetAddresses.size()]);
    }
}

Conclusão

Acredito que com essa refatoração o controller ficou muitas vezes mais legível, fácil de efetuar uma alteração já que temos as partes do processo bem definidas. As classes geradas por esta refatoração são totalmente reutilizáveis, flexíveis e coesas. Nem sempre é possível efetuar uma refatoração de código, pois esta ação exige conhecimento amplo. Um ambiente preparado para testes seria ideal para manter uma rotina de refatoração diminuindo significamente partes repetidas de um sistema que utiliza uma linguagem orientada a objetos. Conceitos aplicados aqui, e muitos deles posso ter passado por cima, deveria sempre ser aplicados, desde o inicio.

Use isEmpty em vez de equals


 "A perfeição é feita de pequenos detalhes - não é apenas um detalhe." (Michelangelo)

Gostaria de começar esse post ressaltando a famosa frase de Michelangelo ditada acima que fala sobre "detalhes". Em minha busca por conhecimento dedico meu tempo justamente a isso, detalhes. Não basta para mim ter a solução em mãos, é preciso mais, é preciso saber como chegar até esta solução, cada detalhe da implementação de um algoritmo é o que o faz ter usabilidade, flexibilidade e clareza, porém nesse post quero tratar de algo mais interessante, um detalhe que envolve desempenho.

Assim como qualquer linguagem, Java tem seus detalhes de implementação que gosto de explorar. Nesse post quero mostrar o porque deve ser usado isEmpty() em vez de equals(""). Primeiramente vamos a documentação de ambos os métodos:

isEmpty - "Returns true if, and only if, length() is 0".
equals - "Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object".

Pela documentação de cada método já temos uma simples noção de implementação onde que isEmpty utiliza um atributo privado para verificar se é igual a zero, apenas isso enquanto equals utiliza verificação de argumento nulo e sequencia de caracteres, o que com certeza é uma implementação mais penosa para um caso tão simples como a verificação de uma string vazia. Equals abrange verificação para casos mais complexos. Vamos ao código de cada método:

isEmpty
public boolean isEmpty() {
    return count == 0;
}
equals
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = count;
        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            while (n-- != 0) {
                if (v1[i++] != v2[j++])
                    return false;
            }
            return true;
        }
    }
    return false;
}

Como esperado, o método equals é dedicado a comparação de strings diversas, enquanto o método isEmpty ataca um problema específico onde posso apenas querer saber se essa string esta vazia ou não.

Desempenho


Para o teste de desempenho fiz dois testes com jUnit, cada um atacando um dos métodos. Cada teste possui um loop de 100 milhões de iterações mostrando uma diferença de tempo considerável. Acredito que grandes empresas com sites que superam acessos de 1 milhão de pessoas rodando diversas classes contendo diversas vezes uma comparação utilizando equals("") resulte em algo semelhante, senão pior.

public class StringEmptyTest {
    
    @Test
    public void isEmpty() {
        String test = "";
        for (int i = 0; i < 100000000; i++) {
            test.isEmpty();
        }
    }
    
    @Test
    public void equals() {
        String test = "";
        for (int i = 0; i < 100000000; i++) {
            test.equals("");
        }
    }

}


No resultado, isEmpty() foi 5,6 vezes menor em seu tempo de execução.

Conclusão


Quando Michelangelo cunhou sua frase, assim como tantas outras, o contexto era outro obviamente, mas programar não deixa de ser uma arte e assim como no contexto de Michelangelo, nós no nosso contexto precisamos entender o quanto um detalhe é importante. Com certeza utilizar isEmpty ou equals em um programa com poucos acessos não fará diferença, o que pretendo dizer com esse post é justamente o que a frase de Michelangelo diz "A perfeição é feita de pequenos detalhes...", junte esse detalhe, mais aquele outro e mais um e terá uma melhoria de desempenho. 

Minicurso na SenaInfo: Código bom é código limpo

Quando fui convidado para criar um minicurso logo pensei em algo que eu pudesse passar aos outros e que também fosse proveitoso. Falar sobre boas práticas ao nível de código parecia algo fácil, parecia, mas percebi que diante de outras pessoas eu acabo tendo vários "apagões", quebras de pensamento que quando estava em casa treinando não tinha.


Mesmo ocorrendo vários pequenos problemas para que esse minicurso realmente existisse, eu fui preparado para muitas pessoas. O que aconteceu? Bom, eu tinha uma turma de 4 pessoas, duas conhecidas e outras duas que acabei por conhecer. Minha conclusão? Foi bom. Ter poucas pessoas me permitiu ficar calmo e apesar dos apagões, passar alguma coisa para eles.


O lado bom de enfiar a cara em algo que eu tinha apenas uma pequena noção é que todo erro se torna um grande aprendizado para as próximas vezes (tomara que aconteça =D). Então enumerei algumas coisas que aprendi com esse minicurso:

Título: O título é muito importante, pois determina o nível que sua turma vai ter, bem como apresenta o assunto específico, um bom título ti tras justamente as pessoas que você quer ter. Se eu pudesse trocar o título desse minicurso colocaria "Boas Práticas para desenvolvedores Java".

Assunto: O assunto a ser abordado deve ser específico. Tentei abraçar mais do que deveria e por fim a teoria se tornou extensa e o exercício inviável.

Público: Esse foi um grande problema, apesar de a própria organização do evento SenaInfo me deixar na mão por não publicar meu minicurso no site, ainda por cima, mesmo que tivesse publicado, não havia em lugar algum detalhes dos minicursos contendo dados como público alvo, foco, assunto e afins que poderiam eleger um público mais propicio ao minicurso. Enfim, aqui um título melhor poderia fortalecer o público.

Agradeço aos que estiveram no minicurso, peço desculpas também por qualquer coisa que deixei passar ou por qualquer atitude que demonstrou despreparo. Uma grande experiência se mostrou nesse momento permitindo evolução, porém quero evoluir ainda mais.