限制函数的参数数量是很是重要的,由于它使你的函数更容易测试。超过三个参数会致使参数之间的组合过多,你必须对每一个单独的参数测试大量不一样的状况。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(); } }