函式引數不要超過兩個
限制函式的引數數量是非常重要的,因為它使你的函式更容易測試。超過三個引數會導致引數之間的組合過多,你必須對每個單獨的引數測試大量不同的情況。
沒有引數是最理想的情況,一個或兩個引數是可以接受的,三個以上則是應該避免的。這很重要的。如果你有兩個以上的引數,那麼你的函式可能試圖做的太多,如果不是,你可能需要將一個高階別的物件傳當做引數傳進去。
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:
class Email
{
//...
public function send()
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// 一目瞭然
$message->send();
函式應該只做一層抽象
當你有多個層次的抽象時,你的函式就已經做的太多了。拆分這些函式,可以讓程式碼可重用性更高且更易測試。
Bad:
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();
}
}