For 迴圈使用
避免使用意義不明的 Array
舉例
$l = ['Austin', 'New York', 'San Francisco'];
for ($i = 0; $i < count($l); $i++) {
$li = $l[$i];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// Wait, what is `$li` for again?
dispatch($li);
}
調整
使用 foreach 來遍歷 Array
$locations = ['Austin', 'New York', 'San Francisco'];
foreach ($locations as $location) {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch($location);
}
命名 Array 變數要有意義
$locations = ['Austin', 'New York', 'San Francisco'];
for ($i = 0; $i < count($locations); $i++) {
$location = $locations[$i];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch($location);
}
Comparison
型別轉換會導致比較失敗,字串轉為整數後比較,但事實字串不是數字
舉例 (一)
$x = '0';
$y = 0;
if ($x != $y) {
// 不會執行
// The expression will always pass
}
調整
強制比較型別和值
$x = '0';
$y = 0;
if ($x !== $y) {
// 會執行
// This ensures the comparison is accurate by type and value
}
舉例 (二)
$arr = null;
$str = '';
if ($arr != $str) {
// 不會執行
// The expression will always pass
}
調整
強制比較型別和值
$arr = null;
$str = '';
if ($arr !== $str) {
// 會執行
// This ensures the comparison is accurate by type and value
}
使用判斷語法糖來簡化語法
舉例
if (isset($_GET['name'])) {
$name = $_GET['name'];
} else {
$name = 'nobody';
}
調整
$name = $_GET['name'] ?? 'nobody';
函式用法
需使用 type hinting 限定參數值,避免內部還需要判斷
舉例
function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
// ...
}
調整
function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
// ...
}
減少函式參數,最好少於 2 個
舉例
class Questionnaire
{
public function __construct(
string $firstName,
string $lastName,
string $patronymic,
string $region,
string $district,
string $city,
string $phone,
string $email
) {
// ...
}
}
調整
class Name
{
private $firstName;
private $lastName;
private $patronymic;
public function __construct(string $firstName, string $lastName, string $patronymic)
{
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->patronymic = $patronymic;
}
}
class City
{
private $region;
private $district;
private $city;
public function __construct(string $region, string $district, string $city)
{
$this->region = $region;
$this->district = $district;
$this->city = $city;
}
}
class Contact
{
private $phone;
private $email;
public function __construct(string $phone, string $email)
{
$this->phone = $phone;
$this->email = $email;
}
}
class Questionnaire
{
public function __construct(Name $name, City $city, Contact $contact)
{
// ...
}
}
function 名稱要表達目的性
舉例
class Email
{
//...
public function handle(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// What is this? A handle for the message? Are we writing to a file now?
$message->handle();
調整
class Email
{
//...
public function send(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
$message->send();
function 內部盡量減少抽象層(abstraction)
程式碼的問題在於抽象層次混亂和函式責任不明確
舉例
tokenize 的細節過多,目的不夠明確
function tokenize(string $code): ~~array~~
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
似乎是為了進行 lexical analysis,但其實它在做的是把 token 轉換為 AST 節點
function lexer(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
function parseBetterPHPAlternative(string $code): void
{
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
}
}
調整
class Tokenizer
{
public function tokenize(string $code): array
{
$regexes = [
// ...
];
$statements = explode(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}
class Lexer
{
public function lexify(array $tokens): array
{
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ... */;
}
return $ast;
}
}
class BetterPHPAlternative
{
private $tokenizer;
private $lexer;
public function __construct(Tokenizer $tokenizer, Lexer $lexer)
{
$this->tokenizer = $tokenizer;
$this->lexer = $lexer;
}
public function parse(string $code): void
{
$tokens = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
}
}
}
函式內不要使用 boolean 作為區分,函式功能違反單一職責原則
舉例
function createFile(string $name, bool $temp = false): void
{
if ($temp) {
touch('./temp/' . $name);
} else {
touch($name);
}
}
調整
function createFile(string $name): void
{
touch($name);
}
function createTempFile(string $name): void
{
touch('./temp/' . $name);
}
函式內不僅僅有回傳,還有改變外部的值,這將更難維護
函式目的請單一化
舉例
// 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 = 'Rootimes\' blog';
function splitIntoFirstAndLastName(): void
{
global $name;
$name = explode(' ', $name);
}
splitIntoFirstAndLastName();
var_dump($name);
// ['Rootimes', 'blog'];
調整
function splitIntoFirstAndLastName(string $name): array
{
return explode(' ', $name);
}
$name = 'Rootimes blog';
$newName = splitIntoFirstAndLastName($name);
var_dump($name);
// 'Rootimes blog';
var_dump($newName);
// ['Rootimes', 'blog'];
避免使用全域函式, php 本身沒有 namespace 會導致衝突
舉例
function config(): array
{
return [
'foo' => 'bar',
];
}
調整
利用類別封裝
class Configuration
{
private $configuration = [];
public function __construct(array $configuration)
{
$this->configuration = $configuration;
}
public function get(string $key): ?string
{
// null coalescing operator
return $this->configuration[$key] ?? null;
}
}
# 使用 class
$configuration = new Configuration([
'foo' => 'bar',
]);
避免使用 singleton pattern
- 隱藏了應用的依賴關係
- 不僅負責自身的邏輯,還負責自己的創建和生命週期管理
舉例
class DBConnection
{
private static $instance;
private function __construct(string $dsn)
{
// ...
}
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// ...
}
$singleton = DBConnection::getInstance();
調整
明確創建 DBConnection 的實例
class DBConnection
{
public function __construct(string $dsn)
{
// ...
}
// ...
}
$connection = new DBConnection($dsn);
物件與資料結構
使用物件封裝
舉例
class BankAccount
{
public $balance = 1000;
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->balance -= 100;
調整
class BankAccount
{
private $balance;
public function __construct(int $balance = 1000)
{
$this->balance = $balance;
}
public function withdraw(int $amount): void
{
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance.');
}
$this->balance -= $amount;
}
public function deposit(int $amount): void
{
$this->balance += $amount;
}
public function getBalance(): int
{
return $this->balance;
}
}
$bankAccount = new BankAccount();
$bankAccount->withdraw($shoesPrice);
$balance = $bankAccount->getBalance();
