“Null 是价值十亿美元的错误!”

“不健康”的代码到处都是Null检查!

作者 |  James Hickey

译者 | 弯月,责编 | 郭芮

出品 | CSDN(ID:CSDNnews)

以下为译文:

你是否知道提出“null”这个概念的人声称这是他的“十亿美元错误!”

这个问题看似很简单,然而一旦你深入大型项目和代码库,就必然会发现有些代码中null的使用简直匪夷所思。

有时,我们希望使对象的属性为可选:

class Product{
  public id: number;
  public title: string;
  public description: string;
}

在TypeScript中,string类型的属性值可以为null。

但是,number属性居然也可以!

const chocolate: Product = new Product();
chocolate.id = null;
chocolate.description = null;

呃……

再举个例子

乍一看去,似乎并没有什么不妥。

但是,这可能会引发如下情况:

const chocolate: Product = new Product(null, null, null);

这段代码有什么问题?它会让你的代码(上述代码中的Product类)陷入不一致的状态。

系统中的Product没有id,这合理吗?应该不合理吧。

理想情况下,一旦创建Product,它就应该有id。

那么,在需要与Product打交道的其他地方会怎么样呢?

实际情况惨不忍睹:

let title: string;

if(product != null) {
    if(product.id != null) {
        if(product.title != null) {
            title = product.title;
        } else {
            title = "N/A";
        }
    } else {
        title = "N/A"
    }
} else {
    title = "N/A"
}

真的有人写这样的代码?

有!

在介绍如何修改上述代码之前,首先让我们来看一看为什么这段代码不健康,然后再考虑“代码的味道”。

这段代码很糟糕吗?

这段代码非常难以阅读和理解。因此,在修改这段代码时很容易出bug。

应用中塞满这样的代码可不是什么好事,你也同意吧。尤其是当在应用程序的重要以及关键部分出现这样的代码!

关于TypeScript中的不可为null类型

有人可能会想到,TypeScript支持不可为null的类型。

你可以在编译选项中添加一个特殊的标志,该标志默认情况下会禁止给任何变量赋值null。

关于这个观点我还有几点想说:

  • 我们大多数人都需要处理现有的代码库,如果想修复这些编译错误就需要付出大量的时间和精力。

  • 如果不进行严格的测试且小心地避免作出任何假设,那么这些修改仍可能造成运行时错误。

  • 本文中谈到的解决方案也同样适用于其他语言,即便这些语言可能没有这样的选项。

无论怎样,安全的做法还是更有针对性地改进代码。同样,这可以确保系统的行为不变,而且还可以避免在修改的时候引入大量风险。

一种解决方案:Null对象模式

空集合

想象一下,你在一家为处理法律案件编写软件的公司工作。

有一天,在开发某项功能时,你看到了如下代码:

const legalCases: LegalCase[] = await fetchCasesFromAPI();
for (const legalCase of legalCases) {
    if(legalCase.documents != null) {
        uploadDocuments(legalCase.documents);
    }
}

记得我们应该谨慎使用null检查吗?如果代码的某个地方忘记了检查null数组,就会造成大问题。

Null对象模式可以帮助你:你可以创建一个代表“空”的对象或null对象。

修改代码

让我们来看看fetchCasesFromAPI()方法。我们可以使用这种模式,而且在JavaScript和TypeScript 中处理数组时,这种做法很普遍。

const fetchCasesFromAPI = async function() {
    const legalCases: LegalCase[] = await $http.get('legal-cases/');

    for (const legalCase of legalCases) {
        // Null Object Pattern
        legalCase.documents = legalCase.documents || [];
    }
    return legalCases;
}

为了避免空数组/集合的值为null,我们将一个空数组赋给它。

这样一来,别处就不需要检查null了!

但是,整个legalCase怎么办呢?如果这个API返回null,该如何是好?

const fetchCasesFromAPI = async function() {
    const legalCasesFromAPI: LegalCase[] = await $http.get('legal-cases/');
    // Null Object Pattern
    const legalCases = legalCasesFromAPI || [];

    for (const case of legalCases) {
        // Null Object Pattern
        case.documents = case.documents || [];
    }
    return legalCases;
}

搞定!

现在我们可以确保每个使用该方法的人都无需再担心null检查了!

示例2

在C#、Java等其他语言中,由于强类型的规定,你不能将一个空数组(即[])赋给集合。

这时候,你可以使用Null对象模式:

class EmptyArray<T> {
    static create<T>() {
        return new Array<T>()
    }
}

// Use it like this:
const myEmptyArray: string[] = EmptyArray.create<T>();

对象又如何呢?

想象一下,你正在开发一款视频游戏,你在某些级别设定了大boss。

在检查该级别是否有大boss的时候,你可能会遇到如下代码:

if(currentLevel.boss != null) {
    currentLevel.boss.fight(player);
}

我们可能会发现其他地方也有这类的null检查:

if(currentLevel.boss != null) {
    currentLevel.completed = currentLevel.boss.isDead();
}

我们只需引入一个null对象,就可以省却这些所有的null检查。

首先,我们需要一个表示大boss的接口:

interface IBoss {
    fight(player: Player);
    isDead();
}

接下来,我们可以创建一个boss类:

class Boss implements IBoss {
    fight(player: Player) {
        // Do some logic and return a bool.
    }

    isDead() {
        // Return whether boss is dead depending on how the fight went.
    }
}

然后,我们创建IBoss接口(代表一个“null” boss)的实现:

class NullBoss implements IBoss {
    fight(player: Player) {
        // Player always wins.
    }
    isDead() {
        return true;
    }
}

这个NullBoss会自动让玩家“通关”,所以我们就可以删除所有的null检查了!

在如下代码示例中,如果boss是NullBoss或Boss的实例,也不需要额外的检查。

currentLevel.boss.fight(player);
currentLevel.completed = currentLevel.boss.isDead();

如何保证代码的健康? 可以参考《Refa ctoring TypeScript 》一书中的部分内容,这本书旨在通过易于入手且实用的工具, 帮助开发人员更好地构建高质量的软件。

原文:https://dev.to/jamesmh/unhealthy-code-null-checks-everywhere-2720

本文为 CSDN 翻译,转载请注明来源出处。

【END】

 热 文推 荐 

华为发布麒麟 990 芯片; 苹果召回部分电源插头转换器;KDevelop 5.4.2 发布 | 极客头条

我如何在 16 岁成为全栈开发者?

2亿日活,日均千万级视频上传,快手推荐系统如何应对技术挑战?

Docker容器化部署Python应用

☞给面试官讲明白:一致性Hash的原理和实践

预警,CSW的50万枚尘封BTC即将重返市场?

☞她说:行!没事别嫁程序员!

点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

你点的每个“在看”,我都认真当成了喜欢

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章