Giá trị của hằng số có thể thay đổi theo thời gian không?


28

Trong giai đoạn phát triển, có một số biến nhất định cần được sửa trong cùng một lần chạy, nhưng có thể cần phải sửa đổi theo thời gian. Ví dụ: booleanchế độ gỡ lỗi để báo hiệu, vì vậy chúng tôi thực hiện mọi thứ trong chương trình mà chúng tôi thường không làm.

Có phải là phong cách xấu khi chứa các giá trị này trong một hằng số, tức là final static int CONSTANT = 0trong Java? Tôi biết rằng một hằng số giữ nguyên trong suốt thời gian chạy, nhưng nó cũng được cho là giống nhau trong toàn bộ quá trình phát triển, ngoại trừ những thay đổi ngoài dự kiến, tất nhiên?

Tôi đã tìm kiếm các câu hỏi tương tự, nhưng không tìm thấy bất cứ điều gì phù hợp với tôi chính xác.


11
Tôi tò mò, tại sao bạn lại tin rằng đó là phong cách xấu để thay đổi những điều này?
Vincent Savard

36
Trừ khi bạn đang mô hình hóa các tính chất vật lý với các hằng số đã biết các giá trị toán học, mọi thứ đều có thể thay đổi vào một lúc nào đó.
Berin Loritsch

19
phần mềm là mềm .
Erik Eidt

10
@GregT Tôi sẽ không đồng ý. finalcung cấp cho bạn một đảm bảo do trình biên dịch thực thi rằng chương trình sẽ không sửa đổi giá trị. Tôi sẽ không phân phối với điều đó chỉ vì lập trình viên có thể muốn sửa đổi giá trị được gán trong mã nguồn.
Alexander - Tái lập Monica

10
Không có nhiều thời gian để đưa ra một câu trả lời đầy đủ, nhưng tôi nghi ngờ các đồng nghiệp của bạn không quan tâm nhiều đến các hằng số, nhưng với việc mã hóa các giá trị cấu hình, có thể có xu hướng biểu hiện như các hằng số. ... Các hằng số bảo vệ bạn trước những sai lầm ngu ngốc, như vô tình gán cho gravitygiữa trò chơi / chạy. Chúng không nhất thiết có nghĩa gravitylà giống nhau trên mọi hành tinh ... Điều đó nói rằng, giải pháp lành mạnh là tạo gravitymột hằng số, nhưng lấy nó từ một planettệp hoặc cơ sở dữ liệu khi bắt đầu phạm vi có liên quan.
Svidgen

Câu trả lời:


6

Trong Java, các hằng số tĩnh cuối cùng có thể được sao chép, bởi trình biên dịch, như các giá trị của chúng, thành mã sử dụng chúng . Do đó, nếu bạn phát hành một phiên bản mới của mã của mình và có một số phụ thuộc xuôi dòng đã sử dụng hằng số, hằng số trong mã đó sẽ không được cập nhật trừ khi mã ngược dòng được biên dịch lại. Đây có thể là một vấn đề nếu sau đó họ sử dụng hằng số đó với mã mong đợi giá trị mới, mặc dù mã nguồn là đúng, nhưng mã nhị phân thì không.

Đây là một bước ngoặt trong thiết kế của Java, vì đây là một trong số rất ít trường hợp (có thể là trường hợp duy nhất) trong đó khả năng tương thích nguồn và khả năng tương thích nhị phân không giống nhau. Ngoại trừ trường hợp này, bạn có thể trao đổi một phụ thuộc với phiên bản tương thích API mới mà không cần người dùng phụ thuộc phải biên dịch lại. Rõ ràng điều này cực kỳ quan trọng được đưa ra theo cách mà các phụ thuộc Java thường được quản lý.

Làm cho vấn đề tồi tệ hơn là mã sẽ chỉ âm thầm làm sai chứ không tạo ra các lỗi hữu ích. Nếu bạn thay thế một phụ thuộc bằng một phiên bản bằng các định nghĩa phương thức hoặc lớp không tương thích, bạn sẽ gặp lỗi trình nạp lớp hoặc lỗi gọi, ít nhất cung cấp manh mối tốt về vấn đề là gì. Trừ khi bạn thay đổi loại giá trị, vấn đề này sẽ chỉ xuất hiện dưới dạng hành vi sai thời gian bí ẩn.

