Classes anônimas em Java

Classes anônimas são exatamente como as classes locais, contudo, sem um nome. Eu usei um exemplo de classe anônima no meu post anterior, falando sobre o pattern Decorator.

Para declará-las, você precisa usar o operador new e uma interface de referência, a qual a classe anônima vai implementar. A interface, neste caso, pode ser qualquer coisa extensível, ou seja, não-final: classes abstratas, concretas e interfaces puras.

Object anom = new Object() {
    @Override
    public String toString() {
        return "My hashCode is: " + super.hashCode();
    };

Você pode ainda adicionar um bloco de inicialização não-estático, que funcionaria como uma especie de construtor (sem argumentos, evidentemente):

Object anom = new Object() {

    private String text;

    {
       text = "My hashCode is: ";
    }

    @Override
    public String toString() {
        return text + super.hashCode();
    }
};

As classes anônimas são muito utilizadas no desenvolvimento de alguns padrões, entre eles o Decorator, já mencionado, o Command (que ainda pretendo explicar) e alguns outros. Um exemplo prático seria, por exemplo, suprimir o método add da implementação de uma lista:

List<String> list = new ArrayList<String>() {
@Override
    public boolean add(String item) {
        throw new UnsupportedOperationException("Not supported");
    }
};

Que lançaria uma UnsupportedOperationException toda vez que o método add fosse invocado no objeto list.

As classes anônimas também podem acessar variáveis do escopo mais próximo, por exemplo, as declaradas dentro do corpo de um método ou definidas como parâmetros, desde que elas sejam declaradas com o modificador final.

Quer um outro exemplo muito legal de classes anônimas (que particularmente acho que serviu de referências para os Lambdas do Java 8)? Dê uma olhada no Apache Commons Collections, principalmente no que diz respeito aos predicados (Predicate).

 

 

 

Decorators

Continuando os posts sobre padrões de projeto, vamos falar sobre composição, mais especificamente decoradores; e vamos começar pelo código! Imagine uma classe de saudação chamada Greetings:

public class Greetings {

   public static Greetings create() {
       return new Greetings();
   }

   public String sayHello(String to) {
        return "Hello " + to;
   }
}

Agora imagine que os objetos da classe Greetings são usados por todo seu código:

// código ...
Greetings.create().sayHello(loggedUser);
// mais código

Agora digamos que, hipoteticamente, em dado momento, alguém mudou os requisitos para que as classe tenha que retornar a saudação entre tags h1 e /h1 (e é claro que isso é hipotético).

Você poderia simplesmente sair alterando o seu código para concatenar a saudação retornada entre as tags HTML. Mas digamos que existam milhares de pontos onde você precisa alterar e você não quer sair alterando manualmente. De repente, você tem a idéia genial de usar o polimorfismo:

public class Greetings {
    public static Greetings create() {
        return new Greetings() {

            // NOTE: eu não sei por quê Satanás o WordPress quebra
            // a formatação desse código, mas você pode ver ele
            // bonitinho e formatado aqui:
            // @see https://pastebin.com/C2nFykjw
            @Override
            public String sayHello(String to) {

                return "
<h1>" + super.sayHello(to) + "</h1>
";

            }
         };
     }

     public String sayHello(String to) {
         return "Hello " + to;
     }
}

E é isso. Você alterou o método de criação da sua classe para retornar um objeto decorado. É claro que aqui usamos o artifício das classes anônimas do Java, mas a idéia é basicamente essa:

a classe decoradora estende a decorada com objetivo de acrescentar comportamentos

Você consegue identificar um strategy por aqui? Para o cliente (ou seja, quem usa) trata-se ainda de um objeto da classe Greetings, no entanto, nos bastidores, com o comportamento modificado por uma de suas subclasses.

Vamos aos conceitos…

A idéia por trás do padrão Decorator é a utilização do contrato (uma interface). Embora não tenhamos usado explicitamente uma, pelo conceito de herança/polimorfismo, a classe Greetings permite um ponto de extensão: um método público não-final. O cliente espera por um objeto da classe Greetings (ou qualquer uma das suas subclasses), desde que especifique um método sayHello com a assinatura especificada. O padrão decorator (se você clicar no link) também é conhecido como Wrapper e possui outra variação muito conhecida: o pattern Proxy.

Pretendo falar sobre o pattern proxy em outra ocasião. Por hora, dê uma lida sobre os Dynamic Proxies que escrevi no Linkedin.

Até o próximo.

Tratamento de exceções em Java

Todo o desenvolvedor de software já passou pela situação de ter que caçar um bug no código do projeto. Na grande maioria das vezes o problema inicia-se com a queixa de um usuário sobre o insucesso ao tentar utilizar uma funcionalidade qualquer. Geralmente ele liga para o suporte e reporta uma mensagem de erro. Outras vezes você tem um pouco mais de artefatos como screenshots e códigos de erros. Nesse tipo de situação, qualquer evidência pode te poupar de perder tempo (dinheiro) e de dores de cabeça. É exatamente por isso que venho estudando boas práticas no que tange o tratamento de exceções e erros há algum tempo. Um pequeno conjunto de diretrizes neste sentido pode te poupar de horas e horas de transtornos.

Em uma das minhas pesquisas, um dos melhores “guias” – vamos chamar assim – que encontrei foi uma resposta à uma questão do StackOverflow resumindo em 10 tópicos as boas práticas do tratamento de exceções. É a partir dessa resposta que estou escrevendo este post. A partir dela vou descambar para outros materiais que venho utilizando.

Tipos de exceções em Java

A estrutura de exceções em Java está definida da seguinte forma (se você não está familiarizado com as exceções em Java, dê uma lida neste post da Caelum):

Throwable, embora esteja nomeada segundo a orientação de se nomear interfaces (sufixo “able”) é na verdade uma classe concreta e permite que, além dela, suas especializações sejam lançadas (cláusula throw*) e capturadas (bloco try-catch) no seu código. Não vamos falar de Errors aqui, contudo, cabe ressaltar que eles são irreversíveis e quase sempre vão interromper a execução da aplicação.

Exceções verificadas

A classe java.lang.Exception é a raiz das exceções em Java e, por natureza, é uma exceção verificada (checked exception). Exceções verificadas são uma das característica do Java que faz com que o desenvolvedor tome algumas medidas em relação à exceção lançada: tratá-la (bloco try-catch) ou propagá-la (assinar o métodos com a cláusula throws**) para alguma outra rotina tratar. Todas as classes que derivam imediatamente de Exception são exceções verificadas, exceto RuntimeException e suas especializações.

Exceções não-verificadas

A classe java.lang.RuntimeException é uma derivada especial de Exception que tem como característica principal não ser verificada (unchecked exception). Exceções não-verificadas podem ser lançadas a qualquer momento e não necessariamente precisam ser tratadas ou propagadas.

Estrutura das exceções

Uma exceção é uma classe Java comum; com construtores, métodos e atributos. Se você não tem o source code do Java instalado, dê uma olhada no GrepCode. A classe, despida do JavaDoc, não passa de vinte e poucas linhas de código sobrescrevendo o próprio construtor:


package java.lang;

public class Exception extends Throwable {

    static final long serialVersionUID = -3387516993124229948L;

    public Exception() {
        super();
    }

    public Exception(String message) {
        super(message);
    }

    public Exception(String message, Throwable cause) {
        super(message, cause);
    }

    public Exception(Throwable cause) {
        super(cause);
    }
}

E para estendê-la é necessário nada mais do que:


public class MyOwnException extends Exception {}

Sim! Uma linha de código. A maior parte do comportamento de Exception está encapsulado na superclasse dela – Throwable – e serve basicamente para auxiliar o rastreamento da origem da exceção na pilha de chamadas de métodos (dê uma olhada em StrackTraceElement).

A grande maioria das extensões de Exception com as quais me deparei na minha vida de programador não faziam nada a mais do que isso. Um pecado! O simples fato de você adicionar algum comportamento extra em uma especialização da classe Exception, seja ela qual for, já seria uma mão na roda na sua leitura de stacktrace em busca de erros. Mas não vamos falar disso aqui. Deixa isso para outra ocasião.

Tratando exceções

Com base na resposta do Vineet Reynolds no StackOverflow, que divide o tratamento de exceções em 10 tópicos primordiais,  pretendo aqui implementá-lo com todo o resto que encontrei por aí, seja em livros, fóruns, vídeos e podcasts. Eu tomei a liberdade de modificá-los um pouco, às vezes trocando a ordem, outras removendo ou adicionando algum tópico de forma que faça mais sentido pra mim. Você, no entanto, está livre para (e deve) dar uma lida na resposta original.

Lance imediatamente

Assim que o código encontra uma situação adversa, é interessante lançar a exceção. Isso ajuda a rastrear a linha onde o erro foi ocasionado.

Encapsule a exceção original

Em complemento ao tópico anterior, se você vai lançar uma exceção nova, ao invés de propagar a que foi gerada, encapsule a original. Isso faz com que seja possível rastrear a pilha inteira de erros até se chegar à causa:


try {
    // código que gera a exception
} catch(Exception originalException) {
    throw new MyOwnException(“Erro ao tentar fazer qualquer coisa”,
        originalException);
}

log ^ throw

Você loga ou (XOR) você lança. Simples assim. Nunca faça os dois! Logar uma exceção relançá-la vai gerar pilhas imensas de log, vai dificultar a leitura do arquivo de saída e consequentemente a busca por origens de bugs.

try {
    // código que gera a exception
} catch(Exception ex) {
    LOG.warn(“Problema ao tentar…”);
    // qualquer coisa que você for fazer para tratar a exception.
    // menos relançá-la
}

 Ou


try {
    // código que gera a exception
} catch(Exception ex) {
    throw new MyOwnException(
        “Erro ao tentar fazer qualquer coisa”, ex);
}

Só capture exceções que você puder tratar

Parece óbvio, não? Se o código tenta abrir um arquivo e o arquivo não existe a pergunta é: devo tratar o IOException lançado? Sim:


public File openConfigFile() {
    try {
        // código que abre o arquivo
    } catch(IOException ex) {
        LOG.warn(“Não foi possível abrir o arquivo: “
            + ex.getMessage());
        return createNewFile();
    }
}

Alternativamente, você pode decidir que o código que está chamando o método openConfigFile deve tomar alguma medida em relação à exceção:


public File openConfigFile() throws IOException {
    // código que abre o arquivo
}

Ou simplesmente você pode decidir que não há nada a fazer:


public File openConfigFile() {
    try {
        // código que abre o arquivo
    } catch(IOException ex) {
        throw new RuntimeException(“Erro ao abrir arquivo”, ex);
    }
}

Opte por exceções da API sempre que puder

A linguagem Java já especifica uma gama de exceções que são usadas dentro das próprias APIs. É encorajado reutilizá-las ao invés de criar uma série de outras exceções dentro da sua própria aplicação. Por exemplo, um método pode receber uma String representando uma data e, antes de gravá-la no banco de dados, ele pode tentar convertê-la em um objeto java.util.Date. Para isso, foi definido que a data tem que estar no padrão ISO 8601: yyyy-mm-dd. O código poderia, por exemplo, especificar (criar) uma InvalidDateFormatException ou qualquer outra coisa parecida. Mas porque não simplesmente lançar uma:

throw new IllegalArgumentException(“Formato de data inválido”);

do pacote java.lang? IllegalArgumentException é uma exceção não-verificada, já que certamente não há o que fazer neste caso. Nada mais justo do que propagá-la para para as camadas mais acima, nas quais ela possa ser logada e eventualmente ter a mensagem de erro ao usuário final.

Lance exceções não-verificadas para erros programação

No caso anterior, com a data em formato inválido, não há o que ser feito. Uma alternativa seria utilizar a data atual (new Date()) dentro do bloco catch, mas isso vai sempre depender do domínio do seu problema. Entre elas está a própria IllegalArgumentException, mencionada no tópico anterior, e qualquer outra que estenda RuntimeException, como IllegalStateException para objetos em estados inválidos, UnsupportedOperationException para operações não implementadas ou métodos não mais suportados (removidos de uma API, por exemplo). Vale frisar que – seja lá qual for a ocasião – nunca lance um NullPointerException e se você não consegue entender o porquê, dá uma lida nisso aqui. Hehehe. Brincadeira =). NullPointerException é uma condição adversa muito especifica: foi invocada uma operação em uma referência de objeto que estava nula. Você consegue imaginar em que situações isso aconteceria? Pois bem:


if(obj == null) {
    throw new NullPointerException(“Objeto obj null”);
} else {
    obj.doSomething();
}

Se você não conseguir entender o que está errado no código acima, sugiro que você então dê uma olhada neste link. E desta vez não é brincadeira.

Lance exceções verificadas quando a exceção deve ser tratada por quem invocou

Eu sei que você questionou o exemplo anterior da data, por isso vou reiterá-lo aqui. Se, por qualquer razão, você espera que o cliente do seu método (o método que o está invocando) trate a situação adversa da data não estar no formato esperado, aí sim: cria uma exception verificada do tipo InvalidDateFormatException, como foi mencionado naquela seção e a lance. Neste caso, o cliente estará ciente de que se a data não for informada no formato esperado, ele terá que tomar alguma providência.

Uma exceção por módulo é mais que suficiente

Não que você não possa especificar suas exceções, mas faça isso com carinho. Se você tem um módulo de controle de clientes, por exemplo, crie uma CustomerModuleException. Fica mais fácil trafegar-la entre as várias camadas ou serviços da sua aplicação. Nada te impedirá de especializá-la internamente, criando subclasses como CustomerNotFoundException, InativeCustomerException ou qualquer outra coisa que possa representar uma condição intrínseca ao seu módulo. Mas dele pra fora, a CustomerModuleException é mais do que suficiente. Dê uma olhada na estrutura de EJBException e de PersistenceException.

