เลขอาคมคืออะไรและทำไมมันถึงไม่ดี? [ปิด]


514

หมายเลขเวทย์มนตร์คืออะไร?

ทำไมจึงควรหลีกเลี่ยง

มีหลายกรณีที่เหมาะสมหรือไม่


2
คุณจะหลีกเลี่ยงตัวเลขเวทย์มนตร์ทำให้คนอื่นดูรหัสของคุณอาจไม่เข้าใจว่าทำไมคุณถึงทำในสิ่งที่คุณกำลังทำ ... เช่นconst myNum = 22; const number = myNum / 11;ตอนนี้ 11 คนของฉันอาจเป็นคนหรือขวดเบียร์หรืออะไรทำนองนั้นแทนฉันจะเปลี่ยน 11 เป็นค่าคงที่ เช่นผู้อยู่อาศัย
JuicY_Burrito

การใช้หมายเลขมายากลในแอททริบิวต์นั้นหลีกเลี่ยงไม่ได้ดังนั้นฉันคิดว่านี่เหมาะสม
donatasj87

คำตอบ:


574

หมายเลขเวทย์มนตร์เป็นการใช้ตัวเลขในรหัสโดยตรง

ตัวอย่างเช่นถ้าคุณมี (ใน Java):

public class Foo {
    public void setPassword(String password) {
         // don't do this
         if (password.length() > 7) {
              throw new InvalidArgumentException("password");
         }
    }
}

สิ่งนี้ควรได้รับการ refactored ถึง:

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");
         }
    }
}

มันช่วยปรับปรุงการอ่านรหัสและง่ายต่อการบำรุงรักษา ลองนึกภาพกรณีที่ฉันกำหนดขนาดของฟิลด์รหัสผ่านใน GUI ถ้าฉันใช้หมายเลขเวทย์เมื่อใดก็ตามที่ขนาดสูงสุดเปลี่ยนไปฉันต้องเปลี่ยนรหัสสองตำแหน่ง หากฉันลืมสิ่งนี้สิ่งนี้จะนำไปสู่ความไม่สอดคล้องกัน

ตั้ง JDK ที่เต็มไปด้วยตัวอย่างเช่นในInteger, CharacterและMathชั้นเรียน

PS: เครื่องมือวิเคราะห์แบบคงที่เช่น FindBugs และ PMD ตรวจจับการใช้ตัวเลขเวทย์มนตร์ในรหัสของคุณและแนะนำการปรับโครงสร้างใหม่


174
0 และ 1 เป็นข้อยกเว้นสำหรับกฎนี้
Jonathan Parker

24
@ Kirill: หากคุณคาดหวังว่าคำจำกัดความของ "ร้อยเปอร์เซนต์" จะเปลี่ยนใช่ วิธีที่ดีกว่าคือการมีตัวแปรจากสิ่งที่มันหมายถึงสิ่งที่มันหมายถึงสาธารณะคงที่สุดท้าย MAX_DOWNLOAD_PERCENTAGE = 100 ถึงแม้ว่ามันจะไม่สมเหตุสมผลก็ตามเพราะ "100 เปอร์เซ็นต์" ถูกนิยามไว้อย่างดี ในทางกลับกันความจริงที่ว่ารหัสผ่านสามารถยาวได้สูงสุด 7 ตัวอักษรไม่ได้ถูกกำหนดไว้ในระดับสากลและแตกต่างกันจริง ๆ ดังนั้นจึงเป็นตัวเลือกสำหรับตัวแปร
Michael Stum

40
@ Jonathan Parker ยกเว้นเมื่อไม่มี ( TRUE/ FALSE)
Brendan Long

82
เพียงเพราะหมายเลขเวทมนต์จะไม่เปลี่ยนแปลงไม่ได้หมายความว่าไม่ควรเปลี่ยนเป็นค่าคงที่ รหัสของฉันเต็มไปด้วยค่าคงที่ทั่วโลกเช่น HzPerMHz และ msecPerSecond สิ่งเหล่านี้จะไม่เปลี่ยนแปลง แต่พวกเขาทำให้ความหมายชัดเจนขึ้นและให้การป้องกันการพิมพ์ผิด
Jeanne Pindar

43
@MarcusJ คุณไม่ผิดหรอก นี่ไม่ใช่เรื่องของความเห็น แต่เป็นประสบการณ์ที่ได้มาอย่างยากลำบากจากโปรแกรมเมอร์หลายคน ฉันไม่สามารถบอกคุณได้ว่าในช่วง 40 ปีที่ผ่านมาของการเขียนโปรแกรมฉันได้สาปแช่งโปรแกรมเมอร์คนก่อนที่ไม่ได้กำหนดค่าคงที่ดังนั้นฉันจึงค้นพบการใช้ตัวเลขโดยตรงซึ่งจำเป็นต้องเข้าใจในระหว่างการบำรุงรักษาโค้ด ถูกฝังไว้ที่ไหนสักแห่งในรหัสจำนวนมากซึ่งความหมายจะชัดเจนโดยการกำหนดค่าคงที่เช่นนี้ โปรแกรมเมอร์อาวุโสคนอื่น ๆ ก็จะมีเรื่องราวสยองขวัญหลายเรื่องในบรรทัดนี้
ToolmakerSteve

145

Magic Number เป็นค่าฮาร์ดโค้ดที่อาจเปลี่ยนแปลงได้ในภายหลัง แต่อาจยากที่จะอัปเดต

