限制函數的參數數量是很是重要的,由於它使你的函數更容易測試。超過三個參數會致使參數之間的組合過多,你必須對每一個單獨的參數測試大量不一樣的狀況。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(); } }