Một con số ma thuật là gì, và tại sao nó xấu? [đóng cửa]


514

Một số ma thuật là gì?

Tại sao nên tránh?

Có trường hợp nào phù hợp không?


2
Bạn sẽ tránh các số ma thuật khiến những người khác xem mã của bạn có thể không hiểu tại sao bạn đang làm những gì bạn đang làm ... ví dụ const myNum = 22; const number = myNum / 11;ngay bây giờ 11 của tôi có thể là người hoặc chai bia hoặc một cái gì đó thay vì tôi sẽ thay đổi 11 thành hằng số chẳng hạn như cư dân.
JuicY_Burrito

Sử dụng số ma thuật trong các thuộc tính là không thể tránh khỏi, vì vậy tôi đoán điều này là phù hợp.
donatasj87

Câu trả lời:


574

Số ma thuật là cách sử dụng trực tiếp một số trong mã.

Ví dụ: nếu bạn có (bằng Java):

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

Điều này nên được tái cấu trúc để:

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

Nó cải thiện khả năng đọc mã và dễ bảo trì hơn. Hãy tưởng tượng trường hợp tôi đặt kích thước của trường mật khẩu trong GUI. Nếu tôi sử dụng số ma thuật, bất cứ khi nào kích thước tối đa thay đổi, tôi phải thay đổi ở hai vị trí mã. Nếu tôi quên một, điều này sẽ dẫn đến sự không nhất quán.

JDK là đầy đủ các ví dụ như trong Integer, CharacterMathcác lớp học.

PS: Các công cụ phân tích tĩnh như FindBugs và PMD phát hiện việc sử dụng các số ma thuật trong mã của bạn và đề xuất tái cấu trúc.


174
0 và 1 là ngoại lệ cho quy tắc này.
Jonathan Parker

24
@Kirill: Nếu bạn mong đợi định nghĩa của "Trăm phần trăm" sẽ thay đổi, thì có. Một cách tiếp cận tốt hơn sẽ là có biến từ những gì nó thể hiện, nghĩa là, kết thúc tĩnh công khai MAX_DOWNLOAD_PERCENTAGE = 100. Mặc dù điều đó sẽ không có ý nghĩa gì, bởi vì "100 phần trăm" được xác định rất rõ. Mặt khác, thực tế là Mật khẩu có thể dài tối đa 7 ký tự không được xác định toàn cầu và thực sự khác nhau, do đó, đó là một ứng cử viên cho một biến.
Michael Stum

40
@Jonathan Parker, ngoại trừ khi họ không ( TRUE/ FALSE)
Brendan Long

82
Chỉ vì một con số ma thuật sẽ không bao giờ thay đổi không có nghĩa là nó không nên được thay thế bằng một hằng số. Mã của tôi chứa đầy các hằng số toàn cầu như HzPerMHz và msecPerSecond. Những điều này sẽ không bao giờ thay đổi, nhưng chúng làm cho ý nghĩa rõ ràng hơn, và cung cấp một số bảo vệ chống lại lỗi chính tả.
Jeanne Pindar

43
@MarcusJ Bạn không thể sai nhiều hơn. Đây không phải là vấn đề quan điểm, mà là kinh nghiệm khó kiếm được, bởi nhiều lập trình viên. Tôi không thể nói cho bạn biết bao nhiêu lần, trong 40 năm qua lập trình, tôi đã nguyền rủa một lập trình viên trước đó không xác định hằng số, vì vậy tôi chỉ phát hiện ra việc sử dụng trực tiếp một số, cần được hiểu trong quá trình bảo trì mã , được chôn ở đâu đó trong rất nhiều mã, mà ý nghĩa của nó sẽ được làm rõ bằng cách định nghĩa một hằng số như vậy. Bất kỳ lập trình viên cao cấp nào khác cũng sẽ có nhiều câu chuyện kinh dị dọc theo dòng này.
ToolmakerSteve

145

Số ma thuật là một giá trị được mã hóa cứng có thể thay đổi ở giai đoạn sau, nhưng do đó khó có thể cập nhật.

Ví dụ: giả sử bạn có một Trang hiển thị 50 Đơn hàng cuối cùng trong Trang Tổng quan về "Đơn hàng của bạn". 50 là Số ma thuật ở đây, vì nó không được đặt theo tiêu chuẩn hoặc quy ước, đó là một số mà bạn tạo ra vì những lý do được nêu trong thông số kỹ thuật.

