Escrevendo código legível

Tirado da apresentação de Dmitry Pashkevich na Conferência UtahJS de 2016.

Slides aqui: https://dpashkevich.github.io/pres-readable-code/

Um código ilegível:

  • leva a erros;
  • é intimidador, e encoraja correções superficiais;
  • toda vez, toma tempo para entender;
  • é um lugar onde o débito técnico se acumula.

O objetivo de um código legível é minimizar o tempo que alguém leva para entendê-lo.

Formatação

  1. Tenha e siga um guia de estilo.
  2. Limite o tamanho das linhas: os monitores são geralmente grandes hoje em dia, mas você pode querer abrir dois documentos lado-a-lado, ou pode ter que mexer em um notebook menor, ou até mesmo queira algo mais legível.

Exemplo de linha longa:

this.document.deserialize(loadedPlugins, docData['state'], changesCallback, docData['action_history_length'], docData['id'], docData['next_connection_id'], docData['creator_id'], function() {...});

Exemplo corrigido:

this.document.deserialize(
    loadedPlugins,
    docData['state'],
    changesCallback,
    docData['action_history_length'],
    docData['id'],
    docData['next_connection_id'],
    docData['creator_id'],
    function() {...}
});

Comentários

O objetivo é escrever um código que não precise de comentários. Um dos motivos é que os comentários geralmente surgem de um código mal-feito.

Por exemplo, o que isso faz?

container.on('scroll', goog.functions.throttle(function(e) {
    var b = this.body;
    if(b.scrollHeight - (b.scrollTop + b.clientHeight) < 150) {
        this.loadNextPage();
    }
}, 20, this))

Solução rápida:

// scroll infinito
container.on('scroll', goog.functions.throttle(function(e) {
    var b = this.body;
    if(b.scrollHeight - (b.scrollTop + b.clientHeight) < 150) {
        this.loadNextPage();
    }
}, 20, this))

Mas poderia ter sido resolvido sem comentário:

this.infiniteScrollHandler = goog.functions.throttle(function(e) {
    var b = this.body;
    if(b.scrollHeight - (b.scrollTop + b.clientHeight) < 150) {
        this.loadNextPage();
    }
}, 20, this);

container.on('scroll', this.infiniteScrollHandler);

Muitas vezes, no lugar dos comentários, vale a pena criar uma função com um bom nome.

Outro caso em que o comentário apenas toma tempo de quem está lendo:

//focus on email field
$('#email').focus();

Não significa que comentários não devam ser usados nunca. Quando algo estranho tem que ser feito, por exemplo:

/* can't group selectors with placeholders for different browsers, won't work :( */
::-webkit-input-placeholder {
    color: grey;
}
::-moz-placeholder {
    color: grey;
}
:-ms-input-placeholder {
    color: grey;
}
::placeholder {
    color: grey;
}

Outra coisa que pode ser feita com comentários é transformá-lo em logs. Por exemplo:

// domain policy found - add new user to existing account
services.user.getDomainAccountId(domain).map { teamAccountId =>
   ...

poderia ser reescrito da seguinte forma:

logger.debug("Domain policy found for $domain. Adding user to existing account")
services.user.getDomainAccountId(domain).map { teamAccountId =>
  ...

Nomes

Bons nomes evitam ambiguidade.

Por exemplo, o que isso faz?

teamUserProvisioner.processLogin(
    newUser,
    "free",
    productAccount
)

O método realiza uma ação após o login de um novo usuário, termina o cadastro desse novo usuário, cria alguns metadados, e mais algumas coisinhas. Esse método faz muitas coisas, por isso o nome do método fica difícil de escolher e escolheram “processar”.

A dica é evitar verbos genéricos como: “checar”, “validar”, “produzir”, “get/set”, “processar”, “iniciar”.

O caso do “get/set” é especial, pois não deve realizar nenhum processamento, apenas retornar ou ajustar um valor do objeto. Mas, às vezes, encontramos métodos que não seguem essa regra, por isso temos que ficar atentos. Por exemplo, se você tiver um getToken(), ele deve apenas retornar um token que já existia, mas não deve criar um novo token. Se o token precisar ser carregado de algum lugar, talvez fetchToken() seja melhor. Se precisarmos criar um token, gerarToken() fica melhor que getToken().

Após enviar um formulário, por exemplo, podemos chamar um método chamado validarFormulario(), mas o que ele está fazendo? Ele retorna alguns erros, ou lança exceção, ou só retorna true/false? Se o nome do método for getErros(), fica bem claro que ele retornará erros. Se eu só quero saber se o formulário é válido, isValido() deixa claro que retorna true/false.

Se estiver difícil encontrar um nome, pode ser que o método esteja fazendo muita coisa, e você precise dividí-lo em vários métodos. Programação é praticamente processamento de dados, não deixe que o método vire um processaX().

5 ou 10 minutos que você gastar com um bom nome já vai ajudar muito no futuro com o entendimento do código.

Booleanos

Um exemplo:

function mostraImportacaoParaDrive() {...}

Você acha que vai apresentar uma tela de importação para o usuário. Mas olha a implementação:

function mostraImportacaoParaDrive() {
    var funcionalidade = this.funcionalidadesOpcionais.get("importacaoDrive");
    return !!feature && feature.isAtivo();
}
[...]
if (mostraImportacaoParaDrive) {
    this.importacaoDrivePanel.show();
}

Na verdade, o método indica se pode ou não mostrar, mas não deixa o nome do método certo.

Você deve deixar claro que é um booleano, use “deve”, “pode”, “tem”, “is”.

Outro exemplo:

pagina.getSVG(dpi, true, false);

Pelo nome, você sabe que retorna uma imagem SVG, mas você só entende o parâmetro “dpi”, não entende o “true” e o “false”.

Para facilitar, crie um objeto. Principalmente se você tiver mais de três ou quatro parâmetros.

Outra alternativa é adicionar um comentário:

page.getSVG(
    /* imageDpi */              dpi,
    /* includePageBackground */ true,
    /* compress */              false
)

Mais dicas

Não jogue “golfe”, tentando resolver o problema com o menor número de linhas.

Pratique revisões de código. Peça feedback, e dê feedback.

Por último, o autor sugere fazer os exercícios do exercism.io.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.