如何写好代码(3)——函数篇其一

课程 shanhuhai 1500℃ 0评论

函数是程序设计中的重要组成部分,下面所说的函数包括了一般的函数,也包括了类里面的方法。

函数参数最好少于两个

为什么要限制函数参数个数:

  1. 如果函数的参数超过2个说明它要处理的事情太多了,如果必须要传入很多数据, 可以选择封装一个高级别对象作为参数。

  2. 函数参数少方便做单元测试, 当函数参数超过3个时,你会发现函数参数组合会爆炸式增长,你会有巨多的参数情形要测试

不好的

function createArticle(string $title, string $content, string $thumb, bool $tags): void
{
    // ...
}

好的


class Article { public $title; public $content; public $thumb; public $tags = ''; } $article = new Article(); $article->title = '如何提升代码质量'; $article->content = '如何提升代码质量...'; $article->thumb = 'http://www.xxx.com/1.jpg'; $article->tags = '技术|编程|代码'; //创建文章 function createArticle(Article $article): void { //... }

函数应该只做一件事

这是目前为止软件工程中最重要的原则。为什么函数要只做一件事:

  1. 易于编写
  2. 易于测试
  3. 方便重构
  4. 可读性高

掌握了这个原则,你的代码水平会高于大多数程序员。

不好的

function emailClients(array $client): void 
{
    foreach ($clients as $client)
    {
        $clientRecord = $db->find($client);
        if($clientRecord->isActive())
        {
            email($client);
        }
    }
}

好的

function emailClients(array $clients): void 
{
    //获取所有激活的邮箱
    $activeClients = getActiveClients($clients);
    //向所有激活的邮箱发送邮件
    array_walk($activeClients, 'email');
}

function getActiveClients(array $clients): array 
{
    //过滤出所有激活的邮箱
    return  array_filter($clients, 'isClientActive');
}

function isClientActive(int $client): bool 
{
    $clientRecord = $db->find($client);
    return $clientRecord->isActive();
}

Tips1:

如何定义『一件事』 本身没有硬性标准,比如我可以把函数定义为『打开冰箱门』, 那这个函> 数就只能做打开冰箱门的事情,如果把函数定义为『开关』冰箱门就可以用来开或关冰箱门,也可以理解为是一件事。
对函数的功能划分是根据需求来不断变化的,做项目的时候要关心这个功能以后的发展方向是什么样的, 然后根据未来迭代对这个功能的修改进行适当的扩展性和维护性的设计,这个这需要经验。

函数的名称应该是自解释的

不好的

class Email
{
    //...

    public function handle(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}
$message = new Email(...);
// handle 的意义不明所以
$message->handle();

好的

class Email 
{
    //...