Bây giờ, những gì bạn làm là bạn có 50 ở những nơi khác nhau - tập lệnh SQL ( SELECT TOP 50 * FROM orders), Trang web của bạn (50 đơn hàng cuối cùng của bạn), đăng nhập đơn hàng của bạn ( for (i = 0; i < 50; i++)) và có thể nhiều nơi khác.

Bây giờ, điều gì xảy ra khi ai đó quyết định thay đổi 50 thành 25? hay 75? hay 153? Bây giờ bạn phải thay thế 50 ở tất cả các nơi, và bạn rất có thể bỏ lỡ nó. Tìm / Thay thế có thể không hoạt động, vì 50 có thể được sử dụng cho những thứ khác và thay thế một cách mù quáng 50 bằng 25 có thể có một số tác dụng phụ xấu khác (ví dụ như Session.Timeout = 50cuộc gọi của bạn , cũng được đặt thành 25 và người dùng bắt đầu báo cáo quá thời gian chờ quá thường xuyên).

Ngoài ra, mã có thể khó hiểu, tức là " if a < 50 then bla" - nếu bạn gặp phải ở giữa một hàm phức tạp, các nhà phát triển khác không quen thuộc với mã có thể tự hỏi "WTF là 50 ???"

Đó là lý do tại sao tốt nhất là có các số mơ hồ và tùy ý như vậy ở đúng 1 vị trí - " const int NumOrdersToDisplay = 50", vì điều đó làm cho mã dễ đọc hơn (" if a < NumOrdersToDisplay", điều đó cũng có nghĩa là bạn chỉ cần thay đổi nó ở 1 vị trí được xác định rõ.

Những nơi có Số ma thuật phù hợp là mọi thứ được xác định thông qua một tiêu chuẩn, nghĩa là SmtpClient.DefaultPort = 25hoặc TCPPacketSize = whatever(không chắc chắn nếu đó là tiêu chuẩn). Ngoài ra, mọi thứ chỉ được xác định trong 1 hàm có thể được chấp nhận, nhưng điều đó phụ thuộc vào Ngữ cảnh.


18
Ngay cả khi nó không thể thay đổi thì đó vẫn là một ý tưởng tồi bởi vì nó không rõ ràng những gì đang xảy ra.
Loren Pechtel

11
Không phải lúc nào cũng không rõ ràng. SmtpClient.DefaultPort = 25có thể rõ ràng er hơn SmtpClient.DefaultPort = DEFAULT_SMTP_PORT.
dùng253751

4
@immibis Tôi cho rằng giả sử rằng hoàn toàn không có mã nào khác sử dụng khái niệm DEFAULT_SMTP_PORT. Nếu cổng SMTP mặc định cho ứng dụng đó bị thay đổi, thì nó sẽ cần được cập nhật ở nhiều nơi gây ra sự không nhất quán.
Russ Bradberry

3
Việc tìm tất cả các cách sử dụng cũng khó hơn - bạn phải tìm kiếm 25tất cả các ứng dụng và đảm bảo rằng bạn chỉ thay đổi các lần xuất hiện của 25Cổng SMTP, không phải là 25, ví dụ như chiều rộng của cột bảng hoặc số hồ sơ để hiển thị trên một trang.
Michael Stum

2
Trong ví dụ đó, tôi hy vọng mã sẽ sử dụng SmtpClient.DefaultPort chứ không phải 25. Vì vậy, bạn chỉ cần thay đổi nó ở một nơi. Và số cổng có thể vẫn giữ nguyên, đó không phải là một số ma thuật ngẫu nhiên, mà là một số được gán bởi IANA.
njsg

34

Bạn đã xem mục Wikipedia cho số ma thuật chưa?

Nó đi vào một chút chi tiết về tất cả các cách tham chiếu số ma thuật được thực hiện. Đây là một trích dẫn về số ma thuật như một thực hành lập trình xấu

Thuật ngữ số ma thuật cũng đề cập đến thực tiễn lập trình xấu về việc sử dụng số trực tiếp trong mã nguồn mà không cần giải thích. Trong hầu hết các trường hợp, điều này làm cho các chương trình khó đọc, hiểu và duy trì hơn. Mặc dù hầu hết các hướng dẫn tạo một ngoại lệ cho các số 0 và một, nhưng nên xác định tất cả các số khác trong mã là các hằng số được đặt tên.


8
Ví dụ hay về RTFW :)
Eva

