Apa yang salah dengan string sihir?

164

Sebagai pengembang perangkat lunak yang berpengalaman, saya telah belajar untuk menghindari string sihir.

Masalah saya adalah sudah lama sekali saya tidak menggunakannya, saya lupa sebagian besar alasannya. Akibatnya, saya kesulitan menjelaskan mengapa itu masalah bagi kolega saya yang kurang berpengalaman.

Apa alasan objektif yang ada untuk menghindarinya? Masalah apa yang mereka sebabkan?

Kramii
sumber
38
Apa itu string ajaib? Hal yang sama dengan angka ajaib ?
Laiv
14
@Laiv: Mereka mirip dengan angka ajaib, ya. Saya suka definisi di deviq.com/magic-strings : "String ajaib adalah nilai string yang ditentukan langsung dalam kode aplikasi yang berdampak pada perilaku aplikasi.". (Definisi di en.wikipedia.org/wiki/Magic_string sama sekali tidak ada dalam pikiran saya)
Kramii
17
ini lucu saya telah belajar untuk membenci ... kemudian argumen apa yang dapat saya gunakan untuk membujuk junior saya ... Kisah tidak pernah berakhir :-). Saya tidak akan mencoba untuk "meyakinkan" saya lebih baik membiarkan mereka belajar sendiri. Tidak ada yang lebih dari pelajaran / ide yang dicapai oleh pengalaman Anda sendiri. Apa yang Anda coba lakukan adalah indoktrinasi . Jangan lakukan itu kecuali Anda menginginkan tim Lemmings.
Laiv
15
@Laiv: Saya ingin membiarkan orang belajar dari pengalaman mereka sendiri, tetapi sayangnya itu bukan pilihan bagi saya. Saya bekerja untuk rumah sakit yang didanai publik di mana bug halus dapat membahayakan perawatan pasien, dan di mana kita tidak mampu membayar biaya perawatan yang dapat dihindari.
Kramii
6
@ Davidvid, itulah yang dia lakukan dengan menanyakan pertanyaan ini.
user56834

Jawaban:

211
  1. Dalam bahasa yang mengkompilasi, nilai string sihir tidak dicentang pada waktu kompilasi . Jika string harus cocok dengan pola tertentu, Anda harus menjalankan program untuk menjamin itu cocok dengan pola itu. Jika Anda menggunakan sesuatu seperti enum, nilainya paling tidak valid pada waktu kompilasi, bahkan jika itu mungkin nilai yang salah.

  2. Jika string ajaib sedang ditulis di banyak tempat Anda harus mengubah semuanya tanpa pengaman (seperti kesalahan waktu kompilasi). Ini dapat diatasi dengan hanya mendeklarasikannya di satu tempat dan menggunakan kembali variabelnya.

  3. Kesalahan ketik dapat menjadi bug serius. Jika Anda memiliki fungsi:

    func(string foo) {
        if (foo == "bar") {
            // do something
        }
    }
    

    dan seseorang secara tidak sengaja mengetik:

    func("barr");
    

    Ini lebih buruk jika stringnya lebih jarang atau lebih kompleks, terutama jika Anda memiliki programmer yang tidak terbiasa dengan bahasa asli proyek.

  4. Senar ajaib jarang mendokumentasikan diri. Jika Anda melihat satu string, itu tidak memberi tahu Anda apa lagi yang bisa / seharusnya menjadi string tersebut. Anda mungkin harus melihat ke implementasi untuk memastikan Anda telah memilih string yang tepat.

    Implementasi semacam itu bocor , membutuhkan dokumentasi eksternal atau akses ke kode untuk memahami apa yang harus ditulis, terutama karena itu harus sempurna karakter (seperti pada poin 3).

  5. Pendek fungsi "temukan string" dalam IDE, ada sejumlah kecil alat yang mendukung pola.

  6. Anda mungkin secara kebetulan menggunakan string sihir yang sama di dua tempat, padahal sebenarnya itu adalah hal yang berbeda, jadi jika Anda melakukan Find & Replace, dan mengubah keduanya, salah satunya dapat rusak sementara yang lain bekerja.

