Menghilangkan Angka Ajaib: Kapan saatnya mengatakan "Tidak"?

36

Kita semua tahu bahwa angka ajaib (nilai kode keras) dapat mendatangkan malapetaka dalam program Anda, terutama ketika saatnya untuk memodifikasi bagian kode yang tidak memiliki komentar, tetapi di mana Anda menggambar garis?

Misalnya, jika Anda memiliki fungsi yang menghitung jumlah detik antara dua hari, apakah Anda menggantinya

seconds = num_days * 24 * 60 * 60

dengan

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

Pada titik apa Anda memutuskan bahwa itu benar-benar jelas apa artinya nilai yang dikodekan dan biarkan saja?

oosterwal
sumber
2
Mengapa tidak mengganti penghitungan itu dengan fungsi atau makro sehingga kode Anda pada akhirnya tampak sepertiseconds = CALC_SECONDS(num_days);
FrustratedWithFormsDesigner
15
TimeSpan.FromDays(numDays).Seconds;
Tidak seorang pun
18
@ oosterwal: Dengan sikap itu ( HOURS_PER_DAY will never need to be altered), Anda tidak akan pernah mengkode perangkat lunak yang digunakan di Mars. : P
FrustratedWithFormsDesigner
23
Saya akan mengurangi jumlah konstanta menjadi hanya SECONDS_PER_DAY = 86400. Mengapa menghitung sesuatu yang tidak akan berubah?
JohnFx
17
Bagaimana dengan detik kabisat?
John

Jawaban:

40

Ada dua alasan untuk menggunakan konstanta simbolik alih-alih literal angka:

  1. Untuk menyederhanakan perawatan jika angka ajaib berubah. Ini tidak berlaku untuk contoh Anda. Sangat tidak mungkin jumlah detik dalam satu jam, atau jumlah jam dalam sehari akan berubah.

  2. Untuk meningkatkan readibility. Ungkapan "24 * 60 * 60" cukup jelas bagi hampir semua orang. "SECONDS_PER_DAY" juga, tetapi jika Anda mencari bug, Anda mungkin harus memeriksa bahwa SECONDS_PER_DAY didefinisikan dengan benar. Ada nilai singkatnya.

Untuk angka ajaib yang muncul tepat sekali, dan tidak tergantung pada sisa program, memutuskan apakah akan membuat simbol untuk angka itu adalah masalah selera. Jika ada keraguan, silakan dan buat simbol.

Jangan lakukan ini:

public static final int THREE = 3;
kevin cline
sumber
3
+1 @kevin cline: Saya setuju dengan poin Anda tentang singkatnya perburuan bug. Manfaat tambahan yang saya lihat untuk menggunakan konstanta bernama, terutama ketika debugging, adalah bahwa jika ditemukan bahwa konstanta didefinisikan secara salah, Anda hanya perlu mengubah satu bagian kode daripada mencari melalui seluruh proyek untuk semua kejadian dari implementasi yang salah diterapkan nilai.
oosterwal
40
Atau bahkan lebih buruk:publid final int FOUR = 3;
gablin
3
Oh sayang, Anda pasti pernah bekerja dengan pria yang sama dengan saya pernah bekerja sama.
cepat
2
@ Gablin: Agar adil, cukup berguna bagi pub untuk memiliki tutup.
Alan Pearce
10
Saya telah melihat ini: public static int THREE = 3;... note - no final!
Stephen C
29

Saya akan menjaga aturan tidak pernah memiliki angka ajaib.

Sementara

seconds = num_days * 24 * 60 * 60

Sangat mudah dibaca sebagian besar waktu, setelah dikodekan selama 10 jam sehari selama tiga atau empat minggu dalam mode crunch

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

jauh lebih mudah dibaca.

Saran FrustratedWithFormsDesigner lebih baik:

seconds = num_days * DAYS_TO_SECOND_FACTOR

atau bahkan lebih baik

seconds = CONVERT_DAYS_TO_SECONDS(num_days)

Hal-hal berhenti menjadi jelas ketika Anda sangat lelah. Kode bertahan .

