读《代码整洁之道》有感
很久之前买过几本编程相关的书,不过到手后翻翻就放起来啦!2017 需要做点改变了,就从读书开始吧!
最近在读《代码整洁之道》,刚开始看,读完函数设计的篇章后有点恍然大悟的感觉,决定写点东西留住这个感觉。
函数的第一规则是要短小。第二条规则则是还要更短小。
函数应该做一件事。做好这件事。只做这一件事。
每个函数一个抽象层级。自顶向下读代码。
《代码整洁之道》
上面三件事是相互关联、承前启后的关系。总结一下我的理解:每个函数都应该保留一个抽象层级,而且尽量只做一件事。同一个抽象层级也会有多件事要处理,但是函数只需要做其中的一件事就行了(当然也不是绝对 ,接口函数一般要同时抽象处理多件事),按照这个规则写完后,你会发现你的代码可读性会有质的飞跃!
拿 laravel 中登陆模块举例,看看到底什么才算抽象。
public function login(Request $request)
{
$this->validateLogin($request);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
从上面的代码,登陆接口做了哪些事儿,一目了然。可读性大大提升。而且,它满足一个函数只保留一个抽象层级(当然函数不能只做一件事儿,不过它仍然是最简洁的)。
抽象完成后,挨个填充抽象出来的函数(填充的过程也要遵循上面的规则),整个登陆模块就完成啦!
插一句我理解的抽象,所谓的抽象,应该指代要做的事情的
概念
,而不是具体的细节实现
。而这个概念,就可以用来命名
抽象出来的这个函数名
。抽象也有点归类
的味道,他抽象出来的可能不止一件简单的事情,而是关系紧密的一系列事情。
还有一个我觉得重要的事情就是,如果函数中出现多个条件分支,应该怎么重新设计?
看一个实例,比如有一个根据服务员类型(全日制、小时工、临时工等等)来计算薪水的函数:
public function calculatePay(Employee $e)
{
switch ($e->type) {
case COMMISSIONED :
return calculateCommissionedPay($e);
case HOURLY :
return calculateHourlyPay($e);
case SALARIED :
return calculateSalariedPay($e);
default:
throw new InvalidEmployeeType($e->type);
}
}
我相信大部分人都会这么写(包括我,哈哈)。说一下这个函数有哪些问题:
- 明显这个函数做了不止一件事
- 当出现新员工类型时需要增加。
- 违反了单一职责原则(SRP)
- 违反了开放闭合原则(OCP)
最重要的而且麻烦的是,同样的switch
语句可能会出现在多个地方,比如是否是发工资的时间isPayday($employee, $date)
以及 发放工资deliverPay($employee, $money)
到这里我们就应该重新设计一下这里的实现了。我们把计算工资
、发放工资
、是否是发薪日
这些函数抽象到一个Employee
的抽象类,然后搭建一个工厂EmployeeFactory
类,用来生产不同种类的雇员。看代码:
// 雇员抽象类
abstract class Employee {
public function isPayday($date);
public function calculatePay();
public function deliverPay($money);
}
// 雇员工厂
interface EmployeeFactory {
public function makeEmployee(EmployeeRecord $record);
}
// 工厂的具体实现
class EmployeeFactoryImpl implements EmployeeFactory {
public function makeEmployee(EmployeeRecord $record)
{
switch ($record->type) {
case COMMISSIONED :
return new CommissionedEmployee($record);
case HOURLY :
return new HourlyEmployee($record);
case SALARIED :
return new SalariedEmployee($record);
default:
throw new InvalidEmployeeType($record->type);
}
}
}
虽然这样的实现也违反上面的规则,不过对于这样的switch 语句,书中作者的解释是,
如果只出现在创建多态对象,并且隐藏在某个继承关系中,在系统其它地方看不到,就还能容忍。(底线好低。。。)
当然还要就事论事,如果有更好的设计,何乐而不为呐~~~
本作品采用《CC 协议》,转载必须注明作者和本文链接
好像没有解决
当出现新员工类型时需要增加
@ralph 可以看看抽象工厂
顶一下,工作中经常用到的思路,对个人的习惯提升也很有帮助。
专门爬上来顶一下!
@ralph php里可以这样:
@takashiki 非常感谢你的解答,尽管我已知悉
一直在努力的写更简洁的代码 :thumbsup:
@takashiki 前两句懂了,第三句的 $field 是指什么?
@maxrisk 写错了,应该是 echo get_class($employee);
增加。
违反了单一职责原则(SRP)
违反了开放闭合原则(OCP)
最重要的而且麻烦的是,同样的switch语句可能会出现在多个地方,比如是否是发工资的时间isPayday($employee, $date) 以及 发放工资deliverPay($employee, $money)
到这里我们就应该重新设计一下这里的实现了。我们把计算工资、发放工资、是否是发薪日这些函数抽象到一个Employee的抽象类,然后搭建一个工厂EmployeeFactory类,用来生产不同种类的雇员。看代码:
// 雇员抽象类
abstract class Employee {
手动点赞
不错哦!赞一个
$this->validateLogin($request);
验证之后不需要做处理吗?难道你是直接 throw 的报错?