“Null 是价值十亿美元的错误!”“不健康”的代码到处都是 Null 检查!
“Null 是价值十亿美元的错误!”

作者 | James Hickey

译者 | 弯月,责编 | 郭芮

出品 | CSDN (ID:CSDNnews)

以下为译文:

你是否知道提出“null”这个概念的人声称这是他的“十亿美元错误!”这个问题看似很简单,然而一旦你深入大型项目和代码库,就必然会发现有些代码中 null 的使用简直匪夷所思。有时,我们希望使对象的属性为可选:
[code]
class Product{
public id: number;
public title: string;
public description: string;
}

[/code]

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

但是,number 属性居然也可以!
[code]
const chocolate: Product = new Product();
chocolate.id = null;
chocolate.description = null;

[/code]

呃……

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

再举个例子
乍一看去,似乎并没有什么不妥。但是,这可能会引发如下情况:
[code]
const chocolate: Product = new Product(null, null, null);

[/code]

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

系统中的 Product 没有 id,这合理吗?应该不合理吧。理想情况下,一旦创建 Product,它就应该有 id。那么,在需要与 Product 打交道的其他地方会怎么样呢?实际情况惨不忍睹:
[code]
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"  
}

[/code]

真的有人写这样的代码?

有!在介绍如何修改上述代码之前,首先让我们来看一看为什么这段代码不健康,然后再考虑“代码的味道”。
“Null 是价值十亿美元的错误!”这段代码很糟糕吗?
这段代码非常难以阅读和理解。因此,在修改这段代码时很容易出 bug。应用中塞满这样的代码可不是什么好事,你也同意吧。尤其是当在应用程序的重要以及关键部分出现这样的代码!
“Null 是价值十亿美元的错误!”**关于 TypeScript 中的不可为 null 类型
有人可能会想到,TypeScript 支持不可为 null 的类型。你可以在编译选项中添加一个特殊的标志,该标志默认情况下会禁止给任何变量赋值 null。关于这个观点我还有几点想说:

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

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

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

无论怎样,安全的做法还是更有针对性地改进代码。同样,这可以确保系统的行为不变,而且还可以避免在修改的时候引入大量风险。
“Null 是价值十亿美元的错误!”
一种解决方案:Null 对象模式
*
空集合
*想象一下,你在一家为处理法律案件编写软件的公司工作。有一天,在开发某项功能时,你看到了如下代码:
[code]
const legalCases: LegalCase[] = await fetchCasesFromAPI();
for (const legalCase of legalCases) {
if(legalCase.documents != null) {
uploadDocuments(legalCase.documents);
}
}

[/code]

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

Null 对象模式可以帮助你:你可以创建一个代表“空”的对象或 null 对象。修改代码让我们来看看 fetchCasesFromAPI() 方法。我们可以使用这种模式,而且在 JavaScript 和 TypeScript 中处理数组时,这种做法很普遍。
[code]
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;  
}

[/code]

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

这样一来,别处就不需要检查 null 了!但是,整个 legalCase 怎么办呢?如果这个 API 返回 null,该如何是好?
[code]
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;  
}

[/code]

搞定!

现在我们可以确保每个使用该方法的人都无需再担心 null 检查了!
“Null 是价值十亿美元的错误!”示例 2
在 C#、Java 等其他语言中,由于强类型的规定,你不能将一个空数组(即 [])赋给集合。这时候,你可以使用 Null 对象模式:
[code]
class EmptyArray {
static create() {
return new Array()
}
}

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

[/code]

对象又如何呢?

想象一下,你正在开发一款视频游戏,你在某些级别设定了大 boss。在检查该级别是否有大 boss 的时候,你可能会遇到如下代码:
[code]
if(currentLevel.boss != null) {
currentLevel.boss.fight(player);
}

[/code]

我们可能会发现其他地方也有这类的 null 检查:
[code]
if(currentLevel.boss != null) {
currentLevel.completed = currentLevel.boss.isDead();
}

[/code]

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

首先,我们需要一个表示大 boss 的接口:
[code]
interface IBoss {
fight(player: Player);
isDead();
}

[/code]

接下来,我们可以创建一个 boss 类:
[code]
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.  
    }  
}

[/code]

然后,我们创建 IBoss 接口(代表一个“null” boss)的实现:
[code]
class NullBoss implements IBoss {
fight(player: Player) {
// Player always wins.
}
isDead() {
return true;
}
}

[/code]

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

在如下代码示例中,如果 boss 是 NullBoss 或 Boss 的实例,也不需要额外的检查。
[code]
currentLevel.boss.fight(player);
currentLevel.completed = currentLevel.boss.isDead();
[/code]

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

原文:https://dev.to/jamesmh/unhealthy-code-null-checks-everywhere-2720 本文为 CSDN 翻译,转载请注明来源出处。

【END】

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

热 文 推 荐

华为发布麒麟 990 芯片;苹果召回部分电源插头转换器;KDevelop 5.4.2 发布 | 极客头条
☞高级软件工程师教会小白的那些事!
☞我如何在 16 岁成为全栈开发者?2 亿日活,日均千万级视频上传,快手推荐系统如何应对技术挑战?Docker 容器化部署 Python 应用☞给面试官讲明白:一致性 Hash 的原理和实践预警,CSW 的 50 万枚尘封 BTC 即将重返市场?☞她说:行!没事别嫁程序员!

“Null 是价值十亿美元的错误!”点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

“Null 是价值十亿美元的错误!”你点的每个“在看”,我都认真当成了喜欢

来源链接:mp.weixin.qq.com