Bevált gyakorlataim – általános

1. Kommentek – átok és áldás

A jó komment művészet. Átgondolva kincs, gyakran mégis haszna nincs.

Az önmagát dokumentáló kód nem azt jelenti, hogy „nincsenek kommentek”. A kódnak lehetőleg magának kell kifejeznie, hogyan működik; a kommentek azt teszik hozzá, miért így, továbbá a nem nyilvánvaló peremfeltételeket és egy API szerződését.

Következzen néhány példa önmagát dokumentáló kódra, valamint jó és rossz kommentekre.

Gyakori érv, hogy a kommentek hajlamosak elavulni. Valójában ez a probléma ugyanúgy érinti a változó- és metódusneveket is!

Jaj – ezt soha ne csináld:

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

Önmagát dokumentáló kód: a beszédes változónév fél siker

Rossz:

var dateTime = DateTimeUtil.getNow();

Jó:

var now = DateTimeUtil.getNow();

Még egy példa:

Rossz:

User user = this.getBruce();

Rossz:

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

Jó:

User userWithoutPermission = this.getBruce();

Önmagát dokumentáló kód: beszédes metódusnevek

Rossz:

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

Jó:

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

Önmagát dokumentáló kód: privát metódusok

Rossz: a metódus arra kényszeríti az olvasót, hogy egyszerre fejtse meg a validációt, a számítást és az üzleti szabályt.

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));
}

Jó: a publikus metódus elmeséli a folyamatot. A privát, stateless metódusok néven nevezik az egyes szándékokat, és a függőségeiket paramétereken és visszatérési értékeken keresztül teszik láthatóvá.

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 minden sornak kell saját metódus. Egy összefüggő gondolatot emelünk ki, amelyet pontosan meg lehet nevezni. Az olyan nevek viszont, mint a processData() vagy a handleStuff(), semmit sem nyernek.

Ökölszabály: azt kommenteld, hogy miért történik valami, ne azt, hogy mi történik

Az világos, hogy itt mi történik:

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

De miért történik?

Jobb:

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);
}

Egy újabb, valós példa. Az Ajax-töltésjelző (spinner) megjelenítésére és elrejtésére szolgáló metódusokat látjuk. Csakhogy négy metódus van. Hogy miért, az első ránézésre nem egészen világos:

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 = '';
}

A metódusok mind nagyon hasonlónak tűnnek, és valahogy CSS-en keresztül módosítják a láthatóságot. Egy rövid kommenttel a felhasználási esetek azonnal világossá válnak:

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 = '';
},

Még egy példa:

Rossz:

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

Jó:

/**
 * 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) {}

Vita a 2021-06-21-i workshopon: Minden fejlesztő egyetértett abban, hogy a Javadocot általában kerülni kell (paraméter-rémálom, folyton elavult és kényelmetlen karbantartani), és csak akkor érdemes használni, ha egy paraméter tényleg részletesebb magyarázatra szorul, vagy ha különben az egész, hosszú metódust el kellene olvasni.

Kivétel az ökölszabály alól: azt kommenteld, hogy mi történik – itt ez ajánlott

Ha ez jelentősen javítja az olvashatóságot, mert a kód különben nem egyértelmű:

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 */
)

Kommentek – káromkodások a Linux kernel kódjában

Végül egy mulatságos adalék a Linux kernelből – az összes káromkodás száma a forráskódban:

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

Jó kommentírást!

2. A privát metódusok a nagy programrutinokban veszélyesek

Figyelem! A mai objektumorientált világban mindig sok privát metódus van. Ez általában nem probléma. Akkor válik azzá, ha ezek a metódusok mezőket, vagyis az állapotot (state-et) érik el, és nagy rutinok részei. Az ilyen almetódusoknak privátnak és stateless-nek kell lenniük. Különben az a hátrányuk, hogy önmagukban meghívva gyakran bajt okozhatnak. A fejlesztőknek eszébe fog jutni, hogy meghívják az almetódust. Az illegális állapotok a bugok egyik fő okai. Érdekes cikk erről John Carmack (id Software, Doom) néhány kijelentéséről, és arról, hogy azokban az alkalmazásokban, ahol a kódnak nagyon stabilnak kell lennie (például az űrrepülésben), gyakran tilosak a szubrutinok, ami determinisztikusabbá teszi a kódot: http://number-none.com/blow/john_carmack_on_inlined_code.html

Ez a történet csak korlátozottan alkalmazható a modern, stateless Java/Spring világra, de ettől még jó gondolatébresztő.