Tôi muốn nói câu trả lời là không đầy đủ.
Skeeve

25

Số ma thuật Vs. Hằng số tượng trưng: Khi nào cần thay thế?

Phép thuật: Không biết ngữ nghĩa

Hằng số tượng trưng -> Cung cấp cả ngữ cảnh chính xác và ngữ cảnh chính xác để sử dụng

Semantic: Ý nghĩa hoặc mục đích của một điều.

"Tạo một hằng số, đặt tên theo ý nghĩa và thay thế số đó bằng nó." - Martin Fowler

Đầu tiên, số ma thuật không chỉ là số. Bất kỳ giá trị cơ bản có thể là "ma thuật". Các giá trị cơ bản là các thực thể rõ ràng như số nguyên, số thực, số nhân, số float, ngày, chuỗi, booleans, ký tự, v.v. Vấn đề không phải là kiểu dữ liệu, mà là khía cạnh "ma thuật" của giá trị như nó xuất hiện trong văn bản mã của chúng tôi.

"Ma thuật" nghĩa là gì? Nói chính xác: Bằng "phép thuật", chúng tôi dự định chỉ ra ngữ nghĩa (ý nghĩa hoặc mục đích) của giá trị trong ngữ cảnh mã của chúng tôi; rằng nó không được biết, không thể biết, không rõ ràng hoặc khó hiểu. Đây là khái niệm "ma thuật". Giá trị cơ bản không phải là phép thuật khi ý nghĩa ngữ nghĩa hoặc mục đích tồn tại của nó nhanh chóng và dễ dàng được biết, rõ ràng và được hiểu (không gây nhầm lẫn) từ bối cảnh xung quanh mà không có từ trợ giúp đặc biệt (ví dụ: hằng số biểu tượng).

Do đó, chúng tôi xác định các số ma thuật bằng cách đo khả năng của người đọc mã để biết, rõ ràng và hiểu ý nghĩa và mục đích của một giá trị cơ bản từ bối cảnh xung quanh. Người đọc càng ít biết đến, ít rõ ràng và càng bối rối thì giá trị cơ bản càng "kỳ diệu".

Định nghĩa hữu ích

  • nhầm lẫn: khiến (ai đó) trở nên hoang mang hoặc bối rối.
  • ngơ ngác: khiến (ai đó) trở nên bối rối và bối rối.
  • bối rối: hoàn toàn bối rối; rất hoang mang.
  • baffled: hoàn toàn hoang mang hoặc bối rối.
  • hoang mang: không thể hiểu được; bối rối
  • hiểu: nhận biết ý nghĩa dự định của (từ, ngôn ngữ hoặc người nói).
  • nghĩa: nghĩa của một từ, văn bản, khái niệm hoặc hành động.
  • có nghĩa là: có ý định truyền đạt, chỉ ra hoặc tham khảo (một điều cụ thể hoặc khái niệm); biểu thị.
  • biểu thị: là một dấu hiệu của.
  • chỉ định: một dấu hiệu hoặc một phần thông tin chỉ ra điều gì đó.
  • chỉ ra: chỉ ra; chỉ.
  • dấu hiệu: một đối tượng, chất lượng hoặc sự kiện có sự hiện diện hoặc sự xuất hiện cho thấy sự hiện diện hoặc sự xuất hiện của một thứ khác.

Khái niệm cơ bản

Chúng tôi có hai kịch bản cho các giá trị cơ bản kỳ diệu của chúng tôi. Chỉ thứ hai là quan trọng chính cho các lập trình viên và mã:

  1. Một giá trị cơ bản đơn độc (ví dụ số) mà từ đó ý nghĩa của nó là không xác định, không thể biết, không rõ ràng hoặc khó hiểu.
  2. Một giá trị cơ bản (ví dụ số) trong ngữ cảnh, nhưng ý nghĩa của nó vẫn chưa được biết, không thể biết, không rõ ràng hoặc khó hiểu.

Một sự phụ thuộc bao trùm của "ma thuật" là cách giá trị cơ bản đơn độc (ví dụ số) không có ngữ nghĩa thường được biết đến (như Pi), nhưng có một ngữ nghĩa được biết đến cục bộ (ví dụ chương trình của bạn), không hoàn toàn rõ ràng từ ngữ cảnh hoặc có thể bị lạm dụng trong bối cảnh tốt hay xấu.

