আমার Best Practices – সাধারণ

১. কমেন্ট – আশীর্বাদ ও অভিশাপ

ভালো কমেন্ট লেখা একটি শিল্প। ভেবেচিন্তে লিখলে তা আশীর্বাদ, নইলে প্রায়ই বৃথা।

Self-documenting কোড-এর মানে এই নয় যে “কোনো কমেন্টই থাকবে না”। কোড নিজেই যতটা সম্ভব কীভাবে প্রকাশ করবে; কমেন্ট যোগ করবে কেন, অস্পষ্ট শর্তাবলি এবং একটি API-র চুক্তি (contract)।

নিচে self-documenting কোডের কিছু উদাহরণ, সেইসাথে ভালো ও খারাপ কমেন্টের নমুনা।

একটি প্রচলিত যুক্তি হলো, কমেন্ট সহজেই outdated হয়ে যায়। আসলে এই সমস্যা ভেরিয়েবল ও মেথডের নামের ক্ষেত্রেও ঠিক সমানভাবে খাটে!

উফ – এটা কখনোই করবেন না:

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

Self-documenting: ভেরিয়েবলের অর্থবহ নামেই অর্ধেক কাজ শেষ

খারাপ:

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: মেথডের অর্থবহ নাম

খারাপ:

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

ভালো:

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

Self-documenting: প্রাইভেট মেথড

খারাপ: মেথডটি পাঠককে একসাথে ভ্যালিডেশন, হিসাব আর ব্যবসায়িক নিয়ম – সবকিছুর জট খুলতে বাধ্য করে।

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

ভালো: পাবলিক মেথডটি পুরো প্রবাহের গল্প বলে। প্রাইভেট, stateless মেথডগুলো প্রতিটি আলাদা উদ্দেশ্যের নাম দেয় এবং প্যারামিটার ও রিটার্ন ভ্যালুর মাধ্যমে নিজেদের নির্ভরতাগুলো দৃশ্যমান করে।

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

প্রতিটি লাইনের জন্য আলাদা মেথডের দরকার নেই। extract করা হয় একটি সুসংবদ্ধ ভাবনাকে, যার একটি সুনির্দিষ্ট নাম দেওয়া যায়। অন্যদিকে processData() বা handleStuff()-এর মতো নামে কোনো লাভ নেই।

মূল নিয়ম: কেন ঘটছে তা কমেন্টে লিখুন, কী ঘটছে তা নয়

এখানে কী ঘটছে তা পরিষ্কার:

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 লোডিং স্পিনার দেখানো ও লুকানোর মেথডগুলো দেখানো হয়েছে। কিন্তু মেথড রয়েছে চারটি। কেন, তা প্রথম দেখায় পুরোপুরি স্পষ্ট নয়:

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

মেথডগুলো দেখতে প্রায় একই রকম, আর সবগুলোই যেন কোনো না কোনোভাবে CSS দিয়ে দৃশ্যমানতা বদলায়। একটি ছোট কমেন্ট যোগ করলেই কোনটা কখন ব্যবহার হয়, তা সঙ্গে সঙ্গে স্পষ্ট হয়ে যায়:

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

২০২১-০৬-২১ তারিখের ওয়ার্কশপের আলোচনা: সব ডেভেলপার একমত ছিলেন যে Javadoc সাধারণত এড়িয়ে চলাই ভালো (প্যারামিটারের দুঃস্বপ্ন, সারাক্ষণ outdated এবং রক্ষণাবেক্ষণে ঝামেলা) এবং কেবল তখনই ব্যবহার করা উচিত, যখন কোনো প্যারামিটারের সত্যিই বাড়তি ব্যাখ্যা দরকার, অথবা তা না হলে পুরো লম্বা মেথডটি পড়তে হতো।

মূল নিয়মের ব্যতিক্রম: কী ঘটছে তা কমেন্টে লিখুন – এখানে সেটাই ভালো

যখন এতে পাঠযোগ্যতা অনেক সহজ হয়, কারণ নইলে কোডটি স্পষ্ট নয়:

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

কমেন্ট – Linux kernel-এর কোডে গালাগালি

শেষে Linux kernel থেকে একটি মজার কাণ্ড – সোর্স কোডে থাকা সব গালির সংখ্যা:

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

মজা করে কমেন্ট লিখুন!

২. বড় প্রোগ্রাম রুটিনে প্রাইভেট মেথড বিপজ্জনক

সাবধান! আজকের অবজেক্ট-ওরিয়েন্টেড দুনিয়ায় সবসময়ই অনেক প্রাইভেট মেথড থাকে। সাধারণত এতে কোনো সমস্যা নেই। সমস্যা তখনই হয়, যখন এই মেথডগুলো ফিল্ড, অর্থাৎ state, অ্যাক্সেস করে এবং বড় রুটিনের অংশ হয়। এমন সাব-মেথড প্রাইভেট ও stateless হওয়া উচিত। নইলে এদের অসুবিধা হলো, আলাদাভাবে কল করলে প্রায়ই ঝামেলা বাধে। কোনো না কোনো ডেভেলপারের মাথায় ঠিকই সাব-মেথডটি আলাদা করে কল করার বুদ্ধি আসবে। অবৈধ state হলো বাগের অন্যতম প্রধান কারণ। এ বিষয়ে John Carmack (id Software, Doom)-এর কিছু বক্তব্য নিয়ে একটি আকর্ষণীয় আর্টিকেল আছে; সেখানে বলা হয়েছে, যেসব অ্যাপ্লিকেশনে কোডকে অত্যন্ত স্থিতিশীল হতে হয় (যেমন মহাকাশযাত্রা), সেখানে প্রায়ই সাবরুটিন নিষিদ্ধ থাকে, ফলে কোড আরও deterministic হয়: http://number-none.com/blow/john_carmack_on_inlined_code.html

এই কাহিনি আধুনিক stateless Java/Spring দুনিয়ায় পুরোপুরি খাটে না, তবুও ভাবনার জন্য এটি একটি ভালো খোরাক।