Recca Chao 的 gitHub page

推廣網站開發,包含 Laravel 和 Kotlin 後端撰寫、自動化測試、讀書心得等。Taiwan Kotlin User Group 管理員。

View on GitHub

萃取函式

重構這本書裡面,提到最常使用的一個重構方式就是萃取函式(Extract method)。

過長的函式,通常代表這個函式所做的事情過度複雜,讓人必須要花費很長的時間才能看懂。如果函式的某塊段落與其他地方互動不多,並且邏輯清楚,這時萃取函式就相當簡單。我們只需要移動這段區塊到獨立的函式裡面,並且取個好名字就可以了。

比方說


public function recordStudentScore($request) {
    // 取得學生 ID
    $studentName = $request->route('studentName');
    $student = Student::where('name', $studentName)
        ->where('status', Student::ACTIVE)
        ->where('payment_status', Student::PAID)
        ->first();
    
    if (!$student) {
        throw new InvalidException('學生姓名錯誤');
    }
    // 計算學生成績
    
    // 寫入學生成績紀錄
    StudentScore::create([
        'student_id' => $student->id,
        'score' => $score,
    ]);
}

這邊有幾段的重構相對容易,只要將各自功能的段落移出 recordStudentScore() 就好了


不過,如果要移動的段落與其他地方有互動,那就比較麻煩了。

段落內有取用其他變數

第一種狀況,是這個段落有取用其他的變數。

比方說上面的例子裡面

$studentName = $request->route('studentName');
$student = Student::where('name', $studentName)
    ->where('status', Student::ACTIVE)
    ->where('payment_status', Student::PAID)
    ->first();
    
if (!$student) {
    throw new InvalidException('學生姓名錯誤');
}

偵測有沒有學生的這個段落,如果希望可以拆分出來,就不能直接用移出的方式進行。

雖然這麼說,但是相信其實很容易看出來,其實我們只要將 $student 這個變數當作函式參數傳進去就可以了:

public function ($student): void
{
    if (!$student) {
        throw new InvalidException('學生姓名錯誤');
    }
    return;
}

段落內有更動其他變數

第二種比較棘手的狀況,是這個段落不僅有讀取其他的變數,還會變更該變數的值。這樣的話,我們就必須使這個段落回傳被更動過的變數,

比方說

這引出了一個問題:會不會我們需要搬移的段落,同時更改了多個以上的變數,所以不能使用以上的重構方式呢?

如果是這樣,可能代表這一個段落的邏輯仍舊是太過複雜了,最簡單的方法是不要去更動這一段的邏輯。或者是將整個段落的邏輯先用其他的方式重構,理清邏輯之後,再用其他的方式進行重構

如果是我的話,我會先將太大的函式分段看懂,並加上對應的註解。之後根據註解命名新的函式。

太多變數

太多變數的話,會導致萃取函式變得非常困難。

這時候我們可以搭配其他的方式使用。比方說 replace with query 或者 method object

拆分心結

另外一個心結,是有時候我們在拆分函式的時候,會覺得「這一段真的有需要拆分出來嗎?會不會拆分得太細了?」

針對這個部分,我自己的答案是:如果函式內部有一個段落,需要用單行註解去說明這段的行為時,那麼就代表這一段的邏輯值得用一個函式包起來,並以函式的名稱來說明這段落的行為是什麼。

有時候這種段落會非常短,只有兩三行,甚至只有一行。即使如此,只要這一個段落值得用註解來說明,那麼就應該值得改用函式名稱來說明。

比方說 laravel 框架裡面

Illuminate\Http\JsonResponse

/**
* Determine if a JSON encoding option is set.
*
* @param  int  $option
* @return bool
*/
public function hasEncodingOption($option)
{
    return (bool) ($this->encodingOptions & $option);
}

能不能不萃取出這函式?其實可以,只要每個地方判斷時都用 (bool) ($this->encodingOptions & $option) 的方式實作就好

不過用 hasEncodingOption($option) 萃取出來之後,看起來就更簡潔了,不是嗎?