Tại sao Options.get () mà không gọi isPftime () xấu, nhưng không phải là iterator.next ()?


23

Khi sử dụng luồng Java8 mới api để tìm một phần tử cụ thể trong bộ sưu tập, tôi viết mã như sau:

    String theFirstString = myCollection.stream()
            .findFirst()
            .get();

Ở đây IntelliJ cảnh báo rằng get () được gọi mà không cần kiểm tra isPftime () trước.

Tuy nhiên, mã này:

    String theFirstString = myCollection.iterator().next();

... không mang lại cảnh báo.

Có một số khác biệt sâu sắc giữa hai kỹ thuật, làm cho cách tiếp cận phát trực tuyến trở nên "nguy hiểm" hơn theo một cách nào đó, rằng điều quan trọng là không bao giờ gọi get () mà không gọi isPftime () trước? Googling xung quanh, tôi tìm thấy các bài báo nói về cách các lập trình viên bất cẩn với Tùy chọn <> và cho rằng họ có thể gọi get () bất cứ khi nào.

Điều đó là, tôi muốn có một ngoại lệ ném vào tôi càng gần càng tốt đến nơi mã của tôi bị lỗi, trong trường hợp này sẽ là nơi tôi đang gọi get () khi không tìm thấy phần tử nào. Tôi không muốn phải viết những bài luận vô dụng như:

    Optional<String> firstStream = myCollection.stream()
            .findFirst();
    if (!firstStream.isPresent()) {
        throw new IllegalStateException("There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>");
    }
    String theFirstString = firstStream.get();

... Trừ khi có một số nguy hiểm trong việc kích động các ngoại lệ từ get () mà tôi không biết?


Sau khi đọc phản hồi từ Karl Bielefeldt và một nhận xét từ Hulk, tôi nhận ra mã ngoại lệ của tôi ở trên là một chút vụng về, đây là một cái gì đó tốt hơn:

    String errorString = "There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>";
    String firstString = myCollection.stream()
            .findFirst()
            .orElseThrow(() -> new IllegalStateException(errorString));

Điều này có vẻ hữu ích hơn và có thể sẽ trở nên tự nhiên ở nhiều nơi. Tôi vẫn cảm thấy mình sẽ muốn có thể chọn một yếu tố từ danh sách mà không phải xử lý tất cả những điều này, nhưng điều đó có thể là tôi cần phải làm quen với các loại cấu trúc này.


7
Phần hrowing ngoại lệ mẫu của ví dụ cuối cùng của bạn có thể được viết ngắn gọn hơn rất nhiều nếu bạn sử dụng orElseThrow - và rõ ràng với tất cả các độc giả rằng bạn có ý định ném ngoại lệ.
Hulk

Câu trả lời:


12

Trước hết, hãy xem xét rằng việc kiểm tra là phức tạp và có thể tương đối tốn thời gian để làm. Nó đòi hỏi một số phân tích tĩnh và có thể có khá nhiều mã giữa hai cuộc gọi.

Thứ hai, cách sử dụng thành ngữ của hai cấu trúc rất khác nhau. Tôi lập trình toàn thời gian trong Scala, sử dụng Optionsthường xuyên hơn Java. Tôi không thể nhớ lại một trường hợp duy nhất mà tôi cần sử dụng .get()trên Option. Bạn hầu như luôn luôn phải trả lại Optionaltừ chức năng của bạn, hoặc đang sử dụng orElse()thay thế. Đây là những cách vượt trội hơn nhiều để xử lý các giá trị còn thiếu hơn là ném ngoại lệ.

Bởi vì .get()hiếm khi được gọi trong sử dụng bình thường, thật hợp lý khi IDE bắt đầu kiểm tra phức tạp khi thấy nó .get()để xem liệu nó có được sử dụng đúng cách hay không. Ngược lại, iterator.next()là cách sử dụng đúng và phổ biến của một trình vòng lặp.

