没有空值的语言的最佳解释


225

当程序员抱怨空错误/异常时,经常有人问我们在没有空的情况下该怎么做。

我对选项类型的简洁性有一些基本了解,但是我没有足够的知识或语言技能来表达它。对以下内容的一个很好的解释是什么,使我们可以指向普通程序员,使之接近普通程序员?

  • 默认情况下,不希望引用/指针为空
  • 选项类型如何工作,包括简化检查空情况的策略,例如
    • 模式匹配和
    • 一元理解
  • 替代解决方案,例如吃零信息
  • (我错过了其他方面)

11
如果您在此问题上添加标签以进行功能编程或F#,则势必会得到一些出色的答案。
斯蒂芬·斯文森

我添加了功能性编程标签,因为选项类型确实来自ml领域。我宁可不要将其标记为F#(过于具体)。顺便说一句,具有分类能力的人需要添加一个也许类型或选项类型的标签。
罗曼·泰切

4
我怀疑,这样的特定标签几乎不需要。标签的主要作用是使人们可以找到相关的问题(例如,“我很了解并且可以回答的问题”,而“功能编程”在那里非常有用。但是诸如“ null”或“期权型”显得苍白无力很少有人可能会监视‘选项类型’标签找他们可以回答问题)。
jalf

我们不要忘记null的主要原因之一是计算机的发展与集合论紧密相关。空是所有集合论中最重要的集合之一。没有它,整个算法将崩溃。例如,执行合并排序。这涉及将列表分成两半。如果列表长7个项目怎么办?首先,将其分为4和3。然后,将2、2、2和1分开。然后将1,1,1,1,1,1,1,.... Null有一个目的,只是您实际上看不到的目的。它存在于理论领域。
stevendesu 2010年

6
@steven_desu-我不同意。在“可空”语言中,您可以引用一个空列表[],也可以引用一个空列表。这个问题与两者之间的混乱有关。
stusmith

Answers:


433

我认为为什么null是不可取的简洁总结是无意义的状态不应该可表示

假设我正在建模一扇门。它可以处于以下三种状态之一:打开,关闭但未锁定以及关闭和锁定。现在我可以按照

class Door
    private bool isShut
    private bool isLocked

很明显如何将我的三个状态映射到这两个布尔变量中。但这留下了第四个不期望的状态:isShut==false && isLocked==true。因为我选择的表示形式的类型承认这种状态,所以我必须花大力气确保类永远不会进入这种状态(也许通过显式编码不变式)。相反,如果我使用的语言具有代数数据类型或经过检查的枚举,则可以定义

type DoorState =
    | Open | ShutAndUnlocked | ShutAndLocked

然后我可以定义

class Door
    private DoorState state

不用担心。类型系统将确保要进入的实例只有三种可能的状态class Door。这就是类型系统所擅长的-在编译时明确排除整个错误类别。

问题null在于,每个引用类型都会在其空间中获得通常不希望的这种额外状态。一个string变量可以是任何字符序列,或者它可能是这个疯狂的额外null不映射到我的问题域值。一个Triangle对象具有三个Points,它们本身具有XY值,但是不幸的是Points或Triangle本身可能是这个疯狂的null值,这对于我正在使用的图形域毫无意义。等等。

当您打算对可能不存在的值进行建模时,则应明确选择使用它。如果我要模拟人的方式是每个人Person都有a FirstName和a LastName,但只有一些人有MiddleNames,那么我想说些类似的话

class Person
    private string FirstName
    private Option<string> MiddleName
    private string LastName

其中,string假设这里是一个非空类型。这样,NullReferenceException在尝试计算某人的姓名长度时,就无需建立棘手的不变式,也不会出现意外的。类型系统确保任何处理MiddleName帐目的代码都将其存在的可能性考虑在内None,而处理帐目的任何代码FirstName都可以安全地假定那里存在值。

因此,例如,使用上面的类型,我们可以编写这个愚蠢的函数:

