Loại bỏ các con số ma thuật: Khi nào nên nói là No No?


36

Tất cả chúng ta đều biết rằng các số ma thuật (giá trị được mã hóa cứng) có thể tàn phá chương trình của bạn, đặc biệt là khi đến lúc sửa đổi một phần mã không có nhận xét, nhưng bạn vẽ đường này ở đâu?

Chẳng hạn, nếu bạn có một hàm tính số giây giữa hai ngày, bạn có thay thế không

seconds = num_days * 24 * 60 * 60

với

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

Tại thời điểm nào bạn quyết định rằng nó hoàn toàn rõ ràng giá trị mã hóa cứng có nghĩa là gì và để nó một mình?


2
Tại sao không thay thế phép tính đó bằng một hàm hoặc macro để mã của bạn trông giống nhưseconds = CALC_SECONDS(num_days);
FrustratedWithFormsDesigner

15
TimeSpan.FromDays(numDays).Seconds;
Không ai

18
@oosterwal: Với thái độ đó ( HOURS_PER_DAY will never need to be altered), bạn sẽ không bao giờ được mã hóa cho phần mềm được triển khai trên Sao Hỏa. : P
Thất vọngWithFormsDesigner

23
Tôi đã giảm số lượng hằng số xuống chỉ còn SECONDS_PER_DAY = 86400. Tại sao tính toán một cái gì đó sẽ không thay đổi?
JohnFx

17
Còn giây nhuận thì sao?
John

Câu trả lời:


40

Có hai lý do để sử dụng hằng số tượng trưng thay vì chữ số:

  1. Để đơn giản hóa bảo trì nếu số ma thuật thay đổi. Điều này không áp dụng cho ví dụ của bạn. Điều cực kỳ khó xảy ra là số giây trong một giờ hoặc số giờ trong một ngày sẽ thay đổi.

  2. Để cải thiện khả năng đọc. Biểu thức "24 * 60 * 60" là khá rõ ràng đối với hầu hết mọi người. "SECONDS_PER_DAY" cũng vậy, nhưng nếu bạn đang tìm kiếm một lỗi, bạn có thể phải kiểm tra xem SECONDS_PER_DAY đã được xác định chính xác chưa. Có giá trị trong sự ngắn gọn.

Đối với các số ma thuật xuất hiện chính xác một lần và độc lập với phần còn lại của chương trình, việc quyết định có tạo biểu tượng cho số đó hay không là vấn đề của hương vị. Nếu có bất kỳ nghi ngờ nào, hãy tiếp tục và tạo một biểu tượng.

Đừng làm điều này:

public static final int THREE = 3;

3
+1 @kevin cline: Tôi đồng ý với quan điểm của bạn về việc tìm kiếm lỗi. Lợi ích bổ sung mà tôi thấy khi sử dụng hằng số có tên, đặc biệt là khi gỡ lỗi, là nếu phát hiện ra rằng hằng số được xác định không chính xác, bạn chỉ cần thay đổi một đoạn mã thay vì tìm kiếm trong toàn bộ dự án cho tất cả các lần xuất hiện không chính xác giá trị.
oosterwal

40
Hoặc thậm chí tệ hơn:publid final int FOUR = 3;
gablin

3
Ôi trời, chắc bạn đã từng làm việc với cùng một người mà tôi từng làm việc cùng.
quick_now

2
@gablin: Công bằng mà nói, nó khá hữu ích cho các quán rượu có nắp đậy.
Alan Pearce

10
Tôi đã thấy điều này: public static int THREE = 3;... lưu ý - không final!
Stephen C

29

Tôi sẽ giữ quy tắc không bao giờ có số ma thuật.

Trong khi

seconds = num_days * 24 * 60 * 60

Hoàn toàn có thể đọc được hầu hết thời gian, sau khi đã mã hóa 10 giờ mỗi ngày trong ba hoặc bốn tuần ở chế độ giòn

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

dễ đọc hơn nhiều

FrustratedWithFormsDesigner là tốt hơn:

seconds = num_days * DAYS_TO_SECOND_FACTOR

hoặc thậm chí tốt hơn

seconds = CONVERT_DAYS_TO_SECONDS(num_days)

Mọi thứ sẽ ngừng rõ ràng khi bạn rất mệt mỏi. Mã phòng thủ .


