నా Best Practices – సాధారణం

1. Commentలు – శాపమూ, వరమూ

మంచి comment రాయడం ఒక కళ. ఆలోచించి రాస్తే వరం; లేకపోతే చాలాసార్లు వృథా.

Self-documenting code అంటే “commentలు అస్సలు ఉండవు” అని కాదు. కోడ్ తనంతట తానే ఎలా అనేది వీలైనంత వరకు వ్యక్తపరచాలి; commentలు ఎందుకు అనేదాన్ని, స్పష్టంగా కనిపించని షరతులను, ఒక API ఒప్పందాన్ని (contract) జోడిస్తాయి.

కింద self-documenting కోడ్‌కు కొన్ని ఉదాహరణలు, మంచి మరియు చెడ్డ commentలతో పాటు.

Commentలు త్వరగా పాతబడిపోతాయి (outdated) అనేది ఒక సాధారణ వాదన. నిజానికి, ఈ సమస్య variable, method పేర్లకు కూడా సరిగ్గా అలాగే వర్తిస్తుంది!

అయ్యో – ఇలా ఎప్పుడూ చేయకండి:

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

Self-documenting: variableలకు అర్థవంతమైన పేర్లు పెడితే సగం పని పూర్తయినట్టే

చెడ్డది:

var dateTime = DateTimeUtil.getNow();

మంచిది:

var now = DateTimeUtil.getNow();

మరో ఉదాహరణ:

చెడ్డది:

User user = this.getBruce();

చెడ్డది:

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

మంచిది:

User userWithoutPermission = this.getBruce();

Self-documenting: methodలకు అర్థవంతమైన పేర్లు

చెడ్డది:

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

మంచిది:

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

Self-documenting: Private methodలు

చెడ్డది: ఈ method validation, లెక్కింపు, వ్యాపార నియమం – అన్నింటినీ ఒకేసారి అర్థం చేసుకోమని పాఠకుడిని బలవంతం చేస్తుంది.

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

మంచిది: Public method మొత్తం ప్రవాహాన్ని ఒక కథలా చెబుతుంది. Private, stateless methodలు ప్రతి ఉద్దేశానికీ ఒక పేరు పెడతాయి; వాటి ఆధారాలను parameterలు, return విలువల ద్వారా స్పష్టంగా కనిపించేలా చేస్తాయి.

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

ప్రతి లైనుకూ ప్రత్యేక method అవసరం లేదు. కచ్చితంగా పేరు పెట్టగలిగే, ఒకే ఆలోచనగా ఉన్న భాగాన్నే వేరుచేయాలి. దానికి భిన్నంగా, processData() లేదా handleStuff() లాంటి పేర్ల వల్ల ఏ లాభమూ లేదు.

ప్రాథమిక నియమం: ఏమి జరుగుతోందో కాదు, ఎందుకు జరుగుతోందో commentలో రాయండి

ఇక్కడ ఏమి జరుగుతోందో స్పష్టమే:

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

కానీ ఎందుకు జరుగుతోంది?

మెరుగైనది:

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

మరో నిజ జీవిత ఉదాహరణ. Ajax loading spinnerను చూపించడానికి, దాచడానికి ఉన్న methodలు ఇక్కడ ఉన్నాయి. కానీ మొత్తం నాలుగు methodలు. ఎందుకో మొదటి చూపులో పూర్తిగా అర్థం కాదు:

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

ఈ methodలన్నీ దాదాపు ఒకేలా కనిపిస్తాయి; అన్నీ ఏదో విధంగా CSS ద్వారా visibilityని మారుస్తున్నట్టు ఉంటాయి. ఒక చిన్న comment జోడిస్తే చాలు, ఏది ఎప్పుడు వాడాలో వెంటనే స్పష్టమవుతుంది:

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

మరో ఉదాహరణ:

చెడ్డది:

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

మంచిది:

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

2021-06-21 నాటి workshopలో చర్చ: Javadocను సాధారణంగా తప్పించుకోవాలనే విషయంలో developerలందరిదీ ఒకే అభిప్రాయం (parameterల తలనొప్పి, ఎప్పుడూ పాతబడిపోతుంది, నిర్వహించడం ఇబ్బంది); ఒక parameterకు నిజంగా అదనపు వివరణ అవసరమైనప్పుడు, లేదా లేకపోతే పొడవైన method మొత్తాన్ని చదవాల్సి వచ్చినప్పుడు మాత్రమే దాన్ని వాడాలి.

ప్రాథమిక నియమానికి మినహాయింపు: ఏమి జరుగుతోందో commentలో రాయండి – ఇక్కడ అదే మంచిది

కోడ్ స్వయంగా స్పష్టంగా లేనందువల్ల, comment చదవడాన్ని గణనీయంగా సులభం చేసినప్పుడు:

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

Commentలు – Linux kernel కోడ్‌లో తిట్లు

చివరగా Linux kernel నుండి ఒక సరదా సంగతి – source codeలో ఉన్న మొత్తం తిట్ల సంఖ్య:

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

Commentలు రాయడాన్ని ఆస్వాదించండి!

2. పెద్ద program routineలలో private methodలు ప్రమాదకరం

జాగ్రత్త! నేటి object-oriented ప్రపంచంలో ఎప్పుడూ చాలా private methodలు ఉంటాయి. సాధారణంగా ఇది సమస్య కాదు. ఈ methodలు fieldలను, అంటే stateను, యాక్సెస్ చేస్తూ పెద్ద routineలలో భాగంగా ఉన్నప్పుడే అది సమస్య అవుతుంది. అలాంటి sub-methodలు private గానూ stateless గానూ ఉండాలి. లేకపోతే, విడిగా పిలిచినప్పుడు అవి తరచూ ఇబ్బందులు తెచ్చిపెడతాయి. ఎవరో ఒక developerకు ఆ sub-methodను నేరుగా పిలవాలనే ఆలోచన తప్పకుండా వస్తుంది. చెల్లని (illegal) stateలు bugలకు ప్రధాన కారణాల్లో ఒకటి. దీనిపై John Carmack (id Software, Doom) చెప్పిన కొన్ని విషయాల గురించి ఒక ఆసక్తికరమైన వ్యాసం ఉంది; కోడ్ చాలా స్థిరంగా ఉండాల్సిన అప్లికేషన్లలో (ఉదా. అంతరిక్ష యానం) subroutineలను తరచూ నిషేధిస్తారని, దానివల్ల కోడ్ మరింత deterministic అవుతుందని అది చెబుతుంది: http://number-none.com/blow/john_carmack_on_inlined_code.html

ఈ కథ ఆధునిక stateless Java/Spring ప్రపంచానికి కొంతవరకే వర్తిస్తుంది; అయినా ఆలోచించడానికి ఇది మంచి ప్రేరణ.