Tại sao sử dụng một ưu tiên tùy chọn để kiểm tra null biến?


25

Lấy hai ví dụ mã:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

Theo như tôi có thể nói sự khác biệt rõ ràng nhất là Tùy chọn yêu cầu tạo một đối tượng bổ sung.

Tuy nhiên, nhiều người đã bắt đầu nhanh chóng áp dụng Tùy chọn. Lợi thế của việc sử dụng tùy chọn so với kiểm tra null là gì?


4
bạn gần như không bao giờ muốn sử dụng isPftime, bạn thường nên sử dụng ifPftime hoặc map
jk.

6
@jk. Bởi vì các iftuyên bố rất ít ỏi trong thập kỷ trước và hiện tại mọi người đang sử dụng các khái niệm trừu tượng và lambdas.
dùng253751

2
@immibis Tôi đã từng có một cuộc tranh luận kéo dài về chủ đề này với một người dùng khác. Vấn đề là ở chỗ if(x.isPresent) fails_on_null(x.get)bạn thoát khỏi hệ thống loại và phải đảm bảo rằng mã sẽ không bị phá vỡ "trong đầu bạn" trong khoảng cách (được thừa nhận ngắn) giữa điều kiện và lệnh gọi hàm. Trong optional.ifPresent(fails_on_null)hệ thống loại đảm bảo điều này cho bạn, và bạn không phải lo lắng.
ziggystar

1
Lỗ hổng lớn trong Java khi sử dụng Optional.ifPresent(và các cấu trúc Java khác) là bạn chỉ có thể sửa đổi các biến cuối cùng có hiệu quả và không thể đưa ra các ngoại lệ được kiểm tra. Đó là lý do đủ để thường xuyên tránh ifPresent, không may.
Mike

Câu trả lời:


19

OptionalKhai thác hệ thống loại để thực hiện công việc mà bạn phải làm tất cả trong đầu: ghi nhớ liệu một tài liệu tham khảo có thể có hay không null. Điều này là tốt Thật thông minh khi để trình biên dịch xử lý công việc ma túy nhàm chán và dự trữ suy nghĩ của con người cho công việc thú vị, sáng tạo.

Không có Optional, mọi tham chiếu trong mã của bạn giống như một quả bom chưa nổ. Truy cập nó có thể làm một cái gì đó hữu ích, hoặc nếu không nó có thể chấm dứt chương trình của bạn với một ngoại lệ.

Với Tùy chọn và không có null, mọi quyền truy cập vào một tham chiếu bình thường đều thành công và mọi tham chiếu đến Tùy chọn đều thành công trừ khi nó không được đặt và bạn không thể kiểm tra điều đó. Đó là một chiến thắng lớn trong khả năng bảo trì.

Thật không may, hầu hết các ngôn ngữ hiện cung cấp Optionalchưa bị bãi bỏ null, vì vậy bạn chỉ có thể kiếm lợi từ khái niệm này bằng cách đưa ra một chính sách nghiêm ngặt "hoàn toàn không null, bao giờ". Do đó, Optionalví dụ Java không hấp dẫn như lý tưởng.


Vì câu trả lời của bạn là câu trả lời hàng đầu, nên có thể đáng để thêm việc sử dụng Lambdas như một lợi thế - cho bất kỳ ai đọc điều này trong tương lai (xem câu trả lời của tôi)
William Dunne

Hầu hết các ngôn ngữ hiện đại cung cấp các loại không nullable, Kotlin, Typecript, AFAIK.
Zen

5
IMO này được xử lý tốt hơn với chú thích @Nullable. Không có lý do để sử dụng Optionalchỉ để nhắc nhở bạn rằng giá trị có thể là null
Hilikus

18

Một Optionalcách gõ mạnh hơn vào các hoạt động có thể thất bại, như các câu trả lời khác đã đề cập, nhưng đó là điều xa vời với những điều thú vị nhất hoặc có giá trị Optionalsmang đến cho bàn. Hữu ích hơn nhiều là khả năng trì hoãn hoặc tránh kiểm tra lỗi và dễ dàng soạn ra nhiều thao tác có thể thất bại.

Xem xét nếu bạn có optionalbiến từ mã ví dụ của mình, thì bạn phải thực hiện hai bước bổ sung mà mỗi bước có thể có khả năng thất bại. Nếu bất kỳ bước nào trên đường đi không thành công, bạn muốn trả về một giá trị mặc định thay thế. Sử dụng Optionalschính xác, bạn kết thúc với một cái gì đó như thế này:

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

Với nulltôi, tôi đã phải kiểm tra ba lần nulltrước khi tiếp tục, điều này làm tăng thêm rất nhiều vấn đề phức tạp và bảo trì cho mã. Optionalscó kiểm tra được xây dựng trong flatMapvà các orElsechức năng.

