使用 PhpStorm 強大的重構功能

PhpStorm 最強悍的就是 Refactoring,這也是文字編輯器無法達到的,善用 Refactoring 將可大幅增加 code review 之後重構 PHP 的速度。

Version


PhpStorm 2017.1.2

Extraction


Extract Method

最需要被重構的程式碼,首推 Long Method,一旦一個 method 的程式碼的行數過多,就會難以閱讀、難以維護、難以單元測試、也違反物件導向的單一職責原則,建議使用 Extract MethodLong Method 拆成多個小 method。

何時該使用 Extract Method 呢?根據 Martin Fowler 在重構這本書的建議 :

  1. 當一段程式碼需要被重複使用時,就該獨立抽成 method。
  2. 當一段程式碼需要寫註解才能讓人理解時,就該獨立抽成 method。
  3. 當一段程式碼抽成 method 後,語意更清楚 ,就該獨立抽成 method。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace App\Services;

class ExtractMethod
{

public function printOwing(string $name)
{

$this->printBanner();

// print details
print("name: " . $name);
print("amount " . $this->getOutstanding());
}
}

重構後

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 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());
}
}

refactor000

選擇要重構成 method 的程式碼,按熱鍵跳出 Refactor This 選單,選擇 Method

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor001

輸入欲建立的 method 名稱,並選擇 publicprotectedprivate,一般來說重構出來的 method 選 private

refactor002

PhpStorm 會自動幫我們將所選的程式碼抽成 printDetails(),連參數、型別與 PHPDoc 都會幫我們加上。

Extract Field

實務上有些在 method 內的 local 變數,原本只有單一 method 使用,若有其他 method 也使用相同變數時,建議使用 Extract Field 將此變數重構成 field。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace App\Services;

class ExtractField
{

public function print1()
{

$name = 'Hello World';

echo($name);
}

public function print2()
{

$name = 'Hello World';

echo($name);
}
}

重構後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace App\Services;

class ExtractField
{

private $name = 'Hello World';

public function print1()
{

echo($this->name);
}

public function print2()
{

echo($this->name);
}
}

refactor003

將滑鼠游標放在變數名稱上,按熱鍵跳出 Refactor This 選單,選擇 Field

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor004

可選擇要將單一變數或者將整個 expression 重構成 field,這裡選擇 $name 即可,因為我們想將 $name 變數重構成 field。

refactor005

輸入欲建立的 field 名稱,並選擇 publicprotectedprivate,為了實現物件導向資料封裝,建議重構出來的 field 選 private

Initialize in 選擇 field 初始化的方式,若是 PHP 原生型別,如 int / string,則選擇 Field declaration,若是物件,則必須選擇 Class constructor

refactor006

PhpStorm 會自動幫我們將變數重構成 field,並將原來引用變數之處重構成引用 field。

Extract Variable

實務上有些原本在 method 內的固定值,想要變成變數,建議使用 Extract Variable 將固定值重構成變數。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}
}

refactor013

將滑鼠游標放在 10 上,按熱鍵跳出 Refactor This 選單,選擇 Variable

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor014

可以將固定值expression 抽成變數,這裡選擇 10 將固定值重構成變數。

refactor015

輸入欲建立的變數名稱。

refactor016

PhpStorm 會自動幫我們加上重構過的變數,並將原有的值都以變數取代。

Extract Parameter

實務上有些原本在 method 內的固定值,想要變成參數可由外部帶入,建議使用 Extract Parameter 將固定值重構成參數。

重構前

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)
{

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;
}
}

refactor010

將滑鼠游標放在固定值上,按熱鍵跳出 Refactor This 選單,選擇 Parameter

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor011

輸入欲建立的 parameter 名稱。

refactor012

PhpStorm 會自動幫我們重構成參數,將原有的固定值都以參數取代,並在 method 呼叫 的地方重構成原來的值。

Extract Constant

實務上不建議將字串數字直接 hardcode 在程式碼中 :

  • 日後難以閱讀與維護
  • 若字串與數字需要變動,需要改很多地方

建議將這類 Magic Number 使用 Extract Constant 重構成 constant。

重構前

1
2
3
4
5
6
7
8
9
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;
}
}

refactor007

將滑鼠游標放在數值上,按熱鍵跳出 Refactor This 選單,選擇 Constant

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor008

輸入欲建立的 constant 名稱。