Converta exceções verificadas em não-verificadas somente quando necessário

Em um dos exemplos da leitura de arquivos, nas seções anteriores, foi encapsulada uma IOException em uma RuntimeException para que ela pudesse “vazar” do método. Tal coportamento não deve ser deliberado. Considere esse tipo de “conversão” (de checked para unchecked) em situações em que a thread em execução deva ser abortada.

Não use Throwable.printStackTrace()

Algumas IDE’s costumam inserir um ex.printStackTrace() quando você opta por englobar um código em um bloco try-catch. Embora seja útil quando se trata de pequenas codificações para testes, é completamente desencorajado em códigos que vão para ambientes de produção. O método printStackTrace é definido na classe Throwable e herdado por todas as subclasses de Exception. Ele faz uso da API System.err que às vezes compartilha os recursos com o System.out (se estiverem sendo redirecionadas para o mesmo dispositivo/arquivo). Só isso já é uma boa razão para evitá-la. Considere o seguinte: uma vez que elas não fornecem qualquer alternativa thread-safe de acesso, o log gerado em uma situação concorrente seria completamente ilegível.

Use enums para mensagens

Eu gosto de exibir mensagens de erros significativas para o usuário. Exibir as mensagens de exceções é melhor ainda, desde que elas sejam compreensíveis. Utilizar enums nesse contexto pode tornar sua vida mais fácil; já que você está definindo uma mensagem source-level como enum e na ponta (front) você pode usar qualquer mecanismo de internacionalização para traduzí-la para o usuário final.

Voltando ao exemplo do CustomerModuleException, poderíamos especificar algo do tipo:

throw new CustomerModuleException(
    CustomerModuleExceptionMessages.EMAIL_ALREADY_TAKEN);

Ainda neste cenário, considere configurar atributos dinâmicos à sua exceção. Lembre-se, exceptions são classes Java serializáveis comuns e você pode facilmente permitir o comportamento abaixo com o emprego de um Map<String, Serializable>:

throw new CustomerModuleException(
    CustomerModuleExceptionMessages.EMAIL_ALREADY_TAKEN)
        .put("email", customer.getEmail())
        .put("username", customer.getUsername());

 

Use o método de logging adequado

Evite usar log.error() ou seus variantes para tudo. Existem situações em que o comportamento excepcional é esperado ou não vai causar qualquer prejuízo para o usuário final. Nestes casos, você pode considerar usar log.warning() ou até mesmo log.info(). Use log.error() para situações irrecuperáveis.

Considerações

Bem, é isso. Gostaria de convidar você à compartilhar suas considerações sobre exception handling aqui nos comentários e me ajude a melhorar o post.

