面向对象编程语言中的引用默认情况下是否应该不可为空?

发布于 2024-08-12 23:28:42 字数 685 浏览 5 评论 0原文

空指针被描述为“十亿美元的错误”。某些语言具有不能分配空值的引用类型。

我想知道在设计一种新的面向对象语言时,默认行为是否应该是引用以防止被分配 null。然后可以使用特殊版本来覆盖此行为。例如:

MyClass notNullable = new MyClass();
notNullable = null; // Error!
// a la C#, where "T?" means "Nullable<T>"
MyClass? nullable = new MyClass();
nullable = null; // Allowed

所以我的问题是,有什么理由不在新的编程语言中这样做?

编辑:

我想添加 我的博客上最近的评论指出,当在数组中使用不可空类型时,会出现一个特殊的问题。我还要感谢大家的有用见解。非常有帮助,抱歉我只能选择一个答案。

Null pointers have been described as the "billion dollar mistake". Some languages have reference types which can't be assigned the null value.

I wonder if in designing a new object-oriented language whether the default behavior should be for references to prevent being assigned null. A special version of the could then be used to override this behavior. For example:

MyClass notNullable = new MyClass();
notNullable = null; // Error!
// a la C#, where "T?" means "Nullable<T>"
MyClass? nullable = new MyClass();
nullable = null; // Allowed

So my question is, is there any reason not to do this in a new programming language?

EDIT:

I wanted to add that a recent comment on my blog pointed out that non-nullable types have a particular problem whenb used in Arrays. I also want to thank everyone for their useful insights. It is very helpful, sorry I could only choose one answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

九公里浅绿 2024-08-19 23:28:42

默认情况下,我认为不可为空引用类型的主要障碍是,编程社区的某些部分更喜欢创建-设置-使用模式:

x = new Foo()
x.Prop <- someInitValue
x.DoSomething()

而不是重载构造函数:

x = new Foo(someInitValue)
x.DoSomething()

这使得 API 设计者在初始值方面陷入困境。否则可能为 null 的实例变量。

当然,就像“null”本身一样,创建-设置-使用模式本身会创建许多无意义的对象状态并阻止有用的不变量,因此摆脱它确实是一种祝福而不是诅咒。然而,它确实以许多人不熟悉的方式影响了一些 API 设计,所以这不是一件容易做的事情。

但总的来说,是的,如果有一场巨大的灾难摧毁了所有现有的语言和编译器,人们只能希望当我们重建时我们不会重复这个特定的错误。可空性是例外,而不是规则!

The main obstruction I see to non-nullable reference types by default is that some portion of the programming community prefers the create-set-use pattern:

x = new Foo()
x.Prop <- someInitValue
x.DoSomething()

to overloaded constructors:

x = new Foo(someInitValue)
x.DoSomething()

and this leaves the API designer in a bind with regards to the initial value of instance variables that might otherwise be null.

Of course, like 'null' itself, the create-set-use pattern itself creates lots of meaningless object states and prevents useful invariants, so being rid of this is really a blessing rather than a curse. However it does affect a bit of API design in a way that many people will be unfamiliar with, so it's not something to do lightly.

But overall, yes, if there is a great cataclysm that destroys all existing languages and compilers, one can only hope that when we rebuild we will not repeat this particular mistake. Nullability is the exception, not the rule!

小嗲 2024-08-19 23:28:42

我喜欢 Ocaml 处理“可能为空”问题的方式。每当 'a 类型的值可能未知/未定义/未初始化时,它都会被包装在 'a Option 类型中,该类型可以是 NoneSome x,其中 x 是实际的不可为 null 的值。当访问x时,你需要使用匹配机制来解包。下面是一个函数,它增加一个可为 null 的整数,并在 None 上返回 0

>>> let f = function  Some x -> x+1 | None->0 ;;
val f : int option -> int = <fun>

它的工作原理:

>>> f Some 5 ;;
- : int = 6
>>> f None ;;
- : int = 0

匹配机制强制您考虑 None 情况。当您忘记它时,会发生以下情况:(

 >>> let f = function  Some x -> x+1 ;;
 Characters 8-31:
 let f = function  Some x -> x+1 ;;
         ^^^^^^^^^^^^^^^^^^^^^^^
 Warning P: this pattern-matching is not exhaustive.
 Here is an example of a value that is not matched:
 None
 val f : int option -> int = <fun>

这只是一个警告,而不是错误。现在,如果您将 None 传递给函数,您将得到一个匹配异常。)

