Từ khóa khẳng định Java làm gì và khi nào nên sử dụng nó?


601

Một số ví dụ thực tế để hiểu vai trò chính của các xác nhận là gì?


8
Trong cuộc sống thực, bạn gần như không bao giờ nhìn thấy chúng. Phỏng đoán: Nếu bạn sử dụng các xác nhận bạn phải suy nghĩ về ba trạng thái: Khẳng định vượt qua, khẳng định thất bại, khẳng định bị tắt, thay vì chỉ hai. Và khẳng định được tắt theo mặc định vì vậy đó là trạng thái có khả năng nhất và thật khó để đảm bảo rằng nó được bật cho mã của bạn. Những gì thêm vào đó là khẳng định là một tối ưu hóa sớm sẽ được sử dụng hạn chế. Như bạn thấy trong câu trả lời của @ Bjorn, thật khó để đưa ra một trường hợp sử dụng mà bạn không muốn thất bại trong việc khẳng định mọi lúc.
Yishai

35
@Yishai: "bạn phải suy nghĩ về ... khẳng định đã bị tắt" Nếu bạn cần làm điều đó, bạn đang làm sai. "khẳng định là một tối ưu hóa sớm của việc sử dụng hạn chế" Điều này khá nhiều ngoài đường ray. Dưới đây là của Sun về vấn đề này: " Sử dụng các xác nhận trong công nghệ Java " và điều này cũng rất tốt để đọc: " Lợi ích của việc lập trình với các xác nhận (còn gọi là các tuyên bố khẳng định) "
David Tonhofer

5
@DavidTonhofer, ngoài đời bạn hầu như không bao giờ nhìn thấy chúng. Điều này có thể kiểm chứng. Kiểm tra nhiều dự án nguồn mở như bạn muốn. Tôi không nói rằng bạn không xác nhận bất biến. Đó không phải là điều tương tự. Đặt cách khác. Nếu các xác nhận là rất quan trọng, tại sao chúng lại tắt theo mặc định?
Yishai

17
Một tài liệu tham khảo, FWIW: Mối quan hệ giữa các xác nhận phần mềm và chất lượng mã : "Chúng tôi cũng so sánh hiệu quả của các xác nhận với các kỹ thuật tìm lỗi phổ biến như các công cụ phân tích tĩnh mã nguồn. Chúng tôi quan sát từ nghiên cứu trường hợp của chúng tôi rằng sự gia tăng mật độ xác nhận trong một tập tin có sự giảm đáng kể về mặt thống kê mật độ lỗi. "
David Tonhofer

4
@DavidTonhofer David, tôi nghĩ rằng tình yêu của bạn dành cho sự khẳng định là dành cho một loại lập trình rất cụ thể mà các bạn đang làm, trong lĩnh vực của tôi hoạt động với các ứng dụng web thoát khỏi chương trình vì BẤT K reason lý do nào là KHÔNG CÓ - tôi cá nhân không bao giờ được sử dụng khẳng định khác với kiểm tra đơn vị / tích hợp
nightograph

Câu trả lời:


426

Các xác nhận (theo cách của từ khóa khẳng định ) đã được thêm vào trong Java 1.4. Chúng được sử dụng để xác minh tính đúng đắn của bất biến trong mã. Chúng không bao giờ được kích hoạt trong mã sản xuất và là dấu hiệu của lỗi hoặc sử dụng sai đường dẫn mã. Chúng có thể được kích hoạt trong thời gian chạy bằng cách -eatùy chọn trên javalệnh, nhưng không được bật theo mặc định.

Một ví dụ:

public Foo acquireFoo(int id) {
  Foo result = null;
  if (id > 50) {
    result = fooService.read(id);
  } else {
    result = new Foo(id);
  }
  assert result != null;

  return result;
}

71
Trong thực tế, Oracle khuyên bạn không nên sử dụng assertđể kiểm tra các tham số phương thức công khai ( docs.oracle.com/javase/1.4.2/docs/guide/lang/assert.html ). Điều đó sẽ ném một Exceptionthay vì giết chương trình.
SJuan76

10
Nhưng bạn vẫn không giải thích tại sao chúng tồn tại. Tại sao bạn không thể thực hiện kiểm tra if () và ném ngoại lệ?
El Mac

7
@ElMac - các xác nhận dành cho các phần dev / debug / test của chu trình - chúng không dành cho sản xuất. Một khối nếu chạy trong prod. Các xác nhận đơn giản sẽ không phá vỡ ngân hàng, nhưng các xác nhận đắt tiền thực hiện xác thực dữ liệu phức tạp có thể làm giảm môi trường sản xuất của bạn, đó là lý do khiến chúng bị tắt ở đó.
hoodaticus 10/03/2016

2
@hoodaticus bạn có nghĩa là chỉ có một thực tế là tôi có thể bật / tắt tất cả các xác nhận cho mã prod là lý do? Bởi vì tôi có thể thực hiện xác nhận dữ liệu phức tạp và sau đó xử lý nó với các ngoại lệ. Nếu tôi có mã sản xuất, tôi có thể tắt các xác nhận phức tạp (và có thể đắt tiền), vì nó sẽ hoạt động và đã được thử nghiệm chưa? Về lý thuyết, họ không nên đưa chương trình xuống vì dù sao bạn cũng sẽ gặp vấn đề.
El Mac

8
This convention is unaffected by the addition of the assert construct. Do not use assertions to check the parameters of a public method. An assert is inappropriate because the method guarantees that it will always enforce the argument checks. It must check its arguments whether or not assertions are enabled. Further, the assert construct does not throw an exception of the specified type. It can throw only an AssertionError. docs.oracle.com/javase/8/docs/technotes/guides/lingu/iêu
Bakhshi

325

Giả sử rằng bạn phải viết một chương trình để điều khiển nhà máy điện hạt nhân. Một điều khá rõ ràng là ngay cả lỗi nhỏ nhất cũng có thể có kết quả thảm khốc, do đó mã của bạn phải không có lỗi (giả sử rằng JVM không có lỗi vì lý do tranh luận).