ตัวอย่างเช่นสมมติว่าคุณมีหน้าเว็บที่แสดงคำสั่งซื้อ 50 รายการสุดท้ายในหน้าภาพรวม "คำสั่งซื้อของคุณ" 50 คือหมายเลขเมจิกที่นี่เนื่องจากไม่ได้กำหนดตามแบบแผนหรือแบบแผนมันเป็นหมายเลขที่คุณประกอบขึ้นด้วยเหตุผลที่ระบุไว้ในข้อมูลจำเพาะ

ตอนนี้สิ่งที่คุณทำคือคุณมี 50 ในสถานที่ที่แตกต่างกัน - สคริปต์ SQL ของคุณ ( SELECT TOP 50 * FROM orders), เว็บไซต์ของคุณ (50 คำสั่งซื้อล่าสุดของคุณ) เข้าสู่ระบบคำสั่งซื้อของคุณ ( for (i = 0; i < 50; i++)) และสถานที่อื่น ๆ

ตอนนี้จะเกิดอะไรขึ้นเมื่อมีคนตัดสินใจเปลี่ยน 50 เป็น 25 หรือ 75 หรือ 153? ตอนนี้คุณต้องแทนที่ 50 ในสถานที่ทั้งหมดและคุณมีแนวโน้มที่จะพลาด ค้นหา / แทนที่อาจใช้งานไม่ได้เพราะ 50 อาจใช้สำหรับสิ่งอื่น ๆ และการแทนที่ 50 ด้วย 25 อาจมีผลข้างเคียงที่ไม่ดีอื่น ๆ (เช่นการSession.Timeout = 50โทรของคุณซึ่งตั้งไว้ที่ 25 และผู้ใช้เริ่มรายงานการหมดเวลาบ่อยเกินไป)

นอกจากนี้รหัสยังยากที่จะเข้าใจเช่น " if a < 50 then bla" - หากคุณพบว่าในระหว่างการทำงานที่ซับซ้อนผู้พัฒนารายอื่นที่ไม่คุ้นเคยกับรหัสอาจถามตัวเองว่า "WTF is 50 ???"

นั่นเป็นสาเหตุที่ดีที่สุดที่จะมีตัวเลขที่คลุมเครือและไม่แน่นอนในที่เดียว - " const int NumOrdersToDisplay = 50" เพราะนั่นทำให้โค้ดอ่านง่ายขึ้น (" if a < NumOrdersToDisplay" มันก็หมายความว่าคุณต้องเปลี่ยนมันในที่ที่กำหนดไว้อย่างดีเพียงแห่งเดียวเท่านั้น

สถานที่ที่มีหมายเลข Magic เหมาะสมทุกอย่างที่กำหนดผ่านมาตรฐานคือSmtpClient.DefaultPort = 25หรือTCPPacketSize = whatever(ไม่แน่ใจว่าเป็นมาตรฐาน) นอกจากนี้ทุกอย่างที่กำหนดไว้ภายใน 1 ฟังก์ชั่นอาจยอมรับได้ แต่ขึ้นอยู่กับบริบท


18
แม้ว่ามันจะไม่สามารถเปลี่ยนแปลงได้ แต่ก็ยังเป็นความคิดที่ไม่ดีเพราะมันไม่ชัดเจนว่าเกิดอะไรขึ้น
Loren Pechtel

11
มันไม่ชัดเจนเสมอไป SmtpClient.DefaultPort = 25อาจจะเป็นที่ชัดเจนเอ้อSmtpClient.DefaultPort = DEFAULT_SMTP_PORTกว่า
user253751

4
@immibis ฉันสมมติว่าสมมติว่าไม่มีรหัสอื่นใดที่ใช้แนวคิดของ DEFAULT_SMTP_PORT หากมีการเปลี่ยนแปลงพอร์ต SMTP เริ่มต้นสำหรับแอปพลิเคชันนั้นจะต้องได้รับการอัปเดตในหลาย ๆ ที่ทำให้เกิดความไม่สอดคล้องกัน
Russ Bradberry

3
นอกจากนี้ยังเป็นการยากที่จะค้นหาวิธีการใช้งานทั้งหมด - คุณจะต้องค้นหา25แอปพลิเคชันทั้งหมดและตรวจสอบให้แน่ใจว่าคุณเปลี่ยนเฉพาะสิ่งที่เกิดขึ้น25สำหรับพอร์ต SMTP ไม่ใช่ 25 ของเช่นความกว้างของคอลัมน์ตารางหรือจำนวน ของระเบียนที่จะแสดงในหน้า
Michael Stum

2
ในตัวอย่างนั้นฉันคาดหวังว่ารหัสจะใช้ SmtpClient.DefaultPort ไม่ใช่ 25 ดังนั้นคุณต้องเปลี่ยนมันในที่เดียว และหมายเลขพอร์ตมีแนวโน้มที่จะยังคงเหมือนเดิมก็ไม่ได้เป็นจำนวนมายากลสุ่ม IANAแต่จำนวนได้รับมอบหมายจาก
njsg

34

คุณลองดูที่รายการ Wikipedia เพื่อหาหมายเลขเวทย์มนตร์หรือไม่?

มันมีรายละเอียดเล็กน้อยเกี่ยวกับวิธีการอ้างอิงหมายเลขเวทย์ นี่คือคำพูดเกี่ยวกับหมายเลขเวทย์มนตร์ว่าเป็นการฝึกเขียนโปรแกรมไม่ดี

คำวิเศษหมายเลขยังหมายถึงการฝึกเขียนโปรแกรมที่ไม่ดีของการใช้ตัวเลขโดยตรงในรหัสที่มาโดยไม่มีคำอธิบาย ในกรณีส่วนใหญ่สิ่งนี้ทำให้โปรแกรมอ่านเข้าใจและบำรุงรักษาได้ยากขึ้น แม้ว่าไกด์ส่วนใหญ่จะสร้างข้อยกเว้นสำหรับตัวเลขศูนย์และหนึ่ง แต่ก็เป็นความคิดที่ดีที่จะกำหนดตัวเลขอื่น ๆ ทั้งหมดในรหัสเป็นค่าคงที่ที่มีชื่อ


8
ตัวอย่างที่ดีของ RTFW :)
Eva