变体类型 + 匹配是通用机制,它也适用于仅将列表与 head :: tail 匹配(忘记空列表情况)。

I like the Ocaml way of dealing with the 'maybe null' issue. Whenever a value of type 'a might be unknown/undefined/unitialized, it is wrapped in an 'a Option type, which can be either None or Some x, where x is the actual non-nullable value. When accessing the x you need to use the matching mechanism for unwrapping. Here is a function that increases a nullable integer and returns 0 on None

>>> let f = function  Some x -> x+1 | None->0 ;;
val f : int option -> int = <fun>

How it works:

>>> f Some 5 ;;
- : int = 6
>>> f None ;;
- : int = 0

The matching mechanism sort of forces you to consider the None case. Here's what happens when you forget it:

 >>> let f = function  Some x -> x+1 ;;
 Characters 8-31:
 let f = function  Some x -> x+1 ;;
         ^^^^^^^^^^^^^^^^^^^^^^^
 Warning P: this pattern-matching is not exhaustive.
 Here is an example of a value that is not matched:
 None
 val f : int option -> int = <fun>

(This is just a warning, not an error. Now if you pass None to the function you'll get a matching exception.)

The variant types + matching is a generic mechanism, it also works for things like matching a list with head :: tail only (forgetting the empty list case).

凉城凉梦凉人心 2024-08-19 23:28:42

更好的是,禁用空引用。在极少数情况下,当“无”是有效值时,可能存在与其对应的对象状态,但引用仍会指向该对象,而不是零值。

Even better, disable null references. In rare cases when "nothing" is a valid value, there could be an object state that corresponds to it, but a reference would still point to that object, not have a zero value.

苏璃陌 2024-08-19 23:28:42

据我了解,Martin Odersky 在 Scala 中包含 null 的基本原理是为了轻松使用 Java 库(即,这样您的所有 api 似乎都不会到处都有“Object?”):

http://www.artima.com/scalazine/articles/goals_of_scala.html

理想情况下,我认为 null 应该作为一项功能包含在语言中,但不可为空应该是所有类型的默认值。这将节省大量时间并防止错误。

As I understand, Martin Odersky's rationale for including null in Scala is to easily use Java libraries (i.e. so all your api's don't appear to have, e.g., "Object?" all over the place):

http://www.artima.com/scalazine/articles/goals_of_scala.html

Ideally, I think null should be included in the language as a feature, but non-nullable should be the default for all types. It would save lots of time and prevent errors.

内心荒芜 2024-08-19 23:28:42

语言设计中最大的“与空相关的错误”是在索引空指针时缺乏陷阱。许多编译器在尝试取消引用时会陷入困境,如果向指针添加偏移量并尝试取消引用空指针,则不会陷入困境。在C标准中,尝试添加偏移量是未定义的行为,并且检查指针的性能成本不会比检查取消引用更糟糕(特别是如果编译器能够意识到如果它之前检查了指针是否为非空)添加偏移量,之后可能不需要重新检查)。

至于对不可空变量的语言支持,有一种方法可能很有用,可以请求声明包含初始值的某些变量或字段自动测试任何写入,以确保在尝试执行以下操作时立即发生异常:向它们写入 null。如果有一种有效的惯用方法可以通过构造所有元素来构造数组,并且在构造完成之前不使数组对象本身可用,则数组可以包含类似的功能。请注意,如果在构造所有元素之前发生异常,则可能还应该有一种方法来指定要对所有先前构造的元素调用的清理函数。

最后,如果可以指定某些实例成员应该通过非虚拟调用来调用,并且即使在空项上也应该可以调用,那将会很有帮助。与 someStringVariable.IsNullOrEmpty 相比,String.IsNullOrEmpty(someStringVariable) 之类的东西是可怕的。

The biggest "null-related mistake" in language design is the lack of a trap when indexing null pointers. Many compiler will trap when trying to dereference a null pointer will not trap if one adds an offset to a pointer and tries to dereference that. In the C standard, trying to add the offset is Undefined Behavior, and the performance cost of checking the pointer there would be no worse than checking the dereference (especially if the compiler could realize that if it checked that the pointer was non-null before adding the offset, it might not need to re-check afterward).