“No mais” [sic], muito obrigado e até o próximo.

 

Strategy

Estou iniciando uma série de posts sobre padrões de projeto. Embora tal assunto se tratar de um assunto repleto de material, pretendo aqui dar uma abordagem mais rápida e prática sobre cada padrão e sobre suas aplicações.

Os design patterns foram um conceito adaptado da construção cívil, onde problemas comuns eram resolvidos sempre de uma forma padrão, conhecida entre os envolvidos da área. Você vai encontrar muito material sobre as origens dos design patterns com uma busca rápida no Google.

O livro Design Patterns: Elements of Reusable Object-Oriented Software (GoF, 1995), que popularizou o assunto entre a comunidade dos desenvolvedores de software, propõe três categorias de padrões, sendo eles: os estruturais, os comportamentais e os de criação. Embora o objetivo dessa empreitada seja apresentar cada padrão dentro do seu próprio domínio de desenvolvimento, vamos por hora ignorar essas classficações e começar pelo padrão que julgo (IMHO) ser a base para os demais: o pattern Strategy.

Segundo os autores do livro acima mencionado, numa tradução livre:

Cada padrão descreve um problema que ocorre recorrentemente em nosso ambiente, e então descreve a essência da solução para ele, de forma que você possa usar tal solução milhares de vezes sem, no entanto, fazer isso do mesmo jeito duas (ou mais) vezes.

Ou seja, um padrão deve descrever a forma com que você resolve um problema. Para isso, ele precisa estar sustentado sob quatro aspectos:

  • o nome do pattern: um nome descritivo, intuitivo e que remeta ao problema assim que mencionado. O objetivo aqui é nivelar e contextualizar os desenvolvedores à respeito de uma solução assim que o pattern for referenciado pelo nome. Em outras palavras, em uma reunião, por exemplo, ao sugerir a adoção (implementação) de um pattern para atacar um problema, todos os envolvidos devem estar cientes das medidas que serão adotadas para saná-lo;
  • o problema: qual o problema e qual o contexto em que o padrão deve ser aplicado;
  • a solução: descreve a metodologia de aplicação. O algorítimo.
  • as consequências: os benefícios e efeitos colaterais relacionados à adoção do padrão. Isso deve levar em conta aspectos que tangem desde à tecnologia de implementação à decisões de design.

Dito isso, seguimos para a definição do primeiro padrão de projeto: o Strategy.

Strategy

Estamos começando pelo padrão Strategy por uma razão muito simples: é o mais simples e ele vai te servir de base para conceber os demais. O Strategy parte de um conceito primordial do paradígma da orientação à objetos: encapsular o que varia. Basicamente, serve para que você possa definir famílias de algorítimos sob um contrato comum, mas que no entanto, permita comportamentos diferentes para cada um deles.

Vamos ao exemplo. Uma interface denominada Animal que defina um método action.

public interface Animal {
    void action();
}

Pode ter uma realização do tipo:

public class Dog implements Animal {
    @Override
    public void action() {
       System.out.println("Bark");
    }
}

Uma outra:

public class Cat implements Animal {
    @Override
    public void action() {
        System.out.println("Meow");
    }
}

E outra:

public class Cow implements Animal {
    @Override
    public void action() {
        System.out.println("Moo");
    }
}

Enquanto que um código cliente pode facilmente mixá-los em uma coleção de animais:

public static void main(String[] args) {
    List<Animal> animals = new ArrayList<>();
    animal.add(new Dog());
    animal.add(new Cat());
    animal.add(new Cow());

    for(Animal item : animals) {
        item.action();
    }
}

Se você observar, a própria definição da interface List é baseada no pattern Strategy: o contrato é definido pela interface enquanto que as variações comportamentais (implementações de facto) são definidas pelas classe concretas ArrayList, LinkedList, Stack, Vector, etc. Toda a API de coleções Java foi concebida sob esse conceito.

 

Tópicos relevantes do livro Código Limpo: Clean Code A Handbook of Agile Software Craftsmanship

O livro Código Limpo (Clean Code – A Handbook of Agile Software Craftsmanship) é basicamente um atalho pra você aprender as boas práticas dos grandes programadores poupando dores de cabeça e, principalmente, tempo e dinheiro. Eu mesmo, durante minha carreira relativamente curta de desenvolvimento de software já colecionava uma série de mandingas, cacoetes e práticas que poderiam ser (algumas) consideradas boas e outras eufêmicamente “teimosas”.