ฉันจะบอกว่าคำตอบคือไกลจากเต็ม
Skeeve

25

Magic Number Vs ค่าคงเชิงสัญลักษณ์: เมื่อใดที่จะเปลี่ยน?

Magic: ความหมายที่ไม่รู้จัก

Symbolic Constant -> ให้ทั้งบริบทความหมายและบริบทที่ถูกต้องสำหรับการใช้งาน

ความหมาย: ความหมายหรือวัตถุประสงค์ของสิ่ง

"สร้างค่าคงที่ตั้งชื่อตามความหมายและแทนที่ด้วย" - Martin Fowler

ประการแรกตัวเลขเวทมนตร์ไม่ได้เป็นเพียงตัวเลข ค่าพื้นฐานใด ๆ อาจเป็น "เวทมนต์" ค่าพื้นฐานคือรายการที่ปรากฏเช่นจำนวนเต็ม, reals, doubles, float, วันที่, สตริง, booleans, ตัวอักษรและอื่น ๆ ปัญหาไม่ได้เป็นประเภทข้อมูล แต่เป็นแง่มุม "วิเศษ" ของค่าตามที่ปรากฏในข้อความรหัสของเรา

"เวทมนต์" หมายถึงอะไร? เพื่อความแม่นยำ: โดย "เวทมนต์" เราตั้งใจจะชี้ไปที่ความหมาย (ความหมายหรือวัตถุประสงค์) ของค่าในบริบทของรหัสของเรา ไม่เป็นที่ทราบไม่สามารถเข้าใจได้ไม่ชัดเจนหรือสับสน นี่คือความคิดของ "เวทมนตร์" ค่าพื้นฐานไม่ใช่เวทมนตร์เมื่อความหมายเชิงความหมายหรือจุดประสงค์ของการเป็นอยู่นั้นเป็นที่รู้จักอย่างรวดเร็วและง่ายดายชัดเจนและเข้าใจ (ไม่สับสน) จากบริบทแวดล้อมโดยไม่มีคำช่วยเหลือพิเศษ (เช่นค่าคงที่เชิงสัญลักษณ์)

ดังนั้นเราจึงระบุหมายเลขเวทย์มนตร์โดยการวัดความสามารถของเครื่องอ่านรหัสที่จะรู้ชัดเจนและเข้าใจความหมายและวัตถุประสงค์ของค่าพื้นฐานจากบริบทโดยรอบ ผู้อ่านที่รู้จักน้อยกว่าชัดเจนน้อยลงและสับสนมากขึ้นคือ "พื้นฐาน" ของค่าพื้นฐานที่มากขึ้น

คำจำกัดความที่เป็นประโยชน์

  • สับสน: ทำให้ (ใครบางคน) สับสนหรือสับสน
  • สับสน: ทำให้ (ใครบางคน) งุนงงและสับสน
  • งงงวย: งงงันอย่างสมบูรณ์; งงมาก
  • งงงัน: ทั้งหมดสับสนหรืองงงวย
  • งงงวย: ไม่เข้าใจ งงงวย
  • เข้าใจ: รับรู้ความหมายที่ต้องการของ (คำภาษาหรือผู้พูด)
  • ความหมาย: ความหมายของคำข้อความแนวคิดหรือการกระทำ
  • หมายถึง: ตั้งใจที่จะถ่ายทอดบ่งบอกหรืออ้างถึง (สิ่งใดหรือความคิด); มีความหมายว่า
  • มีความหมาย: เป็นข้อบ่งชี้ของ
  • ข้อบ่งชี้: สัญญาณหรือชิ้นส่วนของข้อมูลที่บ่งบอกถึงบางสิ่ง
  • บ่งชี้: ชี้ให้เห็น; แสดง.
  • เครื่องหมาย: วัตถุคุณภาพหรือเหตุการณ์ที่การปรากฏตัวหรือเกิดขึ้นบ่งบอกถึงการมีอยู่หรือเกิดขึ้นของสิ่งอื่น

ข้อมูลพื้นฐานเกี่ยวกับ

เรามีสองสถานการณ์สำหรับค่าพื้นฐานมายากลของเรา เฉพาะที่สองมีความสำคัญหลักสำหรับโปรแกรมเมอร์และรหัส:

  1. ค่าพื้นฐานเดียว (เช่นจำนวน) ซึ่งไม่ทราบความหมายไม่สามารถอธิบายได้ไม่ชัดเจนหรือสับสน
  2. ค่าพื้นฐาน (เช่นจำนวน) ในบริบท แต่ความหมายของมันยังไม่ทราบไม่สามารถอธิบายได้ไม่ชัดเจนหรือสับสน

การพึ่งพาอย่างมากของ "เวทย์มนตร์" คือวิธีที่ค่าพื้นฐานเดียว (เช่นจำนวน) ไม่มีความหมายทั่วไปที่รู้จักกัน (เช่น Pi) แต่มีความหมายที่รู้จักในระดับท้องถิ่น (เช่นโปรแกรมของคุณ) ซึ่งไม่ชัดเจนจากบริบททั้งหมดหรืออาจถูกทารุณกรรม ในบริบทที่ดีหรือไม่ดี