Erdrik Ironrose
sumber
34
Mengenai argumen pertama: TypeScript adalah bahasa yang dikompilasi yang dapat mengetikkan centang string literal. Ini juga mematahkan argumen dua hingga empat. Oleh karena itu, bukan string itu sendiri masalahnya, tetapi menggunakan tipe yang memungkinkan terlalu banyak nilai. Alasan yang sama dapat diterapkan untuk menggunakan bilangan bulat ajaib untuk enumerasi.
Yogu
11
Karena saya tidak punya pengalaman dengan TypeScript saya akan tunduk pada penilaian Anda di sana. Apa yang akan saya katakan adalah bahwa string yang tidak diperiksa (seperti halnya semua bahasa yang saya gunakan) adalah masalahnya.
Erdrik Ironrose
23
@Yogu Typescript tidak akan mengubah nama semua string Anda untuk Anda jika Anda mengubah tipe literal string statis yang Anda harapkan. Anda akan mendapatkan kesalahan waktu kompilasi untuk membantu Anda menemukan semuanya, tapi itu hanya peningkatan parsial pada 2. Tidak mengatakan itu adalah sesuatu yang benar-benar luar biasa (karena memang begitu, dan saya suka fitur itu), tetapi jelas tidak langsung menghilangkan keunggulan enum. Dalam proyek kami, kapan harus menggunakan enum dan kapan tidak akan tetap menjadi semacam pertanyaan gaya terbuka yang tidak kami yakini; kedua pendekatan memiliki gangguan dan kelebihan.
KRyan
30
Satu hal besar yang saya lihat bukan untuk string sebanyak angka, tetapi bisa terjadi dengan string, adalah ketika Anda memiliki dua nilai ajaib dengan nilai yang sama. Kemudian salah satu dari mereka berubah. Sekarang Anda akan melalui kode mengubah nilai lama ke nilai baru, yang berfungsi sendiri, tetapi Anda juga melakukan pekerjaan EXTRA untuk memastikan Anda tidak mengubah nilai yang salah. Dengan variabel konstan, Anda tidak hanya harus melewatinya secara manual, tetapi Anda tidak khawatir bahwa Anda telah mengubah hal yang salah.
corsiKa
35
@Yogu Saya lebih jauh akan berpendapat bahwa jika nilai string string sedang diperiksa pada waktu kompilasi, maka tidak lagi menjadi string ajaib . Pada saat itu, hanya nilai const / enum normal yang kebetulan ditulis dengan cara yang lucu. Mengingat perspektif itu, saya sebenarnya akan berpendapat bahwa komentar Anda benar-benar mendukung poin Erdrik, daripada membantahnya.
GrandOpener
89

Puncak dari apa yang dijawab oleh jawaban-jawaban lain, bukanlah bahwa "nilai-nilai sihir" itu buruk, tetapi seharusnya:

  1. didefinisikan sebagai konstanta;
  2. didefinisikan hanya satu kali dalam seluruh domain penggunaannya (jika memungkinkan secara arsitektur);
  3. didefinisikan bersama jika mereka membentuk seperangkat konstanta yang entah bagaimana terkait;
  4. didefinisikan pada tingkat umum yang sesuai dalam aplikasi di mana mereka digunakan; dan
  5. didefinisikan sedemikian rupa untuk membatasi penggunaannya dalam konteks yang tidak sesuai (misalnya menerima mengetik jenis).

Apa yang biasanya membedakan "konstanta" yang dapat diterima dari "nilai magis" adalah beberapa pelanggaran terhadap satu atau lebih aturan ini.

Digunakan dengan baik, konstanta hanya memungkinkan kita untuk mengekspresikan aksioma tertentu dari kode kita.

Yang membawa saya ke poin terakhir, bahwa penggunaan konstanta yang berlebihan (dan karena itu sejumlah asumsi atau kendala yang dinyatakan dalam nilai), bahkan jika itu sesuai dengan kriteria di atas (tetapi terutama jika menyimpang dari mereka), dapat menyiratkan bahwa solusi yang dirancang tidak cukup umum atau terstruktur dengan baik (dan karena itu kita tidak benar-benar berbicara tentang pro dan kontra dari konstanta lagi, tetapi tentang pro dan kontra dari kode terstruktur dengan baik).

Bahasa tingkat tinggi memiliki konstruk untuk pola dalam bahasa tingkat rendah yang harus menggunakan konstanta. Pola yang sama juga dapat digunakan dalam bahasa tingkat yang lebih tinggi, tetapi seharusnya tidak demikian.

Tapi itu mungkin penilaian ahli berdasarkan kesan dari semua keadaan dan seperti apa solusi seharusnya, dan bagaimana tepatnya penilaian itu akan sangat bergantung pada konteksnya. Memang itu mungkin tidak dapat dibenarkan dalam hal prinsip umum apa pun, kecuali untuk menyatakan "Saya sudah cukup umur untuk melihat pekerjaan seperti ini, yang dengannya saya kenal, dilakukan dengan lebih baik"!

