如何在 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 在重構這本書的建議 :

  1. 當一段程式碼需要被重複使用時,就該獨立抽成 method。
  2. 當一段程式碼需要寫註解才能讓人理解時,就該獨立抽成 method。
  3. 當一段程式碼抽成 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());
    }
}

refactor000

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

Windows : Ctrl + Alt + Shift + T
macOS : control + T
refactor001

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

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);
    }
}
refactor003
將滑鼠游標放在變數名稱上,按熱鍵跳出 Refactor This 選單,選擇 Field
Windows : Ctrl + Alt + Shift + T
macOS : control + T
refactor004

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

refactor005

輸入欲建立的 field 名稱,並選擇 publicprotected 或 private,為了實現物件導向資料封裝,建議重構出來的 field 選 private 。
在 Initialize in 選擇 field 初始化的方式,若是 PHP 原生型別,如 int / string,則選擇 Field declaration,若是物件,則必須選擇 Class constructor
refactor006
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;
    }
}
refactor013
將滑鼠游標放在 10 上,按熱鍵跳出 Refactor This 選單,選擇 Variable
Windows : Ctrl + Alt + Shift + T
macOS : control + T
refactor014

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

輸入欲建立的變數名稱。
refactor016

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

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。
重構前
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。
重構前
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

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。
如此 PostService 與 SMSService 的相依僅限於 Sendable interface,大大降低 PostService 與 SMService 之間的耦合,也就是設計模式一書所說的:
根據 interface 寫程式,不要根據 class 寫程式

Reference: http://oomusou.io/phpstorm/phpstorm-refactoring/


























































留言

這個網誌中的熱門文章

Json概述以及python對json的相關操作

遠程控制管理工具ipmitool

從Kubernetes到Cloud Native——雲原生應用之路