ความหมายของภาษาโปรแกรมส่วนใหญ่จะไม่อนุญาตให้เราใช้ค่าพื้นฐานเดียวยกเว้น (บางที) เป็นข้อมูล (เช่นสารบัญ) เมื่อเราพบ "หมายเลขเวทมนตร์" เรามักจะทำเช่นนั้นในบริบท ดังนั้นคำตอบ

"ฉันจะแทนที่หมายเลขเวทย์มนตร์นี้ด้วยค่าคงที่เชิงสัญลักษณ์ได้หรือไม่"

คือ:

"คุณสามารถประเมินและเข้าใจความหมายของตัวเลขอย่างรวดเร็วได้อย่างไรในบริบทของมัน"

ชนิดของเวทมนตร์ แต่ไม่มาก

ด้วยความคิดนี้เราสามารถเห็นได้อย่างรวดเร็วว่าจำนวนเช่น Pi (3.14159) ไม่ใช่ "หมายเลขเวทมนตร์" เมื่ออยู่ในบริบทที่เหมาะสม (เช่น 2 x 3.14159 x รัศมีหรือ 2 * Pi * r) ที่นี่หมายเลข 3.14159 เป็นที่รู้จักทางจิตใจ Pi โดยไม่มีตัวระบุค่าคงที่เชิงสัญลักษณ์

อย่างไรก็ตามโดยทั่วไปเราจะแทนที่ 3.14159 ด้วยตัวระบุค่าคงที่เชิงสัญลักษณ์เช่น Pi เนื่องจากความยาวและความซับซ้อนของตัวเลข ด้านของความยาวและความซับซ้อนของ Pi (ควบคู่กับความต้องการความถูกต้อง) มักจะหมายถึงตัวบ่งชี้เชิงสัญลักษณ์ การรับรู้ "Pi" เป็นชื่อเป็นเพียงโบนัสที่สะดวก แต่ไม่ใช่เหตุผลหลักที่ทำให้ค่าคงที่

ในขณะเดียวกัน: กลับไปที่ไร่

วางค่าคงที่ทั่วไปเช่น Pi ไว้ให้เราจดจ่อกับตัวเลขด้วยความหมายพิเศษเป็นหลัก แต่ความหมายเหล่านั้นถูก จำกัด อยู่ในจักรวาลของระบบซอฟต์แวร์ ตัวเลขดังกล่าวอาจเป็น "2" (เป็นค่าจำนวนเต็มพื้นฐาน)

หากฉันใช้หมายเลข 2 ด้วยตัวเองคำถามแรกของฉันอาจเป็น: "2" หมายถึงอะไร ความหมายของ "2" โดยตัวของมันเองนั้นไม่เป็นที่รู้จักและไม่สามารถหยั่งรู้ได้โดยไม่มีบริบททำให้การใช้งานนั้นไม่ชัดเจนและสับสน แม้ว่าจะมีเพียงแค่ "2" ในซอฟต์แวร์ของเราจะไม่เกิดขึ้นเนื่องจากความหมายของภาษา แต่เราต้องการเห็นว่า "2" นั้นไม่มีความหมายพิเศษหรือมีจุดประสงค์ที่ชัดเจนเพียงอย่างเดียว

มาใส่คำว่า "2" ของเราไว้ในบริบทของ: padding := 2โดยบริบทคือ "GUI Container" ในบริบทนี้ความหมายของ 2 (เป็นพิกเซลหรือหน่วยกราฟิกอื่น ๆ ) ให้เราคาดเดาอย่างรวดเร็วของความหมาย (ความหมายและวัตถุประสงค์) เราอาจหยุดที่นี่และบอกว่า 2 ใช้ได้ในบริบทนี้และไม่มีอะไรที่เราต้องรู้ อย่างไรก็ตามบางทีในจักรวาลซอฟต์แวร์ของเรานี่ไม่ใช่เรื่องทั้งหมด มีมากกว่านั้น แต่ "padding = 2" เนื่องจากบริบทไม่สามารถเปิดเผยได้

มาทำท่าต่อไปว่า 2 ในฐานะที่เป็นช่องว่างภายในพิกเซลในโปรแกรมของเรานั้นมีความหลากหลาย "default_padding" ทั่วทั้งระบบของเรา ดังนั้นการเขียนคำสั่งpadding = 2ไม่ดีพอ ความคิดของ "เริ่มต้น" จะไม่เปิดเผย เฉพาะเมื่อฉันเขียน: padding = default_paddingเป็นบริบทและที่อื่น ๆ : default_padding = 2ฉันจะตระหนักถึงความหมายที่ดีขึ้นและสมบูรณ์ขึ้น (2 ความหมายและความหมาย) ในระบบของเราหรือไม่

ตัวอย่างข้างต้นค่อนข้างดีเพราะ "2" ด้วยตัวเองอาจเป็นอะไรก็ได้ เฉพาะเมื่อเรา จำกัด ช่วงและขอบเขตของความเข้าใจกับ "โปรแกรมของฉัน" โดยที่ 2 คือdefault_paddingในส่วนของ GUI UX ของ "โปรแกรมของฉัน" ในที่สุดเราก็ใช้คำว่า "2" ในบริบทที่เหมาะสม ที่นี่ "2" เป็นหมายเลข "เวทย์มนตร์" ซึ่งแยกออกเป็นค่าคงที่เชิงสัญลักษณ์default_paddingภายในบริบทของ GUI UX ของ "โปรแกรมของฉัน" เพื่อให้สามารถใช้งานdefault_paddingได้อย่างรวดเร็วในบริบทที่ยิ่งใหญ่ของโค้ดที่ล้อมรอบ