EDIT: setelah menerima satu suntingan, menolak yang lainnya, dan sekarang telah melakukan edit saya sendiri, mungkin sekarang saya menganggap gaya pemformatan dan tanda baca dari daftar aturan saya harus diselesaikan sekali dan untuk semua haha!

Steve
sumber
2
Saya suka jawaban ini. Setelah semua "struct" (dan setiap kata lain yang dilindungi undang-undang) adalah string ajaib untuk kompiler C. Ada cara pengkodean yang baik dan buruk untuk mereka.
Alfred Armstrong
6
Sebagai contoh, jika seseorang melihat "X: = 898755167 * Z" dalam kode Anda, mereka mungkin tidak akan tahu apa artinya, dan bahkan lebih kecil kemungkinannya untuk mengetahui bahwa itu salah. Tetapi jika mereka melihat "Speed_of_Light: constant Integer: = 299792456" seseorang akan mencarinya dan menyarankan nilai yang benar (dan mungkin bahkan tipe data yang lebih baik).
WGroleau
26
Beberapa orang benar-benar kehilangan maksud dan menulis COMMA = "," bukan SEPARATOR = ",". Yang pertama tidak membuat apa pun lebih jelas, sedangkan yang kedua menyatakan penggunaan yang dimaksudkan dan memungkinkan Anda untuk mengubah pemisah nanti di satu tempat.
marcus
1
@marcus, memang! Tentu saja ada kasus untuk menggunakan nilai-nilai literal sederhana di tempat - misalnya, jika suatu metode membagi nilai dengan dua, mungkin lebih jelas dan sederhana untuk hanya menulis value / 2, daripada value / VALUE_DIVISORdengan yang terakhir didefinisikan sebagai di 2tempat lain. Jika Anda bermaksud menggeneralisasi metode yang menangani CSV, Anda mungkin ingin pemisah diteruskan sebagai parameter, dan tidak didefinisikan sebagai konstanta sama sekali. Tapi itu semua masalah penilaian dalam konteks - contoh @ WGroleau SPEED_OF_LIGHTadalah sesuatu yang Anda ingin sebutkan secara eksplisit, tetapi tidak setiap literal membutuhkan ini.
Steve
4
Jawaban teratas lebih baik daripada jawaban ini jika perlu diyakinkan bahwa string sihir adalah "hal yang buruk." Jawaban ini lebih baik jika Anda tahu dan menerima bahwa mereka adalah "hal yang buruk" dan perlu menemukan cara terbaik untuk memenuhi kebutuhan yang mereka layani dengan cara yang dapat dipertahankan.
corsiKa
34
  • Mereka sulit dilacak.
  • Mengubah semua mungkin memerlukan perubahan beberapa file dalam beberapa proyek (sulit untuk mempertahankan).
  • Terkadang sulit untuk mengatakan apa tujuan mereka hanya dengan melihat nilainya.
  • Tidak digunakan kembali
jason
sumber
4
Apa yang dimaksud dengan "tidak menggunakan kembali"?
sampai
7
Alih-alih membuat satu variabel / konstan dll dan menggunakannya kembali di semua proyek / kode Anda, Anda membuat string baru di masing-masing yang menyebabkan duplikasi yang tidak perlu.
jason
Jadi poin 2 dan 4 sama?
Thomas
4
@ThomasMoors Tidak, dia berbicara tentang cara Anda harus membangun string baru setiap kali Anda ingin menggunakan string sihir yang sudah ada , poin 2 adalah tentang mengubah string itu sendiri
Pierre Arlaud
25

Contoh kehidupan nyata: Saya bekerja dengan sistem pihak ketiga di mana "entitas" disimpan dengan "bidang". Pada dasarnya sistem EAV . Karena cukup mudah untuk menambahkan bidang lain, Anda mendapatkan akses ke bidang itu dengan menggunakan nama bidang sebagai string:

Field nameField = myEntity.GetField("ProductName");

(perhatikan string ajaib "ProductName")