Khó chịu hơn là các JVM ngày nay có thể dễ dàng nội tuyến tất cả các hằng số trong thời gian chạy mà không bị phạt hiệu năng (ngoài việc cần phải tải lớp xác định hằng số, có thể đang được tải bằng mọi cách), thật không may là ngữ nghĩa của ngôn ngữ ngày từ trước khi JIT . Và họ không thể thay đổi ngôn ngữ vì sau đó mã được biên dịch với các trình biên dịch trước đó sẽ không chính xác. Khả năng tương thích Bugward một lần nữa.

Bởi vì tất cả điều này, một số người khuyên không bao giờ thay đổi giá trị tĩnh cuối cùng. Đối với các thư viện có thể được phân phối rộng rãi và cập nhật theo những cách chưa biết vào thời điểm không xác định, đây là cách thực hành tốt.

Trong mã riêng của bạn, đặc biệt là ở đầu phân cấp phụ thuộc, bạn có thể sẽ thoát khỏi nó. Nhưng trong những trường hợp này, hãy xem xét liệu bạn có thực sự cần hằng số được công khai (hoặc được bảo vệ) hay không. Nếu hằng số chỉ hiển thị gói, điều đó hợp lý, tùy thuộc vào hoàn cảnh và tiêu chuẩn mã của bạn, rằng toàn bộ gói sẽ luôn được biên dịch lại cùng một lúc và vấn đề sẽ biến mất. Nếu hằng số là riêng tư, bạn không có vấn đề gì và có thể thay đổi nó bất cứ khi nào bạn muốn.


85

Bất cứ điều gì trong mã nguồn của bạn, bao gồm constcác hằng số toàn cầu được khai báo, có thể có thể thay đổi với bản phát hành mới của phần mềm của bạn.

Các từ khóa const(hoặc finaltrong Java) có để báo hiệu cho trình biên dịch rằng biến này sẽ không thay đổi trong khi phiên bản này của chương trình đang chạy . Chỉ có bấy nhiêu thôi. Nếu bạn muốn gửi tin nhắn đến người bảo trì tiếp theo, hãy sử dụng một nhận xét trong nguồn, đó là những gì họ đang ở đó.

// DO NOT CHANGE without consulting with the legal department!
// Get written consent form from them before release!
public const int LegalLimitInSeconds = ...

Là một cách tốt hơn để giao tiếp với bản thân tương lai của bạn.


11
Tôi thực sự thích bình luận này cho bản thân trong tương lai.
GregT

4
TaxRatebị publiclàm tôi lo lắng Tôi muốn biết chắc chắn rằng chỉ có bộ phận bán hàng bị ảnh hưởng bởi sự thay đổi này và không phải nhà cung cấp nào cũng tính thuế cho chúng tôi. Ai biết được những gì đã xảy ra trong cơ sở mã kể từ khi bình luận đó được viết.
candied_orange

3
@IllusiveBrian không chỉ trích việc sử dụng hằng số. Đã cảnh báo chống lại việc tin tưởng một bình luận được cập nhật. Luôn chắc chắn về cách sử dụng một cái gì đó trước khi bạn thay đổi nó.
candied_orange

8
Đây là lời khuyên tốt cho Java . Nó có thể khác nhau trong các ngôn ngữ khác. Ví dụ: do cách các giá trị const bị ràng buộc với trang web cuộc gọi trong C #, public constcác trường chỉ nên được sử dụng cho những thứ sẽ không bao giờ thay đổi, như Math.pi. Nếu bạn đang tạo thư viện, những thứ có thể thay đổi trong quá trình phát triển hoặc với phiên bản mới sẽ public static readonlykhông gây ra vấn đề với người dùng thư viện của bạn.
GrandOpener

6
Bạn nên chọn một ví dụ khác ... giá trị tiền không bao giờ là dấu phẩy động!
corsiKa

13

Chúng ta cần phân biệt hai khía cạnh của hằng số:

  • tên cho một giá trị được biết đến tại thời điểm phát triển, mà chúng tôi giới thiệu để duy trì tốt hơn và
  • các giá trị có sẵn cho trình biên dịch.