ดังนั้นค่าพื้นฐานใด ๆ ที่มีความหมาย (ความหมายและวัตถุประสงค์) ไม่สามารถเข้าใจได้อย่างรวดเร็วและเพียงพอจึงเป็นตัวเลือกที่ดีสำหรับค่าคงที่เชิงสัญลักษณ์ในสถานที่ของค่าพื้นฐาน (เช่นเลขกล)

ก้าวต่อไป

ตัวเลขในระดับอาจมีความหมายเช่นกัน ตัวอย่างเช่นหลอกว่าเรากำลังสร้างเกม D&D ที่เรามีแนวคิดเกี่ยวกับสัตว์ประหลาด วัตถุมอนสเตอร์ของเรามีคุณสมบัติที่เรียกว่าlife_forceซึ่งเป็นจำนวนเต็ม ตัวเลขมีความหมายที่ไม่สามารถรู้หรือชัดเจนได้หากไม่มีคำที่สื่อความหมาย ดังนั้นเราเริ่มโดยพลการพูดว่า:

  • full_life_force: INTEGER = 10 - มีชีวิตอยู่มาก (และไม่เป็นอันตราย)
  • Minimum_life_force: INTEGER = 1 - แทบจะไม่มีอะไรเกิดขึ้น (เจ็บมาก)
  • dead: INTEGER = 0 - Dead
  • undead: INTEGER = -1 - Min unadad (เกือบตาย)
  • zombie: INTEGER = -10 - Undead สูงสุด (Undead มาก)

จากค่าคงที่เชิงสัญลักษณ์ข้างต้นเราเริ่มเห็นภาพจิตของความมีชีวิตชีวาความตายและ "ความไม่แน่นอน" (และความเป็นไปได้ของการแตกหรือผลที่ตามมา) สำหรับสัตว์ประหลาดของเราในเกม D&D หากไม่มีคำเหล่านี้ (ค่าคงที่เชิงสัญลักษณ์) เราจะเหลือเพียงตัวเลข-10 .. 10เท่านั้น เพียงแค่ช่วงโดยไม่มีคำใบเราในสถานที่ของความสับสนอาจจะดีและอาจมีข้อผิดพลาดในเกมของเราถ้าชิ้นส่วนที่แตกต่างกันของเกมที่มีการอ้างอิงในสิ่งที่ช่วงของตัวเลขที่หมายถึงการดำเนินการต่างๆเช่นหรือattack_elvesseek_magic_healing_potion

ดังนั้นเมื่อค้นหาและพิจารณาการเปลี่ยน "หมายเลขมายากล" เราต้องการถามคำถามที่มีจุดประสงค์อย่างเต็มที่เกี่ยวกับตัวเลขภายในบริบทของซอฟต์แวร์ของเราและแม้ว่าตัวเลขจะมีความสัมพันธ์กันในเชิงความหมาย

ข้อสรุป

ตรวจสอบคำถามที่เราควรถาม:

คุณอาจมีหมายเลขมายากลหาก ...

  1. ค่าพื้นฐานสามารถมีความหมายหรือวัตถุประสงค์พิเศษในจักรวาลซอฟต์แวร์ของคุณหรือไม่?
  2. ความหมายหรือจุดประสงค์พิเศษอาจเป็นที่รู้จักไม่สามารถหยั่งรู้ไม่ชัดเจนหรือสับสนแม้อยู่ในบริบทที่เหมาะสมหรือไม่?
  3. สามารถใช้ค่าพื้นฐานที่เหมาะสมอย่างไม่เหมาะสมกับผลลัพธ์ที่ไม่ถูกต้องในบริบทที่ผิดหรือไม่?
  4. สามารถใช้ค่าพื้นฐานที่ไม่เหมาะสมกับผลกระทบที่ไม่ดีในบริบทที่เหมาะสมได้หรือไม่?
  5. ค่าพื้นฐานมีความสัมพันธ์ทางความหมายหรือวัตถุประสงค์กับค่าพื้นฐานอื่น ๆ ในบริบทเฉพาะหรือไม่
  6. ค่าพื้นฐานสามารถมีอยู่มากกว่าหนึ่งแห่งในรหัสของเราที่มีความหมายต่างกันในแต่ละอันทำให้ผู้อ่านของเราสับสนหรือไม่?

ตรวจสอบค่าพื้นฐานคงที่อย่างชัดแจ้งแบบสแตนด์อโลนในข้อความรหัสของคุณ ถามคำถามแต่ละข้ออย่างช้าๆและไตร่ตรองเกี่ยวกับคุณค่าดังกล่าวแต่ละตัวอย่าง พิจารณาความแข็งแกร่งของคำตอบของคุณ หลายครั้งคำตอบไม่ใช่ขาวดำ แต่มีเฉดสีของความหมายและวัตถุประสงค์ที่เข้าใจผิดความเร็วในการเรียนรู้และความเร็วในการเข้าใจ นอกจากนี้ยังมีความต้องการที่จะดูว่ามันเชื่อมต่อกับเครื่องซอฟต์แวร์รอบ ๆ มันได้อย่างไร

ในที่สุดคำตอบสำหรับการเปลี่ยนคือคำตอบของความแข็งแกร่งหรือจุดอ่อนของผู้อ่านเพื่อทำการเชื่อมต่อ (เช่น "รับ") ยิ่งพวกเขาเข้าใจความหมายและวัตถุประสงค์ได้เร็วเท่าไรคุณก็ยิ่งมีเวทย์มนตร์น้อยเท่านั้น