refactor009

PhpStorm 會自動幫我們重構成 const,並將原有的值都以 constant 取代。

Extract Interface

為了讓 class 實現不同的角色,且讓 class 與 class 之間的耦合降低,讓物件不要直接相依某個物件,而是僅相依於 interface,建議使用 Extract Interface 重構出 interface。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}

refactor017

欲從 class 抽出 interface,將滑鼠游標放在 class 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Interface

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor018

輸入欲建立的 interface 名稱,選擇欲抽出的 method。

refactor019

PhpStorm 會幫我們重構出 interface。

refactor020

原 class 也會自動加上 implements interface。

refactor021

繼續抽出第 2 個 interface,將滑鼠游標放在 class 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Interface

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor022

輸入欲建立的 interface 名稱,選擇欲抽出的 method。

並勾選 Replace class references with interface where possible,PhpStorm 會自動搜尋所有使用 class 的地方,以 interface 取代。

refactor023

重構前的預覽,PhpStorm 告知即將對以下檔案進行重構,按 Do Refactor 繼續。

refactor024

PhpStorm 會幫我們產生 interface。

refactor025

原 class 也會自動加上 implements interface。

refactor026

原來 constructor 的參數型別,也從 class 變成 interface,field 的型別宣告也變成了 interface。

如此 PostServiceSMSService 的相依僅限於 Sendable interface,大大降低 PostServiceSMService 之間的耦合,也就是設計模式一書所說的:

根據 interface 寫程式,不要根據 class 寫程式

白話就是

若要降低物件之間的耦合程度,讓物件之間方便抽換與組合,就讓物件與物件之間僅相依於 interface,而不要直接相依於 class

更白話就是

黑貓白貓,能抓老鼠的就是好貓

黑貓白貓就是 class,能抓老鼠就是 interface

Rename


實務上常會遇到 variable 名稱、method 名稱、class 名稱…的命名不當,導致程式碼難以閱讀,在 code review 後須加以重構,PhpStorm 支援 Rename ClassRename Method, Rename FieldRename VariableRename Parameter,以下僅對最常用的 Rename VariableRename MethodChange SignatureRename Class 加以介紹。

Rename Variable

當遇到命名不當的變數名稱時,建議使用 Rename Variable 將變數名稱加以重構。

重構前

1
2
3
4
5
6
7
8
9
10
11
namespace App\Services;

class RenameVariable
{

public function print()
{

$address = 'Sam';

echo($address);
}
}

重構後

1
2
3
4
5
6
7
8
9
10
11
namespace App\Services;

class RenameVariable
{

public function print()
{

$name = 'Sam';

echo($name);
}
}

refactor027

將滑鼠游標放在變數名稱上,按熱鍵跳出 Refactor This 選單,選擇 Rename

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor028

$address 重構成 $name 之後,PhpStorm 會將所有原本使用 $address 變數的地方都重構成使用 $name

Rename Method

當遇到命名不當的 method 名稱時,建議使用 Rename Method 將 method 名稱加以重構。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace App\Services;

class RenameMethod
{

public function print()
{

$this->printOutline();
}

public function printOutline()
{

echo('Detail');
}
}

重構後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace App\Services;

class RenameMethod
{

public function print()
{

$this->printDetail();
}

public function printDetail()
{

echo('Detail');
}
}

refactor029

將滑鼠游標放在 method 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Rename

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor030

printOutline() 重構成 printDetail(),PhpStorm 會即時顯示修改後的結果。

refactor031

重構前的預覽,PhpStorm 告知即將對以下檔案進行重構,按 Do Refactor 繼續。

refactor032

除了原來的 method 名稱變更外,PhpStorm 會將所有引用該 method 的地方加以修改。

Change Signature

實務上因需求改變,可能必須增加參數,也可能必須刪除參數,建議使用 Change Signature 來重構參數。

Change Method Signature

修改一般 method 的參數。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace App\Services;

class ChangeSignature
{

public function print()
{

echo($this->sum(1, 2));
}

public function sum(int $num1, int $num2) : int
{

return $num1 + $num2;
}
}

重構後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace App\Services;

class ChangeSignature
{

public function print()
{

echo($this->sum(1, 2, 10));
}

public function sum(int $num1, int $num2, int $num3 = 0) : int
{

return $num1 + $num2;
}
}

refactor033