Và sau đó có một loại thứ ba liên quan: các biến có giá trị không thay đổi, tức là tên của một giá trị. Sự khác biệt giữa các biến bất biến này và hằng số là khi giá trị được xác định / gán / khởi tạo: một biến được khởi tạo trong thời gian chạy, nhưng giá trị của hằng được biết trong quá trình phát triển. Sự khác biệt này là một chút bùn vì một giá trị có thể được biết trong quá trình phát triển nhưng thực sự chỉ được tạo ra trong quá trình khởi tạo.

Nhưng nếu giá trị của hằng số được biết tại thời gian biên dịch, thì trình biên dịch có thể thực hiện tính toán với giá trị đó. Ví dụ, ngôn ngữ Java có khái niệm biểu thức hằng . Biểu thức không đổi là bất kỳ biểu thức nào chỉ bao gồm các chữ gốc của chuỗi nguyên thủy hoặc chuỗi, hoạt động trên các biểu thức không đổi (chẳng hạn như truyền, thêm, nối chuỗi) và các biến không đổi. [ JLS §15.28 ] Biến không đổi là finalbiến được khởi tạo với biểu thức không đổi. [JLS §4.12.4] Vì vậy, đối với Java, đây là hằng số thời gian biên dịch:

public static final int X = 7;

Điều này trở nên thú vị khi một biến không đổi được sử dụng trong nhiều đơn vị biên dịch và sau đó khai báo được thay đổi. Xem xét:

  • A.java:

    public class A { public static final int X = 7; }
  • B.java:

    public class B { public static final int Y = A.X + 2; }

Bây giờ khi chúng ta biên dịch các tệp này, B.classmã byte sẽ khai báo một trường Y = 9B.Ylà một biến không đổi.

Nhưng khi chúng ta thay đổi A.Xbiến thành một giá trị khác (giả sử X = 0) và chỉ biên dịch lại A.javatệp, thì B.Yvẫn đề cập đến giá trị cũ. Trạng thái A.X = 0, B.Y = 9này không phù hợp với các khai báo trong mã nguồn. Chúc mừng gỡ lỗi!

Điều này không có nghĩa là hằng số không bao giờ được thay đổi. Các hằng số rõ ràng tốt hơn các số ma thuật xuất hiện mà không cần giải thích trong mã nguồn. Tuy nhiên, các giá trị của các hằng số công cộng là một phần của API công cộng của bạn . Điều này không đặc trưng cho Java, nhưng cũng xảy ra trong C ++ và các ngôn ngữ khác có các đơn vị biên dịch riêng biệt. Nếu bạn thay đổi các giá trị này, bạn sẽ cần biên dịch lại tất cả các mã phụ thuộc, tức là thực hiện biên dịch sạch.

Tùy thuộc vào bản chất của các hằng số, chúng có thể đã dẫn đến các giả định không chính xác của các nhà phát triển. Nếu những giá trị này bị thay đổi, chúng có thể gây ra lỗi. Ví dụ, một tập các hằng số có thể được chọn để chúng tạo thành các mẫu bit nhất định, ví dụ public static final int R = 4, W = 2, X = 1. Nếu những điều này được thay đổi để tạo thành một cấu trúc khác như R = 0, W = 1, X = 2mã hiện có, chẳng hạn như boolean canRead = perms & Rtrở thành không chính xác. Và chỉ cần nghĩ về những niềm vui sẽ xảy ra là Integer.MAX_VALUEthay đổi! Không có sửa chữa ở đây, điều quan trọng cần nhớ là giá trị của một số hằng thực sự quan trọng và không thể thay đổi một cách đơn giản.

Nhưng đối với phần lớn các hằng số thay đổi chúng sẽ ổn miễn là các hạn chế trên được xem xét. Một hằng số là an toàn để thay đổi khi ý nghĩa, không phải giá trị cụ thể là quan trọng. Đây là ví dụ cho trường hợp điều chỉnh như BORDER_WIDTH = 2hoặc TIMEOUT = 60; // secondshoặc các mẫu như API_ENDPOINT = "https://api.example.com/v2/"- mặc dù có thể cho rằng một số hoặc tất cả những thứ đó phải được chỉ định trong tệp cấu hình thay vì mã.