Ini dapat menyebabkan beberapa masalah:

  • Saya perlu merujuk ke dokumentasi eksternal untuk mengetahui bahwa "ProductName" bahkan ada dan ejaannya persis
  • Ditambah lagi, saya perlu merujuk ke dokumen itu untuk melihat apa tipe data dari bidang itu.
  • Kesalahan ketik dalam string ajaib ini tidak akan tertangkap sampai baris kode ini dieksekusi.
  • Ketika seseorang memutuskan untuk mengganti nama bidang ini di server (sulit saat mencegah dataloss, tetapi bukan tidak mungkin), maka saya tidak dapat dengan mudah mencari melalui kode saya untuk melihat di mana saya harus menyesuaikan nama ini.

Jadi solusi saya untuk ini adalah menghasilkan konstanta untuk nama-nama ini, yang disusun berdasarkan tipe entitas. Jadi sekarang saya bisa menggunakan:

Field nameField = myEntity.GetField(Model.Product.ProductName);

Ini masih berupa konstanta string dan mengkompilasi ke biner yang sama persis, tetapi memiliki beberapa keunggulan:

  • Setelah saya mengetik "Model.", IDE saya hanya menunjukkan jenis entitas yang tersedia, jadi saya dapat memilih "Produk" dengan mudah.
  • Kemudian IDE saya menyediakan hanya nama-nama bidang yang tersedia untuk jenis entitas ini, juga dapat dipilih.
  • Dokumentasi yang dihasilkan secara otomatis menunjukkan apa arti bidang ini plus datatype yang digunakan untuk menyimpan nilainya.
  • Mulai dari konstanta, IDE saya dapat menemukan semua tempat di mana konstanta yang tepat digunakan (sebagai lawan nilainya)
  • Kesalahan pengetikan akan ditangkap oleh kompiler. Ini juga berlaku ketika model baru (mungkin setelah mengganti nama atau menghapus bidang) digunakan untuk mengembalikan konstanta.

Berikutnya dalam daftar saya: sembunyikan konstanta ini di belakang kelas yang diketik dengan sangat kuat - kemudian juga tipe data diamankan.

Hans Ke st ing
sumber
+1 Anda memunculkan banyak poin bagus tidak terbatas pada struktur kode: dukungan dan perkakas IDE, yang dapat menjadi penyelamat dalam proyek
kmdreko
Jika beberapa bagian dari tipe entitas Anda cukup statis yang benar-benar mendefinisikan nama konstan untuk itu bermanfaat, saya pikir akan lebih tepat untuk hanya mendefinisikan model data yang tepat untuk itu sehingga Anda bisa melakukannya nameField = myEntity.ProductName;.
Lie Ryan
@ LieRyan - jauh lebih mudah untuk menghasilkan konstanta polos dan memutakhirkan proyek yang ada untuk menggunakannya. Yang mengatakan, saya sedang bekerja pada menghasilkan jenis statis sehingga saya bisa melakukan tepat bahwa
Hans Ke st ing
9

Senar ajaib tidak selalu buruk , jadi ini mungkin alasan mengapa Anda tidak dapat menemukan alasan selimut untuk menghindarinya. (Dengan "string ajaib" Saya menganggap maksud Anda string literal sebagai bagian dari ekspresi, dan tidak didefinisikan sebagai konstanta.)

Dalam beberapa kasus tertentu, senar ajaib harus dihindari:

  • String yang sama muncul beberapa kali dalam kode. Ini berarti Anda dapat memiliki kesalahan pengejaan di salah satu tempat. Dan itu akan merepotkan dari perubahan string. Ubah string menjadi konstanta, dan Anda akan menghindari masalah ini.
  • String dapat berubah secara independen dari kode tempat munculnya. Misalnya. jika string adalah teks yang ditampilkan kepada pengguna akhir, string tersebut kemungkinan akan berubah terlepas dari perubahan logika apa pun. Memisahkan string tersebut menjadi modul terpisah (atau konfigurasi eksternal atau database) akan membuatnya lebih mudah untuk diubah secara mandiri
  • Arti string tidak jelas dari konteksnya. Dalam hal itu, memperkenalkan konstanta akan membuat kode lebih mudah dipahami.

Tetapi dalam beberapa kasus, "senar ajaib" baik-baik saja. Katakanlah Anda memiliki parser sederhana:

switch (token.Text) {
  case "+":
    return a + b;
  case "-":
    return a - b;
  //etc.
}

Sebenarnya tidak ada keajaiban di sini, dan tidak ada masalah yang dijelaskan di atas berlaku. Tidak akan ada manfaat IMHO untuk mendefinisikan string Plus="+"dll. Sederhanakan.

