Para os que não conhecem, o princípio SOLID é uma “sigla de siglas”, uma tentativa de formalizar códigos limpos, flexíveis e funcionais principalmente para linguagens orientadas a objeto. Os princípios são Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle e Dependency Inversion Principle. Sendo bem sucinto:

  • SRP indica que todo objeto deve ter apenas uma responsabilidade. Ou seja, fazer uma classe para atender apenas uma função específica.
  • OCP indica que toda classe deve ser aberta para adição de novas funcionalidades, mas fechada para modificação. Ou seja, quando for preciso adicionar mais funcionalidades, isso deve ser feito apenas adicionando novos métodos, sem precisar alterar os que já existem.
  • LSP indica que deve ser possível substituir um objeto por seus filhos, na herança, sem dar problema. Na prática, significa que um objeto filho altera a forma como o objeto pai funciona, extendendo ou não suas funcionalidades, mas nunca alterando o comportamento geral
  • ISP indica que todo código deve organizado de forma que jamais seja necessário implementar métodos que não são utilizados. A idéia é melhor explicada (com exemplos) neste post.
  • DIP indica uma necessidade de depender de classes abstratas, não de classes concretas. Ou seja, ao invés de, em um código, receber num método uma classe concreta, receber uma classe abstrata e, quando o método for chamado, passar uma classe concreta

Ok, e aonde o SQL entra nisso? Ele não é orientado a objeto, não é “turing-complete”, é apenas uma linguagem de busca num banco de dados, certo? E sim, eu tou falando de SQL, não de PL/SQL ou qualquer coisa semelhante. A explicação é muito simples: Clean Code

Vamos começar com um exemplo simples: A query a seguir foi feita no dialeto do Oracle:

SELECT * FROM pessoas t1,usuarios t2,funcionarios t3
WHERE
t1.nome LIKE 'A%'
AND t2.pessoa_id = t1.id(+)
AND t3.funcionario_id = t2.id(+)
AND t3.status = 'ativo' OR t3.status IS NULL;

Ok, muita coisa acontecendo aqui… o (+) indica um LEFT JOIN, no dialeto do Oracle. Fora isso, um SQL normal, certo? Mas qual a intenção dele?

Aí vem a parte interessante: SQL precisa deixar claro qual a intenção da query. Principalmente na criação de views e stored procedures, aonde é muito mais que necessário entender o que o código faz (especialmente em views que provavelmente serão usadas por outras views… mas isso é outro caso). Então, vamos um por um:

Single Responsibility Principle

O código acima viola, pois não apresenta claramente qual a intenção da query. Digamos que a intenção era retornar todas as pessoas que ou são usuários ativos, ou não são usuários. Nesse caso, em primeiro lugar, devemos deixar claro que a responsabilidade do código é retornar as pessoas:

SELECT *
FROM pessoas

Porque isso é importante? Bom, eu não sou um DBA, mas conheço alguns DBAs que trabalhar exclusivamente com otimização de queries. O primeiro código não deixa claro o que está sendo buscado, e parece um “all-around SQL”, que pode ser usado em muitas situações, inclusive situações para as quais ele não foi planejado.

Open-Closed Principle

Na query, se precisássemos adicionar uma nova condição, digamos um “OR”, precisaríamos ver se a cláusula WHERE não contém nada que conflita com nosso “OR”. Se tivéssemos um “OR”, ou alguma outra função mais complicada para fazer a comparação, não fica claro nem evidente como fazer sequer um novo join. Portanto:

SELECT pessoas.*, funcionarios.*, usuarios.* 
FROM pessoas
LEFT JOIN funcionarios
  ON funcionarios.pessoa_id = pessoa.id
LEFT JOIN usuarios
  ON usuarios.funcionario_id = funcionarios.id
WHERE
pessoas.nome LIKE 'A%'
AND(funcionarios.status IS NULL OR funcionarios.status = 'ativo');

Note a adição do parênteses ao redor do status. Isso é importate, e PODE sim mudar o comportamento do SQL que escrevemos acima. Mas, evidentemente, fica bem claro o que queremos fazer. Adicionar um novo join é simples como adicionar duas novas linhas antes do WHERE, e adicionar uma nova condição é simples como adicionar uma nova linha no fim.

