Tôi đã nghe một số tiếng nói rằng việc kiểm tra giá trị null được trả về từ các phương thức là thiết kế xấu. Tôi muốn nghe một số lý do cho việc này.
mã giả:
variable x = object.method()
if (x is null) do something
Tôi đã nghe một số tiếng nói rằng việc kiểm tra giá trị null được trả về từ các phương thức là thiết kế xấu. Tôi muốn nghe một số lý do cho việc này.
mã giả:
variable x = object.method()
if (x is null) do something
Câu trả lời:
Lý do đằng sau việc không trả về null là bạn không phải kiểm tra nó và do đó mã của bạn không cần phải đi theo một đường dẫn khác dựa trên giá trị trả về. Bạn có thể muốn kiểm tra Mẫu đối tượng Null cung cấp thêm thông tin về điều này.
Ví dụ, nếu tôi định nghĩa một phương thức trong Java trả về Bộ sưu tập, tôi thường muốn trả về một bộ sưu tập trống (nghĩa là Collections.emptyList()
) thay vì null vì điều đó có nghĩa là mã máy khách của tôi sạch hơn; ví dụ
Collection<? extends Item> c = getItems(); // Will never return null.
for (Item item : c) { // Will not enter the loop if c is empty.
// Process item.
}
... sạch hơn:
Collection<? extends Item> c = getItems(); // Could potentially return null.
// Two possible code paths now so harder to test.
if (c != null) {
for (Item item : c) {
// Process item.
}
}
null
là "thùng chứa", chứa 0 hoặc một con trỏ tới các đối tượng. (Đó chắc chắn là cách mà Haskell mô hình rõ ràng Maybe
trong những trường hợp đó, và sẽ tốt hơn khi làm như vậy.)
Đây là lý do.
Trong Clean Code của Robert Martin , ông viết rằng trả về null là thiết kế tồi khi thay vào đó bạn có thể trả về mảng trống. Vì kết quả mong đợi là một mảng, tại sao không? Nó sẽ cho phép bạn lặp lại kết quả mà không cần thêm bất kỳ điều kiện nào. Nếu đó là số nguyên, có thể 0 sẽ đủ, nếu đó là hàm băm, hàm băm trống. Vân vân.
Tiền đề là không buộc mã gọi để xử lý ngay các vấn đề. Gọi mã có thể không muốn quan tâm đến họ. Đó cũng là lý do tại sao trong nhiều trường hợp ngoại lệ tốt hơn con số không.
Sử dụng tốt để trả về null:
Sử dụng xấu: Cố gắng thay thế hoặc ẩn các tình huống đặc biệt như:
Trong những trường hợp đó, ném một ngoại lệ là đầy đủ hơn kể từ:
Tuy nhiên, không nên sử dụng ngoại lệ để xử lý các điều kiện hoạt động bình thường của chương trình, như:
boolean login(String,String)
có vẻ ổn và cũng vậyAuthenticationContext createAuthContext(String,String) throws AuthenticationException
Vâng, trả lại NULL là một thiết kế khủng khiếp , trong thế giới hướng đối tượng. Tóm lại, việc sử dụng NULL dẫn đến:
Kiểm tra bài đăng trên blog này để được giải thích chi tiết: http://www.yegor256.com/2014/05/13/why-null-is-bad.html . Thêm trong cuốn sách Đối tượng thanh lịch của tôi , Phần 4.1.
bool TryGetBlah(out blah)
hay FirstOrNull()
hay MatchOrFallback(T fallbackValue)
.
isEmpty()
) và chỉ khi nó đúng thì gọi phương thức. Mọi người tranh luận với người thứ hai nói rằng đó là hiệu năng kém hơn - nhưng như Triết lý Unix nói, "giá trị thời gian của con người so với thời gian của máy" (nghĩa là hiệu năng chậm hơn đáng kể làm lãng phí ít thời gian hơn so với các nhà phát triển gỡ lỗi mã lỗi).
Ai nói đây là thiết kế xấu?
Kiểm tra null là một cách phổ biến, thậm chí được khuyến khích, nếu không, bạn sẽ gặp rủi ro về NullReferenceExceptions ở mọi nơi. Tốt hơn là xử lý lỗi một cách duyên dáng hơn là ném ngoại lệ khi bạn không cần.
Dựa trên những gì bạn đã nói cho đến nay, tôi nghĩ rằng không có đủ thông tin.
Trả về null từ phương thức CreatWidget () có vẻ xấu.
Trả về null từ phương thức FindFooInBar () có vẻ tốt.
Create...
trả về một thể hiện mới hoặc ném ; Get...
trả về một thể hiện hiện tại dự kiến, hoặc ném ; GetOrCreate...
trả về một thể hiện hiện có hoặc thể hiện mới nếu không tồn tại hoặc ném ; Find...
trả về một thể hiện hiện có, nếu nó tồn tại, hoặcnull
. Đối với các truy vấn bộ sưu tập - Get...
luôn trả về một bộ sưu tập, trống nếu không tìm thấy mục phù hợp.
Nhà phát minh của nó nói rằng đó là một sai lầm tỷ đô la!
Nó phụ thuộc vào ngôn ngữ bạn đang sử dụng. Nếu bạn đang sử dụng ngôn ngữ như C # trong đó cách thành ngữ biểu thị việc thiếu giá trị là trả về null, thì trả về null là một thiết kế tốt nếu bạn không có giá trị. Ngoài ra, trong các ngôn ngữ như Haskell sử dụng một cách đơn giản là Có thể đơn nguyên cho trường hợp này, sau đó trả về null sẽ là một thiết kế tồi (nếu nó thậm chí có thể).
null
trong các ngôn ngữ như C # và Java thường bị quá tải và có một số ý nghĩa trong miền. Nếu bạn tra null
từ khóa trong thông số kỹ thuật ngôn ngữ, nó chỉ có nghĩa là "một con trỏ không hợp lệ". Điều đó có thể có nghĩa là không có gì trong bất kỳ lĩnh vực vấn đề.
null
hay "không được khởi tạo"null
Nếu bạn đọc tất cả các câu trả lời, nó trở nên rõ ràng câu trả lời cho câu hỏi này phụ thuộc vào loại phương pháp.
Thứ nhất, khi một cái gì đó đặc biệt xảy ra (IOpropet, v.v.), các ngoại lệ logic được đưa ra. Khi chính xác một cái gì đó đặc biệt có lẽ là một cái gì đó cho một chủ đề khác ..
Bất cứ khi nào một phương pháp được dự kiến có thể không có kết quả, có hai loại:
Cho đến khi chúng ta có một cách chính thức để biểu thị rằng một hàm có thể hoặc không thể trả về null, tôi cố gắng có một quy ước đặt tên để biểu thị như vậy.
Giống như bạn có quy ước Thử một cái gì đó () cho các phương thức được dự kiến sẽ thất bại, tôi thường đặt tên cho các phương thức của mình là Safe Something () khi phương thức trả về kết quả trung tính thay vì null.
Tôi vẫn chưa hoàn toàn ổn với cái tên này, nhưng không thể nghĩ ra thứ gì tốt hơn. Vì vậy, bây giờ tôi đang chạy với nó.
Tôi có một hội nghị trong lĩnh vực này phục vụ tôi tốt
Đối với các truy vấn mục đơn:
Create...
trả về một thể hiện mới hoặc némGet...
trả về một thể hiện hiện có hoặc némGetOrCreate...
trả về một thể hiện hiện có hoặc thể hiện mới nếu không tồn tại hoặc némFind...
trả về một thể hiện hiện có, nếu nó tồn tại, hoặcnull
Đối với các truy vấn bộ sưu tập:
Get...
luôn trả về một bộ sưu tập, trống nếu không tìm thấy [1] mục phù hợp[1] đưa ra một số tiêu chí, rõ ràng hoặc ẩn, được đưa ra trong tên hàm hoặc dưới dạng tham số.
Get
khi tôi mong đợi nó ở đó, để nếu nó không ở đó, thì đó là lỗi và tôi ném - tôi không bao giờ cần phải kiểm tra giá trị trả về. Tôi sử dụng Find
nếu tôi thực sự không biết nó có ở đó hay không - sau đó tôi cần kiểm tra giá trị trả lại.
Ngoại lệ là trường hợp ngoại lệ al.
Nếu chức năng của bạn dự định tìm một thuộc tính được liên kết với một đối tượng nhất định và đối tượng đó không có thuộc tính đó, thì có thể phù hợp để trả về null. Nếu đối tượng không tồn tại, ném ngoại lệ có thể phù hợp hơn. Nếu hàm có nghĩa là trả về một danh sách các thuộc tính và không có thuộc tính nào để trả về, thì việc trả về một danh sách trống có ý nghĩa - bạn đang trả về tất cả các thuộc tính bằng không.
Sẽ tốt hơn nếu bạn trả về null nếu làm như vậy có ý nghĩa theo một cách nào đó:
public String getEmployeeName(int id){ ..}
Trong trường hợp như thế này, việc trả về null là không hợp lý nếu id không tương ứng với thực thể hiện có, vì nó cho phép bạn phân biệt trường hợp không tìm thấy kết quả khớp với lỗi hợp pháp.
Mọi người có thể nghĩ rằng điều này là xấu vì nó có thể bị lạm dụng như một giá trị trả về "đặc biệt" biểu thị một điều kiện lỗi, điều này không tốt lắm, giống như trả lại mã lỗi từ một hàm nhưng khó hiểu vì người dùng phải kiểm tra trả về null, thay vì bắt các ngoại lệ thích hợp, vd
public Integer getId(...){
try{ ... ; return id; }
catch(Exception e){ return null;}
}
Nó không nhất thiết là một thiết kế tồi - như với rất nhiều quyết định thiết kế, nó phụ thuộc.
Nếu kết quả của phương thức là một cái gì đó sẽ không có kết quả tốt trong sử dụng bình thường, trả về null là tốt:
object x = GetObjectFromCache(); // return null if it's not in the cache
Nếu thực sự phải luôn luôn có một kết quả khác không, thì có lẽ tốt hơn là ném một ngoại lệ:
try {
Controller c = GetController(); // the controller object is central to
// the application. If we don't get one,
// we're fubar
// it's likely that it's OK to not have the try/catch since you won't
// be able to really handle the problem here
}
catch /* ... */ {
}
Optional<>
Đối với các kịch bản nhất định, bạn muốn nhận thấy một thất bại ngay khi nó xảy ra.
Kiểm tra chống lại NULL và không xác nhận (đối với lỗi lập trình viên) hoặc ném (đối với lỗi người dùng hoặc người gọi) trong trường hợp lỗi có thể có nghĩa là các sự cố sau này khó theo dõi hơn, vì không tìm thấy trường hợp lẻ ban đầu.
Hơn nữa, bỏ qua lỗi có thể dẫn đến khai thác bảo mật. Có lẽ sự vô hiệu đến từ thực tế là một bộ đệm đã bị ghi đè hoặc tương tự. Bây giờ, bạn không gặp sự cố, điều đó có nghĩa là người khai thác có cơ hội thực thi mã của bạn.
Những lựa chọn thay thế nào bạn thấy để trở về null?
Tôi thấy hai trường hợp:
Trong trường hợp này, chúng tôi có thể: Trả về Null hoặc ném ngoại lệ (đã chọn) (hoặc có thể tạo một mục và trả lại)
Trong trường hợp này, chúng tôi có thể trả về Null, trả về một danh sách trống hoặc ném Ngoại lệ.
Tôi tin rằng return null có thể kém hơn các lựa chọn thay thế vì nó yêu cầu khách hàng nhớ kiểm tra null, lập trình viên quên và viết mã
x = find();
x.getField(); // bang null pointer exception
Trong Java, ném một ngoại lệ được kiểm tra, RecordNotFoundException, cho phép trình biên dịch nhắc nhở khách hàng xử lý trường hợp.
Tôi thấy rằng các tìm kiếm trả về danh sách trống có thể khá thuận tiện - chỉ cần điền vào màn hình với tất cả nội dung của danh sách, ồ nó trống, mã "chỉ hoạt động".
Đôi khi, trả về NULL là điều nên làm, nhưng cụ thể là khi bạn xử lý các chuỗi khác nhau (mảng, danh sách, chuỗi, những gì bạn có), có lẽ tốt hơn là trả về chuỗi có độ dài bằng không, vì nó dẫn đến mã ngắn hơn và hy vọng mã dễ hiểu hơn, trong khi không cần viết nhiều hơn về phần của người triển khai API.
Làm cho họ gọi một phương thức khác sau khi thực tế để tìm ra nếu cuộc gọi trước đó là null. ;-) Này, nó đã đủ tốt cho JDBC
Ý tưởng cơ bản đằng sau chủ đề này là lập trình phòng thủ. Đó là, mã chống lại sự bất ngờ. Có một loạt các câu trả lời khác nhau:
Adamski đề nghị xem xét Mô hình đối tượng Null, với câu trả lời được bình chọn cho đề xuất đó.
Michael Valenty cũng đề xuất một quy ước đặt tên để nói với nhà phát triển những gì có thể được mong đợi. ZeroConcept đề xuất sử dụng Exception đúng cách, nếu đó là lý do cho NULL. Và những người khác.
Nếu chúng ta đưa ra "quy tắc" mà chúng ta luôn muốn thực hiện lập trình phòng thủ thì chúng ta có thể thấy rằng những đề xuất này là hợp lệ.
Nhưng chúng tôi có 2 kịch bản phát triển.
Các lớp "tác giả" của một nhà phát triển: Tác giả
Các lớp "được tiêu thụ" bởi nhà phát triển (có thể) khác: Nhà phát triển
Bất kể một lớp có trả về NULL cho các phương thức có giá trị trả về hay không, Nhà phát triển sẽ cần kiểm tra xem đối tượng có hợp lệ hay không.
Nếu nhà phát triển không thể làm điều này, thì lớp / phương thức đó không mang tính quyết định. Đó là, nếu "phương thức gọi" để lấy đối tượng không thực hiện những gì nó "quảng cáo" (ví dụ getEmployee) thì nó đã phá vỡ hợp đồng.
Là một tác giả của một lớp, tôi luôn muốn trở nên tử tế và phòng thủ (và có tính quyết định) khi tạo ra một phương thức.
Vì vậy, nếu NULL hoặc NULL OBOG (ví dụ: nếu (nhân viên là NullEmployee.ISVALID)) cần được kiểm tra và điều đó có thể cần phải xảy ra với một bộ sưu tập Nhân viên, thì cách tiếp cận đối tượng null là cách tiếp cận tốt hơn.
Nhưng tôi cũng thích đề xuất của Michael Valenty về cách đặt tên phương thức PHẢI trả về null, ví dụ getEmployeeOrNull.
Một Tác giả đưa ra một ngoại lệ sẽ loại bỏ sự lựa chọn cho nhà phát triển để kiểm tra tính hợp lệ của đối tượng, điều này rất tệ đối với một bộ sưu tập các đối tượng và buộc nhà phát triển phải xử lý ngoại lệ khi phân nhánh mã tiêu thụ của họ.
Là một nhà phát triển tiêu thụ lớp, tôi hy vọng tác giả cho tôi khả năng tránh hoặc lập trình cho tình huống null mà lớp / phương thức của họ có thể phải đối mặt.
Vì vậy, là một nhà phát triển, tôi sẽ lập trình phòng thủ chống lại NULL từ một phương thức. Nếu tác giả đã đưa cho tôi một hợp đồng luôn trả về một đối tượng (ĐỐI TƯỢNG NULL luôn luôn làm như vậy) và đối tượng đó có một phương thức / thuộc tính để kiểm tra tính hợp lệ của đối tượng, thì tôi sẽ sử dụng phương thức / thuộc tính đó để tiếp tục sử dụng đối tượng , khác đối tượng không hợp lệ và tôi không thể sử dụng nó.
Điểm mấu chốt là Tác giả của Lớp / Phương thức phải cung cấp các cơ chế mà Nhà phát triển có thể sử dụng trong lập trình phòng thủ của họ. Đó là, một ý định rõ ràng hơn của phương pháp.
Nhà phát triển phải luôn sử dụng lập trình phòng thủ để kiểm tra tính hợp lệ của các đối tượng được trả về từ một lớp / phương thức khác.
Trân trọng
GregJF
Các tùy chọn khác cho điều này là: trả về một số giá trị biểu thị thành công hay không (hoặc loại lỗi), nhưng nếu bạn chỉ cần giá trị boolean sẽ biểu thị thành công / thất bại, trả về null cho thất bại và một đối tượng cho thành công sẽ không ít chính xác hơn, sau đó trả về true / false và nhận đối tượng thông qua tham số.
Cách tiếp cận khác sẽ sử dụng ngoại lệ để chỉ ra những thất bại, nhưng ở đây - thực sự có nhiều tiếng nói hơn, nói rằng đây là cách thực hành BAD (vì sử dụng ngoại lệ có thể thuận tiện nhưng có nhiều nhược điểm).
Vì vậy, cá nhân tôi không thấy có gì xấu khi trả về null vì dấu hiệu cho thấy có gì đó không ổn và kiểm tra lại sau (để thực sự biết bạn đã thành công hay chưa). Ngoài ra, mù quáng nghĩ rằng phương thức của bạn sẽ không trả về NULL, và sau đó dựa vào mã của bạn, có thể dẫn đến các lỗi khác, đôi khi khó tìm (mặc dù trong hầu hết các trường hợp, nó sẽ làm sập hệ thống của bạn :), vì bạn sẽ tham khảo 0x00000000 sớm hay muộn).
Các hàm null ngoài ý muốn có thể phát sinh trong quá trình phát triển một chương trình phức tạp và giống như mã chết, các sự cố như vậy chỉ ra các lỗ hổng nghiêm trọng trong cấu trúc chương trình.
Hàm hoặc phương thức null thường được sử dụng làm hành vi mặc định của hàm có thể phục hồi hoặc phương thức có thể ghi đè trong khung đối tượng.
Chà, nó chắc chắn phụ thuộc vào mục đích của phương pháp ... Đôi khi, một lựa chọn tốt hơn sẽ là ném một ngoại lệ. Tất cả phụ thuộc vào từng trường hợp.
Nếu mã là một cái gì đó như:
command = get_something_to_do()
if command: # if not Null
command.execute()
Nếu bạn có một đối tượng giả mà phương thức exec () không làm gì và bạn trả về nó thay vì Null trong các trường hợp thích hợp, bạn không phải kiểm tra trường hợp Null và thay vào đó chỉ có thể làm:
get_something_to_do().execute()
Vì vậy, ở đây, vấn đề không nằm ở việc kiểm tra NULL với một ngoại lệ, mà thay vào đó là giữa người gọi phải xử lý các trường hợp không đặc biệt khác nhau (theo bất kỳ cách nào) hay không.
Đối với trường hợp sử dụng của tôi, tôi cần trả về Bản đồ từ phương thức và sau đó tìm kiếm một khóa cụ thể. Nhưng nếu tôi trả về một Bản đồ trống, thì nó sẽ dẫn đến NullPulumException và sau đó nó sẽ không khác nhiều khi trả về null thay vì một Bản đồ trống. Nhưng từ Java8 trở đi, chúng tôi có thể sử dụng Tùy chọn . Trên đây là lý do rất nhiều khái niệm tùy chọn đã được giới thiệu.
Ngày mai
Trả lại NULL khi bạn không thể tạo đối tượng mới là cách thực hành chuẩn cho nhiều API.
Tại sao cái thiết kế tồi tệ đó tôi không biết.
Chỉnh sửa: Điều này đúng với các ngôn ngữ mà bạn không có ngoại lệ, chẳng hạn như C, nơi nó đã là quy ước trong nhiều năm.
HTH
'Hạnh phúc,