Lưu ý tôi đã không gọi isPresentmột lần, mà bạn nên nghĩ là mùi mã khi sử dụng Optionals. Điều đó không nhất thiết có nghĩa là bạn không bao giờ nên sử dụng isPresent, chỉ là bạn nên xem xét kỹ lưỡng bất kỳ mã nào, để xem có cách nào tốt hơn không. Mặt khác, bạn đúng, bạn chỉ nhận được lợi ích an toàn loại cận biên khi sử dụng null.

Cũng lưu ý rằng tôi không lo lắng về việc đóng gói tất cả vào một chức năng, để bảo vệ các phần khác của mã khỏi các con trỏ null khỏi các kết quả trung gian. Nếu nó có ý nghĩa hơn khi đưa .orElse(defaultValue)chức năng của tôi vào một chức năng khác chẳng hạn, thì tôi có ít kinh nghiệm hơn về việc đặt nó ở đó, và việc soạn thảo các hoạt động giữa các chức năng khác nhau khi cần thiết sẽ dễ dàng hơn nhiều.


10

Nó nhấn mạnh khả năng null là một phản hồi hợp lệ, điều mà mọi người thường cho rằng (đúng hoặc sai) sẽ không được trả lại. Làm nổi bật vài lần khi null là hợp lệ cho phép bỏ qua việc xả rác mã của bạn với số lượng lớn các kiểm tra null vô nghĩa.

Lý tưởng đặt một biến thành null sẽ là một lỗi thời gian biên dịch ở bất cứ đâu nhưng trong một tùy chọn; loại bỏ ngoại lệ con trỏ null thời gian chạy. Rõ ràng là khả năng tương thích ngược lại loại trừ điều này, thật đáng buồn


4

Tôi sẽ cho bạn một ví dụ:

class UserRepository {
     User findById(long id);
}

Nó khá rõ ràng phương pháp này nhằm mục đích gì. Nhưng điều gì xảy ra nếu kho lưu trữ không chứa người dùng với id đã cho? Nó có thể ném một ngoại lệ hoặc có thể nó trả về null?

Ví dụ thứ hai:

class UserRepository {
     Optional<User> findById(long id);
}

Bây giờ, điều gì xảy ra ở đây nếu không có người dùng với id đã cho? Đúng vậy, bạn nhận được cá thể Options.absent () và bạn có thể kiểm tra nó với Options.isPftime (). Chữ ký phương thức với Tùy chọn rõ ràng hơn về hợp đồng của nó. Rõ ràng ngay lập tức, phương pháp được cho là hành xử như thế nào.

Nó cũng có một lợi ích khi đọc mã máy khách:

User user = userRepository.findById(userId).get();

Tôi đã đào tạo mắt của tôi để xem .get () như một mùi mã ngay lập tức. Nó có nghĩa là khách hàng cố tình bỏ qua hợp đồng của phương thức được gọi. Dễ dàng nhận thấy điều này hơn là không có Tùy chọn, vì trong trường hợp đó, bạn không biết ngay hợp đồng của phương thức được gọi là gì (cho dù nó có cho phép null hay không trả lại), vì vậy bạn cần kiểm tra trước.

Tùy chọn được sử dụng với quy ước rằng các phương thức có kiểu trả về Tùy chọn không bao giờ trả về null và thường với quy ước (ít mạnh hơn) mà phương thức không có kiểu trả về Tùy chọn không bao giờ trả về null (nhưng tốt hơn là chú thích nó bằng @Nonnull, @NotNull hoặc tương tự) .


3

An Optional<T>cho phép bạn có "thất bại" hoặc "không có kết quả" làm giá trị phản hồi / trả về hợp lệ cho các phương thức của bạn (ví dụ: nghĩ về việc tra cứu cơ sở dữ liệu). Sử dụng Optionalthay vì sử dụng nullđể chỉ ra thất bại / không có kết quả có một số lợi thế:

  • Nó nói rõ rằng "thất bại" một lựa chọn. Người sử dụng phương thức của bạn không phải đoán xem nullcó thể được trả lại hay không.
  • Giá trị null, ít nhất là theo ý kiến ​​của tôi, không được sử dụng để chỉ ra bất cứ điều gì vì ngữ nghĩa có thể không hoàn toàn rõ ràng. Nó nên được sử dụng để báo cho người thu gom rác rằng đối tượng được đề cập trước đó có thể được thu thập.
  • Các Optionallớp học cung cấp nhiều phương pháp tốt đẹp để có điều kiện làm việc với các giá trị (ví dụ ifPresent()) hoặc xác định giá trị mặc định một cách minh bạch ( orElse()).

Thật không may, nó không loại bỏ hoàn toàn nhu cầu nullkiểm tra mã vì Optionalbản thân đối tượng vẫn có thể null.


4
Đây không thực sự là những gì Tùy chọn dành cho. Để báo cáo lỗi, sử dụng một ngoại lệ. Nếu bạn không thể làm điều đó, sử dụng một loại, trong đó có cả một kết quả hoặc một mã lỗi .
James Youngman

Tôi rất không đồng ý. Tùy chọn.empty () là một cách tuyệt vời để thể hiện sự thất bại hoặc không tồn tại theo ý kiến ​​của tôi.
Truyền thuyết Bước sóng