Java không phải là ngôn ngữ có thể kiểm chứng, điều đó có nghĩa là: bạn không thể tính toán được rằng kết quả hoạt động của bạn sẽ hoàn hảo. Lý do chính cho điều này là con trỏ: chúng có thể chỉ ra bất cứ nơi nào hoặc không nơi nào, do đó chúng không thể được tính là có giá trị chính xác này, ít nhất là không nằm trong một khoảng mã hợp lý. Với vấn đề này, không có cách nào để chứng minh rằng mã của bạn hoàn toàn chính xác. Nhưng những gì bạn có thể làm là chứng minh rằng bạn ít nhất tìm thấy mọi lỗi khi nó xảy ra.

Ý tưởng này dựa trên mô hình Thiết kế theo hợp đồng (DbC): trước tiên bạn xác định (với độ chính xác toán học) phương pháp của bạn sẽ làm gì, sau đó xác minh điều này bằng cách kiểm tra nó trong khi thực hiện thực tế. Thí dụ:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
  return a + b;
}

Mặc dù điều này khá rõ ràng để hoạt động tốt, nhưng hầu hết các lập trình viên sẽ không thấy lỗi ẩn bên trong lỗi này (gợi ý: Ariane V bị hỏng vì một lỗi tương tự). Bây giờ DbC định nghĩa rằng bạn phải luôn kiểm tra đầu vào và đầu ra của hàm để xác minh rằng nó hoạt động chính xác. Java có thể làm điều này thông qua các xác nhận:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
    assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
  final int result = a + b;
    assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
  return result;
}

Nếu chức năng này bây giờ thất bại, bạn sẽ nhận thấy nó. Bạn sẽ biết rằng có một vấn đề trong mã của bạn, bạn biết nó ở đâu và bạn biết nguyên nhân gây ra nó (tương tự như Ngoại lệ). Và điều thậm chí còn quan trọng hơn: bạn ngừng thực thi ngay khi điều đó xảy ra để ngăn chặn bất kỳ mã nào khác hoạt động với các giá trị sai và có khả năng gây thiệt hại cho bất cứ điều gì nó kiểm soát.

Ngoại lệ Java là một khái niệm tương tự, nhưng chúng không thể xác minh mọi thứ. Nếu bạn muốn kiểm tra nhiều hơn (với chi phí tốc độ thực hiện), bạn cần sử dụng các xác nhận. Làm như vậy sẽ làm phồng mã của bạn, nhưng cuối cùng bạn có thể cung cấp một sản phẩm với thời gian phát triển ngắn đáng ngạc nhiên (bạn càng sớm sửa lỗi, chi phí càng thấp). Và ngoài ra: nếu có bất kỳ lỗi nào trong mã của bạn, bạn sẽ phát hiện ra nó. Không có cách nào để xảy ra lỗi và gây ra sự cố sau này.

Điều này vẫn không đảm bảo cho mã không có lỗi, nhưng nó gần với điều đó hơn các chương trình thông thường.


29
Tôi đã chọn ví dụ này bởi vì nó trình bày các lỗi ẩn trong mã dường như không có lỗi rất tốt. Nếu điều này tương tự với những gì người khác trình bày, thì họ có thể có cùng một ý tưởng trong đầu. ;)
TwoThe

8
Bạn chọn khẳng định vì nó thất bại khi khẳng định sai. An nếu có thể có bất kỳ hành vi. Đánh các trường hợp rìa là công việc của Kiểm thử đơn vị. Sử dụng Design by Contract chỉ định hợp đồng khá tốt nhưng cũng như hợp đồng thực tế, bạn cần có sự kiểm soát để chắc chắn rằng chúng được tôn trọng. Với các xác nhận, một cơ quan giám sát được chèn vào đó sẽ cho bạn khi hợp đồng bị thiếu tôn trọng. Hãy nghĩ về nó như một luật sư cằn nhằn hét lên "SAU" mỗi khi bạn làm điều gì đó bên ngoài hoặc chống lại hợp đồng bạn đã ký và sau đó gửi bạn về nhà để bạn không thể tiếp tục làm việc và vi phạm hợp đồng hơn nữa!
Eric

5
Cần thiết trong trường hợp đơn giản này: không, nhưng DbC xác định rằng mọi kết quả phải được kiểm tra. Bây giờ hãy tưởng tượng ai đó sửa đổi chức năng đó thành một cái gì đó phức tạp hơn nhiều, sau đó anh ta cũng phải điều chỉnh hậu kiểm, và sau đó nó đột nhiên trở nên hữu ích.
TwoThe 23/12/13

