读《代码整洁之道》有感

很久之前买过几本编程相关的书,不过到手后翻翻就放起来啦!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 协议》,转载必须注明作者和本文链接
本帖由系统于 7年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 12

好像没有解决 当出现新员工类型时需要增加

7年前 评论

@ralph 可以看看抽象工厂

7年前 评论

顶一下,工作中经常用到的思路,对个人的习惯提升也很有帮助。
专门爬上来顶一下!

7年前 评论
takashiki

@ralph php里可以这样:

$employeeClass = $type.'Employee'; 
$employee = new $employeeClass();
echo get_class($field);
7年前 评论

@takashiki 非常感谢你的解答,尽管我已知悉

7年前 评论

一直在努力的写更简洁的代码 :thumbsup:

7年前 评论

@takashiki 前两句懂了,第三句的 $field 是指什么?

7年前 评论
takashiki

@maxrisk 写错了,应该是 echo get_class($employee);

7年前 评论

增加。
违反了单一职责原则(SRP)
违反了开放闭合原则(OCP)
最重要的而且麻烦的是,同样的switch语句可能会出现在多个地方,比如是否是发工资的时间isPayday($employee, $date) 以及 发放工资deliverPay($employee, $money)

到这里我们就应该重新设计一下这里的实现了。我们把计算工资、发放工资、是否是发薪日这些函数抽象到一个Employee的抽象类,然后搭建一个工厂EmployeeFactory类,用来生产不同种类的雇员。看代码:

// 雇员抽象类
abstract class Employee {

public function isPayday($date);

public function calculatePay
7年前 评论

$this->validateLogin($request); 验证之后不需要做处理吗?难道你是直接 throw 的报错?

5年前 评论

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