@LegendLpm, tôi phải hoàn toàn không đồng ý trong trường hợp thất bại. Nếu một chức năng thất bại, tốt hơn là cho tôi một lời giải thích, không chỉ là một sự trống rỗng, "Tôi đã thất bại"
Winston Ewert

Xin lỗi tôi đồng ý, ý tôi là 'thất bại mong đợi' vì không tìm thấy nhân viên có tên 'x' trong khi tìm kiếm.
Truyền thuyết Bước sóng

3

Ngoài các câu trả lời khác, ưu điểm chính khác của Tùy chọn khi viết mã sạch là bạn có thể sử dụng biểu thức Lambda trong trường hợp có giá trị, như được hiển thị bên dưới:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));

2

Hãy xem xét phương pháp sau:

void dance(Person partner)

Bây giờ hãy xem một số mã gọi:

dance(null);

Điều này gây ra sự cố khó theo dõi ở một nơi khác, vì dancekhông mong đợi một giá trị rỗng.

dance(optionalPerson)

Điều này gây ra một lỗi biên dịch, vì khiêu vũ đã mong một Personkhông phải là mộtOptional<Person>

dance(optionalPerson.get())

Nếu tôi không có một người, điều này gây ra một ngoại lệ trên dòng này, tại thời điểm tôi cố gắng chuyển đổi Optional<Person>một cách bất hợp pháp thành Người. Điều quan trọng là không giống như vượt qua null, ngoại trừ dấu vết ở đây, không phải một số vị trí khác trong chương trình.

Để kết hợp tất cả lại với nhau: lạm dụng tùy chọn sản xuất dễ dàng hơn để theo dõi các vấn đề, lạm dụng null gây ra khó khăn để theo dõi các vấn đề.


1
Nếu bạn đang ném ngoại lệ khi gọi Optional.get()mã của mình bị viết sai - bạn gần như không bao giờ được gọi Tùy chọn. Quên mà không kiểm tra Optional.isPresent()trước (như được chỉ ra trong OP) hoặc bạn có thể viết optionalPersion.ifPresent(myObj::dance).
Chris Cooper

@QmunkE, vâng, bạn chỉ nên gọi Tùy chọn.get () nếu bạn đã biết rằng tùy chọn không trống (vì bạn đã gọi isPftime hoặc vì thuật toán của bạn đảm bảo điều đó). Tuy nhiên, tôi lo lắng về việc mọi người kiểm tra một cách mù quáng là Có mặt và sau đó bỏ qua các giá trị vắng mặt tạo ra một loạt các thất bại thầm lặng sẽ là một nỗi đau để theo dõi.
Winston Ewert

1
Tôi thích tùy chọn. Nhưng tôi sẽ nói rằng các con trỏ null cũ đơn giản rất hiếm khi là một vấn đề trong mã thế giới thực. Họ gần như luôn luôn thất bại gần dòng mã vi phạm. Và tôi nghĩ điều đó bị mọi người chế ngự khi nói về chủ đề này.
Truyền thuyết Bước sóng

@LegendLpm, kinh nghiệm của tôi là 95% các trường hợp thực sự có thể dễ dàng theo dõi để xác định NULL đến từ đâu. 5% còn lại, tuy nhiên, cực kỳ khó theo dõi.
Winston Ewert

-1

Trong Objective-C, tất cả các tham chiếu đối tượng là tùy chọn. Bởi vì điều này, mã như bạn đăng là ngớ ngẩn.

if (variable != nil) {
    [variable doThing];
}

Mã như trên là chưa từng thấy trong Objective-C. Thay vào đó, lập trình viên sẽ chỉ:

[variable doThing];

Nếu variablechứa một giá trị, sau đó doThingsẽ được gọi trên nó. Nếu không, không có hại. Chương trình sẽ coi nó là không hoạt động và tiếp tục chạy.

Đây là lợi ích thực sự của các tùy chọn. Bạn không cần phải vứt mã của mình vớiif (obj != nil) .


Không phải đó chỉ là một hình thức thất bại âm thầm sao? Trong một số trường hợp, bạn có thể quan tâm rằng giá trị là null và muốn làm gì đó với nó.
Truyền thuyết Bước sóng

-3

if (tùy chọn.isPftime ()) {

Vấn đề rõ ràng ở đây là nếu "tùy chọn" thực sự mất tích (tức là null) thì mã của bạn sẽ nổ tung với một NullReferenceException (hoặc tương tự) cố gắng để gọi bất kỳ phương pháp trên một tài liệu tham khảo đối tượng null!

Bạn có thể muốn viết một phương thức lớp Helper tĩnh để xác định giá trị null của bất kỳ loại đối tượng cụ thể nào, đại loại như sau:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

hoặc, có lẽ, hữu ích hơn:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

Nhưng những điều này có thực sự dễ đọc / dễ hiểu / dễ bảo trì hơn bản gốc không?

if ( null == optional ) ... 
if ( null != optional ) ... 
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.