Clean Code - Haruskah saya mengubah literal 1 ke konstanta?

14

Untuk menghindari angka ajaib, kita sering mendengar bahwa kita harus memberikan nama yang bermakna secara literal. Seperti:

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

Saya punya metode dummy seperti ini:

Jelaskan: Saya kira saya memiliki daftar orang untuk dilayani dan secara default, kita menghabiskan $ 5 untuk membeli makanan saja, tetapi ketika kita memiliki lebih dari satu orang, kita perlu membeli air dan makanan, kita harus menghabiskan lebih banyak uang, mungkin $ 6. Saya akan mengubah kode saya, tolong fokus pada literal 1 , pertanyaan saya tentang itu.

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

Ketika saya meminta teman saya untuk meninjau kode saya, yang satu mengatakan memberi nama untuk nilai 1 akan menghasilkan kode yang lebih bersih, dan yang lain mengatakan kita tidak perlu nama konstan di sini karena nilainya bermakna dengan sendirinya.

Jadi, pertanyaan saya adalah Haruskah saya memberi nama untuk nilai literal 1? Kapan nilai merupakan angka ajaib dan kapan bukan? Bagaimana saya membedakan konteks untuk memilih solusi terbaik?

Mendongkrak
sumber
Dari mana datangnya personsdan apa yang digambarkannya? Kode Anda tidak memiliki komentar apa pun sehingga sulit untuk menebak apa yang dilakukannya.
Hubert Grzeskowiak
7
Tidak ada yang lebih jelas dari 1. Saya akan mengubah "ukuran" menjadi "menghitung". Dan moneyService memang bodoh, harus bisa memutuskan berapa banyak uang yang akan dikembalikan tergantung pada koleksi orang. Jadi saya akan mengoper orang untuk mendapatkan Uang dan membiarkannya menyelesaikan setiap kasus luar biasa.
Martin Maat
4
Anda juga bisa mengekstraksi ekspresi logis dari klausa if Anda ke metode baru. Mungkin menyebutnya IsSinglePerson (). Dengan begitu Anda mengekstrak sedikit lebih dari satu variabel itu saja tetapi juga membuat klausa if sedikit lebih mudah dibaca ...
selmaohneh
2
Mungkin logika ini akan lebih cocok di moneyService.getMoney ()? Apakah akan ada saat di mana Anda perlu menelepon getMoney dalam kasus 1 orang? Tetapi saya akan setuju dengan sentimen umum bahwa saya jelas. Angka ajaib adalah angka di mana Anda harus menggaruk-garuk kepala Anda bertanya bagaimana programmer tiba di nomor itu .. yaituif(getErrorCode().equals(4095)) ...
Neil

Jawaban:

23

Dalam contoh itu, 1 sangat bermakna.

Namun, bagaimana jika orang. Ukuran () adalah nol? Tampaknya aneh yang persons.getMoney()berfungsi untuk 0 dan 2 tetapi tidak untuk 1.

pengguna949300
sumber
Saya setuju bahwa saya bermakna. Terima kasih, omong-omong, saya memperbarui pertanyaan saya. Anda bisa melihatnya lagi untuk mendapatkan ide saya.
Jack
3
Sebuah frase yang saya dengar beberapa kali selama bertahun-tahun adalah bahwa angka ajaib adalah konstanta yang tidak disebutkan namanya selain 0 dan 1. Meskipun saya berpotensi memikirkan contoh di mana angka-angka ini harus dinamai, itu aturan yang cukup sederhana untuk mengikuti 99% dari waktu.
Baldrickk
@Baldrickk Terkadang, literal angka lainnya juga tidak boleh disembunyikan di balik nama.
Deduplicator
@Deduplicator, ada contoh yang muncul di benak Anda?
Baldrickk
@Baldrickk Lihat amon .
Deduplicator
18

Mengapa sepotong kode mengandung nilai literal tertentu?

  • Apakah nilai ini memiliki arti khusus dalam domain masalah ?
  • Atau apakah nilai ini hanya detail implementasi , di mana nilai itu merupakan konsekuensi langsung dari kode di sekitarnya?