Các ngữ nghĩa của hầu hết các ngôn ngữ lập trình sẽ không cho phép chúng ta sử dụng các giá trị cơ bản đơn độc, ngoại trừ (có lẽ) làm dữ liệu (tức là các bảng dữ liệu). Khi chúng ta gặp "số ma thuật", chúng ta thường làm như vậy trong một bối cảnh. Do đó, câu trả lời cho

"Tôi có thay thế số ma thuật này bằng hằng số tượng trưng không?"

Là:

"Làm thế nào nhanh chóng bạn có thể đánh giá và hiểu ý nghĩa ngữ nghĩa của số (mục đích của nó là ở đó) trong bối cảnh của nó?"

Loại phép thuật, nhưng không hoàn toàn

Với suy nghĩ này, chúng ta có thể nhanh chóng thấy một số như Pi (3.14159) không phải là "số ma thuật" khi được đặt trong bối cảnh thích hợp (ví dụ: 2 x 3.14159 x bán kính hoặc 2 * Pi * r). Ở đây, số 3.14159 được Pi nhận biết về mặt tinh thần mà không cần định danh hằng số tượng trưng.

Tuy nhiên, chúng tôi thường thay thế 3.14159 bằng một định danh hằng số tượng trưng như Pi vì độ dài và độ phức tạp của số. Các khía cạnh về độ dài và độ phức tạp của Pi (kết hợp với nhu cầu về độ chính xác) thường có nghĩa là định danh hoặc hằng số tượng trưng ít bị lỗi hơn. Công nhận "Pi" như một cái tên đơn giản là một phần thưởng tiện lợi, nhưng không phải là lý do chính để có hằng số.

Trong khi đó lại tại trang trại

Đặt các hằng số phổ biến như Pi, hãy tập trung chủ yếu vào các số có ý nghĩa đặc biệt, nhưng những ý nghĩa đó bị hạn chế trong vũ trụ của hệ thống phần mềm của chúng tôi. Một số như vậy có thể là "2" (dưới dạng giá trị nguyên cơ bản).

Nếu tôi tự sử dụng số 2, câu hỏi đầu tiên của tôi có thể là: "2" nghĩa là gì? Ý nghĩa của "2" tự nó là không xác định và không thể biết được nếu không có ngữ cảnh, khiến cho việc sử dụng nó không rõ ràng và khó hiểu. Mặc dù chỉ có "2" trong phần mềm của chúng tôi sẽ không xảy ra do ngữ nghĩa ngôn ngữ, chúng tôi muốn thấy rằng "2" tự nó không mang ngữ nghĩa đặc biệt hoặc mục đích rõ ràng là một mình.

Hãy đặt "2" đơn độc của chúng tôi trong bối cảnh : padding := 2, trong đó bối cảnh là "GUI Container". Trong ngữ cảnh này, ý nghĩa của 2 (dưới dạng pixel hoặc đơn vị đồ họa khác) cung cấp cho chúng tôi đoán nhanh về ngữ nghĩa của nó (ý nghĩa và mục đích). Chúng ta có thể dừng lại ở đây và nói rằng 2 là ổn trong bối cảnh này và không có gì khác chúng ta cần biết. Tuy nhiên, có lẽ trong vũ trụ phần mềm của chúng tôi, đây không phải là toàn bộ câu chuyện. Có nhiều hơn thế, nhưng "padding = 2" như một bối cảnh không thể tiết lộ nó.

Hãy giả vờ thêm rằng 2 là phần đệm pixel trong chương trình của chúng tôi thuộc loại "default_padding" trong toàn hệ thống của chúng tôi. Do đó, viết hướng dẫn padding = 2là không đủ tốt. Khái niệm "mặc định" không được tiết lộ. Chỉ khi tôi viết: padding = default_paddingnhư một bối cảnh và sau đó ở nơi khác: default_padding = 2tôi mới nhận ra đầy đủ ý nghĩa tốt hơn và đầy đủ hơn (ngữ nghĩa và mục đích) của 2 trong hệ thống của chúng tôi.

Ví dụ trên là khá tốt vì bản thân "2" có thể là bất cứ thứ gì. Chỉ khi chúng tôi giới hạn phạm vi và phạm vi hiểu biết đối với "chương trình của tôi" trong đó 2 là default_paddingphần GUI UX của "chương trình của tôi", cuối cùng chúng ta mới hiểu "2" trong ngữ cảnh phù hợp. Ở đây "2" là một số "ma thuật", được xác định là hằng số tượng trưng default_paddingtrong ngữ cảnh GUI UX của "chương trình của tôi" để làm cho nó được sử dụng default_paddingnhanh chóng trong bối cảnh lớn hơn của mã kèm theo.