    public function send(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// 非常明显,send 发送消息
$message->send();

函数应该只做一层抽象

当你的函数有多个抽象层次时, 表示这个函数做的事情太多了,这时候需要分割函数使得重用性和测试更加容易

抽象的过程,是把复杂的事物简单化的过程, 抽象是对事物本质特性的归纳。比如。每一只?都是不同的,但是他们有着共同的特性,比如都会汪汪叫,会跑,比如四条腿, 我们提取到的?的共性特征越多对?的描述越准确, 在面向对象里我们可以用类来描述这种抽象方法
抽象具有层次性,当我们说『这是一只?』时,如果站在哺乳动物的角度来看,这句话是具体的, 如果站在牧羊犬的角度来看,是抽象的。牧羊犬->?->哺乳动物->动物->生物->事物, 这种抽象的层级可以用面向对象的类的继承来实现,当我们解决具体问题时, 设计好抽象层次, 便于我们把复杂的问题变的简单易于理解便于解决问题。

不好的

class TestArticle {

    public function __construct(){
    }

    public function addArticleWithDetails(){
        $input = new Input();
        $input->fillIn('title', '如何提升代码质量');
        $input->fillIn('description', '本文介绍了如何提升代码质量');
        $input->fillIn('content','xxxxxxxxxx');
        $input->validate();

        $this->fillForm();
        $this->clickAddArticle();
    }

    public function fillOrderForm(){}

    public function clickAddAttendee(){}
}

好的

class TestArticle {
    public function __construct(){}

    public function addArticleWithDetails(){
        $this->fillArticleDetails();
        $this->fillForm();
        $this->clickAddArticle();
    }

    public function fillArticleDetails()
    {
        $input = new Input();
        $input->fillIn('title', '如何提升代码质量');
        $input->fillIn('description', '本文介绍了如何提升代码质量');
        $input->fillIn('content','xxxxxxxxxx');
        $input->validate();
    }

    public function fillForm(){}

    public function clickAddArticle(){}
}

关于对抽象的理解后面的课程会详细讲解

不要使用标记做函数参数

参数中使用了标记做参数,表示函数做不止一件事, 如果函数根据标记执行不同的代码路径, 则应该将函数拆分

不好的:

function createFile($name, $temp = false)
{
    if ($temp) {
        touch('./temp/'.$name);
    } else {
        touch($name);
    }
}

好的:

function createFile($name)
{
    touch($name); 
} 

function createTempFile($name) 
{
    touch('./temp/'.$name);
}

避免副作用

一个函数应该只接受值,并返回其他值,如果在这个过程中还做了其他事,就有可能产生副作用。副作用有可能是意外的篡改了文件 , 修改了全局变量,或者钱转给了陌生人。

如果确实要在函数里做一些其他操作, 比如要写一个文件, 可以把对文件的操作集中到一个类里,不要用几个函数或者类中操作文件,只允许一个单独的服务进行操作。

避免常见的副作用陷阱可以让你比大多数程序员更快乐:

  1. 对象间共享无结构的数据
  2. 使用可能写入任何值的可变数据类型
  3. 不集中处理可能产生副作用的操作

不好的


// 在函数中使用全局变量, 变量可能会被篡改为其他类型 $name = 'Aron Gao'; function splitIntoFirstAndLastName(): void { global name; $name = explode(' ', name); } splitIntoFirstAndLastName(); var_dump($name); // ['Aron', 'Gao'];

推荐的

function splitIntoFirstAndLastName(string $name): array
{
    return explode(' ', name);
}

$name = 'Aron Gao';
$newName = splitIntoFirstAndLastName($name);

var_dump($name); // 'Aron Gao';
var_dump($newName); // ['Aron', 'Gao'];

不要定义全局函数

不定义全局函数主要的目的是避免库之间的命名冲突。
如果你想要拿到配置数组可以编写一个全局的 config() 函数, 但是当其他的库也定义了 config() 函数时,就会产生冲突。

php 中函数在不在命名空间的管理范围下。所以在引入大量库时有可能产生冲突。

不好的

function config()
{
    return  [
        'foo' => 'bar',
    ]
}

好的

class Configuration
{
    private $configuration = [];

    public function __construct(array $configuration)
    {
        $this->configuration = $configuration;
    }

    public function get($key)
    {
        return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
    }
}

// 加载配置并且创建 Configuration 类的实例
$configuration = new Configuration([
    'foo' => 'bar',
]);



总结

本课介绍了函数部分的一些技巧,主要有以下几点:

  1. 函数参数最好不要超过2个, 超过2个函数将可能难以测试和维护
  2. 函数应该只做一件事,如何定义一件事 ,取决于对软件项目未来功能和扩展性的预判
  3. 函数的名称应该是自解释的
  4. 函数应该只做一层抽象, 不同层次的抽象不应该放在统一层次, 关于对抽象的理解,请关注后面的课程
  5. 不要使用标记做函数参数
  6. 避免副作用
  7. 不要定义全局函数

本科主要难于理解掌握的是, 函数只做一件事、只做一层抽象以及避免副作用, 单靠这两个例子还是不够的, 掌握这几点需要非常全面的软件设计思维, 后面的课程中帮助大家会逐步强化锻炼这些思维能力。

作业

检查自己的编写的代码中是否有函数参数超过2个以上的,如果有应该如何优化,把优化前和优化后的代码,发布到留言区。

书籍推荐

《重构》

这本书可以帮助你更好理解, 为什么函数应该只做一件事, 如何设计函数粒度,更好的进行抽象层次的设计,帮助你把代码写的更加易读易维护。

转载请注明:大后端 » 如何写好代码(3)——函数篇其一

付费咨询
喜欢 (1)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址