let TotalNumCharsInPersonsName(p:Person) =
    let middleLen = match p.MiddleName with
                    | None -> 0
                    | Some(s) -> s.Length
    p.FirstName.Length + middleLen + p.LastName.Length

不用担心。相反,在一种对字符串等类型具有可空引用的语言中,则假定

class Person
    private string FirstName
    private string MiddleName
    private string LastName

你最终创作像

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length + p.MiddleName.Length + p.LastName.Length

如果传入的Person对象不具有所有非零的不变性,则该函数会爆炸,或者

let TotalNumCharsInPersonsName(p:Person) =
    (if p.FirstName=null then 0 else p.FirstName.Length)
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + (if p.LastName=null then 0 else p.LastName.Length)

或许

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + p.LastName.Length

假设可以p确保第一个/最后一个存在,但是Middle可以为null,或者您可能会进行检查以抛出不同类型的异常,或者谁知道。所有这些疯狂的实现选择和需要考虑的事情,因为您不需要或不需要这个愚蠢的可表示值。

Null通常会增加不必要的复杂性。 复杂性是所有软件的大敌,您应该在合理的情况下努力降低复杂性。

(请注意,即使是这些简单的示例也有更多的复杂性。即使a FirstNamenot be null,a string也可以表示""(空字符串),这可能也不是我们要建模的人名。因此,即使非同样,您可以选择在运行时通过不变式和条件代码,或者通过使用类型系统(例如,使用NonEmptyString类型)来对付可空字符串,这仍然是“代表无意义的值”的情况。后者可能是不明智的(“好”类型通常在一组常见操作上被“关闭”,例如NonEmptyString.SubString(0,0)),但它展示了设计空间中的更多要点。归根结底,在任何给定的类型系统中,都有一些复杂性可以很好地消除,而其他复杂性从本质上讲很难消除。该主题的关键在于,几乎在每个类型系统中,从“默认为空的引用”到“默认为非空引用”的更改几乎总是一个简单的更改,这使类型系统在应对复杂性和性能方面大为改善。排除某些类型的错误和无意义的状态。因此,如此多的语言不断重复出现此错误是非常疯狂的。)


31
回复:名字-确实。也许您确实希望对悬而未决的门进行建模,但门锁的锁舌会伸出来,从而防止门关闭。世界上有很多复杂性。关键是不要在软件中实现“世界状态”和“程序状态”之间的映射时增加更多的复杂性。
Brian

59
什么,您从未锁过门?
约书亚

58
我不明白为什么人们会为特定领域的语义而烦恼。Brian用简洁明了的方式用null表示缺陷,是的,他在示例中通过说每个人都有姓和名来简化了问题域。布赖恩-这个问题的答案是“ T”,如果您在波士顿,我在这里所做的所有举报都欠您一杯啤酒!
akaphenom

67
@akaphenom:谢谢,但是请注意,并非所有人都喝啤酒(我是非饮酒者)。但是我很欣赏您只是使用简化的世界模型来表达感激之情,所以我不会更多地质疑您的世界模型的错误假设。:P(现实世界中非常复杂!!))
Brian

4
奇怪的是,这个世界上有3国门!在某些酒店中,它们被用作厕所门。一个按钮从内部充当钥匙,从外部锁定门。闩锁螺栓移动后,它将自动解锁。
2010年

65

关于选项类型的好处不是它们是可选的。是所有其他类型都不是

有时,我们需要能够代表一种“空”状态。有时,我们必须表示“无值”选项以及变量可能采用的其他可能值。因此,一种完全禁止这种语言的语言将变得有些残废。

但是通常,我们并不需要它,而允许这种“空”状态只会导致模棱两可和混乱:每次我在.NET中访问引用类型变量时,我都必须考虑它可能为null

通常,它实际上永远不会为空,因为程序员对代码进行结构化以使其永远不会发生。但是编译器无法验证,并且每次看到它时,您都必须问自己“这是否可以为null?我是否需要在此处检查null?”

