快速快速將 Legacy Code 重構成依賴注入

為了可測試性與低耦合,我們會使用依賴注入取代 new 建立物件,對於 legacy code,當然可以使用手動的方式重構,透過 PhpStorm,我們可以使用更簡單的方式將 new 重構成 依賴注入

Motivation


若使用 TDD 方式開發,為了隔離測試,一定會使用依賴注入建立物件,在深入探討依賴注入一文中曾以可測試性角度探討之,不過在實務上,一直沒有以工具的角度探討如何實踐,本文將使用 PhpStorm,實際將 legacy code 的 new 重構成 依賴注入

Version


PHP 7.0.0
Laravel 5.2.39
PhpStorm 2016.1.2

實際案例


PostService.php 1 1GitHub Commit : 建立 PostService()

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

use App\Repositories\PostRepository;
use Illuminate\Database\Eloquent\Collection;

class PostService
{

/**
* @return Collection
*/

public function showLatest3Posts()
{

$postRepository = new PostRepository();
return $postRepository->getLatest3Posts();
}
}

在 legacy code,我們常會發現 class 直接在 method 內被 new,以執行結果角度而言是對的,但有以下兩個問題 :

  1. 由於 PostRepositoryshowLatest3Posts() 內直接被 new,因此無法對 PostRepository 做隔離,導致無法對 PostService 做單元測試。
  2. 由於 PostRepositoryshowLatest3Posts() 內直接被 new,導致 PostService 直接相依於 PostRepository,耦合性太高,無法被抽換,違反 SOLID 原則的 開放封閉原則依賴反轉原則

要解決這兩個問題,又不影響執行結果,最簡單的方式就是將 new 重構依賴注入

Extract Field


將來要將 $postRepository 重構成 field,使用 constructor injection 方式。

將滑鼠游標放在 $postRepository 變數上,按熱鍵 ⌃ + T,出現 Refactor This 選單,選擇 Extract Field

可以重構成兩種方式,因為將使用 constructor injection 來建立物件,不需要 new,因此選擇 $postRepository

PhpStorm 會自動使用原來變數名稱為 field 名稱。

  • Initialize in : 選 Current method
  • Visibility : 選 private

注意 Initialize in 並不是選擇 Class constructor,因為我們要用的是 constructor injection,而不是在 constructor 去 new 一個物件給 field。

PhpStorm 幫我們加上了 field,這是我們預期的。

但 PhpStorm 一樣是在 method 去 new 產生物件,只是改成 field,這不是我們想要的。

將 method 內有 new 的一行手動刪除。

在 field 加上 PHPDoc 描述型別。

在 PhpStorm 要替 field 加上 PHPDoc 很簡單,只要在 field 上面輸入 /**,再按 ␣ 空白鍵,就會出現 /** @var */

@var 之後輸入型別,輸入 Po 之後就會出現語法提示讓你用挑的。

在 field 加上型別描述,為 constructor injection 的 type hint

Generate Constructor

按熱鍵 ⌘ + N,出現 Generate 選單,選擇 Constructor

選擇你要使用依賴注入的 field。

PhpStorm 自動幫我們在 constructor 產生依賴注入。

PostService.php 2 2GitHub Commit : 將 PostService() 重構成依賴注入

app/Services/PostService.php
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
namespace App\Services;

use App\Repositories\PostRepository;
use Illuminate\Database\Eloquent\Collection;

class PostService
{

/** @var PostRepository */
private $postRepository;

/**
* PostService constructor.
* @param PostRepository $postRepository
*/

public function __construct(PostRepository $postRepository)
{

$this->postRepository = $postRepository;
}

/**
* @return Collection
*/

public function showLatest3Posts()
{

return $this->postRepository->getLatest3Posts();
}
}

單元測試


馬上跑單元測試,綠燈 打完收工。

PostServiceTest.php 3 3GitHub Commit : 單元測試 : 建立 PostServiceTest

app/Services/PostService.php
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
use App\Post;
use App\Services\PostService;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class PostServiceTest extends TestCase
{

use DatabaseMigrations;

/** @test */
public function 顯示最新3筆文章()
{

/** arrange */
factory(Post::class, 100)->create();

/** act */
$actual = App::make(PostService::class)
->showLatest3Posts()
->pluck('id')
->all();

/** assert */
$expected = [100, 99, 98];
$this->assertEquals($expected, $actual);
}
}

Conclusion


  • Legacy code 蠻免會使用 new 去建立物件,為了降低耦合度與增加可測試性,勢必改用依賴注入,透過 PhpStorm 的重構,可以快速地將 new 重構成依賴注入,非常方便。

Sample Code


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