Do đó, bất kỳ giá trị cơ bản nào, có ý nghĩa (ngữ nghĩa và mục đích) không thể được hiểu một cách đầy đủ và nhanh chóng là một ứng cử viên tốt cho hằng số biểu tượng thay cho giá trị cơ bản (ví dụ số ma thuật).

Đi xa hơn

Các số trên thang đo cũng có thể có ngữ nghĩa. Ví dụ, giả vờ rằng chúng tôi đang tạo ra một trò chơi D & D, nơi chúng tôi có khái niệm về một con quái vật. Đối tượng quái vật của chúng ta có một tính năng được gọi life_forcelà số nguyên. Các con số có ý nghĩa không thể biết hoặc rõ ràng mà không có từ để cung cấp ý nghĩa. Vì vậy, chúng tôi bắt đầu bằng cách tùy tiện nói:

  • full_life_force: INTEGER = 10 - Rất sống (và không bị thương)
  • Minimal_life_force: INTEGER = 1 - Sống hoàn toàn (rất đau)
  • chết: INTEGER = 0 - Chết
  • Undead: INTEGER = -1 - Minead (gần như đã chết)
  • zombie: INTEGER = -10 - Max undead (rất bất tử)

Từ các hằng số tượng trưng ở trên, chúng ta bắt đầu có được một bức tranh tinh thần về sự sống, sự chết và "sự bất tử" (và sự phân nhánh hoặc hậu quả có thể xảy ra) cho quái vật của chúng ta trong trò chơi D & D của chúng ta. Không có những từ này (hằng số tượng trưng), chúng ta chỉ còn lại các số từ -10 .. 10. Chỉ phạm vi không có từ ngữ khiến chúng ta rơi vào tình trạng có thể gây nhầm lẫn lớn và có khả năng xảy ra lỗi trong trò chơi của chúng ta nếu các phần khác nhau của trò chơi có sự phụ thuộc vào phạm vi số đó có nghĩa gì đối với các hoạt động khác nhau như attack_elveshoặc seek_magic_healing_potion.

Do đó, khi tìm kiếm và xem xét thay thế "số ma thuật", chúng tôi muốn hỏi những câu hỏi rất có mục đích về các con số trong bối cảnh phần mềm của chúng tôi và thậm chí cả cách các con số tương tác với nhau.

Phần kết luận

Hãy xem lại những câu hỏi chúng ta nên hỏi:

Bạn có thể có một số ma thuật nếu ...

  1. Giá trị cơ bản có thể có một ý nghĩa hoặc mục đích đặc biệt trong vũ trụ phần mềm của bạn không?
  2. Ý nghĩa đặc biệt hoặc mục đích có thể không được biết, không thể biết, không rõ ràng hoặc khó hiểu, ngay cả trong bối cảnh thích hợp của nó?
  3. Một giá trị cơ bản thích hợp có thể được sử dụng không đúng cách với hậu quả xấu trong bối cảnh sai?
  4. Một giá trị cơ bản không phù hợp có thể được sử dụng đúng cách với hậu quả xấu trong bối cảnh phù hợp?
  5. Giá trị cơ bản có mối quan hệ ngữ nghĩa hoặc mục đích với các giá trị cơ bản khác trong bối cảnh cụ thể không?
  6. Một giá trị cơ bản có thể tồn tại ở nhiều nơi trong mã của chúng tôi với các ngữ nghĩa khác nhau ở mỗi nơi, do đó khiến độc giả của chúng tôi nhầm lẫn?

Kiểm tra các giá trị cơ bản không đổi của bảng kê khai độc lập trong văn bản mã của bạn. Hỏi từng câu hỏi một cách chậm rãi và chu đáo về từng trường hợp của một giá trị như vậy. Hãy xem xét sức mạnh của câu trả lời của bạn. Nhiều lần, câu trả lời không phải là đen và trắng, nhưng có sắc thái hiểu sai về mục đích và mục đích, tốc độ học tập và tốc độ hiểu. Cũng cần phải xem cách nó kết nối với máy phần mềm xung quanh nó.

