Amalan Terbaik Saya – Umum
1. Komen – antara rahmat dan bala
Komen yang baik ialah satu seni. Ditulis dengan bijak, ia membawa rahmat – tetapi sering juga tiada manfaat.
Kod swadokumentasi (self-documenting code) tidak bermaksud “tiada komen”. Kod sepatutnya menyatakan bagaimana sesuatu dilakukan dengan sendirinya setakat yang boleh; komen pula melengkapkan mengapa, kekangan yang tidak jelas dan kontrak sesebuah API.
Berikut ialah beberapa contoh kod swadokumentasi, serta komen yang baik dan yang buruk.
Hujah yang sering kedengaran ialah komen mudah menjadi lapuk. Hakikatnya, masalah ini sama sahaja bagi nama pemboleh ubah dan nama kaedah!
Aduh – jangan sekali-kali buat begini:
int age = 32; // set the value of the age integer to 32
Swadokumentasi: nama pemboleh ubah yang bermakna sudah separuh jalan
Buruk:
var dateTime = DateTimeUtil.getNow();
Baik:
var now = DateTimeUtil.getNow();
Satu lagi contoh:
Buruk:
User user = this.getBruce();
Buruk:
// testing user without permission
User user = this.getBruce();
Baik:
User userWithoutPermission = this.getBruce();
Swadokumentasi: nama kaedah yang bermakna
Buruk:
public String sanitizeInput(String input) {
// replace underscore with spaces
return input.replaceAll("_", " ");
}
Baik:
public String replaceUnderscoreWithSpaces(String input) {
return input.replaceAll("_", " ");
}
Swadokumentasi: kaedah persendirian
Buruk: Kaedah ini memaksa pembaca menguraikan pengesahan, pengiraan dan peraturan perniagaan serentak.
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: Kaedah awam menceritakan alirannya. Kaedah persendirian yang stateless menamakan setiap niat dan menjadikan kebergantungannya kelihatan melalui parameter dan nilai pulangan.
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 memerlukan kaedahnya sendiri. Yang diekstrak ialah satu idea yang utuh dan boleh dinamakan dengan tepat. Nama seperti processData() atau handleStuff() pula tidak membawa apa-apa faedah.
Petua am: komen mengapa sesuatu berlaku, bukan apa yang berlaku
Sudah jelas apa yang berlaku di sini:
public Duration retryDelay(int retryCount) {
long seconds = Math.min(1L << retryCount, 60);
return Duration.ofSeconds(seconds);
}
Tetapi mengapa ia berlaku?
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 projek sebenar. Berikut ialah kaedah untuk memaparkan dan menyembunyikan spinner pemuatan Ajax. Namun ada empat kaedah, dan sebabnya tidak begitu 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 = '';
}
Kaedah-kaedah ini kelihatan sangat serupa dan seolah-olah semuanya mengubah keterlihatan melalui CSS dengan satu cara atau yang lain. Dengan komen ringkas, kes penggunaannya terus menjadi 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 = '';
},
Satu lagi contoh:
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) {}
Perbincangan dalam bengkel pada 2021-06-21: Semua pembangun sependapat bahawa Javadoc secara amnya patut dielakkan (mimpi ngeri parameter, sentiasa lapuk dan menyusahkan untuk diselenggara) dan hanya digunakan apabila sesuatu parameter benar-benar memerlukan penjelasan lanjut, atau apabila pembaca terpaksa membaca keseluruhan kaedah yang panjang tanpanya.
Pengecualian kepada petua am: komen apa yang berlaku – di sini ia digalakkan
Apabila komen jelas memudahkan pembacaan kerana kod itu sendiri 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 */
)
Komen – kata-kata kesat dalam kod kernel Linux
Akhir sekali, satu cerita lucu dari kernel Linux – jumlah semua kata-kata kesat dalam kod sumbernya:
https://www.vidarholen.net/contents/wordcount/#fuck*,shit*,damn*,idiot*,retard*
Selamat menulis komen!
2. Kaedah persendirian dalam rutin program yang besar itu berbahaya
Awas! Dalam dunia berorientasikan objek hari ini, sentiasa ada banyak kaedah persendirian. Biasanya ini bukan masalah. Ia hanya menjadi masalah apabila kaedah-kaedah ini mengakses medan, iaitu state, dan berada dalam rutin yang besar. Sub-kaedah seperti ini sepatutnya private dan stateless. Jika tidak, ia sering mendatangkan masalah apabila dipanggil secara berasingan, kerana pembangun lain akan terfikir untuk memanggil sub-kaedah itu sendiri. State yang tidak sah ialah salah satu punca utama pepijat. Ada artikel menarik tentang perkara ini, berdasarkan beberapa kenyataan John Carmack (id Software, Doom): dalam aplikasi yang kodnya mesti sangat stabil (contohnya penerbangan angkasa), subrutin sering dilarang, dan ini menjadikan kod lebih deterministik: http://number-none.com/blow/john_carmack_on_inlined_code.html
Cerita ini hanya terpakai secara terhad dalam dunia Java/Spring moden yang stateless, tetapi ia tetap bahan renungan yang baik.