13
Vào một chế độ giòn như bạn mô tả là một phản hạt phản tác dụng nên tránh. Các lập trình viên đạt năng suất duy trì cao nhất vào khoảng 35-40 giờ / tuần.
btilly

4
@btilly Mình hết lòng đồng ý với bạn. Nhưng nó xảy ra, thường là do các yếu tố bên ngoài.
Vitor Py

3
Tôi nói chung xác định các hằng số cho giây, phút, ngày và giờ. Nếu không có gì khác '30 * MINUTE 'thực sự dễ đọc và tôi biết đó là thời gian mà không phải suy nghĩ về nó.
Zachary K

9
@btilly: Đỉnh điểm là 35-40 giờ hoặc mức độ alchohol trong máu nằm trong khoảng từ 0,125% đến 0,138%. Tôi đã đọc nó trên XKCD , vì vậy nó đã trở thành sự thật!
oosterwal

1
Nếu tôi thấy một hằng số như HOURS_PER_DAY, tôi sẽ xóa nó và sau đó công khai làm bẽ mặt bạn trước các đồng nghiệp của bạn. Ok, có lẽ tôi sẽ từ bỏ sự sỉ nhục công khai, nhưng tôi có lẽ sẽ xóa nó.
Ed S.

8

Thời gian để nói không là gần như luôn luôn. Thời gian mà tôi thấy dễ dàng hơn khi chỉ sử dụng các số được mã hóa cứng ở những nơi như bố cục UI - tạo ra một hằng số cho việc định vị mọi điều khiển trên biểu mẫu sẽ rất lập phương và mệt mỏi và nếu mã đó thường được xử lý bởi nhà thiết kế UI không quan trọng lắm ... Trừ khi giao diện người dùng được đặt ra một cách linh hoạt hoặc sử dụng các vị trí tương đối cho một số neo hoặc được viết bằng tay. Trong trường hợp đó, tôi muốn nói rằng tốt hơn là xác định một số hằng có ý nghĩa cho bố cục. Và nếu bạn cần một yếu tố mờ nhạt ở đây hoặc ở đó để căn chỉnh / định vị một cái gì đó "vừa phải", thì điều đó cũng nên được xác định.

Nhưng trong ví dụ của bạn, tôi nghĩ rằng thay thế 24 * 60 * 60bằng DAYS_TO_SECONDS_FACTORlà tốt hơn.


Tôi thừa nhận rằng các giá trị được mã hóa cứng cũng ổn khi bối cảnh và cách sử dụng hoàn toàn rõ ràng. Tuy nhiên, đây là một cuộc gọi phán xét ...

Thí dụ:

Như @rmx đã chỉ ra, sử dụng 0 hoặc 1 để kiểm tra xem danh sách có trống không, hoặc có thể trong giới hạn của vòng lặp là một ví dụ về trường hợp mục đích của hằng số rất rõ ràng.


2
Nó thường được sử dụng 0hoặc 1tôi nghĩ. if(someList.Count != 0) ...là tốt hơn so với if(someList.Count != MinListCount) .... Không phải luôn luôn, nhưng nói chung.
Không ai

2
@Dima: VS Forms designer xử lý tất cả những điều đó. Nếu nó muốn tạo hằng, điều đó tốt với tôi. Nhưng tôi sẽ không đi vào mã được tạo và thay thế tất cả các giá trị được mã hóa cứng bằng các hằng số.
Thất vọngWithFormsDesigner

4
Chúng ta đừng nhầm lẫn mã có nghĩa là được tạo và xử lý bởi một công cụ với mã được viết cho tiêu dùng của con người.
biziclop

1
@FrustratedWithFormsDesigner như @biziclop đã chỉ ra, mã được tạo là một động vật hoàn toàn khác. Các hằng số được đặt tên tuyệt đối phải được sử dụng trong mã được mọi người đọc và sửa đổi. Mã được tạo, ít nhất là trong trường hợp lý tưởng, hoàn toàn không nên sửa đổi.
Dima

2
@FrustratedWithFormsDesigner: Điều gì xảy ra khi bạn có một giá trị nổi tiếng được mã hóa cứng trong hàng tá tệp trong chương trình của bạn đột nhiên cần phải thay đổi? Chẳng hạn, bạn mã hóa giá trị đại diện cho số lượng đồng hồ trên mỗi micrô giây cho bộ xử lý nhúng sau đó được yêu cầu chuyển phần mềm của bạn sang một thiết kế trong đó có một số lượng đồng hồ khác nhau trên mỗi micrô giây. Nếu giá trị của bạn là một cái gì đó phổ biến, như 8, thì việc thực hiện tìm / thay thế trên hàng tá tệp có thể gây ra nhiều vấn đề hơn.
oosterwal

