PHP代碼整潔之道

原文:
https://github.com/jupeter/clean-code-php

譯文:
https://github.com/yangweijie/clean-code-php#變量


  • 變量

使用有意義且可拼寫的變量名

Bad:

$ymdstr = $moment->format('y-m-d');

Good:

$currentDate = $moment->format('y-m-d');

同種類型的變量使用相同詞匯

Bad:

getUserInfo();
getClientData();
getCustomerRecord();

Good:

getUser();
  • 使用易檢索的名稱

我們會讀比寫要多的代碼壤蚜。通過是命名易搜索合蔽,讓我們寫出可讀性和易搜索代碼很重要锅很。

Bad:

// What the heck is 86400 for?
addExpireAt(86400);

Good:

// Declare them as capitalized `const` globals.
interface DateGlobal {
  const SECONDS_IN_A_DAY = 86400;
}

addExpireAt(DateGlobal::SECONDS_IN_A_DAY);
  • 使用解釋型變量

Bad:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);

Good:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
list(, $city, $zipCode) = $matchers;
saveCityZipCode($city, $zipCode);
  • 避免心理映射

明確比隱性好悠栓。

Bad:

$l = ['Austin', 'New York', 'San Francisco'];
foreach($i=0; $i<count($l); $i++) {
  oStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // 等等`$l` 又代表什么?
  dispatch($l);
}

Good:

$locations = ['Austin', 'New York', 'San Francisco'];
foreach($i=0; $i<count($locations); $i++) {
  $location = $locations[$i];

  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch($location);
});
  • 不要添加不必要上下文

如果你的class/object 名能告訴你什么,不要把它重復(fù)在你變量名里冯遂。

Bad:

$car = [
  'carMake'  => 'Honda',
  'carModel' => 'Accord',
  'carColor' => 'Blue',
];

function paintCar(&$car) {
  $car['carColor'] = 'Red';
}

Good:

$car = [
  'make'  => 'Honda',
  'model' => 'Accord',
  'color' => 'Blue',
];

function paintCar(&$car) {
  $car['color'] = 'Red';
}
  • 使用參數(shù)默認(rèn)值代替短路或條件語句喉刘。

Bad:

function createMicrobrewery($name = null) {
  $breweryName = $name ?: 'Hipster Brew Co.';
  // ...
}

Good:

function createMicrobrewery($breweryName = 'Hipster Brew Co.') {
  // ...
}
  • 函數(shù)

函數(shù)參數(shù)最好少于2個

限制函數(shù)參數(shù)個數(shù)極其重要因?yàn)樗悄愫瘮?shù)測試容易點(diǎn)。有超過3個可選參數(shù)參數(shù)導(dǎo)致一個爆炸式組合增長薪夕,你會有成噸獨(dú)立參數(shù)情形要測試。

無參數(shù)是理想情況赫悄。1個或2個都可以原献,最好避免3個。再多舊需要加固了埂淮。通常如果你的函數(shù)有超過兩個參數(shù)姑隅,說明他多做了一些事。 在參數(shù)少的情況里倔撞,大多數(shù)時候一個高級別對象(數(shù)組)作為參數(shù)就足夠應(yīng)付讲仰。

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) {
  // ...
}
  • 函數(shù)應(yīng)該只做一件事

這是迄今為止軟件工程里最重要的一個規(guī)則。當(dāng)函數(shù)做超過一件事的時候痪蝇,他們就難于實(shí)現(xiàn)鄙陡、測試和理解。當(dāng)你隔離函數(shù)只剩一個功能時躏啰,他們就容易被重構(gòu)趁矾,然后你的代碼讀起來就更清晰。如果你光遵循這條規(guī)則丙唧,你就領(lǐng)先于大多數(shù)開發(fā)者了愈魏。

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();
}
  • 函數(shù)名應(yīng)當(dāng)描述他們所做的事

Bad:

function addToDate($date, $month) {
  // ...
}

$date = new \DateTime();

// It's hard to to tell from the function name what is added
addToDate($date, 1);

Good:

function addMonthToDate($month, $date) {
  // ...
}

$date = new \DateTime();
addMonthToDate(1, $date);
  • 函數(shù)應(yīng)當(dāng)只為一層抽象,當(dāng)你超過一層抽象時想际,函數(shù)正在做多件事培漏。拆分功能易達(dá)到可重用性和易用性。.

Bad:

function parseBetterJSAlternative($code) {
  $regexes = [
    // ...
  ];

  $statements = split(' ', $code);
  $tokens = [];
  foreach($regexes as $regex) {
    foreach($statements as $statement) {
      // ...
    }
  }

  $ast = [];
  foreach($tokens as $token) {
     // lex...
  }

  foreach($ast as $node) {
   // parse...
  }
}

Good:

function tokenize($code) {
  $regexes = [
    // ...
  ];

  $statements = split(' ', $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...
  });
}
  • 刪除重復(fù)的代碼

盡你最大的努力來避免重復(fù)的代碼胡本。重復(fù)代碼不好牌柄,因?yàn)樗馕吨绻阈薷囊恍┻壿嫞蔷陀胁恢挂惶幍胤揭叫薷牧恕?/p>

想象一下如果你經(jīng)營著一家餐廳并跟蹤它的庫存: 你全部的西紅柿侧甫、洋蔥珊佣、大蒜、香料等披粟。如果你保留有多個列表咒锻,當(dāng)你服務(wù)一個有著西紅柿的菜,那么所有記錄都得更新守屉。如果你只有一個列表惑艇,那么只需要修改一個地方!

經(jīng)常你容忍重復(fù)代碼,因?yàn)槟阌袃蓚€或更多有共同部分但是少許差異的東西強(qiáng)制你用兩個或更多獨(dú)立的函數(shù)來做相同的事滨巴。移除重復(fù)代碼意味著創(chuàng)造一個處理這組不同事物的一個抽象思灌,只需要一個函數(shù)/模塊/類。

抽象正確非常重要恭取,這也是為什么你應(yīng)當(dāng)遵循SOLID原則(奠定Class基礎(chǔ)的原則)泰偿。壞的抽象可能比重復(fù)代碼還要糟,因?yàn)橐⌒尿诳濉T谶@個前提下耗跛,如果你可以抽象好,那就開始做把攒发!不要重復(fù)你自己课兄,否則任何你想改變一件事的時候你都發(fā)現(xiàn)在即在更新維護(hù)多處。

Bad:

function showDeveloperList($developers) {
  foreach($developers as $developer) {
    $expectedSalary = $developer->calculateExpectedSalary();
    $experience = $developer->getExperience();
    $githubLink = $developer->getGithubLink();
    $data = [
      $expectedSalary,
      $experience,
      $githubLink
    ];

    render($data);
  }
}

function showManagerList($managers) {
  foreach($managers as $manager) {
    $expectedSalary = $manager->calculateExpectedSalary();
    $experience = $manager->getExperience();
    $githubLink = $manager->getGithubLink();
    $data = [
      $expectedSalary,
      $experience,
      $githubLink
    ];

    render($data);
  }
}

Good:

function showList($employees) {
  foreach($employees as $employe) {
    $expectedSalary = $employe->calculateExpectedSalary();
    $experience = $employe->getExperience();
    $githubLink = $employe->getGithubLink();
    $data = [
      $expectedSalary,
      $experience,
      $githubLink
    ];

    render($data);
  }
}
  • 通過對象賦值設(shè)置默認(rèn)值

Bad:

$menuConfig = [
  'title'       => null,
  'body'        => 'Bar',
  'buttonText'  => null,
  'cancellable' => true,
];

function createMenu(&$config) {
  $config['title']       = $config['title'] ?: 'Foo';
  $config['body']        = $config['body'] ?: 'Bar';
  $config['buttonText']  = $config['buttonText'] ?: 'Baz';
  $config['cancellable'] = $config['cancellable'] ?: true;
}

createMenu($menuConfig);

Good:

$menuConfig = [
  'title'       => 'Order',
  // User did not include 'body' key
  'buttonText'  => 'Send',
  'cancellable' => true,
];

function createMenu(&$config) {
  $config = array_merge([
    'title'       => 'Foo',
    'body'        => 'Bar',
    'buttonText'  => 'Baz',
    'cancellable' => true,
  ], $config);

  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu($menuConfig);
  • 不要用標(biāo)志作為函數(shù)的參數(shù)晨继,標(biāo)志告訴你的用戶函數(shù)做很多事了。函數(shù)應(yīng)當(dāng)只做一件事搬俊。 根據(jù)布爾值區(qū)別的路徑來拆分你的復(fù)雜函數(shù)紊扬。

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);
}
  • 避免副作用

一個函數(shù)做了比獲取一個值然后返回另外一個值或值們會產(chǎn)生副作用如果。副作用可能是寫入一個文件唉擂,修改某些全局變量或者偶然的把你全部的錢給了陌生人餐屎。

現(xiàn)在,你的確需要在一個程序或者場合里要有副作用玩祟,像之前的例子腹缩,你也許需要寫一個文件。你想要做的是把你做這些的地方集中起來空扎。不要用幾個函數(shù)和類來寫入一個特定的文件藏鹊。用一個服務(wù)來做它,一個只有一個转锈。

重點(diǎn)是避免常見陷阱比如對象間共享無結(jié)構(gòu)的數(shù)據(jù)盘寡,使用可以寫入任何的可變數(shù)據(jù)類型,不集中處理副作用發(fā)生的地方撮慨。如果你做了這些你就會比大多數(shù)程序員快樂竿痰。

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() {
  $name = preg_split('/ /', $name);
}

splitIntoFirstAndLastName();

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

Good:

$name = 'Ryan McDermott';

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

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

var_export($name); // 'Ryan McDermott';
var_export($newName); // ['Ryan', 'McDermott'];
  • 不要寫全局函數(shù)

在大多數(shù)語言中污染全局變量是一個壞的實(shí)踐,因?yàn)槟憧赡芎推渌悗鞗_突并且你api的用戶不明白為什么直到他們獲得產(chǎn)品的一個異常砌溺。讓我們看一個例子:如果你想配置一個數(shù)組影涉,你可能會寫一個全局函數(shù)像config(),但是可能和試著做同樣事的其他類庫沖突规伐。這就是為什么單例設(shè)計模式和簡單配置會更好的原因蟹倾。

Bad:

function config() {
  return  [
    'foo': 'bar',
  ]
};

Good:

class Configuration {
  private static $instance;
  private function __construct($configuration) {/* */}
  public static function getInstance() {
     if(self::$instance === null) {
         self::$instance = new Configuration();
     }
     return self::$instance;
 }
 public function get($key) {/* */}
 public function getAll() {/* */}
}

$singleton = Configuration::getInstance();
  • 封裝條件語句

Bad:

if ($fsm->state === 'fetching' && is_empty($listNode)) {
  // ...
}

Good:

function shouldShowSpinner($fsm, $listNode) {
  return $fsm->state === 'fetching' && is_empty(listNode);
}

if (shouldShowSpinner($fsmInstance, $listNodeInstance)) {
  // ...
}
  • 避免消極條件

Bad:

function isDOMNodeNotPresent($node) {
  // ...
}

if (!isDOMNodeNotPresent($node)) {
  // ...
}

Good:

function isDOMNodePresent($node) {
  // ...
}

if (isDOMNodePresent($node)) {
  // ...
}
  • 避免條件聲明

這看起來像一個不可能任務(wù)。當(dāng)人們第一次聽到這句話是都會這么說楷力。 "沒有一個if聲明" 答案是你可以使用多態(tài)來達(dá)到許多case語句里的任務(wù)喊式。第二個問題很常見孵户, “那么為什么我要那么做?” 答案是前面我們學(xué)過的一個整潔代碼原則:一個函數(shù)應(yīng)當(dāng)只做一件事岔留。當(dāng)你有類和函數(shù)有很多if聲明,你自己知道你的函數(shù)做了不止一件事夏哭。記住,只做一件事献联。

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:

class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude() - $this->getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude();
  }
}

class Cessna extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude() - $this->getFuelExpenditure();
  }
}
  • Avoid 避免類型檢查 (part 1)

PHP是弱類型的,這意味著你的函數(shù)可以接收任何類型的參數(shù)竖配。 有時候你為這自由所痛苦并且在你的函數(shù)漸漸嘗試類型檢查。有很多方法去避免這么做里逆。第一種是考慮API的一致性进胯。

Bad:

function travelToTexas($vehicle) {
  if ($vehicle instanceof Bicycle) {
    $vehicle->peddle($this->currentLocation, new Location('texas'));
  } else if ($vehicle instanceof Car) {
    $vehicle->drive($this->currentLocation, new Location('texas'));
  }
}

Good:

function travelToTexas($vehicle) {
  $vehicle->move($this->currentLocation, new Location('texas'));
}
  • 避免類型檢查 (part 2)

如果你正使用基本原始值比如字符串、整形和數(shù)組原押,你不能用多態(tài)胁镐,你仍然感覺需要類型檢測,你應(yīng)當(dāng)考慮類型聲明或者嚴(yán)格模式诸衔。 這給你了基于標(biāo)準(zhǔn)PHP語法的靜態(tài)類型盯漂。 手動檢查類型的問題是做好了需要好多的廢話,好像為了安全就可以不顧損失可讀性笨农。保持你的PHP 整潔就缆,寫好測試,做好代碼回顧谒亦。做不到就用PHP嚴(yán)格類型聲明和嚴(yán)格模式來確保安全竭宰。

Bad:

function combine($val1, $val2) {
  if (is_numeric($val1) && is_numeric(val2)) {
    return val1 + val2;
  }

  throw new \Exception('Must be of type Number');
}

Good:

function combine(int $val1, int $val2) {
  return $val1 + $val2;
}
  • 移除僵尸代碼

僵尸代碼和重復(fù)代碼一樣壞。沒有理由保留在你的代碼庫中份招。如果從來被調(diào)用過切揭,見鬼去!在你的版本庫里是如果你仍然需要他的話脾还,因此這么做很安全伴箩。

Bad:

function oldRequestModule($url) {
  // ...
}

function newRequestModule($url) {
  // ...
}

$req = new newRequestModule();
inventoryTracker('apples', $req, 'www.inventory-awesome.io');

Good:

function newRequestModule($url) {
  // ...
}

$req = new newRequestModule();
inventoryTracker('apples', $req, 'www.inventory-awesome.io');
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鄙漏,隨后出現(xiàn)的幾起案子嗤谚,更是在濱河造成了極大的恐慌,老刑警劉巖怔蚌,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巩步,死亡現(xiàn)場離奇詭異,居然都是意外死亡桦踊,警方通過查閱死者的電腦和手機(jī)椅野,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人竟闪,你說我怎么就攤上這事离福。” “怎么了炼蛤?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵妖爷,是天一觀的道長。 經(jīng)常有香客問我理朋,道長絮识,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任嗽上,我火速辦了婚禮次舌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兽愤。我一直安慰自己彼念,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布浅萧。 她就那樣靜靜地躺著国拇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惯殊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天也殖,我揣著相機(jī)與錄音土思,去河邊找鬼。 笑死忆嗜,一個胖子當(dāng)著我的面吹牛己儒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捆毫,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼闪湾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绩卤?” 一聲冷哼從身側(cè)響起途样,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎濒憋,沒想到半個月后何暇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凛驮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年裆站,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡宏胯,死狀恐怖羽嫡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肩袍,我是刑警寧澤杭棵,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站了牛,受9級特大地震影響颜屠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鹰祸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一甫窟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛙婴,春花似錦粗井、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至餐济,卻和暖如春耘擂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背絮姆。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工醉冤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人篙悯。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓蚁阳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸽照。 傳聞我的和親對象是個殘疾皇子螺捐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容