PHP Clean Code
Clean Code 是一種精神,本篇主要蒐集在 PHP 這個語法的實踐
變數使用
使用有意義且常見的變數名稱
舉例
$ymdstr = $moment->format('y-m-d');
調整
$currentDate = $moment->format('y-m-d');
相同的實體使用相同的變數
舉例
getUserInfo();
getUserData();
getUserRecord();
getUserProfile();
調整
getUser();
使用易讀和容易搜尋的名稱,取代特定的數值
舉例 (一)
// What the heck is 448 for?
$json = $serializer->serialize($data, 448);
調整
$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
舉例 (二)
class User
{
// What the heck is 8 for?
public $access = 8;
}
// What the heck is 4 for?
if ($user->access & 4) {
// ...
}
// What's going on here?
$user->access ^= 2;
調整
class User
{
public const ACCESS_READ = 1; // 0001
public const ACCESS_CREATE = 2; // 0010
public const ACCESS_UPDATE = 4; // 0100
public const ACCESS_DELETE = 8; // 1000
// User as default can read, create and update something
public $access = self::ACCESS_READ | self::ACCESS_CREATE | self::ACCESS_UPDATE;
}
if ($user->access & User::ACCESS_UPDATE) {
// do edit ...
}
// Deny access rights to create something
$user->access ^= User::ACCESS_CREATE;
使用有意義單詞作為 Array 的 Key
舉例
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);
調整
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches['city'], $matches['zipCode']);
減少不需要的贅詞命名
舉例
class Car
{
public $carMake;
public $carModel;
public $carColor;
//...
}
調整
class Car
{
public $make;
public $model;
public $color;
//...
}
if 使用
避免過深的巢狀迴圈
舉例 (一)
function isShopOpen($day): bool
{
if ($day) {
if (is_string($day)) {
$day = strtolower($day);
if ($day === 'friday') {
return true;
} elseif ($day === 'saturday') {
return true;
} elseif ($day === 'sunday') {
return true;
}
return false;
}
return false;
}
return false;
}
調整
使用提前回傳和 Array 鍵判斷
function isShopOpen(string $day): bool
{
if (empty($day)) {
return false;
}
$openingDays = ['friday', 'saturday', 'sunday'];
return in_array(strtolower($day), $openingDays, true);
}
舉例 (二)
function fibonacci(int $n)
{
if ($n < 50) {
if ($n !== 0) {
if ($n !== 1) {
return fibonacci($n - 1) + fibonacci($n - 2);
}
return 1;
}
return 0;
}
return 'Not supported';
}
調整
使用提前回傳和整合 if 判斷條件
function fibonacci(int $n): int
{
if ($n === 0 || $n === 1) {
return $n;
}
if ($n >= 50) {
throw new Exception('Not supported');
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
封裝判斷式
舉例
if ($article->state === 'published') {
// ...
}
調整
if ($article->isPublished()) {
// ...
}
避免過多的條件聲明,改變函式目的維持單一職責原則
舉例
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();
}
}
}
調整
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();
}
}
避免類型檢查,使用類型聲明
舉例
function combine($val1, $val2) {
if (is_numeric($val1) && is_numeric(val2)) {
return val1 + val2;
}
throw new \Exception('Must be of type Number');
}
調整
function combine(int $val1, int $val2) {
return $val1 + $val2;
}
避免反向的判斷情形,嘗試語意化封裝
舉例
if (! isDOMNodeNotPresent($node)) {
// ...
}
調整
if (isDOMNodePresent($node)) {
// ...
}