Interface Segregation Principle

Antes do LSP é interessante falar sobre o Interface Segregation. Digamos que o código fosse uma VIEW. Por exemplo, no Oracle é possível criar “Materialized Views”, ou seja, uma view que fica gravada no banco, para otimizar a performance (é possível inclusive criar índices nos campos da view). Outros BDs tem coisas semelhante. Nesse caso, retornar todos os campos é uma perda de espaço descomunal. Então:

SELECT pessoas.id, pessoas.nome, pessoas.cpf, 
  funcionarios.registro, funcionarios.foto,
  usuarios.login, usuarios.ultimo_login
FROM pessoas
LEFT JOIN funcionarios
  ON funcionarios.pessoa_id = pessoa.id
LEFT JOIN usuarios
  ON usuarios.funcionario_id = funcionarios.id
WHERE
pessoas.nome LIKE 'A%'
AND(funcionarios.status IS NULL OR funcionarios.status = 'ativo');

Bem melhor, e fica bem claro o motivo da View. Fica, nesse caso, evidente que ela pode ser usada, digamos, para listar todos os usuários ativos de um sistema, ou então para listar os logins de usuários para autenticá-los em uma base externa, por exemplo. Por fim:

Liskov Substitution Principle

Essa parece difícil. Afinal, não estamos trabalhando com orientação a objetos… mas digamos assim, que no seu sistema você usa essa base, e eventualmente alguns campos são adicionados, modificados, e o código fica assim:

SELECT pessoas.id, pessoas.nome_completo, pessoas.cpf, 
  funcionarios.registro_funcional, funcionarios.foto_reduzida,
  usuarios.login, usuarios.ultimo_login
FROM pessoas
LEFT JOIN funcionarios
  ON funcionarios.pessoa_id = pessoa.id
LEFT JOIN usuarios
  ON usuarios.funcionario_id = funcionarios.id
WHERE
pessoas.nome LIKE 'A%'
AND(funcionarios.status IS NULL OR funcionarios.status = 'ativo');

Mais uma vez, se isso for uma View, outros códigos que dependeriam dela quebrarão: afinal, o nome de alguns campos mudou. Logo:

SELECT pessoas.id AS id, 
  pessoas.nome_completo AS nome, 
  pessoas.cpf AS cpf, 
  funcionarios.registro_funcional AS registro,
  funcionarios.foto_reduzida AS foto,
  usuarios.login AS login, 
  usuarios.ultimo_login AS ultimo_login
FROM pessoas
LEFT JOIN funcionarios
  ON funcionarios.pessoa_id = pessoa.id
LEFT JOIN usuarios
  ON usuarios.funcionario_id = funcionarios.id
WHERE
pessoas.nome LIKE 'A%'
AND(funcionarios.status IS NULL OR funcionarios.status = 'ativo');

Ok, uma nova view, que é completamente substituível pela outra. Para qualquer código que acessasse a view original, ela seria exatamente igual.

Claro, isso sem falar nos princípios básicos: seja consistente (o nome de uma chave estrangeira é “id_pessoa”? Chame-a de “id_pessoa” sempre que uma outra tabela precisar referenciar uma pessoa), não tenha medo ou preguiça de escrever (“registro_funcional_completo” é melhor do que “reg_fun_com”), e nunca, NUNCA misture plurais com singulares. Se uma tabela está no plural, faça todas estarem no plural. E cuidado, também, com os plurais: “pessoas_ativas” é o plural de “pessoa_ativa”, assim como “documentos_pessoa” é o plural de “documento_pessoa” (afinal, você vai buscar os DOCUMENTOS de UMA pessoa. Você não vai buscar AS pessoas que possuem UM documento específico, afinal, documentos são pessoais e intransferíveis).

Claro, como eu disse, eu não sou um DBA. Essas são apenas algumas das regrinhas que eu costumo seguir, e algumas das situações com as quais eu já lidei. E isso ocorre, principalmente, quando DBAs e Desenvolvedores não se comunicam, portanto, fica mais uma dica.