8

Dừng lại khi bạn không thể xác định ý nghĩa hoặc mục đích của số.

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

dễ đọc hơn nhiều so với việc chỉ sử dụng các con số. (Mặc dù có thể dễ đọc hơn bằng cách có một SECONDS_PER_DAYhằng số duy nhất , nhưng đó là một vấn đề hoàn toàn riêng biệt.)

Giả sử rằng một nhà phát triển nhìn vào mã có thể thấy những gì nó làm. Nhưng đừng cho rằng họ cũng biết tại sao. Nếu liên tục của bạn giúp hiểu lý do tại sao, đi cho nó. Nếu không, đừng.

Nếu bạn kết thúc với quá nhiều hằng số, như được đề xuất bởi một câu trả lời, hãy xem xét sử dụng tệp cấu hình bên ngoài, vì có hàng tá hằng số trong một tệp không cải thiện chính xác khả năng đọc.


7

Có lẽ tôi sẽ nói "không" với những thứ như:

#define HTML_END_TAG "</html>"

chắc chắn sẽ nói "không" với:

#define QUADRATIC_DISCRIMINANT_COEF 4
#define QUADRATIC_DENOMINATOR_COEF  2

7

Một trong những ví dụ tốt nhất mà tôi đã tìm thấy để thúc đẩy việc sử dụng hằng số cho điều hiển nhiên như HOURS_PER_DAYlà:

Chúng tôi đã tính toán thời gian ngồi trong hàng đợi công việc của một người. Các yêu cầu được xác định một cách lỏng lẻo và lập trình viên khó mã hóa 24ở một số nơi. Cuối cùng, chúng tôi nhận ra rằng thật không công bằng khi trừng phạt người dùng vì đã ngồi trên một vấn đề trong 24 giờ khi thực sự chỉ hoạt động trong 8 giờ một ngày. Khi tác vụ đến để khắc phục điều này VÀ xem những báo cáo nào khác có thể có cùng một vấn đề, rất khó để grep / tìm kiếm thông qua mã cho 24 sẽ dễ dàng hơn nhiều để grep / tìm kiếmHOURS_PER_DAY


ồ không, vì vậy giờ mỗi ngày thay đổi tùy theo việc bạn đề cập đến giờ làm việc mỗi ngày (tôi làm việc 7,5 BTW) hay giờ trong một ngày. Để thay đổi ý nghĩa của hằng số như thế, bạn muốn thay thế tên của nó thành một cái khác. Quan điểm của bạn về việc tìm kiếm dễ dàng là hợp lệ mặc dù.
gbjbaanb

2
Có vẻ như trong trường hợp này HOURS_PER_DAY cũng không thực sự là hằng số mong muốn. Nhưng việc có thể tìm kiếm nó theo tên là một lợi ích rất lớn, ngay cả khi (hoặc đặc biệt là nếu) bạn cần thay đổi nó thành một cái gì đó khác ở nhiều nơi.
David K

4

Tôi nghĩ rằng miễn là con số hoàn toàn không đổi và không có khả năng thay đổi, điều đó hoàn toàn chấp nhận được. Vì vậy, trong trường hợp của bạn, seconds = num_days * 24 * 60 * 60sẽ ổn thôi (tất nhiên là giả sử rằng bạn không làm điều gì đó ngớ ngẩn như thực hiện phép tính này trong một vòng lặp) và tốt hơn là dễ đọc hơn seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE.

Đó là khi bạn làm những việc như thế này thật tệ:

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

Ngay cả khi bạn không thể phù hợp với các dòng nữa trên trang hoặc ngay cả khi bạn không có ý định thay đổi nó, thay vào đó hãy sử dụng một biến liên tục, bởi vì một ngày nào đó nó sẽ quay trở lại ám ảnh bạn. Cuối cùng, vấn đề là khả năng đọc, không lưu 2 chu kỳ tính toán trên CPU. Đây không còn là năm 1978 khi các byte quý giá đã bị ép cho tất cả giá trị của chúng.