Jika nilai literal memiliki makna yang tidak jelas dari konteks, maka ya, memberikan nilai itu nama melalui konstanta atau variabel sangat membantu. Kemudian, ketika konteks asli dilupakan, kode dengan nama variabel yang bermakna akan lebih dapat dipertahankan. Ingat, audiens untuk kode Anda bukan terutama kompiler (kompiler dengan senang hati akan bekerja dengan kode yang mengerikan), tetapi pemelihara kode yang akan datang - yang akan menghargai jika kode tersebut agak menjelaskannya sendiri.

  • Dalam contoh pertama Anda, makna literal seperti 34, 4, 5tidak jelas dari konteks. Sebagai gantinya, beberapa nilai ini memiliki makna khusus di domain masalah Anda. Karena itu bagus untuk memberi mereka nama.

  • Dalam contoh kedua Anda, makna literal 1sangat jelas dari konteksnya. Memperkenalkan nama tidak membantu.

Bahkan, memperkenalkan nama untuk nilai yang jelas juga bisa buruk karena menyembunyikan nilai sebenarnya.

  • Ini dapat mengaburkan bug jika nilai yang disebutkan diubah atau salah, terutama jika variabel yang sama digunakan kembali dalam potongan kode yang tidak terkait.

  • Sepotong kode mungkin juga berfungsi dengan baik untuk nilai tertentu, tetapi mungkin salah dalam kasus umum. Dengan memperkenalkan abstraksi yang tidak perlu, kodenya tidak lagi benar.

Tidak ada batasan ukuran pada literal "jelas" karena ini sepenuhnya tergantung pada konteks. Misalnya literal 1024mungkin benar-benar jelas dalam konteks perhitungan ukuran file, atau literal 31dalam konteks fungsi hash, atau literal padding: 0.5emdalam konteks CSS stylesheet.

amon
sumber
8

Ada beberapa masalah dengan kode ini, yang, dapat, disingkat menjadi seperti ini:

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. Tidak jelas mengapa satu orang adalah kasus khusus. Saya kira ada aturan bisnis tertentu yang mengatakan bahwa mendapatkan uang dari satu orang secara radikal berbeda dari mendapatkan uang dari beberapa orang. Namun, saya harus pergi dan melihat ke dalam keduanya getMoneyIfHasOnePersondan getMoney, berharap untuk memahami mengapa ada kasus yang berbeda.

  2. Nama getMoneyIfHasOnePersonitu kelihatannya tidak benar. Dari namanya, saya akan mengharapkan metode untuk memeriksa apakah ada satu orang dan, jika ini masalahnya, dapatkan uang darinya; jika tidak, jangan lakukan apa pun. Dari kode Anda, ini bukan yang terjadi (atau Anda melakukan kondisinya dua kali).

  3. Apakah ada alasan untuk mengembalikan List<Money>koleksi daripada koleksi?

Kembali ke pertanyaan Anda, karena tidak jelas mengapa ada perlakuan khusus untuk satu orang, digit yang satu harus diganti dengan konstanta, kecuali ada cara lain untuk membuat aturan eksplisit. Di sini, satu tidak jauh berbeda dari angka ajaib lainnya. Anda dapat memiliki aturan bisnis yang mengatakan bahwa perlakuan khusus berlaku untuk satu, dua atau tiga orang, atau hanya untuk lebih dari dua belas orang.

Bagaimana saya membedakan konteks untuk memilih solusi terbaik?

Anda melakukan apa pun yang membuat kode Anda lebih eksplisit.

Contoh 1

Bayangkan potongan kode berikut:

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

Apakah nol di sini nilai magis? Kode agak jelas: jika tidak ada elemen dalam urutan, jangan diproses dan mengembalikan nilai khusus. Tetapi kode ini juga dapat ditulis ulang seperti ini:

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

Di sini, tidak ada lagi yang konstan, dan kodenya bahkan lebih jelas.

Contoh 2

Ambil satu lagi kode:

const result = Math.round(input * 1000) / 1000;

Tidak perlu terlalu banyak waktu untuk memahami apa yang dilakukannya dalam bahasa seperti JavaScript yang tidak memiliki round(value, precision)kelebihan.

Sekarang, jika Anda ingin memperkenalkan sebuah konstanta, bagaimana ini akan disebut? Istilah terdekat yang bisa Anda dapatkan adalah Precision. Begitu:

const precision = 1000;
const result = Math.round(input * precision) / precision;

Apakah ini meningkatkan keterbacaan? Mungkin. Di sini, nilai konstanta agak terbatas, dan Anda mungkin bertanya pada diri sendiri apakah Anda benar-benar perlu melakukan refactoring. Yang menyenangkan di sini adalah bahwa sekarang, ketepatannya dinyatakan hanya sekali, jadi jika itu berubah, Anda tidak mengambil risiko membuat kesalahan seperti:

const result = Math.round(input * 100) / 1000;

mengubah nilai di satu lokasi, dan lupa melakukannya di yang lain.

Contoh 3

Dari contoh-contoh itu, Anda mungkin memiliki kesan bahwa angka harus diganti dengan konstanta dalam setiap kasus . Ini tidak benar. Dalam beberapa situasi, memiliki konstanta tidak mengarah pada peningkatan kode.

Ambil bagian kode berikut:

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

Jika Anda mencoba mengganti nol dengan variabel, kesulitannya adalah menemukan nama yang bermakna. Bagaimana Anda menyebutkannya? ZeroPosition? Base? Default? Memperkenalkan konstanta di sini tidak akan meningkatkan kode dengan cara apa pun. Itu akan membuatnya sedikit lebih lama, dan hanya itu.

Namun kasus seperti itu jarang terjadi. Jadi, setiap kali Anda menemukan angka dalam kode, berusahalah untuk menemukan bagaimana kode tersebut dapat di-refactored. Tanyakan pada diri Anda apakah ada bisnis yang berarti nomor tersebut. Jika ya, konstanta adalah wajib. Jika tidak, bagaimana Anda menyebutkan nomornya? Jika Anda menemukan nama yang bermakna, itu bagus. Jika tidak, kemungkinan Anda menemukan kasus di mana konstanta tidak diperlukan.

Arseni Mourzenko
sumber
Saya akan menambahkan 3a: Jangan gunakan kata uang dalam nama variabel, gunakan jumlah, saldo atau sejenisnya. Kumpulan uang tidak masuk akal, koleksi jumlah atau saldo tidak masuk akal. (Oke saya punya koleksi kecil uang asing (atau lebih tepatnya koin) tapi itu masalah non-pemrograman yang berbeda).
Bent
3

Anda bisa membuat fungsi yang mengambil parameter tunggal dan mengembalikan kali empat dibagi dengan lima yang menawarkan alias bersih ke apa yang dilakukannya saat masih menggunakan contoh pertama.

Saya hanya menawarkan strategi saya sendiri yang biasa tapi mungkin saya akan belajar sesuatu juga.

Apa yang saya pikirkan adalah.

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

Maaf jika saya salah tetapi saya hanya pengembang javascript. Saya tidak yakin komposisi apa yang saya benarkan ini, saya kira tidak semua harus dalam array atau daftar tetapi. Panjang akan membuat banyak akal. Dan * 4 akan membuat konstanta yang baik karena asalnya tidak jelas.

etisdew
sumber
5
Tetapi apa arti nilai-nilai 4 dan 5 itu? Jika salah satu dari mereka perlu diubah, apakah Anda dapat menemukan semua tempat di mana nomor tersebut digunakan dalam konteks yang sama dan memperbarui lokasi tersebut sesuai? Itulah inti dari pertanyaan awal.
Bart van Ingen Schenau
Saya pikir contoh pertama cukup dang cukup jelas sehingga mungkin bukan yang bisa saya jawab. Operasi tidak boleh berubah di masa depan, ketika Anda perlu saya akan menulis yang baru untuk mencapai apa yang dibutuhkan. Komentar adalah apa yang menurut saya paling menguntungkan tujuan ini. Itu satu panggilan telepon untuk mengurangi dalam bahasa saya. Seberapa pentingkah membuat hal itu benar-benar dapat dimengerti oleh orang awam?
etisdew
@ BartartIngenSchenau Seluruh fungsi tidak benar namanya. Saya lebih suka nama fungsi yang lebih baik, komentar dengan spesifikasi apa fungsi seharusnya dikembalikan, dan komentar mengapa * 4/5 mencapai itu. Nama konstan tidak terlalu dibutuhkan.
gnasher729
@ gnasher729: Jika konstanta muncul tepat satu kali dalam kode sumber dari sebuah fungsi yang didaftarkan dengan baik, maka mungkin tidak diperlukan untuk menggunakan konstanta bernama. Segera setelah konstanta muncul beberapa kali dengan makna yang sama, memberikan konstanta nama memastikan bahwa Anda tidak perlu mencari tahu apakah semua contoh literal 42 memiliki arti yang sama atau tidak.
Bart van Ingen Schenau
Pada intinya, jika Anda berharap Anda mungkin perlu mengubah nilainya ya. Saya akan mengubahnya menjadi const untuk mewakili variabel, namun. Saya tidak berpikir itu yang terjadi dan ini seharusnya hanya menjadi variabel menengah yang berada dalam lingkup di atasnya. Saya tidak ingin mengabaikan dan mengatakan menjadikan segala sesuatu sebagai parameter untuk suatu fungsi, tetapi jika itu dapat berubah tetapi jarang saya akan menjadikannya sebagai parameter internal untuk fungsi atau objek / lingkup lingkup tempat Anda saat ini bekerja untuk membuat perubahan itu meninggalkan ruang lingkup global saja.
etisdew
2

