Um Resumo Sobre Clean Code [Parte 2]

Habilidades Práticas do Agile Software

7 min readAug 15, 2021

--

Nessa segunda parte, continuaremos a explorar o mundo do Código Limpo, agora de forma mais prática, dando exemplos e justificando cada uma das suas sui generis premissas.

Se você perdeu a primeira parte, confira-a clicando na figura abaixo:

Depois de var_z, eu chamo de quê? 🤔

Algumas dicas são importantes ao nomearmos nossas variáveis, funções, métodos, classes…

Nomes precisos: com pequenas exceções (como variáveis usadas para iterar em loops, por exemplo) NUNCA use nomes abreviados, inexpressivos, com piadinhas ou de duplo sentido. Quando usamos nomes assim, abrimos muitas possibilidades de interpretação, o que torna o código difícil de ler e manter.

Prefira:

let elapsedTimeInDays;
let currentDate;
let clients;

Ao invés de:

let d;
let cd;
let thoseWhoPayMySalary;

Nomes tipados: evite nomes de variáveis contendo seu tipo, pois se o tipo de dado mudar, o nome da variável não fará mais sentido.

Prefira:

let users;

Ao invés de:

let usersArray;

Nomes de classes vs nomes de métodos: nomes de classes devem ser substantivos (user, account, product) enquanto nomes de métodos/funções devem ser verbos, respeitando a regra de prefixá-los com “get”, “set” e “is” quando aplicável.

Prefira:

getPrice(): number {
...
return price;
}
setAge(): void {
...
this.user.age = 30;
}
isDocumentValid(document: number|string): boolean {
...
if(...) {
return true;
}
return false;
}

Padrões ao nomear: evite definir nomes com padrões diferentes para métodos que fazem algo parecido, pois isso pode tornar o desenvolvimento confuso, uma vez que você precisará lembrar qual termo ou a ordem deles que foi utilizada.

Prefira:

isDocumentValid(): boolean {
...
}
isUserCreated(): boolean {
...
}
getAccounts(): Account[] {
...
}
getProducts(): Product[] {
...
}

Ao invés de:

isDocumentValid(): boolean {
...
}
userIsCreated(): boolean {
...
}
getAccounts(): Account[] {
...
}
retrieveProducts(): Product[] {
...
}

Nomes com trocadilhos: assim como no caso anterior, ter nomes com termos iguais para propósitos diferentes (como add [adicionar] para inclusão e para soma), torna o desenvolvimento confuso.

Prefira:

getProductPricesSum(product1, product2): number {
...
return product1.price + product2.price;
}
addItemOnCart(item): boolean {
...
this.cartItems.push(item);
}

Ao invés de:

addProductPrices(product1, product2): number {
...
return product1.price + product2.price;
}
addItemOnCart(item): boolean {
...
this.cartItems.push(item);
}

Nomes com contexto: caso necessário, adicione contexto aos nomes das variáveis. Uma variável “state” faria sentido se vista junto com “street” e “city” por exemplo, mas sozinha não deixa claro do que se trata.

Prefira:

let addressState;let address = {
state: ...,
};

Ao invés de:

let state;

Padrões desnecessários: por exemplo, em uma empresa chamada ABC não faria sentido que todas variáveis fossem prefixadas com “ABC”. Isso além de ser inútil, dificultaria muito o autopreenchimento da IDE.

Prefira:

let users;

Ao invés de:

let ABCUsers;

Extra: nomeie tudo que puder. Dê preferência por atribuir valores a variáveis ao invés de utilizar “números mágicos”. Fica mais fácil de ler, entender, refatorar e buscar.

Prefira:

const studentsGroups = 4;
applesPerGroup = apples / studentsGroups;

Ao invés de:

applesPerGroup = (apples / 4);

Não tenha medo de renomear. Mudar um nome para melhor é extremamente bem-vindo e é sinal de se importar 🥰!

Funções e suas funcionalidades 👩‍💻

Quando falamos de funções, precisamos estar atentos a algumas características que definem uma função limpa, incluindo conjuntos de padrões de escrita de código, um deles sendo o famigerado SOLID:

Single Responsibility Principle (Princípio da Responsabilidade Única)
Open/Closed Principle (Princípio do Aberto/Fechado)
Liskov Substitution Principle (Princípio da Substituição de Liskov)
Interface Segregation Principle (Princípio da Segregação de Interfaces)
Dependency Inversion Principle (Princípio da Inversão de Dependências)

Em outras palavras, sem nos aprofundarmos demais nas definições dos princípios do SOLID, as funções precisam ser escritas com a menor quantidade de linhas possível e precisam fazer apenas uma única coisa (se uma função está muito longa, com certeza não faz apenas uma coisa e precisa ser refatorada), a identação deve ser de no máximo dois níveis (se possui grande quantidade de níveis, é sinal de que ela não faz uma única coisa, e precisa ser refatorada) e instruções “if”, “else”, “while”, “for”, etc devem ter no máximo uma chamada de função dentro delas, seus nomes devem dizer exatamente o que elas fazem, sem enganar os leitores.

Prefira:

let user = {};get isEmailAndPasswordDefined() {
return user.email && user.passsword;
}
defineUser() {
user.name = 'John Doe';
user.email = 'johndoe@mail.com'
user.passsword = 'password123';
}
main() {
defineUser();

if(isEmailAndPasswordDefined) {
loginAndRegisterLog(user.email, user.passsword);
}
}
loginAndRegisterLog(email, password) {
// log user
// update logs about last login on database
}

Ao invés de:

let user = {};main() {
user.name = 'John Doe';
user.email = 'johndoe@mail.com'
user.passsword = 'password123';
if(user.email) {
if(user.passsword) {
login(user.email, user.passsword);
}
}
}
login(email, password) {
// log user
// update logs about last login on database
}

