أفضل ممارساتي – عام

1. التعليقات – نقمة ونعمة

التعليقات الجيدة فنّ. إن كُتبت بتأنٍّ كانت هدية، لكنها كثيرًا ما تذهب سدى.

لا يعني الكود الموثِّق لنفسه «لا تعليقات». ينبغي أن يعبّر الكود بنفسه قدر الإمكان عن الكيفية؛ أما التعليقات فتكمّل ذلك بـالسبب، وبالشروط الحدّية غير الواضحة، وبعقد الـ API.

فيما يلي بعض الحالات للكود الموثِّق لنفسه، وللتعليقات الجيدة والسيئة.

من الحجج الشائعة أن التعليقات سرعان ما تتقادم. والحقيقة أن هذه المشكلة تنطبق بالقدر نفسه على أسماء المتغيرات والدوال!

آخ – لا تفعل هذا أبدًا:

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

التوثيق الذاتي: الأسماء المعبّرة للمتغيرات نصف المعركة

سيئ:

var dateTime = DateTimeUtil.getNow();

جيد:

var now = DateTimeUtil.getNow();

مثال آخر:

سيئ:

User user = this.getBruce();

سيئ:

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

جيد:

User userWithoutPermission = this.getBruce();

التوثيق الذاتي: أسماء معبّرة للدوال

سيئ:

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

جيد:

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

التوثيق الذاتي: الدوال الخاصة (private)

سيئ: تُجبر الدالة القارئ على فكّ شيفرة التحقق والحساب وقاعدة العمل في آنٍ واحد.

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

ليس كل سطر بحاجة إلى دالة خاصة به. ما يُستخرج هو فكرة مترابطة يمكن تسميتها بدقة. أما أسماء مثل 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) {}

نقاش في ورشة العمل بتاريخ 2021-06-21: اتفق المطورون جميعًا على أنه ينبغي تجنّب Javadoc عمومًا (كابوس المعاملات، يتقادم باستمرار وصيانته غير مريحة)، وألا يُستخدم إلا حين يحتاج معامل ما فعلًا إلى شرح إضافي، أو حين يكون البديل قراءة الدالة الطويلة بأكملها.

استثناء من القاعدة: علّق على ما يحدث – فهنا يُستحسن ذلك

حين يسهّل ذلك القراءة كثيرًا، لأن الكود بغير ذلك لن يكون واضحًا:

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

وأخيرًا طرفة مسلّية من نواة Linux – عدد كل الشتائم في الكود المصدري:

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

استمتع بكتابة التعليقات!

2. الدوال الخاصة في الإجراءات البرمجية الكبيرة خطيرة

انتبه! في عالم البرمجة كائنية التوجه اليوم توجد دائمًا دوال خاصة كثيرة. وهذا لا يمثّل مشكلة عادةً. لكنه يصبح كذلك حين تصل هذه الدوال إلى الحقول، أي إلى الحالة (State)، وتكون داخل إجراءات كبيرة. ينبغي أن تكون هذه الدوال الفرعية خاصة (private) وعديمة الحالة (stateless). وإلا فإن عيبها أنها كثيرًا ما تسبب المتاعب إذا استُدعيت وحدها. فسيخطر لبعض المطورين أن يستدعوا الدالة الفرعية مباشرة. والحالات غير الصالحة من أهم أسباب الأخطاء. وهذه مقالة مثيرة للاهتمام حول بعض آراء John Carmack (id Software، Doom)، وحول أن الإجراءات الفرعية كثيرًا ما تُحظر في التطبيقات التي يجب أن يكون كودها مستقرًا جدًا (كرحلات الفضاء مثلًا)، مما يجعل الكود أكثر حتمية: http://number-none.com/blow/john_carmack_on_inlined_code.html

لا تنطبق هذه الطرفة إلا جزئيًا على عالم Java/Spring الحديث عديم الحالة، لكنها مع ذلك تبعث على التفكير.