5
Tôi thích phân tích này. Tôi đọc nó như sau: Bạn có thể tự do thay đổi một hằng số miễn là bạn hiểu cách sử dụng nó.
candied_orange

+1 C # cũng "chịu" vấn đề tương tự với các hằng số công cộng.
Reginald Blue

6

Một hằng số chỉ được đảm bảo là hằng số trong vòng đời của ứng dụng . Miễn là điều đó là đúng, không có lý do gì để không tận dụng lợi thế của tính năng ngôn ngữ. Bạn chỉ cần biết hậu quả của việc sử dụng cờ hằng so với trình biên dịch cho cùng một mục đích là gì:

  • Các hằng số chiếm không gian ứng dụng
  • Cờ biên dịch không
  • Mã bị tắt bởi hằng số có thể được cập nhật và thay đổi bằng các công cụ tái cấu trúc hiện đại
  • Mã bị tắt bởi cờ biên dịch không thể

Sau khi duy trì một ứng dụng sẽ bật các hộp giới hạn cho các hình dạng để chúng tôi có thể gỡ lỗi cách chúng được vẽ, chúng tôi gặp phải một vấn đề. Sau khi tái cấu trúc, tất cả các mã bị tắt bởi các cờ biên dịch sẽ không biên dịch. Sau đó, chúng tôi cố tình thay đổi cờ trình biên dịch thành hằng số ứng dụng.

Tôi đang nói rằng để chứng minh rằng có sự đánh đổi. Trọng lượng của một số booleans sẽ không làm cho ứng dụng hết bộ nhớ hoặc chiếm quá nhiều dung lượng. Điều đó có thể không đúng nếu hằng số của bạn thực sự là một đối tượng lớn về cơ bản có khả năng xử lý mọi thứ trong mã của bạn. Nếu bạn không cần phải xóa tất cả các tham chiếu mà nó giữ cho một đối tượng sau khi không còn cần thiết nữa thì đối tượng của bạn có thể là nguồn rò rỉ bộ nhớ.

Bạn cần đánh giá trường hợp sử dụng và lý do tại sao bạn muốn thay đổi các hằng số.

Tôi không phải là một fan hâm mộ của những tuyên bố chăn đơn giản, nhưng nói chung đồng nghiệp cao cấp của bạn là chính xác. Nếu một cái gì đó bị ràng buộc phải thay đổi thường xuyên, nó có thể cần phải là một mặt hàng có thể cấu hình. Ví dụ, bạn có thể thuyết phục đồng nghiệp của mình liên tục IsInDebugMode = truekhi bạn muốn bảo vệ một số mã khỏi bị phá vỡ. Tuy nhiên, một số thứ có thể cần thay đổi thường xuyên hơn là bạn phát hành một ứng dụng. Nếu đó là trường hợp, bạn cần một cách để thay đổi giá trị đó vào thời điểm thích hợp. Bạn có thể lấy ví dụ về a TaxRate = .065. Điều đó có thể đúng vào thời điểm bạn biên dịch mã, nhưng do luật mới, nó có thể thay đổi trước khi bạn phát hành phiên bản tiếp theo của ứng dụng. Đó là thứ cần được cập nhật từ một số cơ chế lưu trữ (như tệp hoặc cơ sở dữ liệu)


Bạn có ý nghĩa gì với các trình biên dịch cờ của cờ vua? Có lẽ bộ tiền xử lý C và các tính năng trình biên dịch tương tự hỗ trợ các macro / định nghĩa và #ifdefs? Vì chúng dựa trên sự thay thế văn bản của mã nguồn, chúng không phải là một phần của ngữ nghĩa ngôn ngữ lập trình. Lưu ý rằng Java không có bộ tiền xử lý.
amon

@amon, Java có thể không, nhưng một số ngôn ngữ thì có. Tôi có nghĩa là #ifdefcờ. Mặc dù chúng không phải là một phần của ngữ nghĩa của C, nhưng chúng là một phần của C #. Tôi đã viết cho bối cảnh lớn hơn của thuyết bất khả tri ngôn ngữ.
Berin Loritsch

Tôi nghĩ rằng đối số "lãng phí bộ nhớ" là tranh luận. Rung trong lớp và rung cây là một bước khá phổ biến trong bất kỳ trình tối ưu hóa chế độ phát hành nào.
Alexander - Tái lập Monica

