Best Practices Saya – Umum
1. Komentar – berkah sekaligus kutukan
Komentar yang baik adalah sebuah seni. Ditulis dengan cermat, ia anugerah; namun sering pula sia-sia belaka.
Self-documenting Code tidak berarti “tanpa komentar”. Kode sebaiknya sebisa mungkin menyampaikan bagaimana dengan sendirinya; komentar melengkapi mengapa, batasan yang tidak kasatmata, dan kontrak sebuah API.
Berikut beberapa contoh self-documenting code, serta komentar yang baik dan yang buruk.
Argumen yang sering muncul adalah komentar mudah usang. Padahal masalah ini berlaku sama persis untuk nama variabel dan nama method!
Aduh – jangan pernah lakukan ini:
int age = 32; // set the value of the age integer to 32
Self-documenting: Nama variabel yang bermakna sudah setengah jalan
Buruk:
var dateTime = DateTimeUtil.getNow();
Baik:
var now = DateTimeUtil.getNow();
Contoh lain:
Buruk:
User user = this.getBruce();
Buruk:
// testing user without permission
User user = this.getBruce();
Baik:
User userWithoutPermission = this.getBruce();
Self-documenting: Nama method yang bermakna
Buruk:
public String sanitizeInput(String input) {
// replace underscore with spaces
return input.replaceAll("_", " ");
}
Baik:
public String replaceUnderscoreWithSpaces(String input) {
return input.replaceAll("_", " ");
}
Self-documenting: Method private
Buruk: Method ini memaksa pembaca mengurai validasi, perhitungan, dan aturan bisnis sekaligus.
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));
}
Baik: Method public menceritakan alurnya. Method private yang stateless menamai setiap maksud secara terpisah dan memperlihatkan dependensinya lewat parameter dan nilai kembalian.
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));
}
Tidak setiap baris butuh method sendiri. Yang diekstrak adalah satu gagasan utuh yang bisa dinamai dengan tepat. Sebaliknya, nama seperti processData() atau handleStuff() sama sekali tidak membantu.
Aturan praktis: Komentari mengapa sesuatu terjadi, bukan apa yang terjadi
Di sini sudah jelas apa yang terjadi:
public Duration retryDelay(int retryCount) {
long seconds = Math.min(1L << retryCount, 60);
return Duration.ofSeconds(seconds);
}
Tapi mengapa hal itu terjadi?
Lebih baik:
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);
}
Satu lagi contoh dari dunia nyata. Berikut method untuk menampilkan dan menyembunyikan spinner loading Ajax. Masalahnya, ada empat method. Alasannya tidak langsung jelas pada pandangan pertama:
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 = '';
}
Semua method tampak sangat mirip dan entah bagaimana mengubah visibilitas lewat CSS. Dengan komentar singkat, kasus penggunaannya langsung jelas:
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 = '';
},
Contoh lain:
Buruk:
/**
* Sets the tool tip text.
*
* @param text the text of the tool tip
*/
public void setToolTipText(String text) {}
Baik:
/**
* 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) {}
Diskusi dalam workshop tanggal 2021-06-21: Semua developer sepakat bahwa Javadoc sebaiknya dihindari secara umum (mimpi buruk parameter, terus-menerus usang, dan tidak nyaman dipelihara) dan hanya digunakan bila sebuah parameter benar-benar memerlukan penjelasan lebih, atau bila tanpanya orang harus membaca seluruh method yang panjang.
Pengecualian aturan praktis: Komentari apa yang terjadi – di sini justru dianjurkan
Jika hal itu jauh mempermudah keterbacaan, karena tanpanya kode tidak jelas:
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 */
)
Komentar – kata-kata umpatan dalam kode Linux Kernel
Sebagai penutup, satu anekdot lucu dari Linux Kernel – jumlah seluruh kata umpatan dalam kode sumbernya:
https://www.vidarholen.net/contents/wordcount/#fuck*,shit*,damn*,idiot*,retard*
Selamat menulis komentar!
2. Method private dalam rutin program yang besar itu berbahaya
Perhatian! Di dunia berorientasi objek saat ini selalu ada banyak method private. Biasanya hal ini bukan masalah. Masalah baru muncul ketika method-method ini mengakses field, alias state, dan berada di dalam rutin yang besar. Sub-method seperti itu sebaiknya private dan stateless. Jika tidak, kelemahannya adalah method itu sering menimbulkan masalah bila dipanggil sendirian. Developer akan terpikir untuk memanggil sub-method tersebut secara langsung. State yang tidak valid adalah salah satu penyebab utama bug. Ada artikel menarik tentang hal ini yang membahas beberapa pernyataan John Carmack (id Software, Doom), dan bahwa pada aplikasi yang kodenya harus sangat stabil (misalnya penerbangan antariksa), subrutin sering kali dilarang, sehingga kode menjadi lebih deterministik: http://number-none.com/blow/john_carmack_on_inlined_code.html
Anekdot ini hanya sebagian berlaku untuk dunia Java/Spring modern yang stateless, tetapi tetap menjadi bahan renungan yang baik.