2
Bạn có thấy mã cứng có giá trị không thay đổi 86400 thay vì sử dụng hằng số được đặt tên là SECONDS_PER_DAY không? Làm thế nào bạn có thể xác minh rằng tất cả các lần xuất hiện của giá trị là chính xác và không thiếu 0 hoặc hoán đổi 6 quảng cáo 4 chẳng hạn?
oosterwal

Vậy thì tại sao không: giây = num_days * 86400? Điều đó cũng không thay đổi.
JeffO

2
Tôi đã thực hiện điều SECONDS_PER_DAY theo cả hai cách - sử dụng tên và sử dụng số. Khi bạn quay lại mã 2 năm sau, số được đặt tên LUÔN LUÔN có ý nghĩa hơn.
quick_now

2
giây = num_days * 86400 không rõ ràng với tôi. Đó là những gì cuối cùng tính. Nếu tôi thấy "giây = num_day * 24 * 60 * 60", ngoài thực tế là tên biến cho vay ý nghĩa khá tốt trong trường hợp này, tôi sẽ tự hỏi ngay tại sao tôi tách chúng ra và ý nghĩa trở nên rõ ràng vì tôi đã rời đi chúng là những con số (do đó chúng là hằng số), không phải là biến mà điều tra thêm sẽ yêu cầu tôi hiểu giá trị của chúng và nếu chúng là hằng số.
Neil

1
Điều mọi người thường không nhận ra: Nếu bạn thay đổi giá trị dòng đó từ 24 thành 25, bạn sẽ phải xem tất cả mã của mình để xem 24 được sử dụng ở đâu và nếu nó cần thay đổi, và sau đó tính toán hàng ngày. nhân với 24 thực sự đi vào cách của bạn.
gnasher729

3
seconds = num_days * 24 * 60 * 60

Là hoàn toàn tốt. Đây không phải là những con số thực sự kỳ diệu vì chúng sẽ không bao giờ thay đổi.

Bất kỳ số nào có thể thay đổi hợp lý hoặc không có ý nghĩa rõ ràng nên được đưa vào các biến. Có nghĩa là khá nhiều tất cả trong số họ.


2
Sẽ seconds = num_days * 86400vẫn được chấp nhận? Nếu một giá trị như thế được sử dụng nhiều lần trong nhiều tệp khác nhau, làm thế nào bạn xác minh rằng ai đó đã không vô tình gõ seconds = num_days * 84600vào một hoặc hai nơi?
oosterwal

1
Viết 86400 rất khác so với viết 24 * 60 * 60.
Carra

4
Tất nhiên nó sẽ thay đổi. Không phải ngày nào cũng có 86.400 giây trong đó. Hãy xem xét, ví dụ, thời gian tiết kiệm ánh sáng ban ngày. Mỗi năm một lần, một số nơi chỉ có 23 giờ trong một ngày, và một ngày khác họ sẽ có 25. poof số của bạn bị hỏng.
Dave DeLong

1
@Dave, điểm tuyệt vời. Leap giây tồn tại - vi.wikipedia.org/wiki/Leap_second

Điểm công bằng. Thêm một chức năng sẽ là một biện pháp bảo vệ nếu bạn cần nắm bắt những ngoại lệ đó.
Carra

3

Tôi sẽ tránh tạo các hằng số (giá trị ma thuật) để chuyển đổi một giá trị từ đơn vị này sang đơn vị khác. Trong trường hợp chuyển đổi tôi thích một tên phương thức nói. Trong ví dụ này, đây sẽ là ví dụ DayToSeconds(num_days)bên trong phương thức không cần các giá trị ma thuật bởi vì, ý nghĩa của "24" và "60" là rõ ràng.

Trong trường hợp này, tôi không bao giờ sử dụng giây / phút / giờ. Tôi sẽ chỉ sử dụng TimeSpan / DateTime.


1

Sử dụng bối cảnh làm tham số để quyết định

Ví dụ: bạn có một hàm gọi là "notifySecondsB between: aDay và: AnotherDay", bạn sẽ không cần phải thực hiện nhiều biểu hiện về những gì các số đó làm, vì tên hàm khá đại diện.

Và một câu hỏi khác là, những khả năng để tính toán nó theo một cách khác? Đôi khi có rất nhiều cách để làm điều tương tự, vì vậy để hướng dẫn các lập trình viên trong tương lai và chỉ cho họ phương pháp bạn đã sử dụng, việc xác định các hằng số có thể giúp tìm ra nó.

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.