函数参数不要超过两个 限制函数的参数数量是非常重要的,因为它使你的函数更容易测试。超过三个参数会导致参数之间的组合过多,你必须对每个单独的参数测试大量不同的情况。
没有参数是最理想的情况,一个或两个参数是可以接受的,三个以上则是应该避免的。这很重要。如果你有两个以上的参数,那么你的函数可能试图做的太多,如果不是,你可能需要将一个高级别的对象传当做参数传进去。
Bad:
1 2 3 4 5 function createMenu ($title, $body, $buttonText, $cancellable) { }
Good:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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:
1 2 3 4 5 6 7 8 9 function emailClients ($clients) { foreach ($clients as $client) { $clientRecord = $db->find($client); if ($clientRecord->isActive()) { email($client); } } }
Good:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Email { public function handle () { mail($this ->to, $this ->subject, $this ->body); } } $message = new Email(...); $message->handle();
Good:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Email { public function send () { mail($this ->to, $this ->subject, $this ->body); } } $message = new Email(...); $message->send();
函数应该只做一层抽象 当你有多个层次的抽象时,你的函数就已经做的太多了。拆分这些函数,可以让代码可重用性更高且更易测试。 Bad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function parseBetterJSAlternative ($code) { $regexes = [ ]; $statements = explode(' ' , $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { } } $ast = []; foreach ($tokens as $token) { } foreach ($ast as $node) { } }
Bad too: 我们从函数中迁出去了一些工作,但是 parseBetterJSAlternative() 函数还是很复杂,不可测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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) { } }
Good:
最好的解决方案是移除 parseBetterJSAlternative 函数的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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) { } } }
不要使用标志作为函数的参数 当你在函数中使用标志来作为参数时,你的函数就不是只做一件事情了,这与我们前面所讲的每个函数只做一件事的原则相违背,所以不要使用标志作为函数的参数。
Bad:
1 2 3 4 5 6 7 8 function createFile ($name, $temp = false) { if ($temp) { touch('./temp/' .$name); } else { touch($name); } }
Good:
1 2 3 4 5 6 7 8 9 10 function createFile ($name) { touch($name); } function createTempFile ($name) { touch('./temp/' .$name); }
避免副作用 如果一个函数做了“拿到一个值并返回一个值或者多个值”以外的事情,那么这个函数就有可能产生副作用,副作用可能是意外的写入了文件、修改了全局变量、或者打钱给了陌生人。
现在假如你确实要在函数中做一些有可能产生副作用的事情。 比如要写一个文件,你需要做的是将写文件的操作集中到一处,而不是在几个函数或者类里对同一个文件做操作,实现一个服务(函数或者类)去操作它,有且仅有一个。
关键是要能避免常见的陷阱:像是在没有结构的对象之间共享状态、使用可能被写入任何值的可变数据类型、 不集中处理有可能产生副作用的操作。 如果你能做到这些,你会比绝大多数程序员更快乐。
Bad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $name = 'Ryan McDermott' ; function splitIntoFirstAndLastName () { global $name; $name = explode(' ' , $name); } splitIntoFirstAndLastName(); var_dump($name);
Good:
1 2 3 4 5 6 7 8 9 10 11 function splitIntoFirstAndLastName ($name) { return explode(' ' , $name); } $name = 'Ryan McDermott' ; $newName = splitIntoFirstAndLastName($name); var_dump($name); var_dump($newName);
不要修改全局变量 在许多编程语言中污染全局是一种糟糕的做法,因为你的库可能会与另一个库冲突,但是你的库的用户却一无所知,直到在生产环境中爆发异常。让我们来考虑一个例子:如果你想要拿到配置数组怎么办?你可以编写全局函数,如config(),但是它可能与另一个试图做同样事情的库冲突。
Bad:
1 2 3 4 5 6 function config () { return [ 'foo' => 'bar' , ] }
Good:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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(); } }