(版本franã§aise:https://dev.to/fredbouchery/dx-setter-wither-and-mutants-4b44)
当我设计PHP代码时,我会花费大量时间来思考其DX(开发人员经验),这就是为什么我喜欢“测试之前”,因为它可以让我考虑如何在实施之前使用该代码。
最近,我正在为数据库访问层设计配置类,这是我最初计划使用它的方式:
<?php
$conf = (new ConnectionConfig())
->connection('main')
->driver(DriverInterface::DRIVER_MYSQL)
->primary()
->host('127.0.0.1', 3306)
->credentials('root', 'root')
->replica()
->host('127.0.0.1', 3307)
->credentials('readonly', 'readonly')
;
实现是一个相当简单的流利接口:
-
connection(string $name)
设置了“$currentConnectionName
”属性。 -
primary()
或replica()
设置了“$currentReplicationServer
”属性。 - 然后,当称为
host()
或credentials()
时,我使用“$currentXxxx
”属性来存储该值。
接下来,我想定义如何阅读这些值,然后开始写这篇文章:
<?php
[
'login' => $login,
'password' => $password
] = $conf
->connection('main')
->primary()
->getCredentials()
;
第一个观察:哎呀...我太懒了,无法定义一个“ ServerCredentials
”对象,无法存储登录和密码,即使关联数组可以做得很好。不要用那种严厉的表情看着我,我们都做了……在懒惰的日子里,正如Krän所说的那样:“有几天你不应该和我在一起!每天都有一天!”但这不是今天的辩论。让我们看一下我想突出的下一个问题。
要读取凭据,我使用了一个getCredentials()
getter,而在定义它时,我使用了简单的credentials($login, $pass)
方法,因为我发现它对我的dx较冷。
实际上,从要检索定义的人的角度来看,拥有一个不以“ set”开头的getter和分配方法不是通常建议这两种方法。
显然,具有两个参数$login
和$pass
的方法应该是分配该值的方法,但是,我仍然更喜欢用setters更改该值。
是的,因为当您设计出良好的DX时,您不应该像开发人员做事的方式更改太多!即使您的想法似乎是革命性的,创新的,从未做过,也可能会严重伤害您的DX。
在这里我如何更改定义:
<?php
$conf = (new ConnectionConfig())
->connection('main')
->setDriver(DriverInterface::DRIVER_MYSQL)
->primary()
->setHost('127.0.0.1', 3306)
->setCredentials('root', 'root')
->replica()
->setHost('127.0.0.1', 3307)
->setCredentials('readonly', 'readonly')
;
然后,我出现了一个新的困难:突变体!
看一下:
<?php
$serverIp = '127.0.0.1';
$login = 'root';
$password = 'root';
$conf = new ConnectionConfig();
$primary = $conf->connection('main')->primary();
$replica = $conf->connection('main')->replica();
// Define hosts
$primary->setHost($serverIp, 3306);
$replica->setHost($serverIp, 3307);
// Define credentials
$primary->setCredentials($login, $password);
$replica->setCredentials($login, $password);
是的,我们设计了一种使用代码的方法,开发人员将以完全不同的方式进行操作,这是他的权利。
但是,在这种情况下,有一个相当令人尴尬的错误:主要服务器配置从未定义。如果您不明白为什么,我会让您在阅读随后的解释之前尝试理解。
。
。
。
。
。
。
。
。
。
正如我在开始时提到的那样,当我们调用primary()
或replica()
时,我们正在设置$currentReplicationServer
属性,并且由于我使用了流利的接口,因此$primary
和$replica
变量都包含相同的对象实例。因此,在调用->replica()
时,将修改$currentReplicationServer
属性以表明我们正在配置复制品,这也会影响$primary
变量中的实例(如果您不太了解它,我会让您重读)。
现在,我们陷入了PHP(和其他语言)的经典陷阱:对象Mutability。
为了解决此问题,使用自动关闭,更改了属性,并返回了新实例。所以,我们从:
<?php
public function primary(): self
{
$this->currentReplicationConfiguration = 'primary';
return $this;
}
to:
<?php
public function primary(): self
{
$self = clone $this;
$self->currentReplicationConfiguration = 'primary';
return $self;
}
将此技术应用于所有方法,但是...它行不通。因为在这种情况下:
<?php
$conf = new ConnectionConfig();
$primary = $conf->connection('main')->primary();
$replica = $conf->connection('main')->replica();
我想要的是,$conf
包含所有配置元素,用于“ main
”连接,带有主服务器和副本,最后,我们有3个具有分离值的配置类的实例。
这就是流利设计的问题,再加上状态/无状态设计:如果方法的行为取决于另一个方法的呼吁,那么您会遇到麻烦!
有一种方法可以通过使开发人员了解该方法返回一个新实例来部分解决这一问题:我们将由Withers替换固定器:
<?php
$conf = new ConnectionConfig();
$primary = $conf->withConnection('main')->withPrimary();
使用前缀“ with
”表示返回将是对象的新实例,因此$conf
与$primary
不是同一实例。开发人员必须连接他的电话,否则他将无法获得他期望的结果。
实际上,我们还没有通过其他代码来解决问题,而是通过重命名,因为是的,命名是设计的一部分,并且DX。
。最终代码将是:
<?php
$conf = (new ConnectionConfig())
->withConnection('main')
->setDriver(DriverInterface::DRIVER_MYSQL)
->withPrimary()
->setHost('127.0.0.1', 3306)
->setCredentials('root', 'root')
->withReplica()
->setHost('127.0.0.1', 3307)
->setCredentials('readonly', 'readonly')
;
,如果我们返回访问权限,那将是:
<?php
[
'login' => $login,
'password' => $password
] = $conf
->withConnection('main')
->withPrimary()
->getCredentials()
;
老实说,我认为这个DX并不令人惊讶,因为在我的小脑中,我倾向于将“枯萎”与建筑物相关联,而不是访问。
这就像在圆圈一样,你不觉得吗?
实际上,不,这仅仅是因为我从一开始就走错了方向,这开始了我写我很懒惰的时候!
- 我的班级应该被称为“
ConnectionsConfiguration
”,带有“ s”和一个完整的词(停止捷径,不是另外7个字符会影响您的生产力) - 应该只有一种返回“
ConnectionConfiguration
”对象的方法“connection(string $name)
”(没有“ s”) -
ConnectionConfiguration
具有三种方法setDriver()
,primary()
和replica()
,后两个返回的ServerConfiguration
的可变实例。 -
ServerConfiguration
包含四个方法setHost()
,setCredentials()
,getHost()
和getCredentials()
管理ServerHost
和ServerCredentials
对象。
最后,代码将是:
<?php
$conf = (new ConnectionsConfiguration())
->connection('main')
->setDriver(DriverInterface::DRIVER_MYSQL)
->primary()
->setHost('127.0.0.1', 3306)
->setCredentials('root', 'root')
->replica()
->setHost('127.0.0.1', 3307)
->setCredentials('readonly', 'readonly')
;
访问配置变为:
<?php
$credentials = $conf
->connection('main')
->primary()
->getCredentials()
;
$login = $credentials->login;
$password = $credentials->password;
最细心的人可能已经注意到我的课程是可变的,对于某些人来说,这是异端,丑闻,可耻的,应该使我掌握危险!对于那些,我会回答:
是的,但是它会产生比解决并打破我的DX更多的问题。因此,我将您推荐给Krãän的咒语:“有几天你不应该和我惹!
总结:
- 将自己从用户的角度来看,而不是实现的
- 不要过多创新,人们有模式
- 不要懒
- 少数不变性
- 在状态对象上谨慎使用流利的接口
- 阅读Krän,这是一本令人愉快的疯狂漫画书。