封装 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 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《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周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!