4
Xin lỗi để phục sinh điều này, nhưng tôi có một câu hỏi cụ thể. Sự khác biệt giữa những gì @TwoThe đã làm và thay vì sử dụng khẳng định chỉ cần ném một new IllegalArgumentExceptiontin nhắn là gì? Ý tôi là, ngoài việc thêm o throwsvào khai báo phương thức và mã để quản lý ngoại lệ đó ở một nơi khác. Tại sao lại assertbỏ ngoại lệ mới? Hoặc tại sao không phải là một ifthay thế assert? Không thể thực sự có được điều này :(
Blueriver

14
-1: Khẳng định kiểm tra tràn là sai nếu acó thể âm. Khẳng định thứ hai là vô ích; đối với các giá trị int, luôn luôn là trường hợp a + b - b == a. Thử nghiệm đó chỉ có thể thất bại nếu máy tính bị hỏng cơ bản. Để chống lại sự dự phòng đó, bạn cần kiểm tra tính nhất quán trên nhiều CPU.
kevin cline

63

Các xác nhận là một công cụ giai đoạn phát triển để bắt lỗi trong mã của bạn. Chúng được thiết kế để dễ dàng gỡ bỏ, vì vậy chúng sẽ không tồn tại trong mã sản xuất. Vì vậy, các xác nhận không phải là một phần của "giải pháp" mà bạn cung cấp cho khách hàng. Họ đang kiểm tra nội bộ để đảm bảo rằng các giả định bạn đưa ra là chính xác. Ví dụ phổ biến nhất là kiểm tra null. Nhiều phương pháp được viết như thế này:

void doSomething(Widget widget) {
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

Rất thường xuyên trong một phương thức như thế này, widget sẽ không bao giờ là null. Vì vậy, nếu nó là null, có một lỗi trong mã của bạn ở đâu đó mà bạn cần theo dõi. Nhưng đoạn mã trên sẽ không bao giờ cho bạn biết điều này. Vì vậy, trong một nỗ lực có chủ đích để viết mã "an toàn", bạn cũng đang ẩn một lỗi. Tốt hơn hết là viết mã như thế này:

/**
 * @param Widget widget Should never be null
 */
void doSomething(Widget widget) {
  assert widget != null;
  widget.someMethod(); // ...
    ... // do more stuff with this widget
}

Bằng cách này, bạn sẽ chắc chắn bắt được lỗi này sớm. (Cũng rất hữu ích khi chỉ định trong hợp đồng rằng tham số này sẽ không bao giờ là null.) Hãy chắc chắn bật các xác nhận khi bạn kiểm tra mã của mình trong quá trình phát triển. (Và thuyết phục đồng nghiệp của bạn làm điều này, thường là rất khó, điều mà tôi thấy rất khó chịu.)

Bây giờ, một số đồng nghiệp của bạn sẽ phản đối mã này, lập luận rằng bạn vẫn nên đưa vào kiểm tra null để ngăn chặn ngoại lệ trong sản xuất. Trong trường hợp đó, khẳng định vẫn hữu ích. Bạn có thể viết nó như thế này:

void doSomething(Widget widget) {
  assert widget != null;
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

Bằng cách này, các đồng nghiệp của bạn sẽ rất vui khi kiểm tra null có mã sản xuất, nhưng trong quá trình phát triển, bạn không còn che giấu lỗi khi widget không có giá trị.

Đây là một ví dụ trong thế giới thực: Tôi đã từng viết một phương pháp so sánh hai giá trị tùy ý cho đẳng thức, trong đó một trong hai giá trị có thể là null:

/**
 * Compare two values using equals(), after checking for null.
 * @param thisValue (may be null)
 * @param otherValue (may be null)
 * @return True if they are both null or if equals() returns true
 */
public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = thisValue.equals(otherValue);
  }
  return result;
}

Mã này ủy thác công việc của equals()phương thức trong trường hợp thisValue không null. Nhưng nó giả định equals()phương thức đáp ứng chính xác hợp đồng equals()bằng cách xử lý đúng một tham số null.

Một đồng nghiệp đã phản đối mã của tôi, nói với tôi rằng nhiều lớp của chúng tôi có equals()các phương thức lỗi không kiểm tra null, vì vậy tôi nên đưa kiểm tra đó vào phương thức này. Thật đáng tranh luận nếu điều này là khôn ngoan, hoặc nếu chúng ta nên buộc lỗi, vì vậy chúng ta có thể phát hiện và sửa nó, nhưng tôi đã hoãn lại với đồng nghiệp của mình và đưa vào một kiểm tra null, mà tôi đã đánh dấu bằng một nhận xét:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = otherValue != null && thisValue.equals(otherValue); // questionable null check
  }
  return result;
}

Việc kiểm tra bổ sung ở đây, other != nullchỉ cần thiết nếu equals()phương thức không kiểm tra null theo yêu cầu của hợp đồng.

Thay vì tham gia vào một cuộc tranh luận không có kết quả với đồng nghiệp của tôi về sự khôn ngoan của việc để mã lỗi ở trong cơ sở mã của chúng tôi, tôi chỉ cần đặt hai xác nhận vào mã. Các xác nhận này sẽ cho tôi biết, trong giai đoạn phát triển, nếu một trong các lớp của chúng tôi không thực hiện equals()đúng, vì vậy tôi có thể sửa nó:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
    assert otherValue == null || otherValue.equals(null) == false;
  } else {
    result = otherValue != null && thisValue.equals(otherValue);
    assert thisValue.equals(null) == false;
  }
  return result;
}

Những điểm quan trọng cần ghi nhớ là:

  1. Các xác nhận chỉ là các công cụ giai đoạn phát triển.

  2. Điểm của một xác nhận là cho bạn biết nếu có lỗi, không chỉ trong mã của bạn, mà trong cơ sở mã của bạn . (Các xác nhận ở đây sẽ thực sự gắn cờ các lỗi trong các lớp khác.)

  3. Ngay cả khi đồng nghiệp của tôi tự tin rằng các lớp học của chúng tôi được viết đúng, các xác nhận ở đây vẫn sẽ hữu ích. Các lớp mới sẽ được thêm vào mà có thể không kiểm tra null và phương thức này có thể gắn cờ các lỗi đó cho chúng tôi.

  4. Trong quá trình phát triển, bạn phải luôn bật các xác nhận, ngay cả khi mã bạn đã viết không sử dụng các xác nhận. IDE của tôi được đặt để luôn luôn làm điều này theo mặc định cho bất kỳ thực thi mới nào.

  5. Các xác nhận không thay đổi hành vi của mã trong sản xuất, vì vậy đồng nghiệp của tôi rất vui khi kiểm tra null ở đó và phương thức này sẽ thực thi đúng ngay cả khi equals()phương thức bị lỗi. Tôi rất vui vì tôi sẽ nắm bắt được bất kỳ equals()phương pháp lỗi nào trong quá trình phát triển.