É recomendado que se use funções com a menor quantidade de parâmetros possível. Se for necessário passar mais que um parâmetro para a função, talvez seja interessante “agrupá-las” e criar um objeto para esses parâmetros específicos, fazendo com que ela seja uma função mônade, (que recebe apenas um parâmetro), permitindo ainda que seja possível seguir mais uma recomendação: a de que seu nome e o de seu parâmetro sejam uma combinação de verbo e substantivo.

Prefira:

let user: User = {
name: 'John',
surname: 'Doe',
age: '30',
};
set(user: User): void {
(...)
}

Ao invés de:

setUser(name: string, surname: string, age: number): void {
(...)
}

Outra recomendação é a de, ao usar instruções try/catch, manter seu conteúdo como uma linha única, o que pode facilitar a leitura e proporcionar a reutilização do código.

Prefira:

delete(item: Item) {
try {
deleteItemAndAllContent(item);
} catch(error) {
handleError(error);
}
}
deleteItemAndAllContent(item) {
// ação de deleção em si
}
handleError(error) {
// tratamento do erro
}

Ao invés de:

delete(item: Item) {
try {
// ação de deleção em si;
} catch(error) {
// tratamento do erro;
}
}

Um pouco mais sobre “uma única coisa” 🤯

Algumas perguntas importantes para identificar se uma função faz uma única coisa são:

  • há como dividí-la em funções menores, ainda tendo um ganho significativo na legibilidade do código?
    ◦ caso ainda haja, é sinal de que a função possui mais de uma responsabilidade;
  • é fácil dar nomes curtos, condizentes e descritivos à ela?
    ◦ caso não seja, é sinal de que a função possui mais de uma responsabilidade (por exemplo uma função de nome “formatTrimReplaceAndSaveData()” claramente faz mais de uma única coisa)
  • a função recebe um parâmetro booleano?
    ◦ caso receba, é sinal de que a função possui mais de uma responsabilidade (como verificar a veracidade desse parâmetro e executar algo baseado nessa decisão)

Em resumo, mantenha suas funções curtas, bem nomeadas, bem organizadas e busque sempre seguir o princípio DRY (Don’t Repeat Yourself [Não Se Repita]). Se algo se repete, provavelmente pode ser convertido em um bloco de código reutilizável.

Lembre-se de que antes de ser um programador, você é um escritor e suas funções devem contar a história do sistema.

Comentários

Para começar a falar sobre comentários, repito: não tente enfeitar o 💩.

Se um código está confuso e não pode ser entendido apenas por si mesmo, não insira comentários para explicá-lo, reescreva-o. Como Uncle Bob bem diz: “o uso de comentários é uma tentativa de compensar o fracasso em se expressar apenas com o código”.

A aversão a comentários mostrada no livro não é sem motivos. Comentários são quase impossíveis de serem atualizados. Logo, tornam-se incorretos com o passar do tempo, deixando o código mentiroso (comentários errados, mentirosos e enganosos são piores do que comentário nenhum).

Ainda há o fato de que oesforço despendido para escrever ou atualizar comentários seria melhor empregado na refatoração do código, tornando-o tão legível que tais comentários seriam totalmente desnecessários.

Muitas vezes, comentários podem ser substituídos por nomes expressivos.

Prefira:

if (employee.isEligibleForExtraHours()) {
(...)
}

Ao invés de:

// verifica se o funcionário tem direito a horas extras
if(employee.paymentType == 'monthly' && employee.workedHours > 20) {
(...)
}

Atualmente, graças as IDEs avançadas, podemos fazer uso dos comentários ToDo, os quais são “comentários do bem”, que podem ser úteis para definir que algo precisa ser alterado num futuro próximo. Mas não podemos usá-los de forma exagerada a todo momento, assim como em todos outros aspectos do Clean Code, bom senso é essencial. Uma forma ainda melhor de lidar com essa questão é utilizarmos ferramentas mais adequadas, como algumas de Kanban, por exemplo. Algumas sugestões de ferramentas que utilizo e recomendo são: Jira, Trello e Asana.

Devemos nos atentar ainda aos comentários “camuflados” de documentação, os quais são totalmente redundantes e desnecessário.

Prefira remover comentários como os do exemplo abaixo, pois são redundantes, considerando que os nomes das funções e variáveis explicam o código por si só:

/**
* variable to define if user is logged
*/
let isUserLogged: boolean = false;
/**
* function to get the user profile picture if user already logged in
* @param user: the user to verify if is logged
* @return the user profile picture
*/
getUserProfilePicture(user) {
/**
* variable to store the user profile picture
*/
let userProfilePicture: blob = undefined;
/**
* verify if user is logged
*/
if (isUserLogged) {
userProfilePicture = user.profilePicture;

return userProfilePicture;
}
}

Outro tipo de comentário bastante comum (e totalmente descartável) são os comentários que contém códigos legados. Se um código é importante, porém não será mais usado e você, por medo de precisar usá-lo novamente, transforma-o num comentário, a melhor abordagem seria apostar nos poderes das ferramentas de versionamento de código (Subversion, Mercurial, GIT, etc) para que seja possível recuperar taís códigos no futuro, se necessário. Códigos legados em forma de comentário só servem para entulhar o arquivo e nunca são apagados, pois o próximo programador que tiver acesso a eles também não o apagará pois não tem certeza da importância dele e assim rumo ao infnito.

Na última parte dessa série, iremos continuar falando sobre a aplicação do Clean Code em mais alguns aspectos do código.

Confira a segunda parte clicando na figura abaixo:

--

--