Enquanto conversava com um amigo, há alguns dias atrás, surgiu um assunto a respeito de algo que costumamos ver com certa frequência. Vou tentar contextualizar essa conversa com o diagrama UML Entidade-Relacionamento a seguir:
Nem irei perder tempo explicando esse modelo, até porque não há nada nele que precise de explicações. Bem, na verdade, há uma coisa sim: por que raios existe uma tabela só para armazenar os tipos de contato?! Qual é o problema com os bons e velhos códigos fixos (1-email, 2-celular, N-…)?
Um argumento a favor dessa abordagem é que “o usuário pode querer cadastrar um contato cujo tipo não seja suportado pela aplicação”. Mas será mesmo que colocar essa informação no banco de dados resolve o problema? E, mais importante, existe algum efeito colateral?
Bem, na minha experiência, embora essa abordagem pareça boa o bastante para resolver a questão num sistema simples de “cadastro”, a bomba explode quando o sistema passa a depender dessas flags para a realização de lógicas de negócio que vão além do famigerado CRUD.
Anti-pattern?
E se eu te dissesse que usar tabelas no banco de dados para armazenar flags é um anti-pattern? Digo isso pois, como veremos a seguir, as vantagens são irrisórias frente às desvantagens:
Poluição do modelo de dados. Um sistema não muito grande provavelmente conta com mais de uma centena de tabelas. Isso todo mundo sabe. O que todo mundo também sabe, mas parece ignorar, é que não seria surpreendente constatar que 10-15% dessas tabelas serviriam apenas para armazenar flags (situação de pedido, estado civil, forma de pagamento, e por aí vai). E o pior: essas tabelas, quase sempre, possuem exatamente a mesma estrutura, que apelidei carinhosamente de tabelas código-descrição. A tabela TipoContato, mostrada no diagrama que encabeça este texto, é um exemplo desse tipo de tabela.
Ruim para o meio ambiente. Poluir o modelo de dados com dezenas de tabelas código-descrição torna o modelo desnecessariamente maior e mais difícil de compreender. Imprimir um modelo desses consome mais papel, o que é ruim numa época onde o desenvolvimento sustentável está em voga. Quantas árvores suas tabelas código-descrição já derrubaram?
Ultra-normalização. Quanto mais tabelas uma consulta envolver, mais joins, e mais dados indo e vindo para o banco de dados. E, considerando que o banco de dados é (quase) sempre o gargalo, isso não é muito bom para a performance do seu sistema.
Poluição do modelo de objetos. As linguagens com suporte à orientação a objetos figuram entre as mais usadas atualmente, por isso eu diria que muito provavelmente você utiliza esse paradigma no desenvolvimento dos seus sistemas. Se este for o caso, também é bem provável que você utilize algum framework ORM para facilitar a tarefa de integração do software com o banco de dados. Como muitos frameworks ORM exigem um modelo de objetos compatível com o modelo de dados, bem, você acaba sendo forçado a poluir seu modelo de objetos com classes burras que apelidei carinhosamente de (pasmem!) classes código-descrição.
Se tem tabela, tem cadastro. Bom, já que todas as flags possuem suas tabelinhas no banco de dados, isso significa que você, pobre desenvolvedor, terá que fazer “cadastros” para todas elas (e reze para não precisar fazer relatórios, também). Além da chatisse costumeira de se fazer um cadastro, devemos levar em consideração que fazê-los consome tempo e, tempo é dinheiro.
Código quebradiço. Essa é feia. Vamos supor que, nessa nossa aplicação de gerenciamento de clientes/contatos, você queira incluir uma funcionalidade que permita enviar um torpedo ao cliente caso ele possua um contato do tipo Celular. Como mostrado no trecho de código a seguir, você fatalmente terá que incluir no código-fonte referências a registros armazenados no banco de dados, tornando o código quebradiço. Consequentemente, um único UPDATE ou DELETE executado por engano no banco de dados pode fazer o sistema cair de joelhos.
Enviando torpedos a um cliente:
public void enviarTorpedo(Cliente cliente, String texto) { // Tenta localizar um contato do tipo celular for (Contato contato : cliente.getContatos()) { // WTF!! Estamos referenciando um registro do banco de dados!! if (contato.getCodigo() == 10L) { SMSUtil.enviarTorpedo(contato.getValor(), texto); break; } } }
A solução
Com certeza existem outros problemas que eu acabei deixando passar, mas espero que os problemas que listei já sejam o suficiente para te convencer que guardar flags em tabelas no banco de dados é uma má idéia.
Mas, como resolver isso então? Simples: use enums sempre que possível. A única desvantagem é que os usuários não poderão mais sair criando novas flags a torto e a direito, o que não é realmente um problema frente às vantagens que as enums oferecem.
Poluição do modelo de dados? As flags virariam campos numéricos ou alfanuméricos nas tabelas que as usam e o framework ORM se encarregaria de converter esses campos de/para objetos enum. Diga adeus às tabelas código-descrição.
Ruim para o meio-ambiente? Sem as tabelas código-descrição, seu modelo fica menor, mais enxuto, facilitando a visualização e gastando menos papel ao imprimí-lo. A natureza agradece.
Ultra-normalização? Usando enums você diminui a quantidade de ligações entre as tabelas, tornando as entidades um pouco mais auto-contidas. Menos tabelas, menos joins, mais performance.
Poluição do modelo de objetos? As enums tomariam o lugar das classes código-descrição, o que por si só tornaria o código mais claro, pois, ao contrário das classes “normais”, as enums possuem um propósito bem definido.
Se tem tabela, tem cadastro? Ué, por que você está com esse sorriso estampado no rosto?
Código quebradiço? Quem mais se beneficia das enums são os trechos de código onde as lógicas de negócio são definidas. Mais especificamente, seu código deixa de referenciar registros do banco de dados, eliminando a fragilidade e melhorando a legibilidade. As enums também impedem que valores arbitrários sejam usados em atribuições e comparações, tornando o código ainda mais confiável. Um exemplo disso pode ser visto no código a seguir, que é uma adaptação da atrocidade do código mostrado anteriormente:
public void enviarTorpedo(Cliente cliente, String texto) { for (Contato contato : cliente.getContatos()) { // Bem melhor agora! if (contato.getTipo() == TipoContato.CELULAR) { SMSUtil.enviarTorpedo(contato.getValor(), texto); break; } } }
O fato de a sua linguagem do coração não ter suporte nativo a enums não deve ser um impedimento para que você deixe de usufruir de suas vantagens. Existem formas de se conseguir uma funcionalidade semelhante sem muito esforço.
Conclusão
Desenvolver software é, antes de tudo, fazer escolhas. É o custo/benefício, os prós e contras antes de se tomar uma decisão. A boa notícia é que o nosso produto, o software, é maleável o suficiente para permitir que decisões ruins sejam re-feitas sempre que elas resultam em problemas como o descrito neste texto.
Bom carnaval!

Posts em Português
Posts in English
Realmente, durante muito tempo, achei que tudo tinha que ser perfeitamente normalizado e blablabla. Mas com o passar do tempo, procurei fazer as coisas mais simples (menos complexo).
Muito bom esse post. Nós temos mania de sofisticar mais, e esquecemos que o simples geralmente funciona bem, consome menos tempo e nos permite modificar no futuro com mais facilidade.
@Edson: Sinto muito em desapontá-lo!
Lá se vai a tabela TipoDocumento do sistema que estou desenvolvendo
@Walter Cruz: Cara, tinha até esquecido da existência dos Gambi Design Patterns… é uma boa fonte de inspiração!
É a aplicação prática do pogpattern #42: Db is our God.
Criar tabelas para armazenar flags pensando em futuramente poder cadastrar mais flags é no mínimo falta de visão em relação aos requisitos. Como criar uma lógica pré-definida para flags que sequer existem? Inútil.
Eu discordo do seu argumento que manter flags no banco de dados facilita na hora de expandir o sistema. Na verdade, pela minha experiência, o efeito é justamente o inverso.
Isso, claro, pode ser verdade caso o sistema não utilize essas flags para o processamento de lógicas de negócio como a demonstrada neste texto. Mas, se estamos pensando em manter o sistema fácil de expandir, isso significa que devemos nos atentar para o fato de que tais flags podem eventualmente acabar sendo necessárias em processamentos mais complexos. Portanto, o argumento não é válido.
Apenas para citar um exemplo, vamos supor que você tenha feito uma alteração na parte de pagamentos do seu sistema, onde ele agora suporta cartão de créditos (além dos já suportados dinheiro e cheque). Além de colocar a nova versão da aplicação em produção, você tem que ir lá no banco de dados e cadastrar a nova flag
Cartão de Créditona tabelaAPP_FORMA_PAGAMENTO, por exemplo. Se isso estivesse fixo no programa, esse último passo não precisaria ser feito.Eu acredito na seguinte idéia: quanto mais não nos prendermos em em flags fixas e colocarm-os tudo em nosso banco de dados, mais fácil ficará para, posteriormente, poder-mos aumentar nosso sistema, claro, se este for o foco. Agora, para um sistema fixo de locadora, por exemplo, acho que vale a pena sim tirar os dados fixos do banco.
Em uma aplicação grande, que cresce sem parar, temos que pensar em facilitar o desenvolvimento e manutenção, e quanto às consultas de banco, temos que montá-las da melhor forma possível. E podemos trabalhar com caches simples de dados que não mudam rapidamente no sistema, não tendo que ir toda a vez no banco para consultar e ainda assim, todos os dados estarão no guardados no banco.
JUDE/Community:
http://jude.change-vision.com/jude-web/index.html
[]s!
A propósito, com qual ferramenta você criou este diagrama ?