Vitor Py
sumber
13
Masuk ke mode crunch seperti yang Anda gambarkan adalah antipattern kontraproduktif yang harus dihindari. Programmer mencapai puncak produktivitas berkelanjutan sekitar 35-40 jam / minggu.
btilly
4
@ tapi saya sepenuh hati setuju dengan Anda. Tetapi itu terjadi, seringkali karena faktor eksternal.
Vitor Py
3
Saya secara umum mendefinisikan konstanta untuk detik, menit, hari dan jam. Jika tidak ada yang lain '30 * MENIT 'benar-benar mudah dibaca dan saya tahu ini waktu tanpa harus memikirkannya.
Zachary K
9
@ btilly: Puncaknya adalah 35-40 jam atau tingkat alchohol darah antara 0,129% dan 0,138%. Saya membacanya di XKCD , jadi itu pasti benar!
oosterwal
1
Jika saya melihat konstanta seperti HOURS_PER_DAY, saya akan menghapusnya dan kemudian secara terbuka mempermalukan Anda di depan rekan-rekan Anda. Ok, mungkin saya akan melupakan penghinaan publik, tapi saya mungkin akan menghapusnya.
Ed S.
8

Waktu untuk mengatakan tidak hampir selalu. Saat-saat di mana saya merasa lebih mudah untuk hanya menggunakan nomor kode keras di tempat-tempat seperti tata letak UI - menciptakan konstanta untuk penentuan posisi setiap kontrol pada formulir menjadi sangat cubmersone dan melelahkan dan jika kode itu biasanya ditangani oleh perancang UI itu tidak masalah. ... kecuali UI ditata secara dinamis, atau menggunakan posisi relatif untuk beberapa jangkar atau ditulis dengan tangan. Dalam hal ini, saya akan mengatakan lebih baik untuk mendefinisikan beberapa konstanta yang bermakna untuk tata letak. Dan jika Anda memerlukan faktor fudge di sini atau di sana untuk menyelaraskan / memposisikan sesuatu "tepat", itu juga harus didefinisikan.

Tetapi dalam contoh Anda, saya pikir mengganti 24 * 60 * 60dengan DAYS_TO_SECONDS_FACTORlebih baik.


Saya mengakui bahwa nilai-nilai hard-coded juga OK ketika konteks dan penggunaannya benar-benar jelas. Namun, ini adalah panggilan penilaian ...

Contoh:

Seperti yang ditunjukkan @rmx, menggunakan 0 atau 1 untuk memeriksa apakah daftar kosong, atau mungkin dalam batas loop adalah contoh kasus di mana tujuan konstanta sangat jelas.

FrustratedWithFormsDesigner
sumber
2
Biasanya OK untuk digunakan 0atau 1kurasa. if(someList.Count != 0) ...lebih baik daripada if(someList.Count != MinListCount) .... Tidak selalu, tetapi secara umum.
Tidak ada yang
2
@Dima: VS bentuk desainer menangani semua itu. Jika ingin membuat konstanta, tidak masalah dengan saya. Tapi saya tidak akan masuk ke kode yang dihasilkan dan mengganti semua nilai yang dikodekan dengan konstanta.
FrustratedWithFormsDesigner
4
Jangan bingung kode yang dimaksudkan untuk dibuat dan ditangani oleh alat dengan kode yang ditulis untuk konsumsi manusia.
biziclop
1
@FrustratedWithFormsDesigner seperti @biziclop tunjukkan, kode yang dihasilkan adalah hewan yang sama sekali berbeda. Konstanta yang dinamai mutlak harus digunakan dalam kode yang dibaca dan dimodifikasi oleh orang-orang. Kode yang dihasilkan, setidaknya dalam kasus yang ideal, tidak boleh dimodifikasi sama sekali.
Dima
2
@FrustratedWithFormsDesigner: Apa yang terjadi ketika Anda memiliki nilai terkenal yang dikodekan dalam lusinan file dalam program Anda yang tiba-tiba perlu diubah? Sebagai contoh, Anda mengkode-keras nilai yang mewakili jumlah clock-ticks per microsecond untuk prosesor yang disematkan kemudian disuruh mem-porting perangkat lunak Anda ke desain di mana ada jumlah tick-clock yang berbeda per microsecond. Jika nilai Anda adalah sesuatu yang umum, seperti 8, melakukan pencarian / ganti pada lusinan file dapat berakhir menyebabkan lebih banyak masalah.
oosterwal
8