理想情况下,在很多情况下null都没有意义的情况下,不应允许使用 null 。

在几乎所有内容都可以为null的.NET中实现这一点很棘手。您必须依靠要调用的代码的作者严格遵守100%的规范,并且要清楚地记录什么可以为null或不能为null,否则您必须保持偏执并检查所有内容

但是,如果类型默认不是不可为null ,则无需检查它们是否为null。您知道它们永远不能为null,因为编译器/类型检查器会为您强制执行该操作。

然后,我们只需要在极少数情况下,我们一个后门需要处理空状态。然后可以使用“选项”类型。然后,在我们有意识地决定需要能够表示“无值”情况的情况下,我们允许null;在其他情况下,我们知道该值永远不会为null。

正如其他人提到的那样,例如在C#或Java中,null可以表示以下两种情况之一:

  1. 变量未初始化。理想情况下,这永远不会发生。除非对其进行初始化,否则该变量不应该存在
  2. 该变量包含一些“可选”数据:它需要能够表示没有数据的情况。有时这是必要的。也许您正在尝试在列表中查找对象,但您事先不知道该对象是否存在。然后,我们需要能够表示“未找到对象”。

第二个含义必须保留,但第一个含义应完全消除。甚至第二个含义也不应该是默认值。我们可以在需要的时候以及何时需要它。但是,当我们不需要某些可选内容时,我们希望类型检查器保证它永远不会为空。


第二个含义是,如果我们尝试访问此类变量而不先检查是否为空,则我们希望编译器向我们发出警告(停止?)。以下是关于即将到来的空/非空的C#功能一个伟大的文章(终于!)blogs.msdn.microsoft.com/dotnet/2017/11/15/...
辖施耐德

44

到目前为止,所有的答案都集中在为什么null是一件坏事上,以及如果一种语言可以保证某些值永远不会为null的话,这是多么方便。

然后他们继续建议,如果您为以下项目强制使用非空性,那将是一个非常巧妙的主意 所有,如果您添加类似OptionMaybe表示可能并不总是具有已定义值的类型的概念,则可以做到这一点。这是Haskell采取的方法。

都是好东西!但这并不排除使用显式可空/非空类型来实现相同的效果。那么,为什么Option仍然是件好事呢?毕竟,Scala支持可为空的值(是必须这样做,因此它可以与Java库一起使用),但也支持Options

问:那么,除了能够从一种语言中完全删除空值之外,还有什么好处?

A.组成

如果您从可识别null的代码中进行幼稚的翻译

def fullNameLength(p:Person) = {
  val middleLen =
    if (null == p.middleName)
      p.middleName.length
    else
      0
  p.firstName.length + middleLen + p.lastName.length
}

选项识别代码

def fullNameLength(p:Person) = {
  val middleLen = p.middleName match {
    case Some(x) => x.length
    case _ => 0
  }
  p.firstName.length + middleLen + p.lastName.length
}

没有太大的区别!但这也是使用Options的一种糟糕方法...这种方法更加简洁:

def fullNameLength(p:Person) = {
  val middleLen = p.middleName map {_.length} getOrElse 0
  p.firstName.length + middleLen + p.lastName.length
}

甚至:

def fullNameLength(p:Person) =       
  p.firstName.length +
  p.middleName.map{length}.getOrElse(0) +
  p.lastName.length

当您开始处理选项列表时,它会变得更好。想象一下,列表people本身是可选的:

people flatMap(_ find (_.firstName == "joe")) map (fullNameLength)

这是如何运作的?

//convert an Option[List[Person]] to an Option[S]
//where the function f takes a List[Person] and returns an S
people map f

//find a person named "Joe" in a List[Person].
//returns Some[Person], or None if "Joe" isn't in the list
validPeopleList find (_.firstName == "joe")

