As 3 Leis da boa Codificação: Um Paralelo com as Leis da Robótica
Assim como Isaac Asimov estabeleceu as Três Leis da Robótica para guiar o comportamento ético dos robôs, podemos estabelecer Três Leis para uma boa codificação para orientar as melhores práticas de programação. Estas leis seguem uma hierarquia similar, onde cada lei subsequente não pode entrar em conflito com as anteriores.
O código é apenas ilustrativo, não levem a sintaxe ao pé da letra
Primeira Lei: Princípio da Responsabilidade Única
Um módulo de código deve ter uma, e somente uma, razão para mudar.
Esta lei é fundamental e tem prioridade sobre as demais. Ela garante que cada parte do código tenha um propósito claro e bem definido, facilitando a manutenção e reduzindo efeitos colaterais indesejados. Ela é a mais importante pois é ela que garante que os módulos dos códigos não se atropelem e levem o projeto a um caminho de desastre.
Veja esse exemplo simples onde queremos enviar um e-mail de boas vindas a um usuário do sistema, mas o código está acoplado a entidade usuário
// Classe que viola o Princípio da Responsabilidade Única
class User {
name: string;
email: string;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
sendWelcomeEmail() {
// Lógica para enviar e-mail
}
}
// Caso você queira mudar a lógica do envio de email, sempre terar que mexe
// na classe usuário, isso pode ser problemático.
const user = new User("João Viciente", "joaozim@email.com");
const user.sendWelcomeEmail();
Versão melhorada
// Refatoração aplicando o Princípio da Responsabilidade Única
class Usuario {
nome: string;
email: string;
constructor(nome: string, email: string) {
this.nome = nome;
this.email = email;
}
}
class ServicoDeEmail {
enviarEmail(usuario: Usuario) {
// Lógica para enviar e-mail
}
}
const usuario = new Usuario("Effort Ricardo", "effort@email.com");
new ServicoDeEmail().enviarEmail(usuario);
Nesse exemplo simples, o código de envio de e-mail funciona agora como um serviço e não está acoplado a uma classe específica. Facilitando até mesmo os testes unitários e integração, assim como para debugar caso um bug aconteça no futuro. E sempre aparece.
Segunda Lei: Don’t Repeat Yourself (DRY — Não Se Repita)
Cada peça de conhecimento deve ter uma representação única, inequívoca e autoritativa dentro de um sistema.
Esta lei é não pode quebrar a primeira lei. Enquanto buscamos eliminar redundâncias, não podemos fazê-lo às custas de violar o Princípio da Responsabilidade Única, ou seja, não podemos centralizar funções, classes ou módulos apenas para não repeti-los caso o código centralizado possua mais de um agente de mudança.
A reutilização de código deve ser implementada de forma a manter a coesão e a clareza do propósito de cada módulo. Vamos ter como exemplo esse código de calcular preço de carrinho de compras
function calcShipping(products: [Product], discount: Discount): number {
let productTotal = 0;
products.forEach((p) => { productTotal += p.price() });
return productTotal - discount.value();
}
function calcShippingToEmployees(
products: [Product],
discount: Discount,
employee: Employee): number
{
let products = 0;
products.forEach((p) => { productTotal += p.price() });
return orderTotal - employee.discount() - discount.value();
}
É um código simples, mas tem um problema, a soma dos produtos está sendo repetida, e a função calcShippingToEmployees não pode chamar o calcShipping normal porque irá quebrar o princípio de responsabilidade única. Nesse casos vamos criar uma terceira função que ajudará as duas
function calcShipping(products: [Product], discount: Discount): number {
return productsSum(products) - discount.value();
}
function calcShippingToEmployees(products: [Product], discount: Discount, employee: Employee): number {
return productsSum(products) - employee.discount() - discount.value();
}
function productsSum(products: [Product]): number {
let sum = 0;
products.forEach((p) => { sum += p.price() });
return sum;
}
Terceira Lei: Keep It Simple (Mantenha Simples)
O código deve ser o mais simples possível, mas não mais simples que o necessário.
Esta lei é subordinada às duas anteriores. Buscamos a simplicidade, mas não podemos comprometer a responsabilidade única dos módulos ou introduzir repetições desnecessárias apenas para simplificar. A simplicidade deve ser alcançada dentro dos limites estabelecidos pelas duas primeiras leis.
Veja essa consulta para listar as compras de um usuário, ela recebe uma Query genérica, com uma ordem qualquer como parametros. O código evita ser repetitivo, mas aumenta a complexidade sem necessidade.
class UserRepository implements IUserRespository {
listOrders(genericQuery: Object, orderBy) {
this.product.joins("inner join users on users.id = orders.user_id").where(genericQuery).orderBy(orderBy).toArray();
}
}
A primeira vista esse código não tem problema, mas ela está muito genérica, buscando provavelmente ser reutilizadas em muitos lugares. Provavelmente difícil de debugar, pois um erro de SQL irá disparar em um lugar usado em muitos lugares, e o erro provavelmente estará em montar a consulta, e não em executa-la.
Vamos deixar esse código mais simples e mais direto, sem quebrar as outras leis.
class Orders {
withUsers {
this.joins("inner join users on users.id = orders.user_id");
return this;
}
}
class UserRepository implements IUserRespository {
constructor(model, user: User) {
this.order = model;
this.order = this.order.where({ user_id: user.id });
}
listOpenOrders(user_id): Array {
return this.order.withUsers.where({ status: 'OPEN' }).orderBy('status', 'desc').toArray();
}
listCloseOrders(user_id): Array {
return this.order.withUsers.where({ status: 'CLOSE' }).orderBy('status', 'asc').toArray();
}
}
Esse código deixa as intenções bem claras, evita códigos muitos genéricos que possam ser usados em muitos lugares de forma desnecessária, isola as responsabilidades das funções e é bem simples de entender.
Conclusão
Assim como as Leis da Robótica de Asimov, estas Leis da Codificação formam uma hierarquia que guia o desenvolvimento de software robusto, manutenível e eficiente. Ao seguir estas leis, os programadores podem criar código que é não apenas funcional, mas também sustentável e adaptável às mudanças futuras.