Berhentilah saat Anda tidak dapat menjabarkan arti atau tujuan nomor tersebut.

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

jauh lebih mudah dibaca daripada hanya menggunakan angka. (Meskipun bisa dibuat lebih mudah dibaca dengan memiliki SECONDS_PER_DAYkonstanta tunggal , tetapi ini adalah masalah yang sepenuhnya terpisah.)

Asumsikan bahwa pengembang yang melihat kode dapat melihat apa yang dilakukannya. Tetapi jangan berasumsi bahwa mereka juga tahu mengapa. Jika konstanta Anda membantu memahami alasannya, lakukanlah. Jika tidak, jangan.

Jika Anda berakhir dengan konstanta terlalu banyak, seperti yang disarankan oleh satu jawaban, pertimbangkan untuk menggunakan file konfigurasi eksternal, karena memiliki puluhan konstanta dalam file tidak benar-benar meningkatkan keterbacaan.

biziclop
sumber
7

Saya mungkin akan mengatakan "tidak" untuk hal-hal seperti:

#define HTML_END_TAG "</html>"

Dan pasti akan mengatakan "tidak" untuk:

#define QUADRATIC_DISCRIMINANT_COEF 4
#define QUADRATIC_DENOMINATOR_COEF  2
dan04
sumber
7

Salah satu contoh terbaik yang saya temukan untuk mempromosikan penggunaan konstanta untuk hal yang jelas seperti HOURS_PER_DAY:

Kami menghitung berapa lama barang-barang itu duduk dalam antrian pekerjaan seseorang. Persyaratannya didefinisikan secara longgar dan pemrogramnya berkode keras 24di sejumlah tempat. Akhirnya kami menyadari bahwa itu tidak adil untuk menghukum pengguna karena duduk pada masalah selama 24 jam ketika benar-benar hanya bekerja selama 8 jam sehari. Ketika tugas datang untuk memperbaikinya DAN melihat laporan lain yang mungkin memiliki masalah yang sama, cukup sulit untuk melakukan grep / mencari kode selama 24 akan lebih mudah untuk melakukan grep / mencariHOURS_PER_DAY

bkulyk
sumber
oh noes, jadi jam per hari bervariasi tergantung apakah Anda merujuk jam kerja per hari (saya bekerja 7,5 BTW) atau jam dalam sehari. Untuk mengubah arti konstanta seperti itu, Anda ingin mengganti namanya menjadi sesuatu yang lain. Poin Anda tentang pencarian menjadi mudah adalah valid.
gbjbaanb
2
Tampaknya dalam hal ini HOURS_PER_DAY juga bukan konstanta yang diinginkan. Tetapi bisa mencarinya dengan nama adalah manfaat besar, bahkan jika (atau terutama jika) Anda perlu mengubahnya ke hal lain di banyak tempat.
David K
4

Saya pikir selama jumlahnya benar-benar konstan dan tidak memiliki kemungkinan untuk berubah, itu bisa diterima. Jadi dalam kasus Anda, seconds = num_days * 24 * 60 * 60tidak masalah (dengan asumsi tentu saja Anda tidak melakukan sesuatu yang konyol seperti melakukan perhitungan semacam ini dalam satu lingkaran) dan bisa dibilang lebih baik untuk dibaca daripada seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE.

Itu ketika Anda melakukan hal-hal seperti ini yang buruk:

lineOffset += 24; // 24 lines to a page

Bahkan jika Anda tidak dapat lagi memasukkan baris pada halaman atau bahkan jika Anda tidak memiliki niat untuk mengubahnya, gunakan variabel konstan sebagai gantinya, karena suatu hari itu akan kembali menghantui Anda. Pada akhirnya, intinya adalah keterbacaan, tidak menyimpan 2 siklus perhitungan pada CPU. Ini bukan lagi tahun 1978 ketika byte berharga diperas untuk semua nilainya.