Ngoài ra, bạn nên kiểm tra chính sách xác nhận của mình bằng cách đưa vào một xác nhận tạm thời sẽ thất bại, do đó bạn có thể chắc chắn rằng bạn được thông báo, thông qua tệp nhật ký hoặc theo dõi ngăn xếp trong luồng đầu ra.


Những điểm hay về "ẩn một lỗi" và cách khẳng định phơi bày lỗi trong quá trình phát triển!
tộc

Không có kiểm tra nào trong số này chậm, vì vậy không có lý do gì để tắt chúng trong sản xuất. Chúng nên được chuyển đổi thành báo cáo ghi nhật ký, để bạn có thể phát hiện các vấn đề không xuất hiện trong "giai đoạn phát triển" của mình. (Thực sự, dù sao cũng không có giai đoạn phát triển nào. Sự phát triển kết thúc khi bạn quyết định ngừng duy trì mã của mình.)
Aleksandr Dubinsky

20

Rất nhiều câu trả lời tốt giải thích những gì asserttừ khóa làm, nhưng ít người trả lời câu hỏi thực sự, "khi nào nên assertsử dụng từ khóa trong cuộc sống thực?"

Câu trả lời: gần như không bao giờ .

Khẳng định, như một khái niệm, là tuyệt vời. Mã tốt có rất nhiều if (...) throw ...tuyên bố (và người thân của họ thích Objects.requireNonNullMath.addExact). Tuy nhiên, một số quyết định thiết kế đã hạn chế rất nhiều tiện ích của chính assert từ khóa .

Ý tưởng lái xe đằng sau asserttừ khóa là tối ưu hóa sớm và tính năng chính là có thể dễ dàng tắt tất cả các kiểm tra. Trong thực tế, assertkiểm tra được tắt theo mặc định.

Tuy nhiên, điều cực kỳ quan trọng là việc kiểm tra bất biến tiếp tục được thực hiện trong sản xuất. Điều này là do phạm vi kiểm tra hoàn hảo là không thể, và tất cả các mã sản xuất sẽ có lỗi mà các xác nhận sẽ giúp chẩn đoán và giảm thiểu.

Do đó, việc sử dụng if (...) throw ...nên được ưu tiên, giống như cần thiết để kiểm tra các giá trị tham số của các phương thức công khai và để ném IllegalArgumentException.

Đôi khi, người ta có thể bị cám dỗ để viết một kiểm tra bất biến mà mất một thời gian dài không mong muốn để xử lý (và thường được gọi là đủ để nó có vấn đề). Tuy nhiên, kiểm tra như vậy sẽ làm chậm thử nghiệm cũng là điều không mong muốn. Kiểm tra tốn thời gian như vậy thường được viết dưới dạng kiểm tra đơn vị. Tuy nhiên, đôi khi nó có thể có ý nghĩa để sử dụng assertcho lý do này.

Đừng sử dụng assertđơn giản vì nó sạch hơn và đẹp hơn if (...) throw ...(và tôi nói điều đó với nỗi đau rất lớn, vì tôi thích sạch sẽ và xinh đẹp). Nếu bạn không thể tự giúp mình và có thể kiểm soát cách ứng dụng của bạn được khởi chạy, thì hãy sử dụng assertnhưng luôn bật các xác nhận trong sản xuất. Phải thừa nhận rằng đây là những gì tôi có xu hướng làm. Tôi đang thúc đẩy một chú thích lombok sẽ gây ra asserthành động như thế nào hơn if (...) throw .... Bình chọn cho nó ở đây.

. trả giá.)


2
@aberglas Một mệnh đề bắt tất cả là catch (Throwable t). Không có lý do gì để không cố gắng bẫy, đăng nhập hoặc thử lại / khôi phục từ OutOfMemoryError, AssertsError, v.v.
Alexanderr Dubinsky

1
Tôi đã bắt và phục hồi từ OutOfMemoryError.
MiguelMunoz

1
Tôi không đồng ý. Nhiều xác nhận của tôi được sử dụng để đảm bảo API của tôi được gọi chính xác. Ví dụ, tôi có thể viết một phương thức riêng chỉ nên được gọi khi một đối tượng giữ khóa. Nếu một nhà phát triển khác gọi phương thức đó từ một phần của mã không khóa đối tượng, xác nhận sẽ cho họ biết ngay rằng họ đã mắc lỗi. Có rất nhiều sai lầm như thế này có thể, chắc chắn, bị mắc kẹt trong giai đoạn phát triển, và khẳng định là rất hữu ích trong những trường hợp này.
MiguelMunoz

2
@MiguelMunoz Trong câu trả lời của tôi, tôi đã nói rằng ý tưởng về sự khẳng định là rất tốt. Đó là việc thực hiện các asserttừ khóa là xấu. Tôi sẽ chỉnh sửa câu trả lời của mình để làm rõ hơn rằng tôi đang đề cập đến từ khóa chứ không phải khái niệm.
Alexanderr Dubinsky

2
Tôi thích thực tế là nó ném Ass AssError thay vì Exception. Quá nhiều nhà phát triển vẫn chưa biết rằng họ không nên bắt Ngoại lệ nếu mã chỉ có thể ném thứ gì đó như IOException. Tôi đã có lỗi trong mã của mình bị nuốt hoàn toàn vì ai đó đã bắt gặp Ngoại lệ. Khẳng định không bị mắc vào cái bẫy này. Các ngoại lệ dành cho các tình huống mà bạn mong đợi sẽ thấy trong mã sản xuất. Đối với việc đăng nhập, bạn cũng nên ghi lại tất cả các lỗi của mình, mặc dù lỗi rất hiếm. Ví dụ, bạn có thực sự muốn để OutOfMemoryError vượt qua mà không đăng nhập không?
MiguelMunoz

14

Đây là trường hợp sử dụng phổ biến nhất. Giả sử bạn đang chuyển sang giá trị enum:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
}