@Alexander, tôi đồng ý. Đó là một cái gì đó để nhận thức mặc dù.
Berin Loritsch

1
"Các hằng số chiếm không gian ứng dụng" - trừ khi bạn đang phát triển một ứng dụng nhúng cho vi điều khiển chỉ với một hoặc hai kilobyte bộ nhớ, bạn thậm chí không nên nghĩ về những điều đó.
vsz

2

Các const, #definehoặc finallà một trình biên dịch gợi ý (lưu ý rằng #definekhông phải là thực sự là một gợi ý, một vĩ mô của nó và đáng kể mạnh hơn). Nó chỉ ra rằng giá trị sẽ không thay đổi so với việc thực hiện chương trình và các tối ưu hóa khác nhau có thể được thực hiện.

Tuy nhiên, như một gợi ý về trình biên dịch, trình biên dịch thực hiện những điều mà không phải lúc nào người lập trình cũng mong đợi. Cụ thể, javac sẽ nội tuyến static final int FOO = 42;để bất cứ nơi nào FOOđược sử dụng, mã byte được biên dịch thực tế sẽ đọc 42.

Điều này không quá bất ngờ cho đến khi ai đó thay đổi giá trị mà không biên dịch lại đơn vị biên dịch khác (tệp .java) - và phần 42còn lại trong mã byte (xem có thể vô hiệu hóa các biến cuối cùng của javac không? ).

Làm một cái gì đó static finalcó nghĩa là nó là như vậy và mãi mãi sẽ là điều đó và thay đổi nó là một Thỏa thuận thực sự lớn - đặc biệt là nếu nó là bất cứ điều gì nhưng private.

Hằng số cho những thứ như final static int ZERO = 0không phải là một vấn đề. final static double TAX_RATE = 0.55(ngoài tiền và gấp đôi là xấu và nên sử dụng BigDecimal, nhưng sau đó nó không phải là nguyên thủy và do đó không được nội tuyến) là một vấn đề và cần được kiểm tra cẩn thận về nơi sử dụng.


cho các giá trị nhỏ của số KHÔNG.

3
is a problem and should be examined with great care for where it is used.Tại sao nó là một vấn đề?
Alexander - Tái lập Monica

1

Như tên cho thấy, hằng số không nên thay đổi trong thời gian chạy và theo tôi, hằng số được xác định là không thay đổi trong thời gian dài (bạn có thể xem câu hỏi SO này để biết thêm thông tin.

Khi cần đến cờ (ví dụ: đối với chế độ phát triển), thay vào đó, bạn nên sử dụng tệp cấu hình hoặc tham số khởi động (nhiều IDE hỗ trợ định cấu hình tham số khởi động trên cơ sở từng dự án; tham khảo tài liệu liên quan) để bật chế độ này - bằng cách này bạn giữ được tính linh hoạt để sử dụng chế độ như vậy và bạn không thể quên thay đổi nó mỗi khi mã hoạt động hiệu quả.


0

Có thể thay đổi giữa các lần chạy là một trong những điểm quan trọng nhất để xác định hằng số trong mã nguồn của bạn!

Hằng số cung cấp cho bạn một vị trí được xác định rõ ràng và được ghi lại (theo nghĩa) để thay đổi giá trị bất cứ khi nào bạn cần trong suốt vòng đời của mã nguồn. Đó cũng là một lời hứa rằng việc thay đổi hằng số tại vị trí này sẽ thực sự thay đổi tất cả các lần xuất hiện của bất kỳ nơi nào mà nó đại diện.

Như một ví dụ bất lợi: sẽ không có nghĩa khi có một hằng số TRUEđánh giá truebằng một ngôn ngữ thực sự có truetừ khóa. Bạn sẽ không bao giờ, thậm chí, không một lần, tuyên bố TRUE=falsengoại trừ như một trò đùa độc ác.

Tất nhiên, có các cách sử dụng khác của hằng số, ví dụ rút ngắn mã ( CO_NAME = 'My Great World Unique ACME Company'), tránh trùng lặp ( PI=3.141), đặt quy ước ( TRUE=1) hoặc bất cứ điều gì, nhưng có một vị trí xác định để thay đổi hằng số chắc chắn là một trong những vị trí nổi bật nhất.

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.