//returns None if people is None
//Some(None) if people is valid but doesn't contain Joe
//Some[Some[Person]] if Joe is found
people map (_ find (_.firstName == "joe")) 

//flatten it to return None if people is None or Joe isn't found
//Some[Person] if Joe is found
people flatMap (_ find (_.firstName == "joe")) 

//return Some(length) if the list isn't None and Joe is found
//otherwise return None
people flatMap (_ find (_.firstName == "joe")) map (fullNameLength)

带有空检查的相应代码(甚至是elvis?:运算符)将很长。真正的窍门是flatMap操作,该操作允许以空值永远无法实现的方式来嵌套嵌套选项和集合。


8
+1,这是需要强调的重点。一个附录:在Haskell-land中flatMap被称为(>>=),即单子的“绑定”运算符。没错,Haskellers非常喜欢对flatMap事物执行ping操作,因此我们将其置于语言的徽标中。
CA McCann 2010年

1
+1希望的表达式Option<T>永远不会为空。可悲的是,Scala是呃,仍然链接到Java :-)(另一方面,如果Scala在Java方面

很容易做到:'List(null).headOption'。请注意,这意味着与返回值“无”非常不同
凯文·赖特

4
我非常赏识您,因为我真的很喜欢您所说的作文,其他人似乎都没有提及。
罗曼·泰切

很好的例子,很好的答案!
thSoft 2011年

38

由于人们似乎想念它:null模棱两可。

爱丽丝的生日是null。这是什么意思?

鲍勃的去世日期是null。那是什么意思?

一种“合理”的解释可能是爱丽丝的生日存在但未知,而鲍勃的死亡日期不存在(鲍勃还活着)。但是为什么我们得到不同的答案?


另一个问题:null是一种边缘情况。

  • null = null
  • nan = nan
  • inf = inf
  • +0 = -0
  • +0/0 = -0/0

答案通常分别 “是”,“否”,“是”,“是”,“否”,“是”。疯狂的“数学家”称NaN为“空”,并说它与自身相等。SQL将null视为不等于任何值(因此,它们的行为类似于NaN)。一个人想知道当您将±∞,±0和NaN存储到同一数据库列中时会发生什么(有2 53个 NaN,其中一半是“负数”)。

更糟糕的是,数据库在对待NULL的方式上有所不同,而且大多数数据库不一致(请参阅 有关概述, SQLite中的NULL处理)。这太可怕了。


现在,关于强制性的故事:

我最近设计了一个具有五列的(sqlite3)数据库表a NOT NULL, b, id_a, id_b NOT NULL, timestamp。因为它是一种通用模式,旨在解决相当随意的应用程序的通用问题,所以存在两个唯一性约束:

UNIQUE(a, b, id_a)
UNIQUE(a, b, id_b)

id_a仅存在于与现有应用程序设计的兼容性上(部分原因是我尚未提出更好的解决方案),并且在新应用程序中未使用。由于NULL在SQL中的工作方式,我可以插入(1, 2, NULL, 3, t)(1, 2, NULL, 4, t)不违反第一唯一性约束(因为(1, 2, NULL) != (1, 2, NULL))。

这之所以特别有效,是因为NULL在大多数数据库中的唯一性约束中是如何工作的(大概是这样,可以为“现实世界”情况建模更容易,例如,没有两个人可以拥有相同的社会安全号码,但并非所有人都拥有一个)。


FWIW,在没有先调用未定义行为的情况下,C ++引用不能“指向” null,并且不可能用未初始化的引用成员变量构造一个类(如果引发异常,则构造失败)。

旁注:有时,您可能需要互斥的指针(即,其中只有一个可以为非NULL),例如在假设的iOS中type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed。相反,我被迫做类似的事情assert((bool)actionSheet + (bool)alertView == 1)


放心,实际的数学家不会使用“ NaN”的概念。
Noldorin 2012年

@Noldorin:他们这样做,但是使用“不确定形式”一词。
IJ肯尼迪

@IJKennedy:那是一所不同的大学,我非常了解,谢谢。某些“ NaN”可能表示不确定的形式,但是由于FPA不会进行符号推理,因此将其与不确定的形式等同会产生误导!
Noldorin

这有什么错assert(actionSheet ^ alertView)?还是您的语言不能异或?

16

默认情况下,不希望具有引用/指针为空。

我认为这不是null的主要问题,而null的主要问题是它们可能意味着两件事:

  1. 引用/指针未初始化:这里的问题与可变性通常相同。首先,它使分析代码变得更加困难。
  2. 变量null实际上意味着什么:这是Option类型实际形式化的情况。

支持选项类型的语言通常也禁止或不鼓励使用未初始化的变量。

选项类型如何工作,包括简化检查空值情况(例如模式匹配)的策略。

为了有效,需要使用该语言直接支持Option类型。否则,将需要大量样板代码来模拟它们。模式匹配和类型推断是两个关键的语言功能,使Option类型易于使用。例如:

在F#中:

//first we create the option list, and then filter out all None Option types and 
//map all Some Option types to their values.  See how type-inference shines.
let optionList = [Some(1); Some(2); None; Some(3); None]
optionList |> List.choose id //evaluates to [1;2;3]

//here is a simple pattern-matching example
//which prints "1;2;None;3;None;".
//notice how value is extracted from op during the match
optionList 
|> List.iter (function Some(value) -> printf "%i;" value | None -> printf "None;")

但是,在不直接支持Option类型的Java之类的语言中,我们会有类似以下内容:

//here we perform the same filter/map operation as in the F# example.
List<Option<Integer>> optionList = Arrays.asList(new Some<Integer>(1),new Some<Integer>(2),new None<Integer>(),new Some<Integer>(3),new None<Integer>());
List<Integer> filteredList = new ArrayList<Integer>();
for(Option<Integer> op : list)
    if(op instanceof Some)
        filteredList.add(((Some<Integer>)op).getValue());

替代解决方案,例如吃零信息

Objective-C的“零吃消息”并不是一种解决方案,而是减轻了null检查的头痛。基本上,表达式在尝试对空对象调用方法时不会抛出运行时异常,而是自身求值为空。令人难以置信,好像每个实例方法都以开头if (this == null) return null;。但是,这会造成信息丢失:您不知道该方法是否返回null,因为它是有效的返回值,或者因为对象实际上为null。这很像吞咽异常,并且在解决之前概述的null问题上没有取得任何进展。


这是一个小问题,但是c#几乎不是一种类似于c的语言。
罗曼·泰切

4
我打算在这里使用Java,因为C#可能会有更好的解决方案……但是,我感谢您的激怒,人们真正的意思是“一种具有c启发式语法的语言”。我继续并替换了“类似c的”声明。
Stephen Swensen 2010年

使用linq,对。我在想C#,却没有注意到。
罗曼·泰切

1
是的,大多数情况下都是使用c语言启发的语法,但是我想我也听说过命令式编程语言,例如python / ruby​​,很少使用c语法(功能程序员称为c语法)。
罗曼·泰切

11

汇编为我们带来了也称为无类型指针的地址。C直接将它们映射为类型指针,但引入Algol的null作为唯一指针值,与所有类型指针兼容。C语言中null的一个大问题是,由于每个指针都可以为null,因此,如果没有手动检查,就永远不能安全地使用指针。

在高级语言中,具有null确实很尴尬,因为它实际上传达了两个不同的概念:

  • 告诉某事未定义
  • 告诉某事是可选的

拥有未定义的变量几乎是无用的,并且每当它们发生时都会产生未定义的行为。我想每个人都同意应该不惜一切代价避免未定义的事物。

第二种情况是可选的,最好是显式提供,例如,使用option type


假设我们在一家运输公司中,我们需要创建一个应用程序来帮助我们为驾驶员制定时间表。对于每个驾驶员,我们存储一些信息,例如:他们拥有的驾驶执照以及在紧急情况下拨打的电话号码。

在C中,我们可以:

struct PhoneNumber { ... };
struct MotorbikeLicence { ... };
struct CarLicence { ... };
struct TruckLicence { ... };

struct Driver {
  char name[32]; /* Null terminated */
  struct PhoneNumber * emergency_phone_number;
  struct MotorbikeLicence * motorbike_licence;
  struct CarLicence * car_licence;
  struct TruckLicence * truck_licence;
};

如您所见,在对驱动程序列表进行的任何处理中,我们都必须检查空指针。编译器不会帮助您,程序的安全性取决于您的肩膀。

在OCaml中,相同的代码如下所示:

type phone_number = { ... }
type motorbike_licence = { ... }
type car_licence = { ... }
type truck_licence = { ... }

type driver = {
  name: string;
  emergency_phone_number: phone_number option;
  motorbike_licence: motorbike_licence option;
  car_licence: car_licence option;
  truck_licence: truck_licence option;
}

现在,我们要打印所有驾驶员的姓名以及他们的卡车执照号码。

在C中:

#include <stdio.h>

void print_driver_with_truck_licence_number(struct Driver * driver) {
  /* Check may be redundant but better be safe than sorry */
  if (driver != NULL) {
    printf("driver %s has ", driver->name);
    if (driver->truck_licence != NULL) {
      printf("truck licence %04d-%04d-%08d\n",
        driver->truck_licence->area_code
        driver->truck_licence->year
        driver->truck_licence->num_in_year);
    } else {
      printf("no truck licence\n");
    }
  }
}

void print_drivers_with_truck_licence_numbers(struct Driver ** drivers, int nb) {
  if (drivers != NULL && nb >= 0) {
    int i;
    for (i = 0; i < nb; ++i) {
      struct Driver * driver = drivers[i];
      if (driver) {
        print_driver_with_truck_licence_number(driver);
      } else {
        /* Huh ? We got a null inside the array, meaning it probably got
           corrupt somehow, what do we do ? Ignore ? Assert ? */
      }
    }
  } else {
    /* Caller provided us with erroneous input, what do we do ?
       Ignore ? Assert ? */
  }
}

在OCaml中,将是:

open Printf

(* Here we are guaranteed to have a driver instance *)
let print_driver_with_truck_licence_number driver =
  printf "driver %s has " driver.name;
  match driver.truck_licence with
    | None ->
        printf "no truck licence\n"
    | Some licence ->
        (* Here we are guaranteed to have a licence *)
        printf "truck licence %04d-%04d-%08d\n"
          licence.area_code
          licence.year
          licence.num_in_year

(* Here we are guaranteed to have a valid list of drivers *)
let print_drivers_with_truck_licence_numbers drivers =
  List.iter print_driver_with_truck_licence_number drivers

在这个简单的示例中可以看到,安全版本没有什么复杂的:

  • 太厉害了
  • 您可以获得更好的保证,根本不需要空检查。
  • 编译器确保您正确处理了该选项

而在C语言中,您可能只是忘记了空检查和繁荣。

注意:这些代码示例未编译,但希望您能理解。


我从未尝试过,但是en.wikipedia.org/wiki/Cyclone_%28programming_language%29声称允许c使用非空指针。
罗曼·泰切

1
我不同意您的说法,即第一种情况没有人感兴趣。许多人,特别是功能语言社区的人对此特别感兴趣,因此不鼓励或完全禁止使用未初始化的变量。
Stephen Swensen 2010年

我相信NULL某些Algol语言的发明是“不指向任何东西的引用”(维基百科同意,请参见en.wikipedia.org/wiki/Null_pointer#Null_pointer)。但是,当然,汇编程序员很可能初始化了指向无效地址的指针(读取:Null = 0)。

1
@斯蒂芬:我们可能是同一意思。对我来说,他们不鼓励或禁止使用未初始化的事物正是因为讨论未定义的事物是没有意义的,因为我们不能对它们进行任何理智或有用的事情。它不会有任何兴趣。
bltxd 2010年

2
作为@tc。说,null与汇编无关。在汇编中,类型通常不可为空。装入通用寄存器的值可能为零,也可能是一些非零的整数。但是它永远不能为空。即使将内存地址加载到寄存器中,在大多数常见体系结构上,也不会单独显示“空指针”。这是一个在高级语言引入一个概念,像C.
jalf

5

微软研究院有一个有趣的项目,叫做

规格#

这是C#扩展,具有非空类型,并且具有某种机制来检查对象是否不为null,尽管恕我直言,应用按合同设计原则可能对于由null引用引起的许多麻烦情况更合适,也更有用。


4

来自.NET背景,我一直以为null有一点,它很有用。直到我了解结构以及如何使用它们轻松避免大量样板代码。Tony Hoare于2009年在QCon London上发表演讲,对发明空引用表示歉意。引用他的话:

我称之为我的十亿美元错误。这是在1965年发明空引用的。那时,我正在设计第一个用于面向对象语言(ALGOL W)的引用的综合类型系统。我的目标是确保所有对引用的使用都绝对安全,并由编译器自动执行检查。但是我无法抗拒引入空引用的诱惑,仅仅是因为它是如此容易实现。这导致了无数的错误,漏洞和系统崩溃,在最近四十年中可能造成十亿美元的痛苦和破坏。近年来,许多程序分析器(例如Microsoft中的PREfix和PREfast)已用于检查引用,并在有可能不为空的风险时发出警告。诸如Spec#之类的最新编程语言引入了非空引用的声明。这是解决方案,我在1965年拒绝了。

程序员也看到这个问题



1

我一直将Null(或nil)视为缺少值

有时您想要这样做,有时则不需要。这取决于您使用的域。如果缺少是有意义的:没有中间名,则您的应用程序可以采取相应措施。另一方面,如果不应存在null值:名字为null,则开发人员会在凌晨2点接到电话。

我也看到过代码过重和检查null的情况过于复杂。对我来说,这意味着两件事之一:
a)应用程序树中更高的错误
b)设计不良/设计不完整