Cuối cùng, câu trả lời cho sự thay thế là trả lời thước đo (trong suy nghĩ của bạn) về điểm mạnh hay điểm yếu của người đọc để tạo kết nối (ví dụ: "lấy nó"). Họ càng hiểu nhanh ý nghĩa và mục đích, bạn càng có ít "phép thuật".

KẾT LUẬN: Chỉ thay thế các giá trị cơ bản bằng các hằng số tượng trưng khi phép thuật đủ lớn để gây khó phát hiện lỗi phát sinh từ sự nhầm lẫn.


1
Cảm ơn. Fwiw các công cụ phân tích tĩnh mà các đồng nghiệp của tôi tiếp tục cài đặt tiếp tục phàn nàn về các con số ma thuật - nhưng làm thế nào một công cụ được cho là hiểu ngữ nghĩa? Kết quả là TẤT CẢ các giá trị cơ bản được thay thế bằng hằng số tượng trưng. Khi tôi đồng ý với kết luận của bạn, tôi thấy điều này ít hơn lý tưởng.
Chomeh

17

Số ma thuật là một chuỗi các ký tự khi bắt đầu định dạng tệp hoặc trao đổi giao thức. Con số này phục vụ như một kiểm tra vệ sinh.

Ví dụ: Mở bất kỳ tệp GIF nào, bạn sẽ thấy ngay khi bắt đầu: GIF89. "GIF89" là con số kỳ diệu.

Các chương trình khác có thể đọc một vài ký tự đầu tiên của tệp và xác định đúng GIF.

Điều nguy hiểm là dữ liệu nhị phân ngẫu nhiên có thể chứa các ký tự tương tự. Nhưng nó rất khó xảy ra.

Đối với trao đổi giao thức, bạn có thể sử dụng nó để nhanh chóng xác định rằng 'thông điệp' hiện tại đang được truyền cho bạn bị hỏng hoặc không hợp lệ.

Số ma thuật vẫn hữu ích.


13
Tôi không nghĩ đó là con số kỳ diệu mà anh ấy đã đề cập đến
Marcio Aguiar

4
Có lẽ bạn nên xóa các thẻ "định dạng tệp" và "kết nối mạng" mà bạn đã thêm vì rõ ràng anh ta không nói về những loại số ma thuật đó.
Landon

9
Vẫn rất hữu ích khi biết rằng số ma thuật có thể đề cập nhiều hơn đơn giản là vấn đề về mã. -Adam
Adam Davis

3
Nếu đối tượng đọc: "Số ma thuật về mã nguồn là gì" thì các thẻ không nên có. Nhưng anh không nói rõ điều này. Vì vậy, có thêm thông tin của tôi là tốt. Tôi nghĩ Kyle, Landon và Marcio đã sai.
Brian R. Bondy

4
Cũng không có cách nào để xác định người mà anh ta đang tìm kiếm. Vì tôi là bài đăng đầu tiên nên tôi không thể đoán được anh ấy đang tìm bài nào.
Brian R. Bondy

12

Trong lập trình, "số ma thuật" là một giá trị nên được đặt tên tượng trưng, ​​nhưng thay vào đó lại được đưa vào mã dưới dạng chữ, thường ở nhiều nơi.

Thật tệ vì cùng một lý do SPOT (Điểm duy nhất) là tốt: Nếu bạn muốn thay đổi hằng số này sau đó, bạn sẽ phải tìm kiếm mã của mình để tìm mọi trường hợp. Nó cũng tệ bởi vì nó có thể không rõ ràng với các lập trình viên khác những gì con số này đại diện, do đó là "ma thuật".

Mọi người đôi khi thực hiện loại bỏ số ma thuật hơn nữa, bằng cách di chuyển các hằng số này vào các tệp riêng biệt để hoạt động như cấu hình. Điều này đôi khi hữu ích, nhưng cũng có thể tạo ra sự phức tạp hơn giá trị của nó.


Bạn có thể cụ thể hơn về lý do tại sao loại bỏ số lượng ma thuật không phải là tốt?
Marcio Aguiar

Trong các công thức toán học như e ^ pi + 1 = 0
Jared Updike

5
Marcio: Khi bạn làm những việc như "const int EIGHT = 8;" và sau đó các yêu cầu thay đổi và bạn kết thúc bằng "const int EIGHT = 9;"
jmucchiello

6
Xin lỗi, nhưng đó đơn giản chỉ là một ví dụ về cách đặt tên xấu hoặc sử dụng cơ sở cho hằng số.
Kzqai

