PHP代碼簡潔之道——函數部分

函數參數不要超過兩個

限制函數的參數數量是很是重要的,由於它使你的函數更容易測試。超過三個參數會致使參數之間的組合過多,你必須對每一個單獨的參數測試大量不一樣的狀況。php

沒有參數是最理想的狀況,一個或兩個參數是能夠接受的,三個以上則是應該避免的。這很重要的。若是你有兩個以上的參數,那麼你的函數可能試圖作的太多,若是不是,你可能須要將一個高級別的對象傳當作參數傳進去。node

Bad:程序員

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

Good:編程

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)
{
    // ...
}

一個函數只作一件事

這是軟件工程中一個重要的原則。這會讓你的代碼清晰易懂以及易於複用。數組

Bad:編程語言

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

Good:函數

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();
}

函數名要能說明它是作什麼的

Bad:測試

class Email
{
    //...

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

$message = new Email(...);
// 這是什麼?一條消息的句柄?仍是要寫一個文件?(讀者的疑問)
$message->handle();

Good:ui

class Email 
{
    //...

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

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

函數應該只作一層抽象

當你有多個層次的抽象時,你的函數就已經作的太多了。拆分這些函數,可讓代碼可重用性更高且更易測試。
Bad:this

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...
    }
}

Bad too:
咱們從函數中遷出去了一些工做,可是 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...
    }
}

Good:

最好的解決方案是移除 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...
        }
    }
}

不要使用標誌做爲函數的參數

當你在函數中使用標誌來做爲參數時,你的函數就不是隻作一件事情了,這與咱們前面所講的每一個函數只作一件事的原則相違背,因此不要使用標誌做爲函數的參數。

Bad:

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

Good:

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

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

避免反作用

若是一個函數作了「拿到一個值並返回一個值或者多個值」之外的事情,那麼這個函數就有可能產生反作用,反作用多是意外的寫入了文件、修改了全局變量、或者打錢給了陌生人。

如今假如你確實要在函數中作一些有可能產生反作用的事情。 好比要寫一個文件,你須要作的是將寫文件的操做集中到一處,而不是在幾個函數或者類裏對同一個文件作操做,實現一個服務(函數或者類)去操做它,有且僅有一個。

關鍵是要能避免常見的陷阱:像是在沒有結構的對象之間共享狀態、使用可能被寫入任何值的可變數據類型、 不集中處理有可能產生反作用的操做。 若是你能作到這些,你會比絕大多數程序員更快樂。

Bad:

// 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'];

Good:

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

$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);

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

不要修改全局變量

在許多編程語言中污染全局是一種糟糕的作法,由於你的庫可能會與另外一個庫衝突,可是你的庫的用戶卻一無所知,直到在生產環境中爆發異常。讓咱們來考慮一個例子:若是你想要拿到配置數組怎麼辦?你能夠編寫全局函數,如config(),可是它可能與另外一個試圖作一樣事情的庫衝突。

Bad:

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

Good:

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 = new Configuration([
    'foo' => 'bar',
]);

避免條件判斷

人們會問「若是不用 if 語句我該怎麼作?」,答案是在許多狀況下,你能夠用多態來實現一樣的效果。那這樣作什麼好處,仍是那句話:「一個函數應該只作一件事」, 當你的類或函數中有了 if 語句,你的函數就不止是隻作一件事情了。

Bad:

class Airplane
{
    // ...

    public function getCruisingAltitude()
    {
        switch ($this->type) {
            case '777':
                return $this->getMaxAltitude() - $this->getPassengerCount();
            case 'Air Force One':
                return $this->getMaxAltitude();
            case 'Cessna':
                return $this->getMaxAltitude() - $this->getFuelExpenditure();
        }
    }
}

Good:

interface Airplane
{
    // ...

    public function getCruisingAltitude();
}

class Boeing777 implements Airplane
{
    // ...

    public function getCruisingAltitude()
    {
        return $this->getMaxAltitude() - $this->getPassengerCount();
    }
}

class AirForceOne implements Airplane
{
    // ...

    public function getCruisingAltitude()
    {
        return $this->getMaxAltitude();
    }
}

class Cessna implements Airplane
{
    // ...

    public function getCruisingAltitude()
    {
        return $this->getMaxAltitude() - $this->getFuelExpenditure();
    }
}
相關文章
相關標籤/搜索