Basicamente, quando leio um livro, gosto de ter à mão um lápis e uma caneta de grifar para poder destacar e anotar o que julgo importante. Também tenho um Kindle™ que me permite fazer isso de forma “ecologicamente correta”. Entretanto, desta vez o livro não era meu, então, tive que anotar minhas considerações em blocos de papel e, posteriormente, no documento que aqui compartilho convosco. Todavia, antes de começar, recomendo a todos que leiam o livro na íntegra, pois realmente vale cada um dos 60 reais que o meu brother William Lopes (conhecido pelas alcunhas Maica, Capi, Non, entre outros) pagou (acabei de consultar a Amazon.com e vi que está custando 170 hoje: http://www.amazon.com.br/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882).

A organização desse post segue-se mais ou menos assim: o título é o capítulo de onde retirei a informação e os tópicos são as práticas que considerei relevantes seguidas de algum comentário (in-) oportuno meu. Também vale a pena frisar que há muito mais no livro do que eu aqui vos transcrevo, no entanto, anotei os pontos que mais chamaram a atenção pra minha insignificante vida de programador.

Nomes

  • Nomes significativos, independente do tamanho, o nome deve expressar o que a variável/método/classe se propõe a fazer. É melhor um nome do tipo somarOTotalPagoAFornecedores do que um método chamado somar e um comentário gigantesco, perecível e ignorável dizendo o que o dissilábico método faz. E é isso que a anotação seguinte expressa:
  • Melhor um nome extenso e significativo do que um nome curto e confuso. Isso quer dizer que você não deve se preocupar em economizar caracteres para descrever o comportamento do seu método enquanto o nomeia. No entanto, cautela e bom-senso devem sempre predominar nas escolhas.
  • Nem sempre é (ou não é) possível começar pelo ótimo, o ideal é fazer um brainstorming inicial e refatorar usando-se as ferramentas (IDE). Em outras palavras, você “vomita” o código funcional e depois faz o refactoring. Tentar começar pelo “código limpo” em muitas ocasiões me custou muito tempo, simplesmente pela minha vaidade de não colocar uns ifs dentro dos outros.
  • O código deve contar uma história. Quem vai lê-lo deve entendê-lo na integra, sem auxílio de comentários e documentações. Aqui a dica vai além da nomenclatura dos artefatos. “Contar uma história facilmente compreensível” é basicamente a dica do livro.
  • Os nomes devem ser refatorados com o tempo. Uma classe/função/método/variável pode perder o significado original, portanto, é sempre importante manter a nomenclatura em acordo com a intenção real e atual do método. Aqui cabe ressaltar o trabalho extra que os comentários geram. Eu, particularmente, até ler o livro, era um cara que gostava de enfeitar as classes com belos textos, às vezes até rebuscados, detalhando o funcionamento das minhas rotinas. Percebi (leia-se: fui convencido) mais tarde que esses comentários só serviam para ficar desatualizados.
  • Nomes pronunciáveis são sempre melhores que nomes acrônomicos. AgentConsoleLoginRequest é melhor que ACLRequest. Obvio, não?.
  • Classes → substantivos, métodos → verbos. Básico de qualquer curso de programação; classe define coisas e, os métodos, como essas coisas se comportam.
  • Nomes devem ser significativos dentro do contexto (domínio) do software. A palavra “nota” pode ter significados diferentes em um sistema de vendas, um acadêmico e um sistema de anotações pessoais, por isso é sempre bom estar de acordo com o vocabulário do seu cliente. Vai por mim, já passei muita raiva porque o cliente mencionava uma funcionalidade ou artefato usando uma palavra e no software estar representada por outra. Usar um vocabulário comum é essencial.

Funções

  • Devem ser pequenas! 5 ou 10 linhas é mais que o necessário. É isso! O Joshua Kerievsky fala no seu livro “Refatoração para Padrões” que a média de linhas dos métodos do seu sistema não devem ser maior do que 5 (“Your methods can be no longer than five lines of code”).
  • Devem fazer somente o que se propõem a fazer.
  • Devem fazer somente 1 tarefa. A 2 e a 3 se complementam. Um método denominado somar(a, b) deve somar a e b (ponto). Não tem que incrementar outras variáveis, não tem que validar se o usuário pode somar a com b, não tem redirecionar para outra página, não tem que fazer mais nada. Tem que somar a com b! Outra dica interessante é que se você usar “e” ou “ou” na nomenclatura do seu método tem grandes chances dele estar acumulando responsabilidades; na dúvida: refatore.
  • Quanto menos parâmetros, melhor: zero parâmetros: o ideal; 1 parâmetro: bom; 2 parâmetros: aceitável; 3 parâmetros: ruim; 4 parâmetros: melhor repensar;
  • Exceções são melhores do que códigos de erro. Concordamos que não devemos exibir um UnauthorizedException, na tela do usuário, mas ainda assim é melhor do que exibir um “error 403”.
  • Uma função que trata códigos de erro não deve fazer mais nada além de tratar os códigos de erro. Basicamente as anotações 1, 2 e 3 juntas.

Comentários

Essa foi pra mim a parte mais dolorosa: parar de comentar. Quando eu aprendi a fazer meu primeiro if no saudoso Clipper 5, mais ou menos no verão de 1996, comentários eram o que diferenciavam o bom programador do ruim; o cara organizado do porco relaxado. Acabei carregando esse hábito comigo para as outras linguagens: C, C++, PHP e mais tarde pro Java. Até pouco tempo atrás eu ficava bravo se alguém me falasse que comentar era ruim.

  • Comentários são ruins. Isso é um fato, aceite.
  • Javadoc é aceitável, mas também é ruim. Geralmente são gerados pelas IDEs aqueles cabeçalhos de métodos entre “/**” e “*/” com as meta-variáveis @param, @see, @author, etc. Isso é de certa forma, segundo o autor, aceitável, porém desencorajado. Você acaba refatorando o método e se esquece de alterar a quantidade de parâmetros, tipos, etc; é justamente isso que diz a a notação seguinte:
  • O programador acaba alterando a função mas não a documentação.
  • A regra básica é: sempre que precisar comentar, refatore. Resumidamente: “se você se sentir tentado a colocar um comentário para explicar seu código, é porque ele não está compreensível” (coloquei entre aspas porque não me lembro quem disse isso).
  • Um tipo de comentário válido é aquele que expressa algo que não pode ficar claro no código: uma decisão de negócio, por exemplo:
// desculpe, me obrigaram a fazer isso

Formatação

  • Melhora a legibilidade.
  • Deve ser empregada por toda a equipe de maneira consensual. “Ao ler um código, não ser possível identificar quem o escreveu”. Todos devem praticar de forma igual. Eu costumo usar o padrão da IDE para evitar entrar numa luta recursiva toda vez que alguém usar o recurso de auto-formação antes de commitar uma classe.
  • Espaçamentos devem ser padronizados. Entre métodos, entre declarações de variáveis, entre parênteses, entre chaves, enfim, reúna a equipe e entre em um acordo sobre o que deverá ser empregado como padrão de espaçamento, redija um documento e disponibilize à todos.
  • Separar contextos dentro da classe. Declaração de variáveis estáticas privadas de públicas; declaração de variáveis dos métodos e por aí vai. No meu caso, uso somente uma quebra de linha simples para separar os contextos, sem traços, pontos ou aquelas horrorosas série de sinais de igualdade (ex: “=======”).
  • Variáveis devem ser declaradas próximo de onde serão usadas (dentro do método). Eu aprendi a declarar as variáveis nos inícios dos métodos, quando comecei a programar em C. Não tenho certeza se isso era um requisito da linguagem, mas sei que me acostumei a fazer isso e acabou sendo outra mania que migrei para as demais linguagens. Não o faça! Declare suas variáveis próximas aos pontos onde você irá utilizá-las.
  • Métodos devem ser posicionados imediatamente abaixo dos métodos que os chamam. Se um método A chama um método B, posicione o método B imediatamente abaixo do método A. Isso facilita a leitura de quem pegar seu código no futuro. Geralmente as ferramentas de refactoring já fazem isso automaticamente quando você vai extrair um bloco de código para um método.
  • Linhas devem ser quebradas em qualquer posição entre 100 e 120 caracteres (eu uso 128… porque sim). Tanto no Netbeans quanto no Eclipse, eu coloco a margem da direita na posição 128 e NUNCA, repito, NUNCA deixo meu código ultrapassá-la. Acho que isso é uma prática que veio das telas 80×24 (tipo Clipper) que a galera usava para programar antigamente; 80 colunas por 24 linhas. Assim, para uma melhor visualização do código, os programadores recomendavam não ultrapassar a coluna 80 para não gerar rolagem horizontal. Hoje, com as telas alta resolução isso não se faz mais necessário, mas o autor acha importante quebrar a linha em algum ponto entre o começo e o fim do seu “range” visual.
  • Aninhamento horizontal são ruins porque geralmente as IDEs não os respeitam. Aquelas declarações de variáveis, enums e qualquer outra coisa que você tenta alinhar em colunas, colocando os sinais de igual exatamente um abaixo do outro, não funcionam. O primeiro aloprado que usar a formatação automática vai destruir em milésimos de segundos os minutos preciosos que você gastou tentando alinhá-las. Eu adotei uma regra simples: não brigue com a IDE.

Estrutura de dados

  • Um objeto não deve expor seus atributos internos. A primeira ideia que vem a mente ao ler isso é gerar métodos get e set para todos os atributos, o que é de certa forma válido, mas ainda devemos considerar a observação seguinte:
  • Getters e setters acabam quebrando o encapsulamento quando indiscriminadamente usados. A lição aqui é ser cauteloso no que sua classe vai expor. Talvez não haja a necessidade da sua classe externar atributos como “precoCusto” e “margemDeLucro” quando você pode simplesmente expor um método que retorna o preço final (eu sei que foi um exemplo medíocre, desculpa). Lei de Demeter: o módulo não deve enxergar o interior do objeto que manipula. Uma função f de uma classe c só deve chamar: métodos de c; objetos usados por f; objetos passados por parâmetro para f; variáveis de instância de c. Esse assunto por si só, rende um post inteiro, portanto, se quiser se aprofundar mais, dá uma olhada nesse post aqui.
  • Train Wrecks (acidentes ferroviários) ocorrem quando há encadeamento de chamada de métodos. Quando há encadeamento, melhor separá-los em variáveis locais conhecidas:
a.getB().getC();

deve ficar:

B b = a.getB();
C c = b.getC();

No entanto, o ideal é que a classe A retorne o objeto esperado a partir de um método adequadamente nomeado e especificado (comportamento estaria em a):

a.getQualquerCoisaQueBFazComC();
  • Objetos DTO não devem possuir comportamentos. São burros, ou como o pessoal gosta de dizer: anêmicos.

Exceções e erros

  • Sempre preferir exceções ao invés de retornar códigos de erro. Como foi mencionado em uma nota anterior, é melhor lançar uma UnauthorizedException do que um “Error 403”.
  • Sempre optar por exceções não verificadas. Exceções verificadas propagam alterações para níveis que não deveriam, por exemplo: um método que passa a lançar uma nova exceção verificada deverá alterar todas as estruturas de outros níveis que o usa.
  • Contextualize as exceções. Geralmente um tipo de exceção por módulo é mais do que o suficiente.
  • Nunca retorne null. Retornar null implica em verificação if-null por todo o código, sem contar as atormentadoras NullPointerExceptions que fazem qualquer programador se sentir um newbie.
  • Null deve ser encarado como uma condição anormal. Você deve considerar que um null em qualquer ponto do código é algo anormal e deve ser verificado.
  • Nunca passar ou receber null como parâmetro.

Classes

  • Devem ser pequenas. Pode ser difícil definir “pequena” aqui, mas considere a dica do Joshua Kerievsky no “Refatoração para Padrões” sobre o tamanho médio dos métodos para mensurar suas classes.
  • Possuir um único propósito e ter a responsabilidade bem definida (coesão). Uma dica: geralmente quando a descrição (Javadoc, por exemplo) de uma classe usa “e” para descrevê-la, significa que ela possui mais de uma responsabilidade.
  • Escolher um bom nome pode ser a forma exata de separar adequadamente as responsabilidades.
  • Recomenda-se começar declarando variáveis públicas e estáticas (constantes) seguidas pelas privada e estáticas, seguidas pelas, seguidas pelas variáveis de instância privadas.

E é isso. As práticas acima, como eu disse, são só um amontoado de anotações que eu considerei importantes para usar no meu dia a dia enquanto eu lia; nem sequer abrange o conteúdo todo do livro. Me arrependi muito de ter negligenciado tal material por tanto tempo e se há um livro de TI que eu recomendaria para alguém que está iniciando sua carreira, não tenho dúvidas que seria esse.

Normalização de bancos de dados

Olá.
Estou iniciando meu trabalho de migrar minhas postagens antigas para este meu novo blog, agora na plataforma WordPress. Como primeiro post, estou compartilhando um texto sobre normalização de bancos de dados que escrevi há quase 10 anos. Espero que seja de bom proveito.

Segundo Heuser (2001), uma Forma Normal (FN) é uma regra que deve ser obedecida por uma tabela para que ela seja considerada “bem projetada”. Existem inúmeras formas normais, ou seja, diversas regras, cada vez mais rígidas, para verificar tabelas em banco de dados relacionais. No entanto, pelo menos 3 FNs são consideradas essenciais para a construção de um bom projeto de banco de dados.

1FN (Primeira Forma Normal)

Primeira forma normal (1FN) = diz que uma tabela está na primeira forma normal quando ela não contém tabelas aninhadas

Considere a planilha a seguir:

CodProj Tipo Descr Emp
CodEmp Nome Cat Sal DataIni TempA1
LSC001 Novo Desenv. Sistema de Estoque 2146 João A1 4 01/11/1991 24
3145 Silvio A2 4 02/10/1991 24
6126 José B1 9 03/10/1992 18
1214 Carlos A2 4 04/10/1992 18
8191 Mário A1 4 01/11/1992 12
PAG02 Manutenção Sistema de RH 8191 Mário A1 4 01/05/1993 12
4112 João A2 4 01/05/1993 24
6126 José B1 9 01/11/1992 12

A planilha acima representada em um modelo relacional não normalizado (ÑN) ficaria assim:

CodProj Tipo Descr CodEmp Nome Cat Sal DataIni TempA1
LSC001 Novo Desenv. Sistema de Estoque 2146 João A1 4 01/11/1991 24
LSC001 Novo Desenv. Sistema de Estoque A2 4 02/10/1991 24
LSC001 Novo Desenv. Sistema de Estoque B1 9 03/10/1992 18
LSC001 Novo Desenv. Sistema de Estoque A2 4 04/10/1992 18
LSC001 Novo Desenv. Sistema 8191 Mário A1 4 01/11/1992 12
PAG02 Manutenção Sistema de RH 8191 Mário A1 4 01/05/1993 12
PAG02 Manutenção Manutenção 4112 João A2 4 01/05/1993 24
PAG02 Manutenção Manutenção 6126 José B1 9 01/11/1992 12

Segundo a definição da 1FN, para normalizar a tabela acima, é necessário decompô-la em duas:

Proj(CodProj, Tipo, Descr);
Emp(CodProj, CodEmp, Nome, Cat, Sal, DataIni, TempAl);

Ou seja:

Proj
CodProj Tipo Descr
LSC001 Novo Desenv. Sistema de Estoque
PAG02 Manutenção Sistema de RH
ProjEmp
CodProj CodEmp Nome Cat Sal DataIni TempA1
LSC001 2146 João A1 4 01/11/1991 24
LSC001 3145 Silvio A2 4 02/10/1991 24
LSC001 6126 José B1 9 03/10/1992 18
LSC001 1214 Carlos A2 4 04/10/1992 12
LSC001 8191 Mário A1 4 01/05/1993 12
LSC001 8191 Mário A1 4 04/01/1991 24
PAG02 4112 João A2 4 04/01/1991 24
PAG02 6126 José B1 9 01/11/1992 12

* As colunas destacadas em cinza compõem as chaves primárias.
** Vale ressaltar que se houvesse a restrição de que um funcionário só pudesse participar de exatamente 1 projeto, a chave-primária da tabela ProjEmp poderia ser composta unicamente de CodEmp.

2FN (Segunda Forma Normal)

Observando a tabela ProjEmp acima, é possível identificar que os dados do funcionário Mário se repetem em duas tuplas. É esse tipo de redundância que a 2FN visa eliminar.

Segunda Forma Normal (2FN) = uma tabela encontra-se na segunda forma normal, quando, além de estar na 1FN, não contem dependências parciais.

Dependência parcial = uma dependência parcial ocorre quando uma coluna depende apenas de parte de uma chave primária composta.

A partir das definições acima, podemos concluir que a tabela Proj já se encontra na 2FN, pois todos seus atributos dependem exclusivamente de sua chave primária, já a tabela ProjEmp não. Verifique, em ProjEmp, que atributos como Nome, Cat, Sal dependem somente de CodEmp, enquanto que DataIni, TempAl dependem da chave primária composta por inteiro (tal fato se justifica porque pode ser necessário identificar a data em que determinado usuário ingressou em um projeto). Sendo assim, para que a tabela ProjEmp fique de acordo com a 2FN, é necessário decompô-la em duas outras (ProjEmp e Emp). O projeto então fica como segue:

Proj(CodProj, Tipo, Descr);
Emp(CodProj, CodEmp, Nome, Cat, Sal, DataIni, TempAl);

Ou seja:

Proj
CodProj Tipo Descr
LSC001 Novo Desenv. Sistema de Estoque
PAG02 Manutenção Sistema de RH
ProjEmp
CodProj CodEmp DataIni TempA1
LSC001 2146 01/11/1991 24
LSC001 3145 02/10/1991 24
LSC001 6126 03/10/1992 18
LSC001 1214 04/10/1992 12
LSC001 8191 01/05/1993 12
LSC001 8191 04/01/1991 24
PAG02 4112 04/01/1991 24
PAG02 6126 01/11/1992 12
Emp
CodEmp Nome Cat Sal DataIni TempA1
2146 João A1 4 01/11/1991 24
3145 Silvio A2 4 02/10/1991 24
6126 José B1 9 03/10/1992 18
1214 Carlos A2 4 04/10/1992 12
8191 Mário A1 4 01/05/1993 12
8191 Mário A1 4 04/01/1991 24
4112 João A2 4 04/01/1991 24
6126 José B1 9 01/11/1992 12

3FN (Terceira Forma Normal)

Observando o modelo gerado aplicando-se as regras da 2FN e supondo que o salário de um empregado é determinado por sua categoria funcional (Cat), podemos notar que ainda restam dados redundantes na tabela Emp; todos os empregados da categoria A1 e A2 possuem salário 4 e B1 possuem salário 9. A 3FN visa eliminar esse tipo de redundância.

Terceira Forma Normal (3FN) = uma tabela encontra-se na terceira forma normal, quando, além de estar na 2FN, não contém dependências transitivas

Dependência transitiva = uma dependência funcional transitiva ocorre quando uma coluna, além de depender da chave primária da tabela, depende de outra coluna ou conjunto de colunas da tabela

Sendo assim, a tabela Emp obtida pela aplicação da 2FN, que contém os dados redundantes, pode ser decomposta em outras duas (Emp e Cat). O modelo fica como segue:

CodProj Tipo Descr
LSC001 Novo Desenv. Sistema de Estoque
PAG02 Manutenção Sistema de RH
ProjEmp
CodProj CodEmp DataIni TempA1
LSC001 2146 01/11/1991 24
LSC001 3145 02/10/1991 24
LSC001 6126 03/10/1992 18
LSC001 1214 04/10/1992 12
LSC001 8191 01/05/1993 12
LSC001 8191 04/01/1991 24
PAG02 4112 04/01/1991 24
PAG02 6126 01/11/1992 12
Emp
CodEmp Nome Cat
2146 João A1
3145 Silvio A2
6126 José B1
1214 Carlos A2
8191 Mário A1
8191 Mário A1
4112 João A2
6126 José B1
Cat Sal
A1 4
A2 4
B1 9

Resumo

1FN

  1. Cria-se uma tabela na 1FN referente à tabela ÑN e que contém apenas colunas com valores atômicos, isto é, sem as tabelas aninhadas;
  2. Para cada tabela aninhada, cria-se uma tabela na 1FN compostas pelas seguintes colunas:
    1. A chave primária de uma das tabelas na qual a tabela em questão está aninhada
    2. As colunas da própria tabela
  3. São definidas as chaves primárias das tabelas na 1FN que correspondem a tabelas aninhadas

2FN

  1. Copiar para a 2FN cada tabela que tenha chave primária simples ou que não tenha colunas além da chave. No caso do exemplo, é o que acontece com a tabela Proj.
  2. Para cada tabela com chave primária composta e com pelo menos uma coluna não chave (no exemplo, a tabela ProjEmp):
    • Criar na 2FN uma tabela com as chaves primárias da tabela na 1FN
    • Para cada coluna não chave fazer a seguinte pergunta:
      a coluna depende de toda a chave ou de apenas parte dela”
    • Caso a coluna dependa de toda a chave
      i. Criar a coluna correspondente na tabela com a chave completa na 2FN
    • Caso a coluna não dependa apenas de parte da chave
      i. Criar, caso ainda não existir, uma tabela na 2FN que tenha como chave primária a parte da chave que é determinante da coluna em questão
      ii. Criar a coluna dependente dentro da tabela na 2FN

3FN

Copiar para o esquema da 3FN cada tabela que tenha menos de duas colunas não chave, pois neste caso não há como haver dependências transitivas

  1. Para tabelas com duas ou mais colunas não chaves, fazer a seguinte pergunta:
    a coluna depende de alguma outra coluna não chave?”

    1. Caso dependa apenas da chave
      i. Copiar a coluna para a tabela na 3FN
    1. Caso a coluna depender de outra coluna
      i. Criar, caso ainda não exista, uma tabela no esquema na 3FN que tenha como chave primária a coluna na qual há a dependência indireta
      ii. Copiar a coluna dependente para a tabela criada
      iii. A coluna determinante deve permanecer também na tabela original

Conclusão

Quem já trabalhou com banco de dados, acaba percebendo que utilizava as FNs mesmo sem conhecê-las. Para desenvolvedores mais experientes, a eliminação de redundâncias em bancos de dados se configura uma prática essencial para o sucesso do projeto. O material utilizado para a criação deste texto menciona a 4FN, porém, para a maioria dos projetos, a 3FN é suficiente.

Bibliografia

HEUSER, Carlos Alberto. Projeto de Banco de Dados. Porto Alegre, RS: Editora Sagra Luzzatto, 2001.