如何在 PhpStorm 使用 Refactoring (重構)
PhpStorm 最強悍的就是 Refactoring,這也是文字編輯器無法達到的,善用 Refactoring 將可大幅增加 code review 之後重構 PHP 的速度。
Version
PhpStorm 2017.1.2
Extraction
Extract Method
最需要被重構的程式碼,首推
Long Method
,一旦一個 method 的程式碼的行數過多,就會難以閱讀、難以維護、難以單元測試、也違反物件導向的單一職責原則
,建議使用 Extract Method
將 Long Method
拆成多個小 method。
何時該使用
Extract Method
呢?根據 Martin Fowler 在重構
這本書的建議 :重構前
- 當一段程式碼需要被重複使用時,就該獨立抽成 method。
- 當一段程式碼需要寫註解才能讓人理解時,就該獨立抽成 method。
- 當一段程式碼抽成 method 後,語意更清楚 ,就該獨立抽成 method。
namespace App\Services; class ExtractMethod { public function printOwing(string $name) { $this->printBanner(); // print details print("name: " . $name); print("amount " . $this->getOutstanding()); } }
重構後
namespace App\Services; class ExtractMethod { public function printOwing(string $name) { $this->printBanner(); // print details $this->printDetails($name); } /** * @param string $name */ private function printDetails(string $name) { print("name: " . $name); print("amount " . $this->getOutstanding()); } }
選擇要重構成 method 的程式碼,按熱鍵跳出
Refactor This
選單,選擇 Method
。Windows : Ctrl + Alt + Shift + TmacOS : control + T
輸入欲建立的 method 名稱,並選擇
public
、protected
或 private
,一般來說重構出來的 method 選 private
。
PhpStorm 會自動幫我們將所選的程式碼抽成
printDetails()
,連參數、型別與 PHPDoc 都會幫我們加上。Extract Field
實務上有些在 method 內的 local 變數,原本只有單一 method 使用,若有其他 method 也使用相同變數時,建議使用
Extract Field
將此變數重構成 field。
重構前
namespace App\Services; class ExtractField { public function print1() { $name = 'Hello World'; echo($name); } public function print2() { $name = 'Hello World'; echo($name); } }
重構後
namespace App\Services; class ExtractField { private $name = 'Hello World'; public function print1() { echo($this->name); } public function print2() { echo($this->name); } } |
將滑鼠游標放在變數名稱上,按熱鍵跳出
Refactor This
選單,選擇 Field
。Windows : Ctrl + Alt + Shift + TmacOS : control + T
可選擇要將單一變數或者將整個 expression 重構成 field,這裡選擇
$name
即可,因為我們想將 $name
變數重構成 field。
輸入欲建立的 field 名稱,並選擇
public
、protected
或 private
,為了實現物件導向資料封裝,建議重構出來的 field 選 private
。
在
Initialize in
選擇 field 初始化的方式,若是 PHP 原生型別,如 int
/ string
,則選擇 Field declaration
,若是物件,則必須選擇 Class constructor
。
PhpStorm 會自動幫我們將變數重構成 field,並將原來引用變數之處重構成引用 field。
Extract Variable
實務上有些原本在 method 內的固定值,想要變成變數,建議使用
Extract Variable
將固定值重構成變數。
重構前
namespace App\Services; class ExtractVariable { public function Calculate(int $i) { while ($i < 10) { $i = $i + 1; return $i; }; } public function DisplaySum() { $a = 1; $result = $this->Calculate($a); echo "The final result is " . $result; } } |
重構後
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | namespace App\Services; class ExtractVariable { public function Calculate(int $i) { $c = 10; while ($i < $c) { $i = $i + 1; return $i; }; } public function DisplaySum() { $a = 1; $result = $this->Calculate($a); echo "The final result is " . $result; } } |
將滑鼠游標放在
10
上,按熱鍵跳出 Refactor This
選單,選擇 Variable
。Windows : Ctrl + Alt + Shift + TmacOS : control + T
可以將固定值或 expression 抽成變數,這裡選擇
10
將固定值重構成變數。輸入欲建立的變數名稱。
PhpStorm 會自動幫我們加上重構過的變數,並將原有的值都以變數取代。
Extract Parameter
實務上有些原本在 method 內的固定值,想要變成參數可由外部帶入,建議使用
Extract Parameter
將固定值重構成參數。
重構前
amespace App\Services; class ExtractParameter { public function Calculate(int $i) { while ($i < 10) { $i = $i + 1; return $i; }; } public function DisplaySum() { $a = 1; $result = $this->Calculate($a); echo "The final result is " . $result; } } |
重構後
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | namespace App\Services; class ExtractParameter { public function Calculate(int $i, int $c) { while ($i < $c) { $i = $i + 1; return $i; }; } public function DisplaySum() { $a = 1; $result = $this->Calculate($a, 10); echo "The final result is " . $result; } } |
將滑鼠游標放在固定值上,按熱鍵跳出
Refactor This
選單,選擇 Parameter
。Windows : Ctrl + Alt + Shift + TmacOS : control + T
輸入欲建立的 parameter 名稱。
PhpStorm 會自動幫我們重構成參數,將原有的固定值都以參數取代,並在 method 呼叫 的地方重構成原來的值。
Extract Constant
實務上不建議將字串或數字直接 hardcode 在程式碼中 :
- 日後難以閱讀與維護
- 若字串與數字需要變動,需要改很多地方
建議將這類 Magic Number 使用
Extract Constant
重構成 constant。
重構前
namespace App\Services; class ExtractConstant { public function potentialEnergy(int $mass, int $height): float { return $mass * $height * 9.81; } } |
重構後
1 2 3 4 5 6 7 8 9 10 11 | namespace App\Services; class ExtractConstant { const GRAVITATIONAL_CONSTANT = 9.81; public function potentialEnergy(int $mass, int $height): float { return $mass * $height * self::GRAVITATIONAL_CONSTANT; } } |
將滑鼠游標放在數值上,按熱鍵跳出
Refactor This
選單,選擇 Constant
。Windows : Ctrl + Alt + Shift + TmacOS : control + T
輸入欲建立的 constant 名稱。
PhpStorm 會自動幫我們重構成
const
,並將原有的值都以 constant 取代。Extract Interface
為了讓 class 實現不同的角色,且讓 class 與 class 之間的耦合降低,讓物件不要直接相依某個物件,而是僅相依於 interface,建議使用
Extract Interface
重構出 interface。
重構前
namespace App\Services; class SMSService { public function printMessage() { echo('Print Message'); } public function sendMessage() : string { return 'Send Message'; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | namespace App\Services; use App\Post; class PostService { /** @var SMSService */ private $SMSService; /** * PostService constructor. * @param SMSService $SMSService */ public function __construct(SMSService $SMSService) { $this->SMSService = $SMSService; } public function showMessage() { return $this->SMSService->sendMessage(); } } |
重構後
1 2 3 4 5 6 | namespace App\Services; interface Sendable { public function sendMessage(): string; } |
1 2 3 4 5 6 | namespace App\Services; interface Printable { public function printMessage(); } |
1 2 3 4 5 6 7 8 9 10 11 12 | class SMSService implements Sendable, Printable { public function printMessage() { echo('Print Message'); } public function sendMessage() : string { return 'Send Message'; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | namespace App\Services; use App\Post; class PostService { /** @var Sendable */ private $SMSService; /** * PostService constructor. * @param ISendable $SMSService */ public function __construct(Sendable $SMSService) { $this->SMSService = $SMSService; } public function showPost() { return $this->SMSService->sendMessage(); } } |
欲從 class 抽出 interface,將滑鼠游標放在 class 名稱上,按熱鍵跳出
Refactor This
選單,選擇 Interface
。Windows : Ctrl + Alt + Shift + TmacOS : control + T
輸入欲建立的 interface 名稱,選擇欲抽出的 method。
PhpStorm 會幫我們重構出 interface。
原 class 也會自動加上
implements
interface。
繼續抽出第 2 個 interface,將滑鼠游標放在 class 名稱上,按熱鍵跳出
Refactor This
選單,選擇 Interface
。Windows : Ctrl + Alt + Shift + TmacOS : control + T
輸入欲建立的 interface 名稱,選擇欲抽出的 method。
並勾選
Replace class references with interface where possible
,PhpStorm 會自動搜尋所有使用 class 的地方,以 interface 取代。
重構前的預覽,PhpStorm 告知即將對以下檔案進行重構,按
Do Refactor
繼續。
PhpStorm 會幫我們產生 interface。
原 class 也會自動加上
implements
interface。
原來 constructor 的參數型別,也從 class 變成 interface,field 的型別宣告也變成了 interface。
如此
PostService
與 SMSService
的相依僅限於 Sendable
interface,大大降低 PostService
與 SMService
之間的耦合,也就是設計模式
一書所說的:根據 interface 寫程式,不要根據 class 寫程式
Reference: http://oomusou.io/phpstorm/phpstorm-refactoring/
留言
張貼留言