Nomor 1 itu, mungkinkah itu nomor yang berbeda? Mungkinkah 2, atau 3, atau adakah alasan logis mengapa harus 1? Jika harus 1, maka menggunakan 1 baik-baik saja. Jika tidak, Anda dapat menentukan konstanta. Jangan sebut SATU konstan itu. (Saya telah melihat itu dilakukan).

60 detik dalam satu menit - apakah Anda memerlukan konstanta? Yah, itu adalah 60 detik, tidak 50 atau 70. Dan semua orang tahu itu. Sehingga bisa tetap nomor.

60 item dicetak per halaman - angka itu bisa dengan mudah menjadi 59 atau 55 atau 70. Sebenarnya, jika Anda mengubah ukuran font, itu mungkin menjadi 55 atau 70. Jadi di sini konstanta yang bermakna lebih ditanyakan.

Ini juga masalah seberapa jelas maknanya. Jika Anda menulis "menit = detik / 60", itu jelas. Jika Anda menulis "x = y / 60", itu tidak jelas. Pasti ada beberapa nama yang bermakna di suatu tempat.

Ada satu aturan absolut: Tidak ada aturan absolut. Dengan latihan Anda akan mengetahui kapan harus menggunakan angka, dan kapan harus menggunakan konstanta bernama. Jangan lakukan itu karena sebuah buku mengatakannya - sampai Anda mengerti mengapa dikatakan demikian.

gnasher729
sumber
"60 detik dalam satu menit ... Dan semua orang tahu itu. Sehingga bisa tetap nomor." - Bukan argumen yang valid untukku. Bagaimana jika saya memiliki 200 tempat dalam kode saya di mana "60" digunakan? Bagaimana cara menemukan tempat-tempat di mana ia digunakan sebagai detik ke menit vs penggunaan lain yang mungkin sama "alami" itu? - Namun dalam praktiknya, saya juga berani menggunakannya minutes*60, kadang-kadang bahkan hours*3600ketika saya membutuhkannya tanpa mendeklarasikan konstanta tambahan. Selama berhari-hari saya mungkin menulis d*24*3600atau d*24*60*60karena 86400dekat dengan tepi di mana seseorang tidak akan mengenali nomor ajaib itu sekilas.
JimmyB
@ JimmyB Jika Anda menggunakan "60" di 200 tempat dalam kode Anda, sangat mungkin solusinya adalah refactoring keluar fungsi daripada memperkenalkan konstanta.
klutt
0

Saya telah melihat cukup banyak kode seperti dalam OP (dimodifikasi) dalam pengambilan DB. Kueri mengembalikan daftar, tetapi aturan bisnis mengatakan hanya ada satu elemen. Dan kemudian, tentu saja, sesuatu berubah untuk 'hanya satu kasus ini' ke daftar dengan lebih dari satu item. (Ya, saya mengatakan cukup banyak kali. Ini hampir seperti mereka ... nm)

Jadi daripada membuat konstanta, saya akan (dalam metode kode bersih) membuat metode untuk memberi nama atau memperjelas apa yang dimaksudkan untuk dideteksi oleh kondisional (dan merangkum bagaimana ia mendeteksi itu):

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}
Kristian H
sumber
Jauh lebih disukai untuk menyatukan penanganan. Daftar n elemen dapat diperlakukan secara seragam apa pun n itu.
Deduplicator
Saya telah melihat kalau bukan, di mana kasing tunggal, pada kenyataannya, nilai (marginal) yang berbeda dari yang seharusnya jika pengguna yang sama telah menjadi bagian dari daftar ukuran-n. Dalam OO yang dilakukan dengan baik, ini mungkin bukan masalah, tetapi, ketika berhadapan dengan sistem legacy, implementasi OO yang baik tidak selalu layak. Yang terbaik yang kami dapatkan adalah fasad OO yang masuk akal pada logika DAO.
Kristian H