文章轉發自專業的Laravel開發者社區,原始連接:learnku.com/laravel/t/3…php
咱們添加到項目的每一行代碼,都會增長了它們的複雜性,而且增長了隨時會產生bug的可能性。多是在客戶開會前幾分鐘,也多是咱們週末在電影院期間,不在咱們的鍵盤前。laravel
爲了防止那些可怕的狀況出現,讓咱們經過下面七個技巧,來編寫更好的代碼:算法
代碼只寫一次,但會被其餘開發人員和您屢次閱讀和解釋。所以,值得花一些額外的時間來命名這個新的類或方法,所以它的名稱顯示了它的真實意圖或內容。 Let’s compare these two lines. Which one is easier to understand?編程
咱們來比較一下這兩行。哪個更容易理解?數組
$evnt->add($req->q);
複製代碼
$event->addTickets($request->quantity);
複製代碼
第一行有輸入錯誤,add
方法不清楚添加了什麼,變量 $req
不夠清楚,很難理解 q
是指數量。bash
另外一方面,第二個例子即便對於非開發人員也很容易理解。函數
永遠不要低估以有序和一致的方式編寫代碼的重要性,由於這樣可讓您更快地發現問題。單元測試
考慮如下兩個例子:測試
public function addTickets($quantity)
{
foreach (range(1, $quantity) as $i)
{
$code = Code::generate(); }
$this->tickets()->create(
[
'code' => $code,
]);
}
複製代碼
public function addTickets($quantity)
{
foreach (range(1, $quantity) as $i) {
$code = Code::generate();
}
$this->tickets()->create([
'code' => $code,
]);
}
複製代碼
兩個代碼塊都有相同的錯誤:當它們應該建立 N 時,兩個代碼塊都只建立一個 ticket 。可是在哪一個代碼塊中,您更快地發現了問題?如今想象一下處理格式錯誤的複雜代碼的後果。ui
儘管我們學習演算法的第一章是如何宣告及使用臨時變量,他們仍然會讓代碼的閱讀及維護變得困難。
思考一下下方的例子:
$contact = array();
$contact['firstname'] = $user->first_name;
$contact['surname'] = $user->last_name;
$contact['id'] = $user->id;
$contact_emails = array();
$contact_email = array();
$contact_email['email'] = $user->email;
$contact_emails[] = $contact_email;
$contact['emails'] = $contact_emails;
$this->create('contact', $contact);
複製代碼
$contact = [
'id' => $user->id,
'firstname' => $user->first_name,
'surname' => $user->last_name,
'emails' => [
[
'email' => $user->email,
],
],
];
$this->create('contact', $contact);
複製代碼
哪個例子更容易理解?
順道一提,使用等號是一個壞習慣。這不僅是違反了 PSR-2,也會讓代碼變得難以維護。
因此,回到我們的第二個例子,這個例子能夠藉由去除 code
變量,以行內的寫法來優化:
public function addTickets($quantity)
{
foreach (range(1, $quantity) as $i) {
$this->tickets()->create([
'code' => Code::generate(6),
]);
}
}
複製代碼
然而,在某些情況下,使用局部變量能夠提升代碼的易讀性,例如:
function calculateCode($price, $quantity, $deliveryCost)
{
$subtotal = $price * $quantity;
if ($subtotal < 30) {
$subtotal += $deliveryCost;
}
return $subtotal;
}
複製代碼
可能會比下面這個更清晰:
<?php
function calculateTotal($price, $quantity, $deliveryCost)
{
if ($price * $quantity < 30) {
return $price * $quantity + $deliveryCost;
}
return $price * $quantity;
}
複製代碼
若是一份訂單低於30元,那麼將不包郵,並支付額外的運費, 這個時候咱們應該使用常量來標識,若是訂單價格低於30元,那麼將支付運費。常量的配置使用以下:
if ($subtotal < DELIVERY_COST_THRESHOLD) {
$subtotal += $deliveryCost;
}
複製代碼
翻譯代碼對照以下:
if ( 訂單價格 < 不包郵的價格) {
$當前訂單價格 += 運費價格;
}
複製代碼
在這個方法中, 咱們展現了使用常量的便捷, 一樣咱們也能夠在其餘項目須要使用的部分重複使用這個常量。
若是咱們須要改變不包郵的規則, 咱們僅僅只須要更新一行常量代碼, 既減小重複,同時也減小了在代碼中使用固定數字來判斷的不肯定性。
許多場景均可以將過長的代碼分離成多個小方法,使得每個方法都有不一樣的職責。例如:
新方法 getContactInfo
將返回帶有用戶聯繫信息的數組:
$this->create('contact', $user->getContactInfo());
複製代碼
面向對象編程要求咱們將數據和函數集中在一個地方(類)。咱們將在包含全部用戶信息的(
User
模型)中組裝包含聯繫人信息的數組。
再看另外一個例子
$subtotal = $item->price * $quantity;
$subtotal = $this->addDeliveryCost($subtotal);
複製代碼
方法 addDeliveryCost
將返回一個交付成本的金額,但前提是該金額不超過設置的閥值,不然將返回原始金額。
如今讓咱們刪除本地變量並內聯代碼:
return $this->addDeliveryCost($price * $quantity);
複製代碼
聲明和使用許多小方法是減小代碼中使用臨時變量的好方法。
許多承諾你將編寫出更好的代碼的教程到最後都會使代碼變的過於複雜。
若是你正在使用 Laravel 和 Eloquent,這些教程就會告訴你,將下面這段代碼放在控制器是錯誤的:
// Somewhere in UserController.php
User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password),
]);
複製代碼
你應該這樣寫:
// Somewhere in UserController.php
$this->commandTransport->handleCommand(
new UserCreationCommand(
new UserNameField($request->name),
new UserEmailField($request->email),
new UserPasswordField(bcrypt($request->password)),
)
);
複製代碼
在 UserCreationCommandHandler
類中,你一樣不能建立 user,由於這違反了 SOLID 原則。你應該使用 repository:
class UserCreationCommandHandler
{
//...
public function handle(UserCreationCommand $command)
{
$this->userRepository->create(
$command->name,
$command->email,
$command->password,
);
}
}
複製代碼
最終,在 UserEloquentRepository
類中, 你將最後調用 User::create
:
class UserEloquentRepository implements UserRepository
{
//...
public function create(
UserNameField $name,
UserEmailField $email,
UserPasswordField $password
) {
return User::create([
'name' => $name->getValue(),
'email' => $email->getValue(),
'password' => bcrypt($password->getValue()),
]);
}
}
複製代碼
過了一會,客戶端要求你向 User 模型添加另外一個字段。
哪一個更簡單?哪一個方案更容易出 bug (極可能你就忘記將一個字段從一個方法傳入另外一個方法)。
同時,你是否注意到在例2中調用了兩次 bcrypt
?因此第二個例子有 bug。
不幸的是,有些接口和類不會阻止你犯錯。因此,須要仔細的去測試代碼,說到測試代碼:
會計師採用一種叫作「複式記帳法」的方式記帳。這種方法要求他們把全部的交易都記錄兩次。編寫單元測試須要咱們編寫兩次代碼,一次定義每個測試:
function test_order_without_delivery_cost()
{
$order = new Order;
$order->addItem(new Item(['price' => 20]), 5);
$expectedTotal = 20 * 5;
$this->assertSame($expectedTotal, $order->getTotal());
}
function test_order_with_delivery_cost()
{
$order = new Order;
$order->addItem(new Item(['price' => 20]), 1);
$expectedTotal = 20 + DELIVERY_COST;
$this->assertSame($expectedTotal, $order->getTotal());
}
複製代碼
第二次編寫代碼的實現 (這個艱鉅的任務就交給你了)。
許多開發人員抱怨這種作法是由於它迫使咱們「加倍工做」,可是經過編寫兩次代碼,咱們減小了以一樣的方式犯一樣錯誤的可能性(若是咱們犯兩個不一樣的錯誤,測試可能會失敗)。這就是爲何實現一些單元測試的項目每每會有很小的bug,然而須要好幾個小時的調試時間。