Blog do Urubatan
msgbartop
Desenvolvedor, Palestrante, Escritor, Nerd Assumido e Pai do Marcus :D
msgbarbottom

21 May 07 Manifesto pela legibilidade de código

Bom, este post foi inspirado por um aluno do curso que eu ministrei fim de semana passado em Pato Branco/PR, mas este post não é culpa só dele :D

O post é inspirado em coisas que eu já vi e já fiz muito por ai …

A idéia básica deste post é que:

Qualquer um pode escrever código que um computador entende, mas apenas um programador muito bom, escreve código que uma pessoa entende.

Como premissa básica, se você programa em uma linguagem, siga os padrões dela, por exemplo:

  • Java
    • Nomes de classes começam com letra maiúscula
    • Nomes de variáveis com letras minúscula
    • Classes nunca são criadas no package default, isto é desencorajado desde o java 1.3
  • Delphi
    • Nomes de classes começam com T
    • nomes de variáveis utilizam a notação ungara (se não estou enganado é este o nome)
      • Se o campo é um text box, txtNome por exemplo

E assim por diante, ou seja, siga os padrões básicos da linguagem que estiver trabalhando.

Alem dos padrões das linguagens, cada empresa normalmente possui um padrão de formatação, por tanto, siga o padrão da empresa, e de preferência, configure a sua IDE para te ajudar neste trabalho.

  • Nomes de classes obrigatoriamente precisam informar a quem lê, do que se trata esta classe.
  • Classes preferencialmente possuem comportamento.
  • “Coisas” no código fonte, devem ser agrupados por domínio de comportamento.
  • Aprenda com os padrões de outras linguagens, por exemplo, o principio DRY (Don`t Repeat Yourself) do Ruby On Rails, deve ser utilizado sempre em qualquer situação, independente da linguagem que esta sendo utilizada.
  • Métodos grandes são difíceis de ler, se um método estiver muito grande, com certeza absoluta você pode dividi-lo em métodos menores.
  • Nomes de métodos precisam, obrigatoriamente, dizer exatamente o que este método faz.
  • Classes não são tabelas, por tanto, se estiver criando uma classe persistente, ela pode ter um “id”, por exemplo a classe Cliente, pode ter um campo “id”, mas pelo amor de Jahl, ela não pode, em ipótese alguma, ter um campo “clienteid”.
  • Classes, por serem a forma de se definir um objeto, que por definição, possui estado e comportamento, 99% das vezes, possuem mais metodos do que “gets” e “sets”, se você olhar para uma classe que não os possui, provavelmente você os colocou no lugar errado.
  • Não crie “gets” e “sets” indiscriminadamente, crieos apenas quando forem necessários, muitos dos atributos de uma classe não devem ser alterados externamente.

Refactoring é seu amigo!

Refatore sempre que precisar para melhorar a legibilidade do código, ou quando perceber que alguma coisa esta implementada no lugar errado.

A boa noticia é que a maioria das IDEs, para diversas linguagens, tem suporte para diversos tipos refactoring.

A má noticia é que se a sua IDE ou linguagem, não tem suporte a refatoring, ou não tem suporte ao refactoring que você precisa, você vai ter que faze-lo manualmente.

DRY (Don’t Repeat Yourself)

Não se repita, se você escreveu alguma coisa em algum lugar, reuse, não escreva novamente!

Mas preste atenção, reuso não é igual a copiar e colar, se você precisar copiar e colar, é por que provavelmente fez errado da primeira vez.

Considere este exemplo (Retirado do livro encontrado aqui)

public class BookRental {
String id;
String customerName;
...
}
public class BookRentals {
private Vector rentals;
public String getCustomerName(String rentalId) {
for (int i = 0; i < rentals.size(); i++) {
BookRental rental = (BookRental) rentals.elementAt(i);
if (rental.getId().equals(rentalId)) {
return rental.getCustomerName();
}
}
throw new RentalNotFoundException();
}
}
public class RentalNotFoundException extends Exception {
...
}

Agora imagine que você precisa adicionar um método para remover um aluguel pelo ID:

public class BookRentals {
private Vector rentals;
public String getCustomerName(String rentalId) {
for (int i = 0; i < rentals.size(); i++) {
BookRental rental = (BookRental) rentals.elementAt(i);
if (rental.getId().equals(rentalId)) {
return rental.getCustomerName();
}
}
throw new RentalNotFoundException();
}
public void deleteRental(String rentalId) {
for (int i = 0; i < rentals.size(); i++) {
BookRental rental = (BookRental) rentals.elementAt(i);
if (rental.getId().equals(rentalId)) {
rentals.remove(i);
return;
}
} throw new RentalNotFoundException();
}
}

Você acha que este código esta OK? lembra-se do DRY? neste exemplo simples ja temos muito código duplicado, o que com certeza não é bom, então, podemos fazer o seguinte refactoring:

public class BookRentals {
private Vector rentals;
public String getCustomerName(String rentalId) {
int rentalIdx = getRentalIdxById(rentalId);
return ((BookRental) rentals.elementAt(rentalIdx)).getCustomerName();
}
public void deleteRental(String rentalId) {
rentals.remove(getRentalIdxById(rentalId));
} private int getRentalIdxById(String rentalId) {
for (int i = 0; i < rentals.size(); i++) {
BookRental rental = (BookRental) rentals.elementAt(i);
if (rental.getId().equals(rentalId)) {
return i;
}
}
throw new RentalNotFoundException();
}
}

Por que remover código duplicado?

Em geral, se um código esta duplicado em 10 lugares, se você precisar alterar este código precisara lembrar de todos os 10 lugares, o qe provavelmente não vai acontecer, por tanto vai criar bugs no sistema.

O seu código não precisa de comentários! des de que ele seja legivel!

Considere este outro exemplo do mesmo livro, em que é desenvolvida uma aplicação para gerenciamento de conferencias, e que existe uma barra lateral onde devem ser exibidas informações sobre o participante:

//It stores the information of a participant to be printed on his badge.
public class Badge {
String pid; //participant ID
String engName; //participant's full name in English
String chiName; //participant's full name in Chinese
String engOrgName; //name of the participant's organization in English
String chiOrgName; //name of the participant's organization in Chinese
String engCountry; //the organization's country in English
String chiCountry; //the organization's country in Chinese
//***********************
//constructor.
//The participant ID is provided. It then loads all the info from the DB.
//***********************
Badge(String pid) {
this.pid = pid;
//***********************
//get the participant's full names.
//***********************
ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
Participant part = partsInDB.locateParticipant(pid);
if (part != null) {
//get the participant's full name in English.
engName = part.getELastName() + ", " + part.getEFirstName();
//get the participant's full name in Chinese.
chiName = part.getCLastName()+part.getCFirstName();
//***********************
//get the organization's name and country.
//***********************
OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
//find the ID of the organization employing this participant.
String oid = orgsInDB.getOrganization(pid);
if (oid != null) {
Organization org = orgsInDB.locateOrganization(oid);
engOrgName = org.getEName();
chiOrgName = org.getCName();
engCountry = org.getEAddress().getCountry();
chiCountry = org.getCAddress().getCountry();
}
}
}
...
}

Agora transforme os comentários em código, fazendo-o tão simples quanto os comentários.

//It stores the information of a participant to be printed on his badge.
public class Badge {
...
}

Para que precisamos deste comentário se podemos escrever assim:

public class ParticipantInfoOnBadge {
...
}

E nesta parte:

public class ParticipantInfoOnBadge {
String pid; //participant ID
String engName; //participant's full name in English
String chiName; //participant's full name in Chinese
String engOrgName; //name of the participant's organization in English
String chiOrgName; //name of the participant's organization in Chinese
String engCountry; //the organization's country in English
String chiCountry; //the organization's country in Chinese
...
}

Por que não transformas os comentários em variáveis assim:

public class ParticipantInfoOnBadge {
String participantId;
String participantEngFullName;
String participantChiFullName;
String engOrgName;
String chiOrgName;
String engOrgCountry;
String chiOrgCountry;
...
}

Ou destes comentários?

public class ParticipantInfoOnBadge {
...
//***********************
//constructor.
//The participant ID is provided. It then loads all the info from the DB.
//***********************
ParticipantInfoOnBadge(String pid) {
this.pid = pid;
...
}
}

Se podemos transforma-los em nomes de parametros:

public class ParticipantInfoOnBadge {
...
//***********************
//constructor.
//It loads all the info from the DB.
//***********************
ParticipantInfoOnBadge(String participantId) {
this.participantId = participantId;
...
}
}

Podemos tambem transformar comentários em partes do código do método:

public class ParticipantInfoOnBadge {
...
//***********************
//constructor.
//***********************
ParticipantInfoOnBadge(String participantId) {
loadInfoFromDB(participantId);
} void loadInfoFromDB(String participantId) {
this.participantId = participantId;
...
}
}

Agora pegue este outro fragmento de código:

void loadInfoFromDB(String participantId) {
this.participantId = participantId;
//***********************
//get the participant's full names.
//***********************
ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
Extract some code to form a method and use the comment to name the method 41
Licensed for viewing only. Printing is prohibited. For hard copies, please purchase from www.agileskills.org
Participant part = partsInDB.locateParticipant(participantId);
if (part != null) {
//get the participant's full name in English.
engFullName = part.getELastName() + ", " + part.getEFirstName();
//get the participant's full name in Chinese.
chiFullName = part.getCLastName()+part.getCFirstName();
//***********************
//get the organization's name and country.
//***********************
OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
//find the ID of the organization employing this participant.
String oid = orgsInDB.getOrganization(participantId);
if (oid != null) {
Organization org = orgsInDB.locateOrganization(oid);
engOrgName = org.getEName();
chiOrgName = org.getCName();
engOrgCountry = org.getEAddress().getCountry();
chiOrgCountry = org.getCAddress().getCountry();
}
}
}

E me diga por que não transformas os comentários em nomes de métodos assim:

void loadInfoFromDB(String participantId) {
this.participantId = participantId;
getParticipantFullNames();
//***********************
//get the organization's name and country.
//***********************
//find the ID of the organization employing this participant.
OrganizationsInDB orgsInDB = OrganizationsInDB.getInstance();
String oid = orgsInDB.getOrganization(participantId);
if (oid != null) {
Organization org = orgsInDB.locateOrganization(oid);
engOrgName = org.getEName();
chiOrgName = org.getCName();
engOrgCountry = org.getEAddress().getCountry();
chiOrgCountry = org.getCAddress().getCountry();
}
} void getParticipantFullNames() {
ParticipantsInDB partsInDB = ParticipantsInDB.getInstance();
Participant part = partsInDB.locateParticipant(participantId);
if (part != null) {
//get the participant's full name in English.
engFullName = part.getELastName() + ", " + part.getEFirstName();
//get the participant's full name in Chinese.
chiFullName = part.getCLastName()+part.getCFirstName();
}
}

E assim por diante, seguindo algumas regras básicas, o código vai ser bastante legível, dispensando boa parte dos comentários, o que vai fazer com que o seu desenvolvimento seja mais rápido, e vai também fazer com que outras pessoas consigam ler o seu código sem problemas.
Mas eu não estou dizendo que não é para comentar nada, mas com certeza, boa parte os comentários é dispensavel, se o seu código for legível!

Bom, acho que é isto por enquanto …

Por favor, contribua sua ideia para melhorar este manifesto, e se gostou dele,divulge-o, isto ainda vai lhe poupar muito tempo!

<MODO SARCASMO ON>

Apenas para deixar claro, a maior parte das regras definidas neste post, esta acima de qualquer contestação, e é por definição, verdade absoluta. (Ou seja, até que se prove ao contrário, estas regras estão acima de contestação).

</MODO SARCASMO ON>

If you enjoyed this post, make sure you subscribe to my RSS feed!

Tags:

Reader's Comments

  1. |

    Se antes disseste que não fazia sentido ter um atributo clientId numa classe Cliente, podendo só utilizar id, o mesmo vale para a possivel classe ParticipantInfoOnBadge, que não deveria ter um participantId.

    Reply to this comment
  2. |

    eu até concordo, se o nome do campo fosse ID, mas participantID é muito melhor do que pid …
    Mas ainda tem outra coisa, o nome da classe é: ParticipantInfoOnBadge
    se ela tiver um atributo “id” ele é o ID do Participant? ou o ID do conjunto ParticipantInfo? ou o ID do ParticipantInfoOnBadge?
    se a classe fosse Participant concordo que o nome do atributo deveria ser “id” e não participantID, mas como o nome da classe não é Participant e sim ParticipantInfoOnBadge é necessário informar de quem é o ID em questão …
    Caso contrario em vez de ficar mais claro o código, ele ficaria mais ambíguo.
    por tanto um Participant não tem participantID e sim “id” mas um ParticipantInfo ou um ParticinaptInfoOnBadge podem ter um participantID …
    não sei se fui claro …

    Reply to this comment
  3. |

    Concordo veementemente com o post, salvo a última parte. Se não é possível contestar algo, como melhorar? Como falar que existe um mísero problema em uma dessas regras que se modificado melhoraria consideravelmente a regra? E quando chegar o momento de tornar a regra obsoleta? E, brincando um pouco com as palavras escolhidas na última frase, como eu vou provar o contrário, que essas regras podem ser contestadas, se para tal terei que constestá-las em primeiro lugar? Considerar algo como inconstetável sempre foi sinônimo de problemas. Por isso, se me permite uma humilde sugestão, seria interressante remover o último paragráfo como um todo.

    Verdades absolutas são a raiz de todo mal.

    Reply to this comment
  4. |

    Sim, verdades absolutas são a raiz de todo o mal, mas eu achei que o até que se prove o contrario tinha deixado claro o sarcasmo na frase anterior :D
    mas beleza, ja que o sarcasmo não ficou tão claro quando eu achei que tinha ficado, eu removo a ultima frase …

    Reply to this comment
  5. |

    Com a adição dessas tags de sacarsmo, o texto ficou perfeito, concordo com tudo. :D

    Como dica para os iniciantes, eu sugiro ler o link abaixo sobre as conveções de códido que a Sun publicou vários anos atrás. Eu utilizei o Checkstyle junto com o Eclipse para que eu me adequasse às regras apresentadas ali.

    http://java.sun.com/docs/codeconv/

    Reply to this comment
  6. |

    ParticipantId? Isso tem cheiro de modelo relacional vazando para o de objetos. Que tal ter referências entre objetos e não chaves estrangeiras? ;)

    Reply to this comment

Leave a Comment