2 de março de 2012

Convenções e obrigações

Aviso. Este post apenas levanta algumas questões sem oferecer nenhuma resposta, apenas sugestões.

Na programação orientada a objetos, os principios SOLID são bem difundidos. São cinco principios reunindo boas práticas em forma de convenções que tornam a programação orientada a objetos melhor, porém não são exigências do paradigma, i.e. os principios melhoram a vida e deveriam ser aplicados, mas não existe maneira de assegurar que eles foram. São eles:

  1. Single responsibility principle (SRP): Cada classe deve ter apenas uma responsabilidade e apenas uma razão para que ela sofra mudanças.
  2. Open/closed principle (OCP): As classes devem estar abertas para extensão e fechadas para modificações. Funcionalidades são adicionadas ao sistema através de código novo e não através de modificações de código existente.
  3. Liskov substitution principle (LSP): Uma classe só pode ser subclasse de outra se o funcionamento do código que utilize esta hierarquia não for alterado ao substituir uma pela outra.  Isto é, se uma função depende de uma coleção de objetos, não importa se o argumento é uma lista ou uma árvore, o resultado deve ser o mesmo.
  4. Interface segregation principle (ISP): Contratos devem ser resumidos, funções devem depender de contratos que reflitam apenas sua necessidade e nada mais.  Vários contratos pequenos são melhores do que um alguns contratos grandes.
  5. Dependency inversion principle (DIP): Métodos devem depender de contratos e não de tipos concretos. 

Além destes existe um principio que vale para todos os paradigmas: "Don't repeat yourself" (DRY).  O nome deste é bem intuitivo: não repita código.

Apesar de todas estas convenções serem boas, todas elas possuem a mesma falha: são convenções. Convenções são facilmente esquecidas, postergadas e até mesmo ignoradas. Nem todos os programadores possuem o mesmo nível de apreço pelo código produzido. O respeito a cada uma das convenções é medido de forma qualitativa e não quantitativa. Não existe fornecimento de segurança apenas utilizando convenções.

Esta falta de segurança resulta em dividas técnicas para as bases de código. O sistema inteiro vira um espagueti, quanto mais o código é trabalhado, mais enrolado ele fica.

Assim como utilizamos equipamentos de segurança para garantir a integridade de algum objeto ou entidade, por que não asseguramos a integridade de nossas bases de código? Fábricas de software asseguram a integridade do ambiente de trabalho utilizando catracas, cancelas, guaritas, etc. Mas utilizam o quê para assegurar a integridade do seu produto (código)? Sistemas de controle de versão apenas garantem a versão atual do seu sistema caso uma cagada colossal aconteça em uma versão futura.

Dividas técnicas custam caro no fechamento das contas: quanto mais espagueti o código está, mais tempo e empenho serão gastos para novas funcionalidades e correções. Então por quê não previnimos isto de alguma forma? Como poderíamos prevenir?

Recentemente li o artigo Why Concatenative Programming Matters de Jon Purdy e notei a ligação direta entre o paradigma de programação concatenativa e o principio DRY. Pelo fato da programação concatenativa ser livre de contexto, é possível eliminar todo código duplicado com um simples find/replace. Sendo assim, este é o paradigma de programação ideal para que o principio DRY seja aplicado. É possível que o próprio compilador dê sugestões para eliminar ou obrigue a eliminação de repetições no código, garantindo um código melhor para todos.

Com a leitura do artigo de Jon Purdy, comecei a ponderar sobre as questões lançadas neste post e percebi que as type classes da linguagem Haskell se encaixam como uma solução brilhante para OCP. Elas permitem que contratos sejam definidos de forma aberta, aceitando extensões de forma fácil e com garantia em tempo de compilação (nada de monkey patching).

Se olharmos as type classes, podemos perceber que elas sempre abstraem um único tipo de dado e suas funções. Garantindo coesão de código e o cumprimento da convenção ISP, pois cada type class se restringe a um tipo de operação.

A linguagem Haskell possui inferencia de tipos, o que permite que o compilador produza assinaturas dos tipos mais abstratos possíveis, garantindo o cumprimento da DIP de forma automática.

Sobre a LSP, acredito que linguagens como Coq podem garantir o cumprimento desta convenção. Porém, não me aventurei muito sobre este tipo de linguagem ainda, então não posso garantir. Mas vale a pena dar uma verificada no assunto.

tl;dr Convenções podem ser checadas de forma estática:

  • DRY: Concatenative programming.
  • OCP & ISP: Haskell type classes.
  • DIP: Haskell type inference.
  • LSP: Coq.