Miễn là bạn xử lý mọi trường hợp, bạn ổn. Nhưng một ngày nào đó, ai đó sẽ thêm fig vào enum của bạn và quên thêm nó vào câu lệnh switch của bạn. Điều này tạo ra một lỗi có thể gặp khó khăn để bắt, bởi vì các hiệu ứng sẽ không được cảm nhận cho đến khi bạn rời khỏi câu lệnh chuyển đổi. Nhưng nếu bạn viết công tắc của mình như thế này, bạn có thể bắt nó ngay lập tức:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
  default:
    assert false : "Missing enum value: " + fruit;
}

4
Đó là lý do tại sao bạn nên bật cảnh báo và cảnh báo được coi là lỗi. Bất kỳ trình biên dịch tốt nửa chừng nào cũng có khả năng cho bạn biết, nếu bạn cho phép nó nói với bạn rằng bạn đang thiếu một kiểm tra enum, và nó sẽ làm như vậy vào thời gian biên dịch, tốt hơn nhiều so với (có lẽ, một ngày) tìm ra tại thời gian chạy
Mike Nakis

9
tại sao sử dụng một khẳng định ở đây chứ không phải là một ngoại lệ của một số loại, ví dụ, một ngoại lệ bất hợp pháp?
liltitus27

4
Điều này sẽ đưa ra một AssertionErrorxác nhận nếu được kích hoạt ( -ea). Hành vi mong muốn trong sản xuất là gì? Một thảm họa im lặng và tiềm năng sau này trong vụ hành quyết? Chắc là không. Tôi sẽ đề nghị một rõ ràng throw new AssertionError("Missing enum value: " + fruit);.
aioobe

1
Có một lý lẽ tốt được đưa ra khi chỉ cần ném Ass AssEEror. Đối với hành vi đúng đắn trong sản xuất, toàn bộ quan điểm khẳng định là giữ cho điều này không xảy ra trong sản xuất. Các xác nhận là một công cụ giai đoạn phát triển để bắt lỗi, có thể dễ dàng xóa khỏi mã sản xuất. Trong trường hợp này, không có lý do để loại bỏ nó khỏi mã sản xuất. Nhưng trong nhiều trường hợp, kiểm tra tính toàn vẹn có thể làm mọi thứ chậm lại. Bằng cách đặt các thử nghiệm này bên trong các xác nhận không được sử dụng trong mã sản xuất, bạn có thể tự do viết các thử nghiệm kỹ lưỡng mà không phải lo lắng rằng chúng sẽ làm chậm mã sản xuất của bạn.
MiguelMunoz

Điều này dường như là sai. IMHO bạn không nên sử dụng defaultđể trình biên dịch có thể cảnh báo bạn về các trường hợp mất tích. Bạn có thể returnthay vì break(điều này có thể cần trích xuất phương thức) và sau đó xử lý trường hợp bị thiếu sau khi chuyển đổi. Bằng cách này bạn có được cả cảnh báo và cơ hội assert.
maaartinus

12

Các xác nhận được sử dụng để kiểm tra các điều kiện hậu và "không bao giờ thất bại" các điều kiện trước. Mã chính xác không bao giờ thất bại trong một xác nhận; khi họ kích hoạt, họ sẽ chỉ ra một lỗi (hy vọng tại một địa điểm gần với vị trí thực tế của vấn đề).

Một ví dụ về khẳng định có thể là kiểm tra xem một nhóm phương thức cụ thể được gọi theo đúng thứ tự chưa (ví dụ: hasNext()được gọi trước next()trong một Iterator).


1
Bạn không phải gọi hasNext () trước next ().
DJClayworth

6
@DJClayworth: Bạn cũng không cần phải tránh kích hoạt xác nhận. :-)
Donal Fellows

8

Từ khóa khẳng định trong Java làm gì?

Chúng ta hãy nhìn vào mã byte được biên dịch.

Chúng tôi sẽ kết luận rằng:

public class Assert {
    public static void main(String[] args) {
        assert System.currentTimeMillis() == 0L;
    }
}

tạo ra gần như chính xác mã byte như:

public class Assert {
    static final boolean $assertionsDisabled =
        !Assert.class.desiredAssertionStatus();
    public static void main(String[] args) {
        if (!$assertionsDisabled) {
            if (System.currentTimeMillis() != 0L) {
                throw new AssertionError();
            }
        }
    }
}

nơi Assert.class.desiredAssertionStatus()truekhi-ea được truyền trên dòng lệnh, và sai khác.

Chúng tôi sử dụng System.currentTimeMillis()để đảm bảo rằng nó sẽ không được tối ưu hóa (assert true; đã).

Trường tổng hợp được tạo để Java chỉ cần gọi Assert.class.desiredAssertionStatus()một lần khi tải và sau đó lưu trữ kết quả ở đó. Xem thêm: Ý nghĩa của "tổng hợp tĩnh" là gì?

Chúng tôi có thể xác minh rằng với:

javac Assert.java
javap -c -constants -private -verbose Assert.class

Với Oracle JDK 1.8.0_45, một trường tĩnh tổng hợp đã được tạo (xem thêm: Ý nghĩa của "tổng hợp tĩnh" là gì? ):

static final boolean $assertionsDisabled;
  descriptor: Z
  flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

cùng với một trình khởi tạo tĩnh:

 0: ldc           #6                  // class Assert
 2: invokevirtual #7                  // Method java/lang Class.desiredAssertionStatus:()Z
 5: ifne          12
 8: iconst_1
 9: goto          13
12: iconst_0
13: putstatic     #2                  // Field $assertionsDisabled:Z
16: return

và phương pháp chính là:

 0: getstatic     #2                  // Field $assertionsDisabled:Z
 3: ifne          22
 6: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J
 9: lconst_0
10: lcmp
11: ifeq          22
14: new           #4                  // class java/lang/AssertionError
17: dup
18: invokespecial #5                  // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return