將滑鼠游標放在 method 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Change Signature

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor034

+ 新增參數,Default 則為其他 mehtod 呼叫參數時,所提供的預設值。

refactor035

PhpStorm 除了在 method 增加參數外,其他呼叫 method 的地方都會加上預設值。

Change Constructor Signature

修改 constructor 的參數。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace App\Services;

class ChangeConstructorSignature
{

/** @var PostService */
private $postService;

/**
* ChangeConstructorSignature constructor.
* @param PostService $postService
*/

public function __construct(PostService $postService)
{

$this->postService = $postService;
}
}

重構後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace App\Services;

class ChangeConstructorSignature
{

/** @var PostService */
private $postService;
/** @var SMSService */
private $SMSService;

/**
* ChangeConstructorSignature constructor.
* @param PostService $postService
* @param SMSService $SMSService
*/

public function __construct(PostService $postService, SMSService $SMSService)
{

$this->postService = $postService;
$this->SMSService = $SMSService;
}
}

refactor036

將滑鼠游標放在 __construct() 上,按熱鍵跳出 Refactor This 選單,選擇 Change Signature

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor037

若是對 constructor 使用 Change Signature,會出現 Create and initialize class properties,預設會打勾。

refactor038

PhpStorm 除了自動幫我們在 constructor 加上參數外,還會對 field 加以初始化。

Rename Class

當遇到命名不當的 class 名稱時,建議使用 Rename Class 將 class 名稱加以重構。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
namespace App\Services;

class RenameClass
{

public function print()
{

echo('Hello World');
}
}

$obj = new RenameClass();
$obj->print();

重構後

1
2
3
4
5
6
7
8
9
10
11
12
namespace App\Services;

class MyRenameClass
{

public function print()
{

echo('Hello World');
}
}

$obj = new MyRenameClass();
$obj->print();

refactor039

將滑鼠游標放在 class 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Rename

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor040

直接修改 class 名稱,PhpStorm 會即時顯示修改後的結果。

refactor041

重構前的預覽,PhpStorm 告知即將對以下檔案進行重構,按 OK 繼續。

refactor042

class 名稱重構後,PhpStorm 將所有原本 new 之處都改用新的 class 名稱。

除此之外,檔案名稱也從重構成新的名稱。

Movement


Move Static Member

實務上因需求改變,原本的 field 與 method 可能不再適合目前的 class,建議使用 Move Static Member 搬移 field 與 method 到適當的 class。

重構前

1
2
3
4
5
6
7
8
9
10
11
namespace App\Services;

class MoveStaticMember1
{

public static $var1 = 'Hello World';

public static function print()
{

echo(self::$var1);
}
}
1
2
3
4
5
6
namespace App\Services;

class MoveStaticMember2
{


}
1
2
3
4
5
6
7
8
9
namespace App\Services;

class MoveStaticMember0
{

public function print()
{

MoveStaticMember1::print();
}
}

重構後

1
2
3
4
5
6
namespace App\Services;

class MoveStaticMember1
{


}
1
2
3
4
5
6
7
8
9
10
11
namespace App\Services;

class MoveStaticMember2
{

public static $var1 = 'Hello World';

public static function print()
{

echo(self::$var1);
}
}
1
2
3
4
5
6
7
8
9
namespace App\Services;

class MoveStaticMember0
{

public function print()
{

MoveStaticMember2::print();
}
}

refactor043

將滑鼠游標放在 $var1print() 上,按熱鍵跳出 Refactor This 選單,選擇 Move

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor044

選擇要重構的 member,並指定要重構到的新 class。

refactor045

原 class 已經無任何 member。

refactor046

所有的 member 都已經重構到新 class。

refactor047

原來使用舊 class 之處已經重構成新 class。

目前 PhpStorm 僅支援對 static member 的重構到其他 class,對於一般的 field / const / method,則必須手動重構。

Move Class

實務上因需求改變,原本在某一 namespace 下的 class,可能不再適合目前的 namespace,建議使用 Move Class 將 class 重構到適當的 namespace。