Nói cách khác, đó không phải là vấn đề của một người xấu và người kia thì không, đó là vấn đề phổ biến, đó là vấn đề cơ bản đối với hoạt động của nó và rất có khả năng đưa ra một ngoại lệ trong quá trình thử nghiệm của nhà phát triển, và người khác hiếm khi được sử dụng trường hợp có thể không được thực hiện đủ để đưa ra một ngoại lệ trong quá trình thử nghiệm của nhà phát triển. Đó là những trường hợp bạn muốn IDE cảnh báo bạn.


Có thể tôi cần tìm hiểu thêm về doanh nghiệp Tùy chọn này - Tôi hầu như đã xem công cụ java8 là một cách hay để lập bản đồ danh sách các đối tượng mà chúng tôi làm rất nhiều trong ứng dụng web của mình. Vì vậy, bạn phải gửi tùy chọn <> s ở mọi nơi? Điều đó sẽ làm quen với một số, nhưng đó là một bài SE khác! :)
TV của Frank

@ TV's Frank Ít hơn là họ phải ở khắp mọi nơi và hơn nữa họ cho phép bạn viết các hàm biến đổi giá trị của loại được chứa và bạn xâu chuỗi chúng bằng maphoặc flatMap. OptionalChủ yếu là một cách tuyệt vời để tránh phải đóng dấu mọi thứ bằng nullséc.
Morgen

15

Lớp Tùy chọn được dự định sẽ được sử dụng khi không biết liệu nó có chứa hay không. Cảnh báo tồn tại để thông báo cho các lập trình viên rằng có một đường dẫn mã có thể bổ sung mà họ có thể xử lý một cách duyên dáng. Ý tưởng là để tránh các ngoại lệ bị ném đi. Trường hợp sử dụng mà bạn trình bày không phải là thiết kế dành cho nó, và do đó cảnh báo không liên quan đến bạn - nếu bạn thực sự muốn ném ngoại lệ, hãy tiếp tục và vô hiệu hóa nó (mặc dù chỉ dành cho dòng này, không phải trên toàn cầu - bạn nên đưa ra quyết định về việc có hay không xử lý trường hợp không có mặt trên cơ sở theo trường hợp, và cảnh báo sẽ nhắc nhở bạn thực hiện việc này.

Có thể cho rằng, một cảnh báo tương tự nên được tạo ra cho các trình vòng lặp. Tuy nhiên, nó chỉ đơn giản là chưa bao giờ được thực hiện, và thêm một cái bây giờ có thể sẽ gây ra quá nhiều cảnh báo trong các dự án hiện tại là một ý tưởng tốt.

Cũng lưu ý rằng việc ném ngoại lệ của riêng bạn có thể hữu ích hơn chỉ là ngoại lệ mặc định, vì bao gồm mô tả về yếu tố nào được dự kiến ​​tồn tại nhưng không thể rất hữu ích trong việc gỡ lỗi vào thời điểm sau.


6

Tôi đồng ý rằng không có sự khác biệt cố hữu trong những điều này, tuy nhiên, tôi muốn chỉ ra một lỗ hổng lớn hơn.

Trong cả hai trường hợp, bạn có một loại, cung cấp một phương thức không được đảm bảo để hoạt động và bạn phải gọi một phương thức khác trước đó. Việc IDE / trình biên dịch / etc của bạn cảnh báo bạn về một trường hợp này hay trường hợp khác chỉ phụ thuộc vào việc nó có hỗ trợ trường hợp này hay không và bạn có cấu hình nó để làm như vậy không.

Mặc dù vậy, cả hai đoạn mã đều có mùi mã. Những biểu thức gợi ý các lỗi thiết kế cơ bản và mang lại mã giòn.

Tất nhiên, bạn đúng khi muốn thất bại nhanh chóng, nhưng giải pháp ít nhất là đối với tôi, không thể chỉ là nhún vai và ném tay lên không trung (hoặc một ngoại lệ lên chồng).

Bộ sưu tập của bạn có thể được biết là không trống bây giờ, nhưng tất cả những gì bạn thực sự có thể dựa vào là loại của nó: Collection<T>- và điều đó không đảm bảo cho bạn không trống rỗng. Mặc dù bây giờ bạn biết nó không trống, một yêu cầu thay đổi có thể dẫn đến việc trả trước mã bị thay đổi và đột nhiên mã của bạn bị tấn công bởi một bộ sưu tập trống.

Bây giờ bạn có thể tự do đẩy lùi và nói với những người khác rằng bạn phải có một danh sách không trống. Bạn có thể vui mừng khi bạn thấy rằng "lỗi" nhanh. Tuy nhiên, bạn có một phần mềm bị hỏng và cần thay đổi mã của bạn.

Đối chiếu điều này với cách tiếp cận thực tế hơn: Vì bạn nhận được một bộ sưu tập và nó có thể trống, bạn buộc mình phải giải quyết trường hợp đó bằng cách sử dụng # map, #orElse hoặc bất kỳ phương pháp nào khác, ngoại trừ #get.

Trình biên dịch thực hiện càng nhiều công việc càng tốt cho bạn để bạn có thể dựa càng nhiều càng tốt vào hợp đồng kiểu tĩnh để có được a Collection<T>. Khi mã của bạn không bao giờ vi phạm hợp đồng này (chẳng hạn như thông qua một trong hai chuỗi cuộc gọi có mùi này), mã của bạn sẽ dễ vỡ hơn nhiều khi thay đổi điều kiện môi trường.

Trong trường hợp trên, một thay đổi mang lại một bộ sưu tập đầu vào trống sẽ không ảnh hưởng gì đến mã của bạn. Nó chỉ là một đầu vào hợp lệ và do đó vẫn được xử lý chính xác.

Cái giá phải trả là bạn phải đầu tư thời gian vào các thiết kế tốt hơn, trái ngược với a) rủi ro mã giòn hoặc b) làm phức tạp mọi thứ một cách không cần thiết bằng cách ném ngoại lệ xung quanh.