Chúng tôi kết luận rằng:

  • không có hỗ trợ cấp độ mã byte cho assert : đó là một khái niệm ngôn ngữ Java
  • assertcó thể được mô phỏng khá tốt với các thuộc tính hệ thống -Pcom.me.assert=trueđể thay thế -eatrên dòng lệnh và a throw new AssertionError().

2
Vì vậy, catch (Throwable t)điều khoản có thể bắt vi phạm khẳng định quá? Đối với tôi, giới hạn tiện ích của họ chỉ trong trường hợp cơ thể của xác nhận là tốn thời gian, điều này rất hiếm.
Evgeni Sergeev

1
Tôi không chắc tại sao nó lại giới hạn tính hữu dụng của khẳng định. Bạn không bao giờ nên bắt một Ném, trừ những trường hợp rất hiếm. Nếu bạn cần phải bắt throwable nhưng muốn nó không bắt được các xác nhận, bạn có thể chỉ cần bắt AssertionErrorđầu tiên và suy nghĩ lại.
MiguelMunoz

7

Một ví dụ trong thế giới thực, từ một lớp Stack (từ Xác nhận trong các bài viết Java )

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}

80
Điều này sẽ được tán thành trong C: Một khẳng định là điều KHÔNG THỰC SỰ nên xảy ra - xuất hiện một ngăn xếp trống sẽ ném NoElementsException hoặc một cái gì đó dọc theo dòng. Xem trả lời của Donal.
Konerak

4
Tôi đồng ý. Mặc dù điều này được lấy từ một hướng dẫn chính thức, nó là một ví dụ tồi.
DJClayworth


7
Có lẽ có một rò rỉ bộ nhớ ở đó. Bạn nên đặt stack [num] = null; để cho GC thực hiện công việc của mình đúng cách.
H.Rabiee

3
Tôi nghĩ trong một phương thức riêng tư, sẽ là chính xác khi sử dụng một xác nhận, vì sẽ thật kỳ lạ khi có ngoại lệ cho một sự cố của một lớp hoặc phương thức. Trong một phương thức công khai, gọi nó từ một nơi nào đó bên ngoài, bạn thực sự không thể biết được mã khác sử dụng nó như thế nào. Nó có thực sự kiểm tra isEmpty () hay không? Bạn không biết.
Vlasec

7

Một xác nhận cho phép phát hiện các khiếm khuyết trong mã. Bạn có thể bật các xác nhận để kiểm tra và gỡ lỗi trong khi tắt chúng khi chương trình của bạn đang được sản xuất.

Tại sao khẳng định một cái gì đó khi bạn biết nó là sự thật? Nó chỉ đúng khi mọi thứ đang hoạt động đúng. Nếu chương trình có một khiếm khuyết, nó có thể không thực sự đúng. Phát hiện điều này sớm hơn trong quá trình cho phép bạn biết có gì đó không ổn.

Một asserttuyên bố có chứa tuyên bố này cùng với một Stringthông điệp tùy chọn .

Cú pháp cho một câu lệnh khẳng định có hai dạng:

assert boolean_expression;
assert boolean_expression: error_message;

Dưới đây là một số quy tắc cơ bản chi phối nơi xác nhận nên được sử dụng và nơi không nên sử dụng. Các xác nhận nên được sử dụng cho:

  1. Xác thực các tham số đầu vào của một phương thức riêng. KHÔNG cho các phương pháp công cộng. publicphương pháp nên ném ngoại lệ thường xuyên khi thông qua các tham số xấu.

  2. Bất cứ nơi nào trong chương trình để đảm bảo tính hợp lệ của một thực tế gần như chắc chắn là đúng.

Ví dụ: nếu bạn chắc chắn rằng nó sẽ chỉ là 1 hoặc 2, bạn có thể sử dụng một xác nhận như thế này:

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...
  1. Xác nhận các điều kiện bài ở cuối của bất kỳ phương pháp. Điều này có nghĩa, sau khi thực hiện logic nghiệp vụ, bạn có thể sử dụng các xác nhận để đảm bảo rằng trạng thái bên trong của các biến hoặc kết quả của bạn phù hợp với những gì bạn mong đợi. Ví dụ, một phương thức mở một ổ cắm hoặc một tệp có thể sử dụng một xác nhận ở cuối để đảm bảo rằng ổ cắm hoặc tệp thực sự được mở.

Các xác nhận không nên được sử dụng cho:

  1. Xác thực các tham số đầu vào của một phương thức công khai. Vì các xác nhận có thể không luôn luôn được thực thi, nên sử dụng cơ chế ngoại lệ thông thường.

  2. Xác thực các ràng buộc trên một cái gì đó được nhập bởi người dùng. Giống như trên.

  3. Không nên được sử dụng cho các tác dụng phụ.

Ví dụ, đây không phải là một cách sử dụng đúng vì ở đây khẳng định được sử dụng cho tác dụng phụ của việc gọi doSomething()phương thức.

public boolean doSomething() {
...    
}
public void someMethod() {       
assert doSomething(); 
}

Trường hợp duy nhất mà điều này có thể được biện minh là khi bạn đang cố gắng tìm hiểu xem liệu các xác nhận có được bật trong mã của bạn hay không:   

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}

5

Ngoài tất cả các câu trả lời tuyệt vời được cung cấp ở đây, hướng dẫn lập trình Java SE 7 chính thức có một hướng dẫn khá súc tích về cách sử dụng assert; với một vài ví dụ điển hình về việc khi nào đó là một ý tưởng tốt (và quan trọng là xấu) để sử dụng các xác nhận và cách nó khác với việc ném ngoại lệ.

Liên kết


1
Tôi đồng ý. Bài viết có nhiều ví dụ tuyệt vời. Tôi đặc biệt thích một phương thức để đảm bảo một phương thức chỉ được gọi khi đối tượng giữ khóa.
MiguelMunoz

4

Khẳng định là rất hữu ích khi phát triển. Bạn sử dụng nó khi điều gì đó không thể xảy ra nếu mã của bạn hoạt động chính xác. Nó dễ sử dụng và có thể tồn tại trong mã mãi mãi, vì nó sẽ bị tắt trong cuộc sống thực.

