Design de código e Clean Code: como escrever um código limpo?
Publicado por
Caio Sales
Publicado por
Caio Sales
Publicado em
Categorias:
É comum que desenvolvedores passem mais tempo tentando entender um código ruim do que efetivamente escrevendo código novo. A proporção média de leitura e escrita de um código fonte é 10:1. Isto é, passamos 10 vezes mais tempo na tentativa de compreensão de um código já existente do que realizando um novo código devido a princípios mal estabelecidos.
Robert Cecil Martin, também conhecido como Uncle Bob, é uma referência de boas práticas na escrita de códigos. O autor atua desde a década de 70 na área tech e é um dos profissionais por trás do manifesto ágil (2001). Em 2008 ele lançou o livro Clean Code, no qual apresenta técnicas para desenvolvimento de software que se associa aos princípios agile.
O que pode-se destacar como grande descoberta da obra é: o gargalo principal de desenvolvimento de software está na manutenção. Ou seja: um código que funciona, mas está mal escrito desde a primeira versão, pode gerar prejuízos enormes.
Abaixo listamos alguns dos principais pilares para que o código se mantenha de fácil entendimento e limpo:
Nomenclatura: nomes são importantes!
Nomeie as variáveis de forma descritiva, indicando o que ela armazena. Por exemplo, uma variável chamada nc não diz nada. Porém, o nome de variável nomeCompleto é bem mais sugestivo para quem está olhando o código e realizará a manutenção.
Nomes de funções, classes e métodos também devem seguir os mesmos princípios de nomenclatura de variáveis.
// before
interface p {
n: string;
d: boolean;
v: number;
}
function myFunc(array: p[]) {
const x: p[] = [];
for (const v of array) {
if (v.d) {
x.push(v);
}
}
return x;
}
// after
interface Product {
name: string;
isDeleted: boolean;
value: number;
}
function filterDeletedProducts(products: Product[]) {
const deletedProducts: Product[] = [];
for (const product of products) {
if (product.isDeleted) {
deletedProducts.push(product);
}
}
return deletedProducts;
}
// more improvements
function filterDeletedProducts(products: Product[]) {
return products.filter((product) => product.isDeleted);
}
Deixe o código mais limpo do que estava
Ao realizar manutenção/alterações em um código já existente, tente deixá-lo mais semântico do que o encontrou. Ao refatorar nomes de variáveis, quebrando funções grandes em funções menores, remova comentários obsoletos, por exemplo. O mais importante é nunca deixar em um estado pior do que estava.
// before
async function createUser(userData: UserDTO): Promise<User> {
if (!userData.name){
throw new Error('Name is required');
}
if (!userData.cpf){
throw new Error('CPF is required');
}
if (!userData.phoneNumber){
throw new Error('Phone number is required');
}
const createdUser = await User.query().insertAndFetch({
...userData
createdAt: new Date().toISOString(),
status: 'enabled',
});
return createdUser;
}
// after
async function createUser(userData: UserDTO): Promise<User> {
await throwsErrorIfUserDataIsInvalid(userData);
const createdUser = await insertAndFetchUser(userData);
return createdUser;
}
Evite repetições
O ideal é não repetir códigos. Se você estiver repetindo código em locais diferentes, algo pode ser melhorado. As mudanças no código precisam ocorrer em todos os locais em que o código está se repetindo, evitando ambiguidade de ideias.
Funções pequenas e com uma responsabilidade
Os nomes de funções devem seguir o mesmo padrão de nomes de variáveis - semânticas e descritivas. As funções devem ter poucas e, se possível, apenas uma responsabilidade. Funções menores e com poucos fluxos alternativos podem ser reutilizadas em outros locais do código. Assim, repetições são evitadas.
// before
function emailClients(clients) {
clients.forEach((client) => {
const clientRecord = database.get(client);
if (clientRecord.isActive) {
sendEmail(client);
}
});
}
// after
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(sendEmail);
}
function isActiveClient(client) {
const clientRecord = database.get(client);
return clientRecord.isActive;
}
Comentários
Evite comentários ao máximo! Um código bem escrito não precisa de diversas linhas com comentários extensos. A questão principal é: geralmente, o código é atualizado, mas os comentários não, o que pode gerar muita confusão.
Um código limpo diz por si só o que está sendo realizado.
// before
// verifica os dados do usuario e lança erro se forem invalidos
function throwsErrorIfUserDataIsInvalid (userData: UserDTO) {
// lanca erro se nao for um email valido
if (!isValidEmail(userData.email)) {
throw new Error('Invalid email');
}
// lanca erro se nao for um cpf valido
if (!isValidCpf(userData.cpf)) {
throw new Error('Invalid cpf');
}
// lanca erro se nao for um telefone valido
if (!isValidPhoneNumber(userData.phoneNumber)) {
throw new Error('Invalid phone number');
}
}
// after
function throwsErrorIfUserDataIsInvalid (userData: UserDTO) {
if (!isValidEmail(userData.email)) {
throw new Error('Invalid email');
}
if (!isValidCpf(userData.cpf)) {
throw new Error('Invalid cpf');
}
if (!isValidPhoneNumber(userData.phoneNumber)) {
throw new Error('Invalid phone number');
}
}
Tratamento de erros
Idealmente, deve-se tratar de forma específica os locais onde é possível ocorrer erros. Uma vez que eles são imprevisíveis, lidar com eles de forma não genérica pode evitar horas e horas de 'debug' em busca de 'bugs'.
// before
try {
functionThatThrow();
} catch (error) {
console.log(error);
}
// after
try {
functionThatThrow();
} catch (error) {
console.error(error);
notifyUserError(error);
reportErrorToService(error);
}
Testes
Testes devem ser capazes de serem executados de forma rápida, independente e repetida, testando condições determinísticas e pré acompanhando o desenvolvimento do software. Desse modo, conseguimos garantir mais qualidade no que está sendo desenvolvido. A qualidade do que está sendo desenvolvido é mais possível de ser garantida
Assim, mesmo que um sistema esteja pronto e funcionando, ele não está finalizado. Sempre haverá necessidades de atualizações e implementação de novas funcionalidades, pois o código envelhece e pode se tornar obsoleto.
Em suma, falar sobre código limpo e de fácil manutenção, é criar um código com Baixo Acoplamento, Alta Coesão, usando SOLID, aplicando Design Patterns, minimizando Side Effects, maximizar o uso de Funções Puras e várias outros princípios. Desse modo, fazer um bom Design de Código é uma parte essencial para a manutenção de código.
Publicado por
Caio Sales
Desenvolvedor de Software e Bacharel em Ciência da Computação, acredita que somos eternos aprendizes no apaixonante mundo das tecnologias
Desenvolvedor de Software e Bacharel em Ciência da Computação, acredita que somos eternos aprendizes no apaixonante mundo das tecnologias