สรุป: แทนที่ค่าพื้นฐานด้วยค่าคงที่เชิงสัญลักษณ์เฉพาะเมื่อเวทย์มนตร์มีขนาดใหญ่พอที่จะทำให้ตรวจจับข้อบกพร่องที่เกิดจากการสับสนได้ยาก


1
ขอบคุณ Fwiw เครื่องมือวิเคราะห์แบบคงที่เพื่อนร่วมงานของฉันติดตั้งไว้ตลอดเวลาบ่นเกี่ยวกับหมายเลขเวทย์มนตร์ - แต่เครื่องมือควรจะเข้าใจความหมายได้อย่างไร? ผลลัพธ์คือค่าพื้นฐานทั้งหมดจะถูกแทนที่ด้วยค่าคงที่เชิงสัญลักษณ์ เมื่อฉันเห็นด้วยกับข้อสรุปของคุณฉันพบว่าน้อยกว่าอุดมคติ
Chomeh

17

หมายเลขอาถรรพ์คือลำดับของอักขระที่จุดเริ่มต้นของรูปแบบไฟล์หรือการแลกเปลี่ยนโปรโตคอล หมายเลขนี้ทำหน้าที่ตรวจสอบสติ

ตัวอย่าง: เปิดไฟล์ GIF ใด ๆ คุณจะเห็นตอนเริ่มต้น: GIF89 "GIF89" เป็นหมายเลขเวทย์มนตร์

โปรแกรมอื่นสามารถอ่านอักขระสองสามตัวแรกของไฟล์และระบุ GIF ได้อย่างถูกต้อง

อันตรายคือข้อมูลไบนารีแบบสุ่มสามารถมีอักขระเหมือนกันได้ แต่มันไม่น่าเป็นไปได้

สำหรับการแลกเปลี่ยนโพรโทคอลคุณสามารถใช้เพื่อระบุว่า 'ข้อความ' ปัจจุบันที่ถูกส่งไปยังคุณอย่างรวดเร็วเสียหายหรือไม่ถูกต้อง

ตัวเลขเวทย์มนตร์ยังมีประโยชน์


13
ฉันไม่คิดว่ามันเป็นหมายเลขเวทย์มนตร์ที่เขาอ้างถึง
Marcio Aguiar

4
บางทีคุณควรลบแท็ก "รูปแบบไฟล์" และ "เครือข่าย" ที่คุณเพิ่มเพราะเขาไม่ได้พูดถึงตัวเลขเวทย์มนตร์เหล่านั้นอย่างชัดเจน
แลนดอน

9
มันยังมีประโยชน์มากที่จะรู้ว่าตัวเลขเวทย์มนตร์อาจอ้างถึงมากกว่าแค่ปัญหารหัส -Adam
Adam Davis

3
หากหัวเรื่องอ่าน: "หมายเลขวิเศษในแง่ของซอร์สโค้ดคืออะไร" ดังนั้นแท็กไม่ควรอยู่ที่นั่น แต่เขาไม่ได้ระบุสิ่งนี้ ดังนั้นการมีข้อมูลเพิ่มเติมของฉันจึงเป็นเรื่องดี ฉันคิดว่า Kyle, Landon และ Marcio ผิด
Brian R. Bondy

4
ไม่มีทางที่จะระบุว่าเขากำลังมองหาใครอยู่ ตั้งแต่ฉันเป็นโพสต์แรกฉันไม่สามารถคาดเดาสิ่งที่เขากำลังมองหา
Brian R. Bondy

12

ในการเขียนโปรแกรม "หมายเลขเวทมนตร์" คือค่าที่ควรได้รับชื่อสัญลักษณ์ แต่ถูกแทนที่ลงในรหัสเป็นตัวอักษรโดยปกติจะอยู่ในสถานที่มากกว่าหนึ่งแห่ง

มันไม่ดีด้วยเหตุผลเดียวกัน SPOT (Single Point of Truth) ก็ดี: ถ้าคุณต้องการเปลี่ยนค่าคงที่นี้ในภายหลังคุณจะต้องค้นหารหัสของคุณเพื่อค้นหาทุกตัวอย่าง มันก็ไม่ดีเพราะมันอาจไม่ชัดเจนสำหรับโปรแกรมเมอร์คนอื่น ๆ ที่ตัวเลขนี้แสดงถึงดังนั้นจึงเป็น "เวทมนต์"

บางครั้งผู้คนใช้การกำจัดจำนวนเวทย์มนตร์เพิ่มเติมโดยการย้ายค่าคงที่เหล่านี้เป็นไฟล์แยกเพื่อทำหน้าที่กำหนดค่า บางครั้งสิ่งนี้มีประโยชน์ แต่ก็สามารถสร้างความซับซ้อนได้มากกว่าที่คุ้มค่า


คุณจะเจาะจงมากขึ้นว่าทำไมการกำจัดหมายเลข maginc ไม่ดีเสมอไป?
Marcio Aguiar

ในสูตรคณิตศาสตร์อย่าง e ^ pi + 1 = 0
Jared Updike

5
Marcio: เมื่อคุณทำสิ่งต่าง ๆ เช่น "const int EIGHT = 8;" แล้วความต้องการก็เปลี่ยนไปและคุณจะได้ "const int EIGHT = 9;"
jmucchiello

6
ขออภัยนั่นเป็นเพียงตัวอย่างของการตั้งชื่อไม่ดีหรือการใช้งานพื้นฐานสำหรับค่าคงที่
Kzqai