Nếu có bất kỳ cơ hội nào mà tình trạng có thể xảy ra trong cuộc sống thực, thì bạn phải xử lý nó.

Tôi thích nó, nhưng không biết làm thế nào để bật nó trong Eclipse / Android / ADT. Nó dường như được tắt ngay cả khi gỡ lỗi. (Có một luồng về vấn đề này, nhưng nó đề cập đến 'Java vm', không xuất hiện trong Cấu hình chạy ADT).


1
Để bật xác nhận trong IDE nhật thực, vui lòng theo dõi tutoringcenter.cs.usfca.edu/resource/
mẹo

Tôi không nghĩ có cách nào để bật các xác nhận trong Android. Điều này rất đáng thất vọng.
MiguelMunoz

3

Đây là một khẳng định tôi đã viết trong một máy chủ cho dự án Hibernate / SQL. Một bean thực thể có hai thuộc tính boolean hiệu quả, được gọi là isActive và isDefault. Mỗi có thể có một giá trị "Y" hoặc "N" hoặc null, được coi là "N". Chúng tôi muốn đảm bảo ứng dụng khách trình duyệt được giới hạn ở ba giá trị này. Vì vậy, trong setters của tôi cho hai thuộc tính này, tôi đã thêm khẳng định này:

assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;

Lưu ý những điều sau.

  1. Khẳng định này chỉ dành cho giai đoạn phát triển. Nếu khách hàng gửi một giá trị xấu, chúng tôi sẽ bắt sớm và sửa nó, rất lâu trước khi chúng tôi đạt được sản xuất. Các xác nhận là dành cho các khuyết điểm mà bạn có thể nắm bắt sớm.

  2. Khẳng định này là chậm và không hiệu quả. Không sao đâu. Khẳng định là miễn phí để được chậm. Chúng tôi không quan tâm vì chúng là công cụ chỉ phát triển. Điều này sẽ không làm chậm mã sản xuất vì các xác nhận sẽ bị vô hiệu hóa. (Có một số bất đồng về điểm này, mà tôi sẽ nói sau.) Điều này dẫn đến điểm tiếp theo của tôi.

  3. Khẳng định này không có tác dụng phụ. Tôi có thể đã kiểm tra giá trị của mình so với Bộ cuối cùng tĩnh không thể thay đổi, nhưng bộ đó sẽ tồn tại trong sản xuất, nơi nó sẽ không bao giờ được sử dụng.

  4. Khẳng định này tồn tại để xác minh hoạt động đúng của khách hàng. Vì vậy, khi chúng tôi đạt được sản xuất, chúng tôi sẽ chắc chắn rằng khách hàng đang hoạt động đúng, vì vậy chúng tôi có thể tắt xác nhận một cách an toàn.

  5. Một số người hỏi điều này: Nếu khẳng định không cần thiết trong sản xuất, tại sao bạn không loại bỏ chúng khi bạn hoàn thành? Bởi vì bạn vẫn sẽ cần chúng khi bạn bắt đầu làm việc trên phiên bản tiếp theo.

Một số người đã lập luận rằng bạn không bao giờ nên sử dụng các xác nhận, bởi vì bạn không bao giờ có thể chắc chắn rằng tất cả các lỗi đã biến mất, vì vậy bạn cần phải giữ chúng xung quanh ngay cả trong sản xuất. Và do đó, không có điểm nào trong việc sử dụng tuyên bố khẳng định, vì lợi thế duy nhất để khẳng định là bạn có thể tắt chúng đi. Do đó, theo suy nghĩ này, bạn nên (gần như) không bao giờ sử dụng các khẳng định. Tôi không đồng ý. Điều chắc chắn là nếu một bài kiểm tra thuộc về sản xuất, bạn không nên sử dụng một xác nhận. Nhưng bài kiểm tra này không thuộc về sản xuất. Đây là một lỗi để bắt lỗi không thể sản xuất được, vì vậy nó có thể bị tắt một cách an toàn khi bạn hoàn thành.

BTW, tôi có thể đã viết nó như thế này:

assert value == null || value.equals("Y") || value.equals("N") : value;

Điều này tốt cho chỉ ba giá trị, nhưng nếu số lượng giá trị có thể lớn hơn, phiên bản Hashset trở nên thuận tiện hơn. Tôi đã chọn phiên bản Hashset để đưa ra quan điểm của mình về hiệu quả.


Tôi mạnh mẽ nghi ngờ rằng việc sử dụng như một nhỏ một HashSetmang lại bất kỳ lợi thế tốc độ hơn một ArrayList. Hơn nữa, các sáng tạo tập hợp và danh sách thống trị thời gian tra cứu. Họ sẽ ổn khi sử dụng hằng số. Tất cả đã nói, +1.
maaartinus

Tất cả đều đúng. Tôi đã làm điều này một cách không hiệu quả để minh họa quan điểm của tôi rằng các xác nhận là miễn phí bị chậm. Điều này có thể được thực hiện hiệu quả hơn, nhưng có những cái khác không thể. Trong một cuốn sách xuất sắc có tên "Viết mã vững chắc", Steve Maguire kể về một khẳng định trong Microsoft Excel để kiểm tra mã cập nhật gia tăng mới bỏ qua các ô không nên thay đổi. Mỗi khi người dùng thực hiện thay đổi, xác nhận sẽ tính toán lại toàn bộ bảng tính để đảm bảo kết quả khớp với các tính năng theo tính năng cập nhật gia tăng. Nó thực sự làm chậm phiên bản gỡ lỗi, nhưng họ đã bắt được tất cả các lỗi sớm.
MiguelMunoz

