Minhas boas práticas – Geral

1. Comentários – maldição e bênção

Bons comentários são uma arte. Usados com critério, são um presente – mas muitas vezes também são em vão.

Código autodocumentado não significa “sem comentários”. O próprio código deve expressar o como da melhor forma possível; os comentários complementam o porquê, as condições de contorno não óbvias e o contrato de uma API.

A seguir, alguns casos de código autodocumentado, além de comentários bons e ruins.

Um argumento comum é que os comentários acabam ficando desatualizados. Na verdade, esse problema vale igualmente para nomes de variáveis e de métodos!

Ai – nunca faça isso:

int age = 32; // set the value of the age integer to 32

Autodocumentação: bons nomes de variáveis já resolvem metade do problema

Ruim:

var dateTime = DateTimeUtil.getNow();

Bom:

var now = DateTimeUtil.getNow();

Mais um exemplo:

Ruim:

User user = this.getBruce();

Ruim:

// testing user without permission
User user = this.getBruce();

Bom:

User userWithoutPermission = this.getBruce();

Autodocumentação: bons nomes de métodos

Ruim:

public String sanitizeInput(String input) {
    // replace underscore with spaces
    return input.replaceAll("_", " ");
}

Bom:

public String replaceUnderscoreWithSpaces(String input) {
    return input.replaceAll("_", " ");
}

Autodocumentação: métodos privados

Ruim: o método obriga quem lê a decifrar validação, cálculo e regra de negócio ao mesmo tempo.