1
@MarcioAguiar: ในบางแพลตฟอร์มนิพจน์เช่น(foo[i]+foo[i+1]+foo[i+2]+1)/3นั้นอาจได้รับการประเมินเร็วกว่าลูป หากมีการแทนที่3โดยไม่ต้องเขียนโค้ดใหม่เป็นลูปใครบางคนที่เห็นว่าITEMS_TO_AVERAGEถูกกำหนด3อาจคิดว่าพวกเขาสามารถเปลี่ยนเป็น5และมีรหัสรายการเฉลี่ยมากขึ้น ในทางตรงกันข้ามคนที่ดูการแสดงออกด้วยตัวอักษร3จะรู้ว่า3จำนวนนั้นเป็นรายการที่รวมเข้าด้วยกัน
supercat

10

เลขอาถรรพ์อาจเป็นตัวเลขที่มีความหมายพิเศษแบบฮาร์ดโค้ด ตัวอย่างเช่นฉันเคยเห็นระบบที่มีการบันทึกรหัส> 0 ได้รับการปฏิบัติตามปกติ 0 ตัวเองคือ "บันทึกใหม่" -1 คือ "นี่คือราก" และ -99 เป็น "สิ่งนี้ถูกสร้างขึ้นในราก" 0 และ -99 จะทำให้ WebService จัดหา ID ใหม่

สิ่งที่แย่เกี่ยวกับเรื่องนี้คือคุณกำลังนำพื้นที่ (จำนวนเต็มที่ลงนามแล้วสำหรับรหัสบันทึก) มาใช้เพื่อความสามารถพิเศษ บางทีคุณอาจไม่ต้องการสร้างบันทึกด้วย ID 0 หรือลบ ID แต่ถ้าไม่ใช่ทุกคนที่ดูรหัสหรือที่ฐานข้อมูลอาจสะดุดกับเรื่องนี้และสับสนในตอนแรก มันไปโดยไม่บอกว่าค่าพิเศษเหล่านั้นไม่มีเอกสารที่ดี

เนื้อหานับว่า22, 7, -12 และ 620ถือเป็นตัวเลขเวทย์มนตร์เช่นกัน ;-)


10

ปัญหาที่ไม่ได้รับการกล่าวถึงด้วยการใช้หมายเลขเวทย์มนตร์ ...

หากคุณมีจำนวนมากอัตราต่อรองเป็นสิ่งที่ดีพอสมควรที่คุณมีวัตถุประสงค์ที่แตกต่างกันสองประการที่คุณใช้ตัวเลขเวทย์มนตร์สำหรับค่าที่เกิดขึ้นจะเหมือนกัน

และจากนั้นคุณจะต้องเปลี่ยนค่า ... เพื่อจุดประสงค์เดียวเท่านั้น


นี่ไม่ได้ดูทั้งหมดที่เป็นไปได้เมื่อพูดถึงตัวเลข (อย่างน้อยก็ไม่ใช่กับฉัน) แต่ฉันวิ่งเข้าไปด้วยสายอักขระและมันก็เป็นที่นิยม: ก่อนอื่นคุณต้องอ่านรหัสจำนวนมากเพื่อดูว่ามีการใช้ที่ใดมากกว่าคุณ ต้องสังเกตว่ามันถูกใช้เพื่อสิ่งต่าง ๆ ... ไม่ใช่งานอดิเรกที่ฉันโปรดปราน
Tomislav Nakic-Alfirevic

4

ฉันถือว่านี่เป็นคำตอบสำหรับคำตอบของคุณสำหรับคำถามก่อนหน้านี้ ในการเขียนโปรแกรมหมายเลขมายากลคือค่าคงตัวตัวเลขที่ฝังอยู่ซึ่งปรากฏขึ้นโดยไม่มีคำอธิบาย หากปรากฏในสถานที่สองแห่งที่แตกต่างกันก็สามารถนำไปสู่สถานการณ์ที่มีการเปลี่ยนแปลงอินสแตนซ์หนึ่งและไม่อีก ด้วยเหตุผลทั้งสองนี้สิ่งสำคัญคือการแยกและกำหนดค่าคงที่ตัวเลขนอกสถานที่ที่ใช้


3

ฉันมักจะใช้คำว่า "หมายเลขเวทมนตร์" แตกต่างกันเป็นค่าที่คลุมเครือเก็บไว้ในโครงสร้างข้อมูลซึ่งสามารถตรวจสอบได้เป็นการตรวจสอบความถูกต้องอย่างรวดเร็ว ตัวอย่างเช่นไฟล์ gzip มี 0x1f8b08 เป็นสามไบต์แรกไฟล์คลาส Java เริ่มต้นด้วย 0xcafebabe เป็นต้น

คุณมักจะเห็นเลขอาถรรพ์ที่ฝังอยู่ในรูปแบบไฟล์เนื่องจากไฟล์สามารถส่งไปได้ค่อนข้างชัดเจนและสูญเสียข้อมูลเมตาเกี่ยวกับวิธีการสร้าง อย่างไรก็ตามบางครั้งก็ใช้หมายเลขเวทย์มนตร์สำหรับโครงสร้างข้อมูลในหน่วยความจำเช่นการเรียก ioctl ()

การตรวจสอบหมายเลขมายากลอย่างรวดเร็วก่อนการประมวลผลไฟล์หรือโครงสร้างข้อมูลทำให้สามารถส่งสัญญาณข้อผิดพลาดได้เร็วกว่าการประมวลผลที่มีความยาวมากเพื่อแจ้งให้ทราบว่าอินพุตเป็น balderdash ที่สมบูรณ์