1
@MarcioAguiar: Trên một số nền tảng, một biểu thức như (foo[i]+foo[i+1]+foo[i+2]+1)/3có thể được đánh giá nhanh hơn nhiều so với vòng lặp. Nếu một người thay thế 3mà không viết lại mã thành một vòng lặp, một người nào đó ITEMS_TO_AVERAGEđược xác định là 3có thể hình họ có thể thay đổi nó 5và có mã trung bình nhiều mục hơn. Ngược lại, ai đó nhìn vào biểu thức với nghĩa đen 3sẽ nhận ra rằng 3đại diện cho số lượng vật phẩm được tổng hợp lại với nhau.
supercat

10

Một số ma thuật cũng có thể là một số với ngữ nghĩa đặc biệt, mã hóa cứng. Ví dụ, tôi đã từng thấy một hệ thống trong đó ID hồ sơ> 0 được xử lý bình thường, 0 chính là "bản ghi mới", -1 là "đây là gốc" và -99 là "điều này được tạo ra trong thư mục gốc". 0 và -99 sẽ khiến WebService cung cấp ID mới.

Điều tệ ở đây là bạn đang sử dụng lại một khoảng trắng (số nguyên đã ký cho ID hồ sơ) cho các khả năng đặc biệt. Có thể bạn sẽ không bao giờ muốn tạo một bản ghi với ID 0 hoặc với ID âm, nhưng ngay cả khi không, mọi người nhìn vào mã hoặc tại cơ sở dữ liệu có thể vấp phải điều này và lúc đầu bị nhầm lẫn. Không cần phải nói những giá trị đặc biệt đó không được ghi chép lại.

Có thể cho rằng, 22, 7, -12 và 620 cũng được tính là số ma thuật. ;-)


10

Một vấn đề chưa được đề cập khi sử dụng số ma thuật ...

Nếu bạn có rất nhiều trong số chúng, tỷ lệ cược khá tốt là bạn có hai mục đích khác nhau mà bạn đang sử dụng số ma thuật, trong đó các giá trị xảy ra là như nhau.

Và sau đó, chắc chắn, bạn cần thay đổi giá trị ... chỉ với một mục đích.


Điều này không giống tất cả những gì có thể xảy ra khi nói về các con số (ít nhất là không phải với tôi), nhưng tôi đã chạy vào nó bằng các chuỗi và đó là một điểm nhấn: đầu tiên bạn phải đọc rất nhiều mã để xem nó được sử dụng ở đâu, hơn bạn phải lưu ý rằng nó được sử dụng cho những thứ khác nhau ... không phải là trò tiêu khiển yêu thích của tôi.
Tomislav Nakic-Alfirevic

4

Tôi cho rằng đây là một câu trả lời cho câu trả lời của tôi cho câu hỏi trước đó của bạn. Trong lập trình, một số ma thuật là một hằng số được nhúng xuất hiện mà không cần giải thích. Nếu nó xuất hiện ở hai vị trí riêng biệt, nó có thể dẫn đến trường hợp một trường hợp bị thay đổi và không phải là một trường hợp khác. Vì cả hai lý do này, điều quan trọng là cô lập và xác định các hằng số bên ngoài những nơi chúng được sử dụng.


3

Tôi đã luôn sử dụng thuật ngữ "số ma thuật" khác nhau, như một giá trị tối nghĩa được lưu trữ trong cấu trúc dữ liệu có thể được xác minh là kiểm tra tính hợp lệ nhanh chóng. Ví dụ: các tệp gzip chứa 0x1f8b08 là ba byte đầu tiên của chúng, các tệp lớp Java bắt đầu bằng 0xcafebabe, v.v.

Bạn thường thấy các số ma thuật được nhúng trong các định dạng tệp, bởi vì các tệp có thể được gửi xung quanh khá bừa bãi và mất bất kỳ siêu dữ liệu nào về cách chúng được tạo. Tuy nhiên, số ma thuật đôi khi cũng được sử dụng cho các cấu trúc dữ liệu trong bộ nhớ, như các cuộc gọi ioctl ().

Việc kiểm tra nhanh số ma thuật trước khi xử lý tệp hoặc cấu trúc dữ liệu cho phép người ta báo hiệu sớm các lỗi, thay vì xử lý tất cả các cách thông qua quá trình xử lý có khả năng kéo dài để thông báo rằng đầu vào đã hoàn thành balderdash.