Hoàn toàn đồng ý. Các xác nhận là loại thử nghiệm - chúng kém linh hoạt hơn các thử nghiệm thông thường, nhưng chúng có thể bao gồm các phương pháp riêng tư và chúng rẻ hơn nhiều để viết. Tôi sẽ cố gắng sử dụng chúng nhiều hơn nữa.
maaartinus

2

Xác nhận về cơ bản được sử dụng để gỡ lỗi ứng dụng hoặc nó được sử dụng để thay thế xử lý ngoại lệ cho một số ứng dụng để kiểm tra tính hợp lệ của ứng dụng.

Khẳng định hoạt động trong thời gian chạy. Một ví dụ đơn giản, có thể giải thích toàn bộ khái niệm rất đơn giản, nằm ở đây - Từ khóa khẳng định làm gì trong Java? (WikiAnswers).


2

Các xác nhận được tắt theo mặc định. Để kích hoạt chúng, chúng ta phải chạy chương trình với -eacác tùy chọn (độ chi tiết có thể thay đổi). Ví dụ , java -ea AssertionsDemo.

Có hai định dạng để sử dụng các xác nhận:

  1. Đơn giản: vd. assert 1==2; // This will raise an AssertionError.
  2. Tốt hơn: assert 1==2: "no way.. 1 is not equal to 2"; Điều này sẽ đưa ra một AssertsError với thông báo được hiển thị quá và do đó tốt hơn. Mặc dù cú pháp thực tế là assert expr1:expr2nơi expr2 có thể là bất kỳ biểu thức nào trả về giá trị, tôi đã sử dụng nó thường xuyên hơn chỉ để in một tin nhắn.

1

Tóm lại (và điều này đúng với nhiều ngôn ngữ không chỉ Java):

"khẳng định" chủ yếu được sử dụng như một công cụ gỡ lỗi của các nhà phát triển phần mềm trong quá trình gỡ lỗi. Tin nhắn khẳng định sẽ không bao giờ xuất hiện. Nhiều ngôn ngữ cung cấp tùy chọn thời gian biên dịch sẽ khiến tất cả các "xác nhận" bị bỏ qua, để sử dụng trong việc tạo mã "sản xuất".

"ngoại lệ" là một cách thuận tiện để xử lý tất cả các loại điều kiện lỗi, cho dù chúng có biểu hiện lỗi logic hay không, bởi vì, nếu bạn gặp phải tình trạng lỗi để bạn không thể tiếp tục, bạn có thể chỉ cần "ném chúng lên không trung" "Từ bất cứ nơi nào bạn đến, mong đợi một người khác ở ngoài đó sẵn sàng" bắt "họ. Kiểm soát được chuyển trong một bước, trực tiếp từ mã đã ném ngoại lệ, thẳng đến mitt của người bắt. (Và người bắt có thể thấy toàn bộ phần sau của các cuộc gọi đã diễn ra.)

Hơn nữa, những người gọi chương trình con đó không phải kiểm tra xem liệu chương trình con có thành công hay không: "nếu chúng ta ở đây bây giờ, nó đã thành công, vì nếu không nó sẽ ném một ngoại lệ và chúng ta sẽ không ở đây ngay bây giờ!" Chiến lược đơn giản này làm cho thiết kế mã và gỡ lỗi dễ dàng hơn nhiều.

Các ngoại lệ thuận tiện cho phép các điều kiện lỗi nghiêm trọng là chính chúng: "ngoại lệ cho quy tắc". Và, để chúng được xử lý bởi một đường dẫn mã cũng là "một ngoại lệ đối với quy tắc ... " bóng bay! "


1

Các xác nhận là các kiểm tra có thể bị tắt. Chúng hiếm khi được sử dụng. Tại sao?

  • Chúng không được sử dụng để kiểm tra các đối số phương thức công khai vì bạn không có quyền kiểm soát chúng.
  • Chúng không nên được sử dụng cho các kiểm tra đơn giản như kiểm tra như result != nullvậy rất nhanh và hầu như không có gì để lưu.

Vì vậy, những gì còn lại? Kiểm tra tốn kém cho các điều kiện thực sự dự kiến là đúng. Một ví dụ điển hình là các bất biến của cấu trúc dữ liệu như cây RB. Trên thực tế, trong ConcurrentHashMapJDK8, có một vài khẳng định có ý nghĩa như vậy cho TreeNodes.

  • Bạn thực sự không muốn bật chúng trong sản xuất vì chúng có thể dễ dàng chi phối thời gian chạy.
  • Bạn có thể muốn bật hoặc tắt chúng trong khi thử nghiệm.
  • Bạn chắc chắn muốn bật chúng khi làm việc với mã.

Đôi khi, séc không thực sự đắt tiền, nhưng đồng thời, bạn khá chắc chắn, nó sẽ vượt qua. Trong mã của tôi, có ví dụ,

assert Sets.newHashSet(userIds).size() == userIds.size();

nơi tôi khá chắc chắn rằng danh sách tôi vừa tạo có các yếu tố độc đáo, nhưng tôi muốn ghi lại và kiểm tra lại nó.


0

Về cơ bản, "khẳng định đúng" sẽ vượt qua và "khẳng định sai" sẽ thất bại. Hãy xem cách nó sẽ hoạt động:

public static void main(String[] args)
{
    String s1 = "Hello";
    assert checkInteger(s1);
}

private static boolean checkInteger(String s)
{
    try {
        Integer.parseInt(s);
        return true;
    }
    catch(Exception e)
    {
        return false;
    }
}

-8

assertlà một từ khóa. Nó được giới thiệu trong JDK 1.4. Có hai loại asserts

  1. assertBáo cáo rất đơn giản
  2. Những assertcâu nói đơn giản .

Theo mặc định, tất cả các assertcâu lệnh sẽ không được thực thi. Nếu một asserttuyên bố nhận được sai, thì nó sẽ tự động đưa ra một lỗi xác nhận.


1
Nó không cung cấp bất kỳ ví dụ thực tế nào, đó là mục tiêu của câu hỏi
rubenafo

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.