重構前

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 PostService
{

/** @var Sendable */
private $SMSService;

/**
* PostService constructor.
* @param Sendable $SMSService
*/

public function __construct(Sendable $SMSService)
{

$this->SMSService = $SMSService;
}

public function showMessage()
{

return $this->SMSService->sendMessage();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use App\Services\PostService;
use App\Services\SMSService;

class PostServiceIntegrationTest extends TestCase
{

/** @var PostService */
protected $target;

protected function setUp()
{

parent::setUp();
$SMSService = new SMSService();
$this->target = new PostService($SMSService);
}

/** @test */
public function 顯示正確簡訊()
{

/** arrange */

/** act */
$actual = $this->target->showMessage();

/** assert */
$expected = 'Send Message';
$this->assertEquals($expected, $actual);
}
}

重構後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace App\Services\Post;

class PostService
{

/** @var Sendable */
private $SMSService;

/**
* PostService constructor.
* @param Sendable $SMSService
*/

public function __construct(Sendable $SMSService)
{

$this->SMSService = $SMSService;
}

public function showMessage()
{

return $this->SMSService->sendMessage();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use App\Services\Post\PostService;
use App\Services\SMSService;

class PostServiceIntegrationTest extends TestCase
{

/** @var PostService */
protected $target;

protected function setUp()
{

parent::setUp();
$SMSService = new SMSService();
$this->target = new PostService($SMSService);
}

/** @test */
public function 顯示正確簡訊()
{

/** arrange */

/** act */
$actual = $this->target->showMessage();

/** assert */
$expected = 'Send Message';
$this->assertEquals($expected, $actual);
}
}

refactor048

將滑鼠游標放在 class 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Move

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor049

Move Class PostService to namespace 填入新的 namespace 完整路徑,可以是既有 namespace,也可以是新的 namespace,若是新的 namespace,PhpStorm 會自動幫你建立目錄。

要勾選 Search in comments and stringsSearch for text occurences,PhpStorm 會一併將有用到此 class 的地方一起修改。

refactor050

重構前的預覽,PhpStorm 告知即將對以下檔案進行重構,按 Do Refactor 繼續。

refactor051

PhpStorm 會幫我們建立新的子目錄,且 namespace 也做了修改。

refactor052

使用到 class 的地方,use 也跟會自動修改。

Move Namespace

實務上因需求改變,原本在某一 namespace 下的所有 class,可能不再適合目前的 namespace,建議使用 Move Namespace 將所有 class 重構到適當的 namespace。

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace App\Services;

class SMSService implements Printable, Sendable
{

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
namespace App\Libs;

class SMSService implements Printable, Sendable
{

public function printMessage()
{

echo('Print Message');
}

public function sendMessage() : string
{

return 'Send Message';
}
}

refactor053

將滑鼠游標放在 namespace 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Move

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor054

Move Namespace 填入新的 namespace 完整路徑,可以是既有 namespace,也可以是新的 namespace,若是新的 namespace,PhpStorm 會自動幫你建立目錄。

要勾選 Search in comments and stringsSearch for text occurences,PhpStorm 會一併將有用到原 namespace 的地方一起修改。

refactor055

PhpStorm 會列出所有即將重構的 class。

refactor056

重構前的預覽,PhpStorm 告知即將對以下檔案進行重構,按 Do Refactor 繼續。

refactor057

PhpStorm 幫我們將原來 Services 目錄下的 class 都重構到 Libs 目錄,且 namespace 也做了修改。

Inheritance


Pull Members Up

若 class 之間有共用的邏輯,建議使用 Pull Members Up 重構到 super class,讓邏輯不再重複,符合 DRY 原則。

重構前

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\Post;

use App\Services\Sendable;

class PostService extends AbstractPostService
{

/** @var Sendable */
private $SMSService;

/**
* PostService constructor.
* @param Sendable $SMSService
*/

public function __construct(Sendable $SMSService)
{

$this->SMSService = $SMSService;
}

public function showMessage()
{

return $this->SMSService->sendMessage();
}
}

重構後

1
2
3
4
5
6
7
8
namespace App\Services\Post;

use App\Services\Sendable;

class PostService extends AbstractPostService
{


}
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\Post;

use App\Services\Sendable;

class AbstractPostService
{

/** @var Sendable */
protected $SMSService;

/**
* PostService constructor.
* @param Sendable $SMSService
*/

public function __construct(Sendable $SMSService)
{

$this->SMSService = $SMSService;
}

public function showMessage()
{

return $this->SMSService->sendMessage();
}
}

refactor058

將滑鼠游標放在欲 pull up 的 member 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Pull Members Up

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor059

選擇要 pull up 的 member,若是 private,PhpStorm 會升格成 protected

refactor060

原 class 內所有 member 都被 pull up。

refactor061

所有 member 都 pull up 到 super class 了。

Push Members Down

實務上因需求改變,原本共用的邏輯可能不再共用,建議使用 Pull Members Down 將 member 從 super class 降回 sub class。

重構前

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\Post;

use App\Services\Sendable;

class AbstractPostService
{

/** @var Sendable */
protected $SMSService;

/**
* PostService constructor.
* @param Sendable $SMSService
*/

public function __construct(Sendable $SMSService)
{

$this->SMSService = $SMSService;
}

public function showMessage()
{

return $this->SMSService->sendMessage();
}
}
1
2
3
4
5
6
7
8
namespace App\Services\Post;

use App\Services\Sendable;

class PostService extends AbstractPostService
{


}

重構後

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\Post;

use App\Services\Sendable;

class PostService extends AbstractPostService
{

/** @var Sendable */
private $SMSService;

/**
* PostService constructor.
* @param Sendable $SMSService
*/

public function __construct(Sendable $SMSService)
{

$this->SMSService = $SMSService;
}

public function showMessage()
{

return $this->SMSService->sendMessage();
}
}
1
2
3
4
5
6
7
8
namespace App\Services\Post;

use App\Services\Sendable;

class AbstractPostService
{


}

refactor062

將滑鼠游標放在欲 pull down 的 member 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Push Members Down

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor063

選擇要 push down 的 member,若是 __ 的 magic method,PhpStorm 會提出警告。

refactor064

原 class 內所有 member 都被 push down。

refactor065

所有 member 都 push down 到 sub class 了。

Others


If Else to Switch

由於 if else 較容易思考,所以很容易寫出 if else 的程式碼,PhpStorm 可以幫我們重構成可讀性較高的 switch

重構前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class OrderService
{

/**
* @param string $type
* @param int $days
* @return int
*/

public function calculatePrice(string $type, int $days): int
{

if ($type == 'Regular') {
return ($days - 7) * 10;
} elseif ($type == 'NewRelease') {
return ($days - 3) * 30;
} elseif ($type == 'Children') {
return ($days - 7) * 10;
} else {
return ($days - 7) * 10;
}
}
}

重構後

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;

class OrderService
{

/**
* @param string $type
* @param int $days
* @return int
*/

public function calculatePrice(string $type, int $days): int
{

switch ($type) {
case 'Regular':
return ($days - 7) * 10;
case 'NewRelease':
return ($days - 3) * 30;
case 'Children':
return ($days - 7) * 10;
default:
return ($days - 7) * 10;
}
}
}

refactor066

將滑鼠游標放在 if 之前,按熱鍵選擇 Replace if with switch

Windows : Alt + enter

macOS : option + return

refactor067

PhpStorm 會幫我們將 if else 重構成 switch

Inline

大部分的狀況, Extract Variable 會讓程式碼可讀性更高,也更好維護,若發現因此造成程式碼更為複雜,或者多餘,建議使用 Inline Variable 重構刪除變數。

重構前

1
2
3
4
5
6
7
8
9
10
11
namespace App\Services;

class InlineVariable
{

public function print()
{

$name = 'Sam';

echo($name);
}
}

重構後

1
2
3
4
5
6
7
8
9
namespace App\Services;

class InlineVariable
{

public function print()
{

echo('Sam');
}
}

refactor068

將滑鼠游標放在欲 inline 的 variable 名稱上,按熱鍵跳出 Refactor This 選單,選擇 Inline

Windows : Ctrl + Alt + Shift + T

macOS : control + T

refactor069

PhpStorm 會列出它所找到該 inline 的變數個數,按 OK 繼續。

refactor070

PhpStorm 會自動幫我們將變數刪除,直接使用值取代變數。

Conclusion


  • 所有的重構動作若不滿意,都可以取消重構,此外,每個重構步驟都該搭配 git 做版控,確保重構失敗後可以正確還原。
  • 認為重構很花時間的人,是因為沒有善用工具,若能善用 PhpStorm 的重構功能,就能大幅節省重構所花的時間,減少技術債。

Sample Code


完整的範例可以在我的 GitHub 上找到。

Reference


Martin Fowler, Refactoring: Improving The Design of Existing Code
GoF, Design Pattern