2

เป็นที่น่าสังเกตว่าบางครั้งคุณต้องการตัวเลขที่ "ไม่กำหนดรหัส" ในรหัสของคุณ มีคนที่มีชื่อเสียงจำนวนมากรวมถึง 0x5F3759DF ซึ่งใช้ในอัลกอริทึมรากที่สองที่ได้รับการปรับปรุง

ในบางกรณีที่ฉันพบว่าจำเป็นต้องใช้ Magic Numbers เช่นนั้นฉันตั้งให้พวกเขาเป็น const ในรหัสของฉันและจัดทำเอกสารเกี่ยวกับสาเหตุที่ใช้งานวิธีการทำงานและที่มาของพวกเขา


1
ในความคิดของฉันกลิ่นรหัสเวทมนตร์หมายถึงค่าคงที่ที่ไม่สามารถอธิบายได้โดยเฉพาะ ตราบใดที่คุณใส่มันในค่าคงที่ที่ตั้งชื่อไว้มันก็ไม่น่าจะมีปัญหาอะไร
Don Kirkby

2

สิ่งที่เกี่ยวกับการเริ่มต้นตัวแปรที่ด้านบนของชั้นเรียนด้วยค่าเริ่มต้น? ตัวอย่างเช่น:

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;
    }

ในกรณีนี้ 15,000 หมายเลขมายากล (ตาม CheckStyles) สำหรับฉันการตั้งค่าเริ่มต้นก็โอเค ฉันไม่ต้องการทำ:

private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;

นั่นทำให้อ่านยากขึ้นหรือไม่ ฉันไม่เคยพิจารณาเรื่องนี้จนกว่าฉันจะติดตั้ง CheckStyles


ฉันคิดว่ามันจะไม่เป็นไรถ้านวกรรมิกเริ่มต้นค่า มิฉะนั้นหากค่าเริ่มต้นนอกตัวสร้างฉันแค่เห็นว่ามันยุ่งยากและเป็นอะไรที่อ่านยากกว่า
โทมัส Eding

ฉันคิดว่าstatic finalค่าคงที่มากเกินไปเมื่อคุณใช้พวกมันในวิธีเดียว finalประกาศตัวแปรที่ด้านบนของวิธีการที่เป็น IMHO อ่านได้มากขึ้น
Eva

0

@ eed3si9n: ฉันอยากจะแนะนำว่า '1' เป็นหมายเลขเวทย์มนตร์ :-)

หลักการที่เกี่ยวข้องกับตัวเลขเวทย์มนตร์คือความจริงทุกข้อเกี่ยวกับรหัสของคุณควรถูกประกาศครั้งเดียว หากคุณใช้หมายเลขเวทย์มนตร์ในรหัสของคุณ (เช่นตัวอย่างความยาวรหัสผ่านที่ @marcio มอบให้คุณสามารถทำซ้ำความจริงนั้นได้อย่างง่ายดายและเมื่อคุณเข้าใจถึงความจริงที่ว่าคุณมีปัญหาในการบำรุงรักษา


5
ควรเขียนโค้ด IOW ดังนี้: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
Thomas Eding

0

สิ่งที่เกี่ยวกับตัวแปรส่งคืน

ฉันเป็นพิเศษพบว่ามันมีความท้าทายเมื่อใช้วิธีการจัดเก็บ

ลองนึกภาพกระบวนงานที่เก็บไว้ถัดไป (ฉันเข้าใจว่าผิดเพื่อแสดงตัวอย่าง):

int procGetIdCompanyByName(string companyName);

จะส่งคืนรหัสของ บริษัท หากมีอยู่ในตารางที่ระบุ มิฉะนั้นจะส่งคืน -1 ยังไงก็เถอะมันเป็นหมายเลขเวทย์มนตร์ คำแนะนำที่ฉันได้อ่านจนถึงตอนนี้บอกว่าฉันจะต้องออกแบบบางอย่างเช่น:

int procGetIdCompanyByName(string companyName, bool existsCompany);

โดยวิธีการสิ่งที่มันควรจะกลับมาถ้า บริษัท ไม่อยู่? ตกลง: มันจะตั้งค่ามีอยู่บริษัทเป็นเท็จแต่จะส่งคืน -1 ด้วย

ตัวเลือก Antoher คือการสร้างสองฟังก์ชั่นแยก:

bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);

ดังนั้นเงื่อนไขล่วงหน้าสำหรับขั้นตอนการจัดเก็บที่สองคือ บริษัท นั้นมีอยู่

แต่ฉันกลัวการเห็นพ้องด้วยเพราะในระบบนี้ บริษัท สามารถสร้างโดยผู้ใช้รายอื่น

บรรทัดล่างคือ: คุณคิดอย่างไรเกี่ยวกับการใช้ "หมายเลขเวทมนตร์" ที่ค่อนข้างเป็นที่รู้จักและปลอดภัยที่จะบอกว่ามีบางสิ่งที่ไม่ประสบความสำเร็จหรือมีบางสิ่งที่ไม่มีอยู่จริง?


ในกรณีเฉพาะนั้นถ้าเอกสารของฟังก์ชันระบุว่าค่าส่งคืนติดลบหมายถึงไม่พบ บริษัท ก็ไม่มีเหตุผลในการใช้ค่าคงที่
Vincent Fourmond

-1

ข้อดีอีกอย่างของการแยกเลขอาถรรพ์เป็นค่าคงที่ช่วยให้สามารถบันทึกข้อมูลทางธุรกิจได้อย่างชัดเจน

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();
         }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.