从积极的方面来看,Null可能是检查某些内容是否缺失的更有用的概念之一,而没有null概念的语言在进行数据验证时最终会使事情变得过于复杂。在这种情况下,如果未初始化新变量,则这些语言通常会将变量设置为空字符串,0或空集合。但是,如果空字符串或0或空集合是有效值您的应用程序的,那么您就遇到了问题。

有时通过为字段表示特殊的/怪异的值来表示未初始化状态来避免这种情况。但是,当一个好主意的用户输入特殊值时会发生什么呢?而且不要让数据验证例程陷入混乱。如果该语言支持null概念,那么所有关注点都将消失。


嗨,@ Jon,在这里关注您有点困难。我终于意识到,“特殊/怪异”值可能意味着类似Javascript的“ undefined”或IEEE的“ NaN”。但是除此之外,您并没有真正解决OP提出的任何问题。几乎可以肯定地说,“空值可能是检查是否存在某些内容的最有用的概念”。选项类型是null的备受推崇,类型安全的替代方案。
斯蒂芬·斯文森

@Stephen-实际上回头看我的消息,我认为整个第二部分应该移到一个尚未提出的问题上。但是我仍然说null对于检查是否缺少某些东西非常有用。
乔恩

0

向量语言有时会因为没有null而逃脱。

在这种情况下,空向量用作类型为null的空值。


我想我理解您在说什么,但您能列举一些例子吗?尤其是将多个函数应用于可能为null的值?
罗曼·泰切

将向量变换很好地应用于空向量会导致另一个空向量。仅供参考,SQL主要是一种矢量语言。
约书亚

1
好吧,我最好澄清一下。SQL是用于行的向量语言,是用于列的值语言。
约书亚
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.