PHP 代码简洁之道——函数部分(一)

教程 shanhuhai 4595℃ 0评论

1. 函数参数(不要超过两个)

限制函数的参数数量是非常重要的,因为它使你的函数更容易测试。超过三个参数会导致参数之间的组合过多,你必须对每个单独的参数测试大量不同的情况。

没有参数最理想的情况,一个或两个参数是可以接受的,三个以上是应该避免的。这是很重要的。通常,如果你有两个以上的参数,那么你的函数可能试图做的太多,如果不是,你可能需要将一个高级别的对象传当做参数传进去。

不好的

function createMenu($title, $body, $buttonText, $cancellable)
{
    // ...
}

好的:

class MenuConfig
{
    public $title;
    public $body;
    public $buttonText;
    public $cancellable = false;
}

$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;

function createMenu(MenuConfig $config)
{
    // ...
}

2. 一个函数只做一件事

这是软件工程中最重要的原则。
不好的:


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

不好的

function emailClients($clients)
{
    $activeClients = activeClients($clients);
    array_walk($activeClients, 'email');
}

function activeClients($clients)
{
    return array_filter($clients, 'isClientActive');
}

function isClientActive($client)
{
    $clientRecord = $db->find($client);

    return $clientRecord->isActive();
}

3. 函数名要能说明它是做什么的

不好的:

class Email
{
    //...

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

$message = new Email(...);
// 这是什么?一条消息的句柄? 你是要写一个文件么?(读者的疑问)
$message->handle();

好的

class Email 
{
    //...

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

$message = new Email(...);
//  一目了然
$message->send();

4. 函数应该只做一层抽象

当你有多个层次的抽象时,你的函数通常都在试图做的太多。拆分这些函数,可以让代码可重用性更高且更易测试。

不好的:

function parseBetterJSAlternative($code)
{
    $regexes = [
        // ...
    ];

    $statements = explode(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            // ...
        }
    }

    $ast = [];
    foreach ($tokens as $token) {
        // lex...
    }

    foreach ($ast as $node) {
        // parse...
    }
}

同样不好的:
我们以前从函数中迁出去了一些工作,但是 parseBetterJSAlternative() 函数还是很复杂,不可测试。

function tokenize($code)
{
    $regexes = [
        // ...
    ];

    $statements = explode(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            $tokens[] = /* ... */;
        }
    }

    return $tokens;
}

function lexer($tokens)
{
    $ast = [];
    foreach ($tokens as $token) {
        $ast[] = /* ... */;
    }

    return $ast;
}

function parseBetterJSAlternative($code)
{
    $tokens = tokenize($code);
    $ast = lexer($tokens);
    foreach ($ast as $node) {
        // parse...
    }
}

好的:
最好的解决方案是移除 parseBetterJSAlternative 函数的依赖

class Tokenizer
{
    public function tokenize($code)
    {
        $regexes = [
            // ...
        ];

        $statements = explode(' ', $code);
        $tokens = [];
        foreach ($regexes as $regex) {
            foreach ($statements as $statement) {
                $tokens[] = /* ... */;
            }
        }

        return $tokens;
    }
}

class Lexer
{
    public function lexify($tokens)
    {
        $ast = [];
        foreach ($tokens as $token) {
            $ast[] = /* ... */;
        }

        return $ast;
    }
}

class BetterJSAlternative
{
    private $tokenizer;
    private $lexer;

    public function __construct(Tokenizer $tokenizer, Lexer $lexer)
    {
        $this->tokenizer = $tokenizer;
        $this->lexer = $lexer;
    }

    public function parse($code)
    {
        $tokens = $this->tokenizer->tokenize($code);
        $ast = $this->lexer->lexify($tokens);
        foreach ($ast as $node) {
            // parse...
        }
    }
}

5.不要使用标志做函数参数

标志相当于告诉使用者,这个函数不止做一件事。函数应该只做一件事,如果函数根据布尔值执行不同代码路径,则你应该将函数拆分。

不好的:

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

好的:

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

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

6.避免副作用

如果一个函数做了“拿到一个值并返回一个值或者多个值”以外的事情,那么这个函数就有可能产生副作用,副作用可能是意外的写入了文件、修改了全局变量、或者打钱给了陌生人。

现在假如你确实要在函数中做一些有可能产生副作用的事情。 比如要写一个文件,你需要做的是将写文件的操作集中到一处,而不是在几个函数或者类里对同一个文件做操作,实现一个服务(函数或者类)去操作它,有且仅有一个。

关键是要能避免常见的陷阱:像是在没有结构的对象之间共享状态、使用可能被写入任何值的可变数据类型、 不集中处理有可能产生副作用的操作。 如果你能做到这些,你会比绝大多数程序员更快乐。

不好的:

// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
$name = 'Ryan McDermott';

function splitIntoFirstAndLastName()
{
    global $name;

    $name = explode(' ', $name);
}

splitIntoFirstAndLastName();

var_dump($name); // ['Ryan', 'McDermott'];

好的:


function splitIntoFirstAndLastName($name) { return explode(' ', $name); } $name = 'Ryan McDermott'; $newName = splitIntoFirstAndLastName($name); var_dump($name); // 'Ryan McDermott'; var_dump($newName); // ['Ryan', 'McDermott'];

7. 不要修改全局变量

在许多编程语言中污染全局是一种糟糕的做法,因为你的库可能会与另一个库冲突,但是你的库的用户却一无所知,直到在生产环境中爆发异常。让我们来考虑一个例子:如果你想要拿到配置数组怎么办?你可以编写全局函数,如config(),但是它可能与另一个试图做同样事情的库冲突。

不好的:

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',
]);

现在你应该在你的应用程序中使用 Configuration类 的实例 。

转载请注明:大后端 » PHP 代码简洁之道——函数部分(一)

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

表情

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

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