封装 Optional 对象来判断空值

这篇文档讲述了我们如何来处理代码中的空值(null),我封装了一个 Optional 的对象来处理空值。

两种处理空值的方法的对比

首先我们来对比一下两种写法, PHP 中传统判断空值的写法。首先,我先创建一个获取用户的函数, 正常的写法去判断是否为空:

function getUser(int $id): ?UserModel { return UserModel::find($id); } $user = getUser(1); if ($user === null) { throw new Exception('The user does not exists.') }

如果使用 Optional 对象,其写法如下:

function getUser(): ?UserModel { // 使用 Optional 对象包裹一层 return Optional::ofNullable( UserModel::find($id) ); } $user = getUser() // 如果为空,则抛出异常 ->ofElseThrow(fn () => new BadRequestException('The user does not exists.')) ->get(); // 如果不会空,则通过 get() 方法获取

这种写法来自 Java,为了解决 Java 中令人头疼的 NPE(空指针异常)。这个 Optional 的做法是来自 Google 的 Guava 工具库,后在 Java8 中被吸收,从语言层面给与支持。

两种写法的深入思考

看上去,第二种写法更加抽象与复杂(即使是在 Java 中,很多开发者也是不习惯使用 Optional 对象的。但是我们要清楚,这种封装要解决的是什么问题:强制开发者判断返回值是否为空

第一种写法更加简洁,但是在实际编码中,开发者经常会有意或无意的忽略去检查返回值是否为空。 我封装这个的目的,不是为了推广这种写法,而是来说,针对 null 我们存在不同的处理方法。

一种方法依赖于开发者的习惯,第二种方法从语言层面来进行约束。所以, PHP 开发者核心成员的鸟哥说:其实动态类型的语言,更加考验开发者的能力。 我是这么理解的:弱类型的语言,想要灵活玩转他的灵活性其实很难,不管是 PHP 还是 JavaScript 或是 Python,它给了开发者更多的自由,但如果自由过了火,对大型的团队项目来说就是灾难。

我要强调的是,语言往往不是关键,关键在于开发者本身。用 C 也能写出面向对象的代码,用 Java 也能写出 PHP 的代码,用 PHP 也能写出 Laravel 源码一般的优雅的代码。

这样的实现在 PHP 中是存在代价的,IDE 会丧失了对返回值类型的推断能力。但是在 Java 中,由于泛型的存在,是可以的。

下面评论中有提到可以使用 PHPStorm 的泛型注释,我也试了一下,非常棒。

PHP、Python 这种语言是永远不会去实现泛型的,因为本身就是动态类型语言,支持鸭子类型,故不需要泛型

Optional 的简单实现

然后我们简单来实现一下,上文中演示的 Optional 的封装,就 3 个方法:

class Optional { private mixed $value; public function __construct(mixed $value) { $this->value = $value; } // 如果为空,则抛出异常,否则返回值 public function get(): mixed { if ($this->value === null) { throw new NotSuchElementException(); } return $this->value; } // 构造一个 Optional 的对象,允许为空 public static function ofNullable(mixed $value): self { return new self($value); } // 如果存在 null,则执行回调函数并抛出异常,否则返回自身实例 public function ofElseThrow(callable $exception): self { if ($this->value !== null) { return $this; } throw $exception(); } }

总结

通过这篇文档,我想说的是,我们不应该只关注语言本身,而是关注语言背后的那些思想,以及所解决的问题。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 14

延申开去讲,管理一个团队是需要 Java 一般从语言层面的约束的,但是像 PHP 一般自由的团队,可能会更有创造力

1年前 评论

laravel 中 默默认就有Optional

1年前 评论
苏近之 (楼主) 1年前

捉个虫,其实 python 是强类型语言 :wink:

1年前 评论
苏近之 (楼主) 1年前
fatrbaby

Laravel本来就有Optional的实现吧

1年前 评论
苏近之 (楼主) 1年前

另外,如果写一篇文章来介绍 Laravel 中 Optional 的使用,不是太枯燥了嘛。所以这篇文章主要说的是,如何判空、不同做法之间的区别,以及如何自行封装(或者你说借鉴 Java 也行)。

1年前 评论
zds

phpstorm 支持泛型注释
你说的 ide 提示只需要稍微改动一下就可以了

Optional 示例

/** * @template T * @template-implements T */ class Optional { private mixed $value; /** * @param mixed|T $value */ public function __construct(mixed $value) { $this->value = $value; } // 如果为空,则抛出异常,否则返回值 /** * @return mixed|T */ public function get() { if ($this->value === null) { throw new NotSuchElementException(); } return $this->value; } /** * 构造一个 Optional 的对象,允许为空 * @param mixed|T|<class-string> $value * @return Optional<T|class-string> */ public static function ofNullable(mixed $value): self { return new self($value); } // 如果存在 null,则执行回调函数并抛出异常,否则返回自身实例 public function ofElseThrow(callable $exception): self { if ($this->value !== null) { return $this; } throw $exception(); } }

User

class User { public function __construct( private int $id ){} public function getId(): int { return $this->id; } public function setId(int $id): User { $this->id = $id; return $this; } }

Laravel

Conn

class Conn { public function __construct( private int $fd ){} public function getFd(): int { return $this->fd; } public function setFd(int $fd): void { $this->fd = $fd; } }

Laravel

1年前 评论
苏近之 (楼主) 1年前
zds (作者) 1年前
苏近之 (楼主) 1年前

看博主的文章学到了, 看评论区更是学到了 :+1:

1年前 评论
苏近之 (楼主) 1年前