public Invoice createInvoice(Order order, Customer customer) {
    if (order.items().isEmpty()) {
        throw new IllegalArgumentException("Order needs at least one item");
    }

    BigDecimal subtotal = order.items().stream()
            .map(item -> item.unitPrice().multiply(BigDecimal.valueOf(item.quantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    BigDecimal discount = customer.isPremium()
            ? subtotal.multiply(new BigDecimal("0.10"))
            : BigDecimal.ZERO;

    return new Invoice(order.id(), subtotal, discount, subtotal.subtract(discount));
}

Bom: o método público conta o fluxo. Métodos privados e stateless dão nome a cada intenção e deixam suas dependências visíveis por meio de parâmetros e valores de retorno.

public Invoice createInvoice(Order order, Customer customer) {
    requireItems(order);
    BigDecimal subtotal = calculateSubtotal(order.items());
    BigDecimal discount = loyaltyDiscountFor(customer, subtotal);

    return invoiceFor(order, subtotal, discount);
}

private static void requireItems(Order order) {
    if (order.items().isEmpty()) {
        throw new IllegalArgumentException("Order needs at least one item");
    }
}

private static BigDecimal calculateSubtotal(List<OrderItem> items) {
    return items.stream()
            .map(item -> item.unitPrice().multiply(BigDecimal.valueOf(item.quantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
}

private static BigDecimal loyaltyDiscountFor(Customer customer, BigDecimal subtotal) {
    return customer.isPremium()
            ? subtotal.multiply(new BigDecimal("0.10"))
            : BigDecimal.ZERO;
}

private static Invoice invoiceFor(
        Order order,
        BigDecimal subtotal,
        BigDecimal discount
) {
    return new Invoice(order.id(), subtotal, discount, subtotal.subtract(discount));
}

Nem toda linha precisa de um método próprio. O que se extrai é uma ideia coesa que possa ser nomeada com precisão. Já nomes como processData() ou handleStuff() não acrescentam nada.

Regra prática: comente por que algo acontece, e não o que acontece

Está claro o que acontece aqui:

public Duration retryDelay(int retryCount) {
    long seconds = Math.min(1L << retryCount, 60);
    return Duration.ofSeconds(seconds);
}

Mas por que isso acontece?

Melhor:

public Duration retryDelay(int retryCount) {
    // Cap the delay so an outage never blocks queued jobs for more than one minute.
    long seconds = Math.min(1L << retryCount, 60);
    return Duration.ofSeconds(seconds);
}

Outro exemplo da vida real: métodos para mostrar e ocultar o spinner de carregamento do Ajax. Só que são quatro métodos, e o motivo não fica muito claro à primeira vista:

function showAjaxLoader() {
    $('.layout-ajax-loader').children().first().show();
}

function hideAjaxLoader() {
    $('.layout-ajax-loader').children().first().hide();
}

function suppressAjaxLoader() {
    getOnlyElementByClassOrNull('layout-ajax-loader').style.display = 'none';
}

function releaseAjaxLoader() {
    getOnlyElementByClassOrNull('layout-ajax-loader').style.display = '';
}

Os métodos parecem todos muito semelhantes e, de algum jeito, manipulam a visibilidade via CSS. Com um comentário curto, os casos de uso ficam claros na hora:

showAjaxLoader: function() {
    $('.layout-ajax-loader').children().first().show();
},

hideAjaxLoader: function() {
    $('.layout-ajax-loader').children().first().hide();
},

// take the control from primefaces and don't allow the spinner to show, no matter what
suppressAjaxLoader: function() {
    getOnlyElementByClassOrNull('layout-ajax-loader').style.display = 'none';
},

// reset the state after hiding, to give the control back to primefaces
releaseAjaxLoader: function() {
    getOnlyElementByClassOrNull('layout-ajax-loader').style.display = '';
},

Mais um exemplo:

Ruim:

/**
 * Sets the tool tip text.
 *
 * @param text  the text of the tool tip
 */
public void setToolTipText(String text) {}

Bom:

/**
 * Registers the text to display in a tool tip. The text
 * displays when the cursor lingers over the component.
 *
 * @param text  the string to display. If the text is null,
 *              the tool tip is turned off for this component.
 */
public void setToolTipText(String text) {}

Discussão no workshop de 21/06/2021: consenso entre todos os desenvolvedores de que o Javadoc deve, em geral, ser evitado (pesadelo de parâmetros, sempre desatualizado e nada agradável de manter) e só deve ser usado quando um parâmetro realmente precisa de mais explicação ou quando, sem ele, seria preciso ler o método inteiro, por mais longo que seja.

Exceção à regra prática: comente o que acontece – aqui vale a pena

Quando isso facilita muito a leitura, porque de outra forma o código não fica claro:

let isAlwaysAllowedKey = (
    e.which === 8  || /* BACKSPACE */
    e.which === 35 || /* END */
    e.which === 36 || /* HOME */
    e.which === 37 || /* LEFT */
    e.which === 38 || /* UP */
    e.which === 39 || /* RIGHT*/
    e.which === 40 || /* DOWN */
    e.which === 46 || /* DEL*/
    e.ctrlKey === true && e.which === 65 || /* CTRL + A */
    e.ctrlKey === true && e.which === 88 || /* CTRL + X */
    e.ctrlKey === true && e.which === 67 || /* CTRL + C */
    e.ctrlKey === true && e.which === 90   /* CTRL + Z */
)

Comentários – palavrões no código do kernel Linux

Para encerrar, uma curiosidade divertida do kernel Linux – a contagem de todos os palavrões no código-fonte:

https://www.vidarholen.net/contents/wordcount/#fuck*,shit*,damn*,idiot*,retard*

Divirta-se escrevendo comentários!

2. Métodos privados em rotinas grandes são perigosos

Atenção! No mundo orientado a objetos de hoje, sempre existem muitos métodos privados. Normalmente isso não é problema. Passa a ser quando esses métodos acessam campos, ou seja, o estado, e ficam dentro de rotinas grandes. Esses submétodos deveriam ser privados e stateless. Caso contrário, têm a desvantagem de, quando chamados isoladamente, muitas vezes causarem dor de cabeça. Algum desenvolvedor vai ter a ideia de chamar o submétodo. Estados ilegais estão entre as principais causas de bugs. Um artigo interessante sobre isso traz algumas declarações de John Carmack (id Software, Doom) e mostra que, em aplicações nas quais o código precisa ser muito estável (voos espaciais, por exemplo), sub-rotinas muitas vezes são proibidas, o que deixa o código mais determinístico: http://number-none.com/blow/john_carmack_on_inlined_code.html

Essa história só se aplica em parte ao mundo moderno e stateless de Java/Spring, mas mesmo assim é um bom ponto de reflexão.