Trên một lưu ý cuối cùng: vì không phải tất cả các IDE / trình biên dịch đều cảnh báo về các cuộc gọi iterator (). Next () hoặc tùy chọn.get () mà không có kiểm tra trước, nên mã này thường được gắn cờ trong các đánh giá. Đối với những gì nó có giá trị, tôi sẽ không để một mã như vậy cho phép vượt qua đánh giá và yêu cầu một giải pháp sạch thay thế.


Tôi đoán tôi có thể đồng ý một chút về mùi mã - tôi đã làm ví dụ ở trên để thực hiện một bài viết ngắn. Một trường hợp hợp lệ khác có thể là lấy một phần tử bắt buộc từ danh sách, đây không phải là điều lạ xuất hiện trong ứng dụng IMO.
TV của Frank

Tại chỗ trên. "Chọn phần tử có thuộc tính p từ danh sách" -> list.stream (). Filter (p) .findFirst () .. và một lần nữa chúng ta buộc phải đặt câu hỏi "nếu nó không có trong đó thì sao?" .. bánh mì và bơ hàng ngày. Nếu đó là nhiệm vụ và "chỉ ở đó" - tại sao bạn lại giấu nó trong một loạt các thứ khác ở nơi đầu tiên?
Frank

4
Bởi vì có thể đó là một danh sách được tải từ cơ sở dữ liệu. Có thể danh sách này là một bộ sưu tập thống kê được tập hợp trong một số chức năng khác: chúng tôi biết chúng tôi có các đối tượng A, B và C, tôi muốn tìm số lượng bất cứ thứ gì cho B. Nhiều trường hợp hợp lệ (và trong ứng dụng của tôi, phổ biến).
TV của Frank

Tôi không biết về cơ sở dữ liệu của bạn, nhưng tôi không đảm bảo ít nhất một kết quả cho mỗi truy vấn. Tương tự cho các bộ sưu tập thống kê - ai sẽ nói chúng sẽ luôn trống rỗng? Tôi đồng ý, rằng thật đơn giản để lý do rằng nên có yếu tố này hoặc yếu tố đó, nhưng tôi thích gõ tĩnh hơn tài liệu hoặc tệ hơn một số hiểu biết sâu sắc từ một cuộc thảo luận.
Frank
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.