JacquesB
sumber
7
Saya pikir definisi Anda tentang "string sihir" tidak cukup, perlu memiliki konsep menyembunyikan / mengaburkan / membuat-misterius. Saya tidak akan menyebut "+" dan "-" dalam contoh tandingan itu sebagai "sihir", seperti yang saya sebut nol sebagai sihir if (dx != 0) { grad = dy/dx; }.
Rupe
2
@Rupe: Saya setuju, tetapi OP menggunakan definisi " nilai-nilai string yang ditentukan secara langsung dalam kode aplikasi yang berdampak pada perilaku aplikasi. " Yang tidak memerlukan string menjadi misterius, jadi ini adalah definisi yang saya gunakan dalam jawabannya.
JacquesB
7
Dengan merujuk pada contoh Anda, saya telah melihat pernyataan pergantian yang diganti "+"dan "-"dengan TOKEN_PLUSdan TOKEN_MINUS. Setiap kali saya membacanya, saya merasa lebih sulit untuk membaca dan men-debug karena itu! Pasti tempat di mana saya setuju bahwa menggunakan string sederhana lebih baik.
Cort Ammon
2
Saya setuju bahwa ada saat-saat string sihir sesuai: menghindarinya adalah aturan praktis, dan semua aturan praktis memiliki pengecualian. Mudah-mudahan, ketika kita jelas tentang mengapa itu bisa menjadi hal yang buruk, kita akan dapat membuat pilihan yang cerdas, daripada melakukan hal-hal karena (1) kita tidak pernah mengerti bahwa mungkin ada cara yang lebih baik, atau (2) kita telah diperintahkan untuk melakukan berbagai hal secara berbeda oleh pengembang senior atau standar pengkodean.
Kramii
2
Saya tidak tahu apa "sihir" di sini. Itu terlihat seperti string literal dasar bagi saya.
tchrist
6

Untuk menambah jawaban yang ada:

Internasionalisasi (i18n)

Jika teks yang akan ditampilkan di layar adalah kode-keras dan dikubur dalam lapisan fungsi, Anda akan mengalami kesulitan menyediakan terjemahan teks itu ke bahasa lain.

Beberapa lingkungan pengembangan (misalnya Qt) menangani terjemahan dengan mencari dari string teks bahasa dasar ke bahasa yang diterjemahkan. String sihir umumnya dapat bertahan seperti ini - sampai Anda memutuskan ingin menggunakan teks yang sama di tempat lain dan mendapatkan salah ketik. Bahkan kemudian, sangat sulit untuk menemukan string ajaib mana yang perlu diterjemahkan ketika Anda ingin menambahkan dukungan untuk bahasa lain.

Beberapa lingkungan pengembangan (mis. MS Visual Studio) mengambil pendekatan lain dan mengharuskan semua string terjemahan disimpan dalam basis data sumber daya dan membaca kembali untuk lokal saat ini dengan ID unik dari string itu. Dalam hal ini aplikasi Anda dengan string ajaib tidak dapat diterjemahkan ke bahasa lain tanpa pengerjaan ulang besar. Pengembangan yang efisien mengharuskan semua string teks dimasukkan ke dalam basis data sumber daya dan diberikan ID unik ketika kode pertama kali ditulis, dan setelah itu relatif mudah. Mencoba untuk mengisi ulang ini setelah fakta biasanya akan membutuhkan upaya yang sangat besar (dan ya, saya pernah ke sana!) Jadi jauh lebih baik untuk melakukan hal-hal yang benar sejak awal.

Graham
sumber
3

Ini bukan prioritas untuk semua orang, tetapi jika Anda ingin dapat menghitung metrik kopling / kohesi pada kode Anda secara otomatis, string sihir membuat ini hampir mustahil. Sebuah string di satu tempat akan merujuk ke kelas, metode atau fungsi di tempat lain, dan tidak ada cara otomatis yang mudah untuk menentukan bahwa string tersebut digabungkan ke kelas / metode / fungsi hanya dengan menguraikan kode. Hanya kerangka kerja yang mendasarinya (Angular, misalnya) yang dapat menentukan bahwa ada keterkaitan - dan hanya dapat melakukannya pada saat run-time. Untuk mendapatkan informasi penggandengan sendiri, parser Anda harus mengetahui segala sesuatu tentang kerangka kerja yang Anda gunakan, di atas dan di luar bahasa dasar tempat Anda membuat kode.

Tapi sekali lagi, ini bukan sesuatu yang banyak pengembang pedulikan.

pengguna3511585
sumber