As for language support for non-nullable variables, it may be useful to have a means of requesting that certain variables or fields whose declarations include an initial value should automatically test any writes to ensure that an immediate exception will occur if an attempt is made to write null to them. Arrays could include a similar feature, if there were an efficient idiomatic way of constructing an array by constructing all the elements and not making the array object itself available before construction was complete. Note that there should probably also be a way of specifying a cleanup function to be called on all previously-constructed elements if an exception occurs before all elements have been constructed.

Finally, it would be helpful if one could specify that certain instance members should be invoked with non-virtual calls, and should be invokable even on null items. Something like String.IsNullOrEmpty(someStringVariable) is hideous compared with someStringVariable.IsNullOrEmpty.

最单纯的乌龟 2024-08-19 23:28:42

Null 只是一个问题,因为开发人员在使用某些东西之前不会检查它是否有效,但是,如果人们开始滥用新的可空构造,那么它不会解决任何实际问题。

重要的是要检查每个可以为空的变量在使用之前都经过检查,如果这意味着您必须使用注释来允许绕过检查,那么这可能是有意义的,否则编译器可能无法编译,直到您查看。

我们将越来越多的逻辑放入编译器中,以保护开发人员免受自身伤害,这是可怕且非常悲伤的,因为我们知道应该做什么,但有时会跳过步骤。

因此,您的解决方案也将受到滥用,不幸的是,我们将回到我们开始的地方。

更新:

根据这里的一些评论,这是我答案中的一个主题。我想我应该在原来的答案中更明确:

基本上,如果目标是限制空变量的影响,那么每当未检查变量是否为空时,编译器都会抛出错误,并且如果您想假设它永远不会为空,然后需要一个注释来跳过检查。通过这种方式,您可以让人们进行假设,但也可以轻松找到代码中具有假设的所有位置,并且在代码审查中可以评估假设是否有效。

这将有助于保护,同时不限制开发人员,但可以轻松知道哪里被假定为不为空。

我相信我们需要灵活性,我宁愿编译时间更长,也不愿对运行时产生负面影响,而且我认为我的解决方案可以达到预期的效果。

Null is only a problem because developers don't check that something is valid before using it, but, if people start to misuse the new nullable construct it will not have solved any real problems.

It is important to just check that every variable that can be null is checked before it is used, and if this means that you have to use annotations to allow bypassing the check then that may make sense, otherwise the compiler could fail to compile until you check.

We put more and more logic into compilers to protect developers from themselves, which is scary and very sad, as we know what we should do, and yet sometimes skip steps.

So, your solution will also be subject to abuse, and we will be back to where we started, unfortunately.

UPDATE:

Based on some comments here was a theme in my answers. I guess I should have been more explicit in my original answer:

Basically, if the goal is to limit the impact of null variables, then have the compiler throw an error whenever a variable is not checked for null, and if you want to assume that it will never be null, then require an annotation to skip the check. This way you give people the ability to assume, but you also make it easy to find all the places in the code that have the assumption, and in a code review it can be evaluated if the assumption is valid.

This will help to protect while not limiting the developer, but making it easy to know where it is assumed not to be null.

I believe we need flexibility, and I would rather have the compilation take longer than have something negatively impact the runtime, and I think my solution would do what is desired.

荒人说梦 2024-08-19 23:28:42

不。

由于逻辑上的必要性,未初始化状态将以某种方式存在;目前该符号为空。

也许可以设计一个“有效但未初始化”的对象概念,但这有什么显着不同呢? “访问未初始化的对象”的语义仍然存在。

更好的方法是进行静态时间检查,确保您不会访问未分配给的对象(除了字符串评估之外,我想不出有什么可以阻止这种情况的发生)。

No.

The state of uninitialized will exist in some fashion due to logical necessity; currently the denotation is null.

Perhaps a "valid but uninitialized" concept of an object can be designed in, but how is that significantly different? The semantics of "accessing an uninitialized object" still will exist.

A better route is to have a static-time checking that you do not access an object that is not assigned to(I can't think of something off the top of my head that would prevent that besides string evals).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文