2009-09-18 23 views
13

Tôi biết rằng NULL không cần thiết trong ngôn ngữ lập trình và gần đây tôi đã quyết định không bao gồm NULL bằng ngôn ngữ lập trình của mình. Tuyên bố được thực hiện bằng cách khởi tạo, vì vậy không thể có một biến chưa được khởi tạo. Hy vọng của tôi là điều này sẽ loại bỏ các NullPointerException trong lợi của trường hợp ngoại lệ có ý nghĩa hơn hoặc đơn giản là không có một số loại lỗi.Những hệ quả của việc không bao gồm NULL trong một ngôn ngữ?

Tất nhiên, vì ngôn ngữ được triển khai trong C, sẽ có các NULL được sử dụng trong các trang bìa. Câu hỏi của tôi là, ngoài việc sử dụng NULL làm cờ báo lỗi (điều này được xử lý với ngoại lệ) hoặc làm điểm cuối cho cấu trúc dữ liệu như danh sách liên kết và cây nhị phân (điều này được xử lý với các công đoàn phân biệt). trường hợp cho NULL mà tôi cần phải có một giải pháp? Có bất kỳ ý nghĩa thực sự quan trọng của việc không có NULL mà có thể gây ra cho tôi vấn đề?

+0

bản sao có thể có của [Loại tham chiếu không thể vô hiệu hóa] (http://stackoverflow.com/questions/693325/non-nullable-reference-types), [về-không-rỗng-loại-tranh luận] (http : //stackoverflow.com/questions/641328/about-the-non-nullable-types-debate) – nawfal

Trả lời

8

Có một bài báo gần đây được giới thiệu trên LTU bởi Tony Hoare tựa đề Null References: The Billion Dollar Mistake trong đó mô tả một phương pháp để cho phép sự hiện diện của NULLs trong một ngôn ngữ lập trình, mà còn loại bỏ nguy cơ tham khảo như một tài liệu tham khảo NULL. Nó có vẻ rất đơn giản nhưng đó là một ý tưởng mạnh mẽ.

Cập nhật: đây là một liên kết đến giấy thực tế mà tôi đọc, mà nói về việc thực hiện trong Eiffel: http://docs.eiffel.com/book/papers/void-safety-how-eiffel-removes-null-pointer-dereferencing

+0

Tôi đọc bài viết đó trên λtU nhưng tôi đã hiểu nhầm ý nghĩa ban đầu của nó. Cảm ơn vì đã sửa cho tôi điều đó. Tôi sẽ đọc về cách Eiffel thực hiện điều này. – Imagist

1

Một trong đó ngay lập tức nói đến cái tâm là pass-by-reference tham số. Tôi chủ yếu là một coder Objective-C, vì vậy tôi thường thấy những loại như thế này:

NSError *error; 
[anObject doSomething:anArgumentObject error:&error]; 
// Error-handling code follows...

Sau khi mã này thực hiện, đối tượng error có thông tin chi tiết về lỗi rằng đã gặp phải, nếu có. Nhưng nói rằng tôi không quan tâm nếu một lỗi xảy ra:

[anObject doSomething:anArgumentObject error:nil];

Vì tôi không chuyển bất kỳ giá trị thực nào cho đối tượng lỗi, tôi không nhận được kết quả và tôi không thực sự lo lắng về việc phân tích cú pháp lỗi (vì tôi không quan tâm ở nơi đầu tiên nếu nó xảy ra).

Bạn đã đề cập bạn đang xử lý lỗi một cách khác nhau, vì vậy ví dụ cụ thể điều này không thực sự áp dụng, nhưng điểm đứng: bạn sẽ làm gì khi bạn vượt qua một cái gì đó trở lại bằng cách tham khảo? Hay ngôn ngữ của bạn không làm điều đó?

+0

Tôi không truyền lại mọi thứ bằng cách tham chiếu; mọi thứ đều là tham chiếu pass-by-const. Tuy nhiên, tôi muốn được quan tâm để hiểu những gì các mã trên không. Tôi chưa bao giờ mã hóa Objective-C trước đây vì vậy tôi không biết điều gì đang xảy ra trong mã đó. Bạn có sẵn sàng đưa ra một lời giải thích ngắn gọn? – Imagist

+0

Chắc chắn. Về cơ bản, các lỗi trong Objective-C không bảo đảm các ngoại lệ được biểu diễn bằng các cá thể của đối tượng NSError. Rất nhiều phương pháp có thể thất bại trong quá trình thực thi, nhưng không nhất thiết; những gì họ làm trong trường hợp này là thêm một đối số tham chiếu là một đối tượng NSError, do đó, để tìm ra lỗi, lập trình viên tạo ra một trong các đối tượng đó và chuyển nó bằng tham chiếu đến phương thức. Nếu phương thức gặp một vấn đề, nó đặt các chi tiết trong đối tượng và lập trình viên chịu trách nhiệm xử lý nó sau này. Tuy nhiên, nếu lập trình viên không quan tâm đến lỗi, họ có thể vượt qua bằng không. – Tim

0

Trong tâm trí của tôi có hai sử dụng trường hợp mà NULL thường được sử dụng:

  • Biến trong câu hỏi không có một giá trị (Không có gì)
  • Chúng tôi không biết giá trị của biến số trong câu hỏi (Chưa biết)

Cả hai trường hợp thông thường và, trung thực, sử dụng NULL cho cả hai có thể gây nhầm lẫn.

Worth chú ý là một số ngôn ngữ không hỗ trợ NULL làm hỗ trợ gì về Không có gì/không xác định. Ví dụ, Haskell hỗ trợ "Có thể", có thể chứa giá trị hoặc Không có gì. Do đó, các lệnh có thể trả về (và chấp nhận) một kiểu mà chúng biết sẽ luôn có một giá trị hoặc chúng có thể trả về/chấp nhận "Có thể" để cho biết rằng có thể không có giá trị.

+0

Javascript làm cho sự khác biệt này giữa null và không xác định. – ndp

0

Tôi thích khái niệm có con trỏ không có giá trị mặc định là con trỏ mặc định, với khả năng con trỏ không có khả năng. Bạn gần như có thể làm điều này với c + + thông qua tài liệu tham khảo (&) chứ không phải là con trỏ, nhưng nó có thể nhận được khá gnarly và irksome trong một số trường hợp.

Một ngôn ngữ có thể thực hiện mà không có ý nghĩa Java/C, ví dụ Haskell (và hầu hết các ngôn ngữ chức năng khác) có kiểu "Có thể" có hiệu quả là chỉ cung cấp khái niệm về một con trỏ null tùy chọn.

3

Vay một trang từ Haskell's Maybe monad, bạn sẽ xử lý trường hợp giá trị trả lại có thể tồn tại hoặc không tồn tại như thế nào? Ví dụ: nếu bạn đã cố gắng cấp phát bộ nhớ nhưng không có bộ nhớ nào khả dụng. Hoặc có thể bạn đã tạo một mảng để chứa 50 foos, nhưng không có foos nào được khởi tạo - bạn cần một số cách để có thể kiểm tra những thứ này.

Tôi đoán bạn có thể sử dụng ngoại lệ để bao gồm tất cả các trường hợp này, nhưng điều đó có nghĩa là một lập trình viên sẽ phải quấn tất cả của những người trong khối try-catch? Điều đó sẽ gây phiền nhiễu nhất. Hoặc tất cả mọi thứ sẽ phải trả lại giá trị riêng của nó cộng với một boolean cho biết liệu giá trị là hợp lệ, mà chắc chắn là không tốt hơn.

FWIW, tôi không biết về bất kỳ chương trình mà không có một số loại khái niệm NULL - bạn đã có null trong tất cả các ngôn ngữ C-phong cách và Java; Python có None, sơ đồ, Lisp, Smalltalk, Lua, Ruby tất cả đều có nil; VB sử dụng Nothing; và Haskell có một loại khác nhau của nothing.

Điều đó không có nghĩa là một ngôn ngữ hoàn toàn phải có một số loại null, nhưng nếu tất cả các ngôn ngữ lớn khác sử dụng nó, chắc chắn có một số lý do âm thanh đằng sau nó.

Mặt khác, nếu bạn chỉ tạo một DSL nhẹ hoặc một số ngôn ngữ không phải chung khác, bạn có thể nhận được mà không có null nếu không có loại dữ liệu gốc nào của bạn yêu cầu.

+0

Ngoại lệ không được kiểm soát, vì vậy nếu bạn có thể đảm bảo rằng tình huống không xảy ra, bạn không phải thử/nắm bắt. – Imagist

+0

Về mặt kỹ thuật, bạn không bao giờ có thể "đảm bảo" rằng tình huống sẽ không xảy ra, trừ khi bạn có toàn quyền kiểm soát tất cả các khía cạnh của phần cứng và phần mềm được đề cập. –

+0

@Chris Đúng, nhưng có đảm bảo hợp lý. Đọc từ các tệp, kết nối mạng và đầu vào của người dùng thường không thể đảm bảo. Tuy nhiên, nếu bạn có một cái gì đó như 'x/2', bạn có thể chắc chắn rằng nó sẽ không ném một' DivisionByZeroException'. – Imagist

1

Tôi nghĩ rằng nó hữu ích cho một phương thức trả về NULL - ví dụ cho một phương thức tìm kiếm phải trả về một số đối tượng, nó có thể trả về đối tượng tìm thấy, hoặc NULL nếu nó không được tìm thấy.

Tôi bắt đầu tìm hiểu Ruby và Ruby có một khái niệm rất thú vị cho NULL, có lẽ bạn có thể xem xét việc triển khai một cái gì đó silimar. Trong Ruby, NULL được gọi là Nil, và nó là một đối tượng thực sự giống như bất kỳ đối tượng nào khác. Nó xảy ra được thực hiện như một đối tượng Singleton toàn cầu. Cũng trong Ruby, có một đối tượng False, và cả Nil và False đều đánh giá sai trong các biểu thức boolean, trong khi mọi thứ khác đánh giá đúng (thậm chí 0, ví dụ, đánh giá là true).

+0

Nếu một phương pháp thất bại trong công việc của mình, nó sẽ ném một ngoại lệ. – Imagist

+0

Một số người không phải là người hâm mộ ngoại lệ. Joel có toàn bộ bài đăng trên blog về chủ đề đang chuyển sang báo giá mới của Jamie Zawinski regex trên trang web này. –

+0

@Chris Link? Ngoài ra, đáng chú ý là các ngôn ngữ khác nhau sử dụng ngoại lệ rất khác nhau. Trong C++ có những nguy hiểm rò rỉ bộ nhớ với các ngoại lệ, và trong Java, các ngoại lệ được kiểm tra được sử dụng không đúng cách thường xuyên như không. Python, tuy nhiên, sử dụng ngoại lệ rất hiệu quả, đến mức nó thường tốt hơn để thử và bắt lỗi hơn để kiểm tra lỗi trước. – Imagist

0

Nó không rõ ràng với tôi tại sao bạn muốn loại bỏ khái niệm 'null' từ một ngôn ngữ. Bạn sẽ làm gì nếu ứng dụng của bạn yêu cầu bạn thực hiện một số khởi tạo 'lười biếng' - nghĩa là, bạn không thực hiện thao tác cho đến khi dữ liệu cần thiết? Ví dụ:

public class ImLazy { 
public ImLazy() { 
    //I can't initialize resources in my constructor, because I'm lazy. 
    //Maybe I don't have a network connection available yet, or maybe I'm 
    //just not motivated enough. 
} 

private ResourceObject lazyObject; 
public ResourceObject getLazyObject() { //initialize then return 
    if (lazyObject == null) { 
    lazyObject = new DatabaseNetworkResourceThatTakesForeverToLoad(); 
    } 
} 

public ResourceObject isObjectLoaded() { //just return the object 
    return (lazyObject != null); 
} 
} 

Trong trường hợp này, làm thế nào chúng ta có thể trả về giá trị cho getObject()? Chúng tôi có thể đưa ra một trong hai điều sau:

-yêu cầu người dùng khởi tạo LazyObject trong tuyên bố. Người dùng sau đó sẽ phải điền vào một số đối tượng giả (UselessResourceObject), yêu cầu họ viết tất cả cùng một mã kiểm tra lỗi (if (lazyObject.equals (UselessResourceObject) ...) Hoặc:

-come lên với một số giá trị khác, mà hoạt động giống như null, nhưng có một cái tên khác nhau

Đối với bất kỳ phức tạp ngôn ngữ/OO bạn cần chức năng này, hoặc một cái gì đó giống như nó, như xa như Tôi có thể nhìn thấy. Nó có thể có giá trị để có một loại tham chiếu không null (ví dụ, trong một chữ ký phương thức, để bạn không phải thực hiện kiểm tra null trong mã phương thức), nhưng chức năng rỗng sẽ có sẵn cho các trường hợp bạn sử dụng nó.

+0

Ngôn ngữ của tôi có hỗ trợ tích hợp cho các khối. Trong trường hợp này, hàm tạo 'ImLazy()' sẽ khởi tạo 'lazyObject = new Thunk (new DatabaseNetworkResourceThatTakesForeverToLoad());' (gần đúng; cú pháp của ngôn ngữ khác). Bằng cách này, ngay cả khi getLazyObject() được gọi, đối tượng không được khởi tạo (nó được khởi tạo khi một cái gì đó được thực hiện bằng cách sử dụng nó sẽ gây ra một tác dụng phụ). Nếu bạn muốn nạp đối tượng trước đó, bạn có thể gọi 'lazyObject.resolve()' để giải quyết vấn đề này.Tôi cũng đang xem xét việc có một chuỗi được xây dựng trong sử dụng chu kỳ nhàn rỗi để giải quyết các khối sớm. – Imagist

+0

Điều đó có vẻ như nó có ý nghĩa. Nếu bạn có một hàm tạo với các đối số, tôi đoán bạn sẽ lưu các đối số được truyền vào cho đến khi Thunk khởi tạo đối tượng? – RMorrisey

+0

Phải. Bạn không thực sự truyền vào đối tượng, bạn truyền vào hàm tạo và các đối số cho hàm tạo. – Imagist

0

Thảo luận thú vị diễn ra tại đây.

Nếu tôi đang xây dựng một ngôn ngữ, tôi thực sự không biết mình có khái niệm về null hay không. Tôi đoán nó phụ thuộc vào cách tôi muốn ngôn ngữ để xem xét. Trường hợp tại điểm: Tôi đã viết một ngôn ngữ lập trình đơn giản có sức mạnh chính là các thẻ lồng nhau và dễ tạo ra một mã thông báo danh sách các giá trị. Nó không có khái niệm về null, nhưng sau đó nó không thực sự có khái niệm về bất kỳ loại nào khác ngoài chuỗi.

Để so sánh, ngôn ngữ được xây dựng trong, Biểu tượng, sử dụng rộng rãi vô giá trị. Có lẽ điều tốt nhất mà các nhà thiết kế ngôn ngữ cho Icon đã làm với null là làm cho nó đồng nghĩa với một biến chưa được khởi tạo (tức là bạn không thể biết sự khác biệt giữa một biến không tồn tại và biến hiện đang giữ giá trị null). Và sau đó tạo hai toán tử tiền tố để kiểm tra null và không null.

Trong PHP, đôi khi tôi sử dụng null làm giá trị boolean 'thứ ba'. Điều này là tốt trong các lớp loại "hộp đen" (ví dụ: lõi ORM), nơi một trạng thái có thể là True, False hoặc I Don't Know. Null được sử dụng cho giá trị thứ ba.

Tất nhiên, cả hai ngôn ngữ này không có con trỏ theo cùng cách C, do đó, con trỏ không tồn tại.

0

Chúng tôi sử dụng null mọi lúc trong ứng dụng của chúng tôi để đại diện cho trường hợp "không có gì". Ví dụ, nếu bạn được yêu cầu tra cứu một số dữ liệu trong cơ sở dữ liệu cho một id, và không có bản ghi nào khớp với id đó: return null. Điều này rất tiện dụng vì chúng ta có thể lưu trữ các giá trị rỗng trong bộ nhớ đệm, điều đó có nghĩa là chúng ta không phải quay trở lại cơ sở dữ liệu nếu ai đó yêu cầu lại id đó sau vài giây.

Bản thân bộ nhớ cache có hai loại phản hồi khác nhau: null, có nghĩa là không có mục nhập nào trong bộ nhớ cache hoặc đối tượng mục nhập. Đối tượng nhập có thể có một giá trị null, đó là trường hợp khi chúng ta lưu trữ một tra cứu db rỗng.

Ứng dụng của chúng tôi được viết bằng Java, nhưng ngay cả với các ngoại lệ không được kiểm soát làm điều này với ngoại lệ sẽ cực kỳ khó chịu.

+0

Cơ sở dữ liệu nulls và nulls ngôn ngữ là rất khác nhau về khái niệm (có thực sự là một loại phân biệt được thực hiện trong C#). – Imagist

+0

@Imagist: Tôi không nói về nulls cơ sở dữ liệu, tôi đang nói về kịch bản không tìm thấy bản ghi. Đối với một phương thức trả về một đối tượng duy nhất, bạn cần một số cách để chỉ ra rằng không có đối tượng nào có thể được trả về. Một ngoại lệ có thể làm điều đó, nhưng nó rất cồng kềnh để lập trình theo cách đó, ít nhất là trong Java. Có lẽ ngôn ngữ của bạn có một số cách tiếp cận thanh lịch giúp dễ dàng hơn. Nhưng trong kinh nghiệm của tôi null là rất hữu ích. –

0

Nếu người ta chấp nhận các mệnh đề rằng ngôn ngữ mạnh nên có loại con trỏ hoặc kiểu tham chiếu nào đó (tức là thứ có thể chứa tham chiếu đến dữ liệu không tồn tại trong thời gian biên dịch) và một số dạng mảng (hoặc phương tiện khác) có một tập hợp các khe lưu trữ có thể định địa chỉ tuần tự qua chỉ số nguyên) và các vị trí sau có thể giữ vị trí cũ và chấp nhận khả năng có thể đọc một số vùng của một chuỗi con trỏ/tham chiếu trước khi các giá trị hợp lý tồn tại cho tất cả chúng, thì sẽ có các chương trình, từ góc độ của trình biên dịch, sẽ đọc một khe mảng trước khi một giá trị hợp lý được ghi vào nó (cố gắng xác định trong trường hợp chung). trước khi nó được viết sẽ tương đương với vấn đề dừng lại).

Mặc dù ngôn ngữ có thể yêu cầu tất cả các vùng mảng được khởi tạo với một số tham chiếu không trống trước khi có thể đọc bất kỳ phần tử nào trong nhiều trường hợp. tốt hơn null: nếu một nỗ lực được thực hiện để đọc một mảng mảng chưa được khai thác và dereference (không) mục chứa ở đó, đại diện cho một lỗi, và nó sẽ là tốt hơn để có bẫy hệ thống điều kiện hơn để truy cập một số đối tượng tùy ý có mục đích duy nhất cho sự tồn tại là cung cấp cho các khe mảng một số thứ không rỗng mà chúng có thể tham chiếu.

Các vấn đề liên quan