Apa itu angka ajaib?
Kenapa harus dihindari?
Apakah ada kasus yang sesuai?
Apa itu angka ajaib?
Kenapa harus dihindari?
Apakah ada kasus yang sesuai?
Jawaban:
Nomor ajaib adalah penggunaan langsung nomor dalam kode.
Misalnya, jika Anda memiliki (di Jawa):
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
Ini harus di refactored untuk:
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
Ini meningkatkan keterbacaan kode dan lebih mudah untuk mempertahankannya. Bayangkan kasus di mana saya mengatur ukuran bidang kata sandi di GUI. Jika saya menggunakan angka ajaib, setiap kali ukuran max berubah, saya harus mengubah di dua lokasi kode. Jika saya lupa satu, ini akan menyebabkan inkonsistensi.
JDK penuh dengan contoh-contoh seperti di Integer
, Character
dan di Math
kelas.
PS: Alat analisis statis seperti FindBugs dan PMD mendeteksi penggunaan angka ajaib dalam kode Anda dan menyarankan refactoring.
TRUE
/ FALSE
)
Angka ajaib adalah nilai hard-kode yang dapat berubah pada tahap selanjutnya, tetapi karena itu bisa sulit untuk diperbarui.
Misalnya, katakanlah Anda memiliki Halaman yang menampilkan 50 Pesanan terakhir di Halaman Ikhtisar "Pesanan Anda". 50 adalah Angka Ajaib di sini, karena itu tidak ditetapkan melalui standar atau konvensi, itu nomor yang Anda buat untuk alasan yang diuraikan dalam spesifikasi.
Sekarang, yang Anda lakukan adalah memiliki 50 di tempat yang berbeda - skrip SQL Anda ( SELECT TOP 50 * FROM orders
), Situs Web Anda (Pesanan 50 Terakhir Anda), login pesanan Anda ( for (i = 0; i < 50; i++)
) dan mungkin banyak tempat lain.
Sekarang, apa yang terjadi ketika seseorang memutuskan untuk mengubah 50 ke 25? atau 75? atau 153? Anda sekarang harus mengganti 50 di semua tempat, dan kemungkinan besar Anda akan melewatkannya. Find / Replace mungkin tidak berfungsi, karena 50 dapat digunakan untuk hal-hal lain, dan mengganti secara acak 50 dengan 25 dapat memiliki beberapa efek samping buruk lainnya (yaitu Session.Timeout = 50
panggilan Anda , yang juga diatur ke 25 dan pengguna mulai melaporkan timeout yang terlalu sering).
Juga, kodenya bisa sulit dipahami, yaitu " if a < 50 then bla
" - jika Anda menemukan bahwa di tengah fungsi yang rumit, pengembang lain yang tidak terbiasa dengan kode mungkin bertanya pada diri sendiri "WTF is 50 ???"
Itulah mengapa yang terbaik adalah memiliki angka ambigu dan sewenang-wenang di tepat 1 tempat - " const int NumOrdersToDisplay = 50
", karena itu membuat kode lebih mudah dibaca (" if a < NumOrdersToDisplay
", itu juga berarti Anda hanya perlu mengubahnya di 1 tempat yang terdefinisi dengan baik.
Tempat-tempat di mana Angka Ajaib sesuai adalah segala sesuatu yang didefinisikan melalui standar, yaitu SmtpClient.DefaultPort = 25
atau TCPPacketSize = whatever
(tidak yakin apakah itu standar). Juga, semua yang hanya didefinisikan dalam 1 fungsi mungkin dapat diterima, tetapi itu tergantung pada Konteks.
SmtpClient.DefaultPort = 25
mungkin jelas er daripada SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
25
seluruh aplikasi dan memastikan Anda hanya mengubah kemunculan 25
yang untuk SMTP Port, bukan 25 yang misalnya lebar kolom tabel atau angka catatan untuk ditampilkan di halaman.
IANA
.
Sudahkah Anda melihat entri Wikipedia untuk nomor ajaib?
Ini masuk ke sedikit detail tentang semua cara referensi nomor ajaib dibuat. Berikut adalah kutipan tentang angka ajaib sebagai praktik pemrograman yang buruk
Istilah angka ajaib juga merujuk pada praktik pemrograman yang buruk dengan menggunakan angka secara langsung dalam kode sumber tanpa penjelasan. Dalam kebanyakan kasus ini membuat program lebih sulit untuk dibaca, dimengerti, dan dipelihara. Meskipun sebagian besar panduan membuat pengecualian untuk angka nol dan satu, merupakan ide bagus untuk mendefinisikan semua angka lain dalam kode sebagai konstanta bernama.
Sihir: Semantik tidak dikenal
Simbolik Konstan -> Menyediakan semantik yang benar dan konteks yang benar untuk digunakan
Semantik: Arti atau tujuan suatu hal.
"Buat konstanta, beri nama sesuai artinya, dan ganti angkanya." - Martin Fowler
Pertama, angka ajaib bukan hanya angka. Nilai dasar apa pun bisa "ajaib". Nilai dasar adalah entitas manifes seperti integer, real, double, float, tanggal, string, boolean, karakter, dan sebagainya. Masalahnya bukan tipe data, tetapi aspek "ajaib" dari nilai seperti yang muncul dalam teks kode kami.
Apa yang kita maksud dengan "sihir"? Tepatnya: Dengan "sihir", kami bermaksud menunjukkan semantik (makna atau tujuan) nilai dalam konteks kode kami; bahwa itu tidak diketahui, tidak diketahui, tidak jelas, atau membingungkan. Ini adalah gagasan "sihir". Nilai dasar bukanlah sihir ketika makna semantiknya atau tujuan keberadaannya ada dengan cepat dan mudah diketahui, jelas, dan dipahami (tidak membingungkan) dari konteks sekeliling tanpa kata-kata penolong khusus (misalnya konstanta simbolis).
Oleh karena itu, kami mengidentifikasi angka ajaib dengan mengukur kemampuan pembaca kode untuk mengetahui, menjelaskan, dan memahami makna dan tujuan dari nilai dasar dari konteks sekitarnya. Semakin sedikit diketahui, kurang jelas, dan semakin bingung pembaca, semakin "ajaib" nilai dasarnya.
Kami memiliki dua skenario untuk nilai-nilai dasar sihir kami. Hanya yang kedua yang sangat penting bagi programmer dan kode:
Ketergantungan menyeluruh dari "sihir" adalah bagaimana nilai dasar tunggal (mis. Angka) tidak memiliki semantik yang dikenal (seperti Pi), tetapi memiliki semantik yang dikenal secara lokal (misalnya program Anda), yang tidak sepenuhnya jelas dari konteksnya atau dapat disalahgunakan dalam konteks yang baik atau buruk.
Semantik dari sebagian besar bahasa pemrograman tidak akan memungkinkan kita untuk menggunakan nilai-nilai dasar tunggal, kecuali (mungkin) sebagai data (yaitu tabel data). Ketika kita menemukan "angka ajaib", kita biasanya melakukannya dalam konteks. Karena itu, jawabannya
"Apakah saya mengganti angka ajaib ini dengan konstanta simbolis?"
adalah:
"Seberapa cepat Anda dapat menilai dan memahami makna semantik dari angka (tujuannya untuk berada di sana) dalam konteksnya?"
Dengan pemikiran ini, kita dapat dengan cepat melihat bagaimana angka seperti Pi (3.14159) bukan "angka ajaib" ketika ditempatkan dalam konteks yang tepat (misalnya 2 x 3,14159 x jari-jari atau 2 * Pi * r). Di sini, angka 3.14159 diakui secara mental Pi tanpa pengenal simbolis konstan.
Namun, kami biasanya mengganti 3.14159 dengan pengenal konstanta simbolis seperti Pi karena panjang dan kerumitan jumlahnya. Aspek panjang dan kompleksitas Pi (ditambah dengan kebutuhan akan keakuratan) biasanya berarti pengenal simbolik atau konstanta kurang rentan terhadap kesalahan. Pengakuan "Pi" sebagai nama hanyalah bonus yang mudah, tetapi bukan alasan utama untuk memiliki konstanta.
Mengesampingkan konstanta umum seperti Pi, mari kita fokus terutama pada angka-angka dengan makna khusus, tetapi makna-makna itu dibatasi pada semesta sistem perangkat lunak kita. Angka seperti itu mungkin "2" (sebagai nilai integer dasar).
Jika saya menggunakan angka 2 dengan sendirinya, pertanyaan pertama saya mungkin: Apa artinya "2"? Arti "2" dengan sendirinya tidak diketahui dan tidak diketahui tanpa konteks, meninggalkan penggunaannya tidak jelas dan membingungkan. Meskipun hanya memiliki "2" dalam perangkat lunak kami tidak akan terjadi karena semantik bahasa, kami ingin melihat bahwa "2" dengan sendirinya tidak membawa semantik khusus atau tujuan yang jelas sendirian.
Mari kita letakkan satu-satunya "2" dalam konteks:, di padding := 2
mana konteksnya adalah "GUI Container". Dalam konteks ini makna 2 (sebagai piksel atau unit grafis lainnya) menawarkan kepada kita tebakan semantiknya (makna dan tujuan). Kita mungkin berhenti di sini dan mengatakan bahwa 2 tidak apa-apa dalam konteks ini dan tidak ada hal lain yang perlu kita ketahui. Namun, mungkin di dunia perangkat lunak kami ini bukan keseluruhan cerita. Ada lebih dari itu, tetapi "padding = 2" sebagai konteks tidak dapat mengungkapkannya.
Lebih jauh kita berpura-pura bahwa 2 sebagai bantalan piksel dalam program kami adalah variasi "default_padding" di seluruh sistem kami. Karena itu, menulis instruksi padding = 2
tidak cukup baik. Gagasan "default" tidak terungkap. Hanya ketika saya menulis: padding = default_padding
sebagai konteks dan kemudian di tempat lain: default_padding = 2
saya sepenuhnya menyadari makna yang lebih baik dan lebih lengkap (semantik dan tujuan) dari 2 dalam sistem kami.
Contoh di atas cukup bagus karena "2" dengan sendirinya bisa apa saja. Hanya ketika kita membatasi rentang dan domain pemahaman pada "program saya" di mana 2 adalah bagian default_padding
GUI UX dari "program saya", akhirnya kita memahami "2" dalam konteks yang tepat. Di sini "2" adalah angka "ajaib", yang difaktorkan ke konstanta simbolis default_padding
dalam konteks GUI UX dari "program saya" untuk membuatnya digunakan dengan default_padding
cepat dipahami dalam konteks yang lebih besar dari kode penutup.
Dengan demikian, setiap nilai dasar, yang maknanya (semantik dan tujuan) tidak dapat cukup dan cepat dipahami adalah kandidat yang baik untuk konstanta simbolis di tempat nilai dasar (misalnya angka ajaib).
Angka dalam skala mungkin memiliki semantik juga. Sebagai contoh, berpura-pura kita membuat game D & D, di mana kita memiliki gagasan tentang monster. Objek monster kami memiliki fitur yang disebut life_force
, yang merupakan bilangan bulat. Angka-angka memiliki makna yang tidak dapat diketahui atau jelas tanpa kata-kata untuk memasok makna. Jadi, kita mulai dengan mengatakan secara sewenang-wenang:
Dari konstanta simbolis di atas, kita mulai mendapatkan gambaran mental tentang semangat, kematian, dan "undeadness" (dan kemungkinan konsekuensi atau konsekuensi) untuk monster kita dalam permainan D & D kita. Tanpa kata-kata ini (konstanta simbolik), kita hanya memiliki angka mulai dari -10 .. 10
. Hanya rentang tanpa kata-kata membuat kita berada di tempat yang kemungkinan besar kebingungan dan berpotensi dengan kesalahan dalam permainan kita jika bagian-bagian yang berbeda dari permainan memiliki ketergantungan pada apa arti kisaran angka itu untuk berbagai operasi seperti attack_elves
atau seek_magic_healing_potion
.
Oleh karena itu, ketika mencari dan mempertimbangkan penggantian "angka ajaib" kami ingin mengajukan pertanyaan yang sangat terarah tentang angka-angka dalam konteks perangkat lunak kami dan bahkan bagaimana angka-angka berinteraksi secara semantik satu sama lain.
Mari kita tinjau pertanyaan apa yang harus kita tanyakan:
Anda mungkin memiliki nomor ajaib jika ...
Periksa nilai-nilai dasar konstan konstan yang ada dalam teks kode Anda. Ajukan setiap pertanyaan dengan perlahan dan penuh pertimbangan tentang setiap contoh nilai seperti itu. Pertimbangkan kekuatan jawaban Anda. Sering kali, jawabannya bukan hitam dan putih, tetapi memiliki nuansa makna dan tujuan yang disalahpahami, kecepatan belajar, dan kecepatan pemahaman. Ada juga kebutuhan untuk melihat bagaimana terhubung ke mesin perangkat lunak di sekitarnya.
Pada akhirnya, jawaban untuk penggantian adalah menjawab ukuran (dalam pikiran Anda) dari kekuatan atau kelemahan pembaca untuk membuat koneksi (mis. "Dapatkan"). Semakin cepat mereka memahami makna dan tujuan, semakin sedikit "keajaiban" yang Anda miliki.
KESIMPULAN: Ganti nilai-nilai dasar dengan konstanta simbolik hanya ketika sihir cukup besar untuk menyebabkan sulit untuk mendeteksi bug yang timbul dari kebingungan.
Nomor ajaib adalah urutan karakter di awal format file, atau pertukaran protokol. Nomor ini berfungsi sebagai cek kewarasan.
Contoh: Buka file GIF, Anda akan melihat di awal: GIF89. "GIF89" menjadi angka ajaib.
Program lain dapat membaca beberapa karakter pertama file dan mengidentifikasi GIF dengan benar.
Bahayanya adalah bahwa data biner acak dapat berisi karakter yang sama ini. Tetapi sangat tidak mungkin.
Sedangkan untuk pertukaran protokol, Anda dapat menggunakannya untuk mengidentifikasi dengan cepat bahwa 'pesan' saat ini yang dikirimkan kepada Anda rusak atau tidak valid.
Angka ajaib masih berguna.
Dalam pemrograman, "angka ajaib" adalah nilai yang harus diberi nama simbolis, tetapi dimasukkan ke dalam kode sebagai literal, biasanya di lebih dari satu tempat.
Itu buruk karena alasan yang sama SPOT (Single Point of Truth) baik: Jika Anda ingin mengubah konstanta ini nanti, Anda harus mencari kode Anda untuk menemukan setiap contoh. Ini juga buruk karena mungkin tidak jelas bagi programmer lain apa angka ini mewakili, karenanya "ajaib".
Orang-orang kadang-kadang mengambil penghapusan angka ajaib lebih lanjut, dengan memindahkan konstanta ini ke dalam file terpisah untuk bertindak sebagai konfigurasi. Ini kadang-kadang bermanfaat, tetapi juga bisa menciptakan lebih banyak kompleksitas daripada nilainya.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
mungkin dievaluasi lebih cepat daripada loop. Jika seseorang mengganti 3
tanpa menulis ulang kode sebagai loop, seseorang yang melihat ITEMS_TO_AVERAGE
didefinisikan sebagai 3
angka mereka dapat mengubahnya 5
dan memiliki rata-rata kode lebih banyak item. Sebaliknya, seseorang yang melihat ekspresi dengan literal 3
akan menyadari bahwa 3
mewakili jumlah item yang dirangkum bersama.
Bilangan ajaib juga bisa berupa bilangan dengan semantik khusus yang dikodekan dengan keras. Sebagai contoh, saya pernah melihat sebuah sistem di mana record ID> 0 diperlakukan secara normal, 0 itu sendiri adalah "record baru", -1 adalah "ini adalah root" dan -99 adalah "ini dibuat di root". 0 dan -99 akan menyebabkan WebService menyediakan ID baru.
Apa yang buruk tentang ini adalah bahwa Anda menggunakan kembali ruang (bilangan bulat yang ditandatangani untuk ID rekaman) untuk kemampuan khusus. Mungkin Anda tidak akan pernah ingin membuat catatan dengan ID 0, atau dengan ID negatif, tetapi bahkan jika tidak, setiap orang yang melihat kode atau database mungkin tersandung pada ini dan menjadi bingung pada awalnya. Tak perlu dikatakan bahwa nilai-nilai khusus itu tidak terdokumentasi dengan baik.
Bisa dibilang, 22, 7, -12 dan 620 juga dihitung sebagai angka ajaib. ;-)
Masalah yang belum disebutkan dengan menggunakan angka ajaib ...
Jika Anda memiliki sangat banyak dari mereka, kemungkinannya cukup baik bahwa Anda memiliki dua tujuan berbeda yang Anda gunakan untuk angka ajaib, di mana nilainya sama.
Dan kemudian, tentu saja, Anda perlu mengubah nilainya ... hanya untuk satu tujuan.
Saya menganggap ini adalah jawaban atas jawaban saya untuk pertanyaan Anda sebelumnya. Dalam pemrograman, angka ajaib adalah konstanta numerik tertanam yang muncul tanpa penjelasan. Jika itu muncul di dua lokasi yang berbeda, itu dapat menyebabkan keadaan di mana satu contoh diubah dan bukan yang lain. Untuk kedua alasan ini, penting untuk mengisolasi dan menetapkan konstanta numerik di luar tempat mereka digunakan.
Saya selalu menggunakan istilah "angka ajaib" secara berbeda, sebagai nilai yang tidak jelas disimpan dalam struktur data yang dapat diverifikasi sebagai pemeriksaan validitas cepat. Misalnya file gzip berisi 0x1f8b08 sebagai tiga byte pertama, file kelas Java mulai dengan 0xcafebabe, dll.
Anda sering melihat angka ajaib yang tertanam dalam format file, karena file dapat dikirim agak sembarangan dan kehilangan metadata tentang bagaimana mereka dibuat. Namun angka ajaib juga kadang-kadang digunakan untuk struktur data dalam memori, seperti panggilan ioctl ().
Pemeriksaan cepat dari angka ajaib sebelum memproses file atau struktur data memungkinkan seseorang untuk menandai kesalahan lebih awal, daripada schlep sepanjang jalan melalui pemrosesan yang berpotensi panjang untuk mengumumkan bahwa inputnya benar-benar balderdash.
Perlu dicatat bahwa kadang-kadang Anda ingin nomor "hard-coded" yang tidak dapat dikonfigurasi dalam kode Anda. Ada beberapa yang terkenal termasuk 0x5F3759DF yang digunakan dalam algoritma root kuadrat terbalik yang dioptimalkan.
Dalam kasus langka di mana saya menemukan kebutuhan untuk menggunakan Angka Ajaib tersebut, saya menetapkannya sebagai const dalam kode saya, dan mendokumentasikan mengapa mereka digunakan, bagaimana mereka bekerja, dan dari mana mereka berasal.
Bagaimana dengan menginisialisasi variabel di bagian atas kelas dengan nilai default? Sebagai contoh:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
Dalam hal ini, 15000 adalah angka ajaib (menurut CheckStyles). Bagi saya, pengaturan nilai default tidak masalah. Saya tidak mau harus melakukan:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
Apakah itu membuatnya lebih sulit dibaca? Saya tidak pernah mempertimbangkan ini sampai saya menginstal CheckStyles.
static final
konstanta berlebihan ketika Anda menggunakannya dalam satu metode. Sebuah final
variabel dideklarasikan di bagian atas metode ini lebih mudah dibaca IMHO.
@ eed3si9n: Saya bahkan menyarankan bahwa '1' adalah angka ajaib. :-)
Prinsip yang terkait dengan angka ajaib adalah bahwa setiap fakta yang ditangani oleh kode Anda harus dinyatakan tepat satu kali. Jika Anda menggunakan angka ajaib dalam kode Anda (seperti contoh panjang kata sandi yang diberikan @marcio, Anda dapat dengan mudah menduplikasi fakta itu, dan ketika Anda memahami fakta itu berubah, Anda mengalami masalah pemeliharaan.
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
Bagaimana dengan variabel pengembalian?
Saya secara khusus merasa sulit ketika menerapkan prosedur tersimpan .
Bayangkan prosedur tersimpan berikutnya (salah sintaks, saya tahu, hanya untuk menunjukkan contoh):
int procGetIdCompanyByName(string companyName);
Ini mengembalikan ID perusahaan jika ada di tabel tertentu. Jika tidak, ia mengembalikan -1. Entah bagaimana itu angka ajaib. Beberapa rekomendasi yang saya baca sejauh ini mengatakan bahwa saya harus benar-benar merancang sesuatu seperti itu:
int procGetIdCompanyByName(string companyName, bool existsCompany);
Ngomong-ngomong, apa yang harus dikembalikan jika perusahaan tidak ada? Oke: ini akan menetapkan ada perusahaan sebagai salah , tetapi juga akan mengembalikan -1.
Opsi antoher adalah membuat dua fungsi terpisah:
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
Jadi pra-kondisi untuk prosedur tersimpan kedua adalah perusahaan itu ada.
Tetapi saya takut akan konkurensi, karena dalam sistem ini, sebuah perusahaan dapat dibuat oleh pengguna lain.
Intinya adalah: apa pendapat Anda tentang penggunaan "angka ajaib" semacam itu yang relatif dikenal dan aman untuk mengatakan bahwa ada sesuatu yang tidak berhasil atau ada sesuatu yang tidak ada?
Keuntungan lain dari mengekstraksi angka ajaib sebagai konstanta memberikan kemungkinan untuk secara jelas mendokumentasikan informasi bisnis.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
saat ini 11 orang saya mungkin orang atau botol bir atau sesuatu jadi saya akan mengubah 11 ke konstanta seperti penduduk.