Neil
sumber
2
Apakah Anda merasa dapat diterima untuk melakukan hard-code pada nilai yang tidak berubah 86400 alih-alih menggunakan konstan yang disebut SECONDS_PER_DAY? Bagaimana Anda memverifikasi bahwa semua kemunculan nilai itu benar dan tidak melewatkan 0 atau menukar 6 iklan 4, misalnya?
oosterwal
Lalu mengapa tidak: detik = num_days * 86400? Itu juga tidak akan berubah.
JeffO
2
Saya telah melakukan hal SECONDS_PER_DAY dua arah - menggunakan nama dan menggunakan nomor. Ketika Anda kembali ke kode 2 tahun kemudian, nomor yang disebutkan SELALU lebih masuk akal.
cepat_now
2
detik = num_days * 86400 tidak jelas bagi saya. Itulah yang akhirnya diperhitungkan. Jika saya melihat "detik = num_days * 24 * 60 * 60", selain dari fakta bahwa nama variabel memberikan makna dengan cukup baik dalam contoh ini, saya akan segera bertanya pada diri sendiri mengapa saya memisahkan mereka dan artinya menjadi jelas karena saya meninggalkan mereka sebagai angka (maka mereka konstan), bukan variabel yang memerlukan penyelidikan lebih lanjut untuk memahami nilai-nilai mereka dan jika mereka konstan.
Neil
1
Apa yang orang sering tidak sadari: Jika Anda mengubah nilai lineOffset dari 24 menjadi 25, Anda harus melalui semua kode Anda untuk melihat di mana 24 digunakan dan jika perlu diubah, dan kemudian semua perhitungan hari ke jam mengalikan dengan 24 benar-benar masuk ke jalan Anda.
gnasher729
3
seconds = num_days * 24 * 60 * 60

Baik-baik saja. Ini bukan angka ajaib karena mereka tidak akan pernah berubah.

Angka apa pun yang dapat secara wajar berubah atau tidak memiliki makna yang jelas harus dimasukkan ke dalam variabel. Yang berarti hampir semuanya.

Carra
sumber
2
Apakah seconds = num_days * 86400masih bisa diterima? Jika nilai seperti itu digunakan beberapa kali dalam banyak file berbeda, bagaimana Anda memverifikasi bahwa seseorang tidak sengaja mengetik seconds = num_days * 84600di satu atau dua tempat?
oosterwal
1
Menulis 86400 sangat berbeda dari menulis 24 * 60 * 60.
Carra
4
Tentu saja itu akan berubah. Tidak setiap hari memiliki 86.400 detik di dalamnya. Pertimbangkan, misalnya, waktu musim panas. Setahun sekali, beberapa tempat hanya memiliki 23 jam dalam sehari, dan hari lain mereka akan memiliki 25. poof nomor Anda rusak.
Dave DeLong
1
@Dave, titik bagus. Ada detik kabisat - en.wikipedia.org/wiki/Leap_second
Titik adil. Menambahkan fungsi akan menjadi perlindungan jika Anda perlu menangkap pengecualian itu.
Carra
3

Saya akan menghindari membuat konstanta (nilai ajaib) untuk mengkonversi nilai dari satu unit ke unit lainnya. Dalam kasus konversi saya lebih suka nama metode berbicara. Dalam contoh ini, ini akan menjadi misalnya DayToSeconds(num_days)internal metode tidak perlu nilai-nilai ajaib karena, arti "24" dan "60" jelas.

Dalam hal ini saya tidak akan pernah menggunakan detik / menit / jam. Saya hanya akan menggunakan TimeSpan / DateTime.

KayS
sumber
1

Gunakan konteks sebagai parameter untuk memutuskan

Misalnya, Anda memiliki fungsi yang disebut "calculSecondsBetween: aDay dan: anotherDay", Anda tidak perlu melakukan banyak penjabaran tentang apa yang dilakukan angka-angka itu, karena nama fungsi cukup representatif.

Dan pertanyaan lain adalah, manakah kemungkinan untuk menghitungnya dengan cara yang berbeda? Kadang-kadang ada banyak cara untuk melakukan hal yang sama, jadi untuk memandu programmer masa depan dan menunjukkan kepada mereka metode apa yang Anda gunakan, mendefinisikan konstanta dapat membantu mengetahuinya.

guiman
sumber