2

Điều đáng chú ý là đôi khi bạn không muốn các số "mã hóa cứng" không thể định cấu hình trong mã của mình. Có một số cái nổi tiếng bao gồm 0x5F3759DF được sử dụng trong thuật toán căn bậc hai được tối ưu hóa.

Trong những trường hợp hiếm hoi mà tôi thấy cần phải sử dụng Số ma thuật như vậy, tôi đặt chúng dưới dạng const trong mã của mình và ghi lại lý do tại sao chúng được sử dụng, cách chúng hoạt động và chúng đến từ đâu.


1
Theo tôi, mùi mã số ma thuật đặc biệt liên quan đến các hằng số không giải thích được . Miễn là bạn đặt chúng vào một hằng số có tên, thì đó không phải là vấn đề.
Don Kirkby

2

Điều gì về việc khởi tạo một biến ở đầu lớp với giá trị mặc định? Ví dụ:

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

Trong trường hợp này, 15000 là một con số ma thuật (theo CheckStyles). Đối với tôi, thiết lập một giá trị mặc định là được. Tôi không muốn phải làm:

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

Điều đó làm cho nó khó đọc hơn? Tôi chưa bao giờ xem xét điều này cho đến khi tôi cài đặt CheckStyles.


Tôi nghĩ rằng điều này sẽ ổn nếu nhà xây dựng khởi tạo giá trị. Mặt khác, nếu giá trị được khởi tạo bên ngoài hàm tạo, tôi chỉ xem nó là một rắc rối và là một thứ khó đọc hơn.
Thomas Eding

Tôi nghĩ rằng static finalhằng số là quá mức khi bạn sử dụng chúng trong một phương thức. Một finalbiến được khai báo ở đầu phương thức là IMHO dễ đọc hơn.
Eva

0

@ eed3si9n: Tôi thậm chí còn đề xuất rằng '1' là một con số kỳ diệu. :-)

Một nguyên tắc liên quan đến số ma thuật là mọi thực tế mã của bạn phải được khai báo chính xác một lần. Nếu bạn sử dụng số ma thuật trong mã của mình (chẳng hạn như ví dụ về độ dài mật khẩu mà @marcio đưa ra, bạn có thể dễ dàng kết thúc việc sao chép thực tế đó và khi bạn hiểu về thực tế đó thay đổi, bạn đã gặp vấn đề về bảo trì.


5
Mã IOW nên được viết như thế này: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

Còn các biến trả về thì sao?

Tôi đặc biệt thấy nó thách thức khi thực hiện các thủ tục được lưu trữ .

Hãy tưởng tượng thủ tục được lưu trữ tiếp theo (tôi biết cú pháp sai, chỉ để hiển thị một ví dụ):

int procGetIdCompanyByName(string companyName);

Nó trả về Id của công ty nếu nó tồn tại trong một bảng cụ thể. Nếu không, nó trả về -1. Bằng cách nào đó nó là một con số kỳ diệu. Một số khuyến nghị tôi đã đọc cho đến nay nói rằng tôi thực sự sẽ phải thiết kế một cái gì đó như thế:

int procGetIdCompanyByName(string companyName, bool existsCompany);

Nhân tiện, nó sẽ trở lại cái gì nếu công ty không tồn tại? Ok: nó sẽ đặt tồn tại Công tysai , nhưng cũng sẽ trả về -1.

Tùy chọn Antoher là tạo hai chức năng riêng biệt:

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

Vì vậy, một điều kiện tiên quyết cho thủ tục lưu trữ thứ hai là công ty đó tồn tại.

Nhưng tôi sợ đồng thời, bởi vì trong hệ thống này, một công ty có thể được tạo bởi một người dùng khác.

Điểm mấu chốt là: bạn nghĩ gì về việc sử dụng loại "số ma thuật" tương đối nổi tiếng và an toàn để nói rằng một cái gì đó không thành công hoặc một cái gì đó không tồn tại?


Trong trường hợp cụ thể đó, nếu tài liệu của hàm nói rằng giá trị trả về âm có nghĩa là không tìm thấy công ty nào, thì không có lý do gì để sử dụng hằng số.
Vincent Fourmond 18/07/2015

-1

Một lợi thế khác của việc trích xuất một số ma thuật là một hằng số cho khả năng ghi lại thông tin kinh doanh rõ ràng.

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();
         }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.