การรีแฟกเตอร์เป็นเทคนิคหลักในการปรับปรุงโค้ด หนังสืออ้างอิงหลักเกี่ยวกับการรีแฟกเตอร์คือหนังสือ Refactoring: Improving the Design of Existing Code ของ Martin Fowler (Addison-Wesley, 1999) ขอแนะนำให้คุณดูข้อมูลเพิ่มเติมจากหนังสือเล่มนั้นเกี่ยวกับการรีแฟกเตอร์รูปแบบต่างๆ ที่คุณสามารถทำได้เมื่อมี Tests อยู่ในโค้ดของคุณ

ในบทนี้ ผมจะอธิบายการรีแฟกเตอร์หลักหนึ่งอย่าง นั่นคือ Extract Method ซึ่งจะช่วยให้คุณเห็นภาพกลไกที่เกี่ยวข้องกับการรีแฟกเตอร์โดยใช้ Tests

Extract Method (การแยกเมธอด)

ในบรรดาการรีแฟกเตอร์ทั้งหมด Extract Method น่าจะมีประโยชน์มากที่สุด แนวคิดเบื้องหลัง Extract Method คือการที่เราสามารถแบ่งเมธอดขนาดใหญ่ที่มีอยู่ออกเป็นเมธอดย่อยๆ อย่างเป็นระบบ เมื่อเราทำแบบนี้ โค้ดของเราจะเข้าใจได้ง่ายขึ้น นอกจากนี้ เรายังสามารถนำชิ้นส่วนเหล่านั้นไปใช้ซ้ำและหลีกเลี่ยงการทำลอจิกซ้ำซ้อนในส่วนอื่นๆ ของระบบได้ด้วย


ในฐานโค้ดที่ได้รับการบำรุงรักษาไม่ดี เมธอดมักจะขยายใหญ่ขึ้นเรื่อยๆ นักพัฒนาจะเพิ่มลอจิกเข้าไปในเมธอดที่มีอยู่ และเมธอดเหล่านั้นก็จะใหญ่ขึ้นเรื่อยๆ เมื่อเป็นเช่นนี้ เมธอดอาจลงเอยด้วยการทำงานที่แตกต่างกันสองหรือสามอย่างให้กับผู้เรียกใช้ ในกรณีที่รุนแรง อาจกลายเป็นหลายสิบหรือหลายร้อยอย่าง Extract Method คือวิธีการแก้ไขในกรณีเหล่านี้


เมื่อคุณต้องการแยกเมธอด สิ่งแรกที่คุณต้องมีคือชุด Tests หากคุณมี Tests ที่ครอบคลุมการทำงานของเมธอดอย่างละเอียด คุณสามารถแยกเมธอดจากมันได้โดยใช้ขั้นตอนดังนี้

1. ระบุโค้ดที่คุณต้องการแยก และคอมเมนต์มันออกไว้

2. คิดชื่อสำหรับเมธอดใหม่ และสร้างมันขึ้นมาเป็นเมธอดเปล่า

3. วางการเรียกไปยังเมธอดใหม่ในเมธอดเดิม

4. คัดลอกโค้ดที่คุณต้องการแยกไปไว้ในเมธอดใหม่

5. ใช้เทคนิค Lean On the Compiler ( 315 ) เพื่อค้นหาว่าคุณต้องส่งพารามิเตอร์อะไรบ้างและต้องคืนค่าอะไรบ้าง

6. ปรับการประกาศเมธอดให้รองรับพารามิเตอร์และค่าที่จะคืน (ถ้ามี)

7. รัน Tests ของคุณ

8. ลบโค้ดที่ถูกคอมเมนต์ไว้

นี่คือตัวอย่างง่ายๆ ในภาษา Java:

public class Reservation
{
    public int calculateHandlingFee(int amount) {
        int result = 0;

        if (amount < 100) {
            result += getBaseFee(amount);
        }
        else {
             result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
        }
        return result;
    }
    ...
}

ลอจิกใน else-statement คำนวณค่าธรรมเนียมการจัดการสำหรับการจองแบบพรีเมียม เราจำเป็นต้องใช้ลอจิกนั้นในที่อื่นในระบบของเรา แทนที่จะทำซ้ำโค้ด เราสามารถแยกมันออกจากตรงนี้แล้วนำไปใช้ในที่อื่นได้

นี่คือขั้นตอนแรก:

public class Reservation
{
    public int calculateHandlingFee(int amount) {
        int result = 0;

        if (amount < 100) {
            result += getBaseFee(amount);
        }
        else {
             // result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
        }
        return result;
    }
    ...
}

เราต้องการเรียกเมธอดใหม่ชื่อ getPremiumFee ดังนั้นเราจึงเพิ่มเมธอดใหม่และการเรียกใช้มัน:

public class Reservation
{
    public int calculateHandlingFee(int amount) {
        int result = 0;

        if (amount < 100) {
            result += getBaseFee(amount);
        }
        else {
             // result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
             result += getPremiumFee();
        }
        return result;
    }

    int getPremiumFee() {
    }
    ...
}

จากนั้นเราคัดลอกโค้ดเก่าไปไว้ในเมธอดใหม่และดูว่ามันคอมไพล์ได้หรือไม่:

public class Reservation
{
    public int calculateHandlingFee(int amount) {
        int result = 0;

        if (amount < 100) {
            result += getBaseFee(amount);
        }
        else {
             // result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
             result += getPremiumFee();
        }
        return result;
    }

    int getPremiumFee() {
         result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
    }
    ...
}

มันคอมไพล์ไม่ได้ โค้ดใช้ตัวแปรชื่อ result และ amount ที่ไม่ได้ประกาศไว้ เนื่องจากเรากำลังคำนวณเพียงส่วนหนึ่งของผลลัพธ์ เราจึงแค่คืนค่าที่เราคำนวณได้ นอกจากนี้เรายังสามารถเข้าถึง amount ได้ถ้าเราทำให้มันเป็นพารามิเตอร์ของเมธอดและเพิ่มมันเข้าไปในการเรียก:

public class Reservation
{
    public int calculateHandlingFee(int amount) {
        int result = 0;

        if (amount < 100) {
            result += getBaseFee(amount);
        }
        else {
             // result += (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
             result += getPremiumFee(amount);
        }
        return result;
    }

    int getPremiumFee(int amount) {
         return (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
    }
    ...
}

ตอนนี้เราสามารถรัน Tests และดูว่ายังทำงานได้อยู่หรือไม่ ถ้าใช้ได้ เราก็กลับไปลบโค้ดที่ถูกคอมเมนต์ออก:

public class Reservation
{
    public int calculateHandlingFee(int amount) {
        int result = 0;

        if (amount < 100) {
            result += getBaseFee(amount);
        }
        else {
            result += getPremiumFee(amount);
        }
        return result;
    }

    int getPremiumFee(int amount) {
        return (amount * PREMIUM_RATE_ADJ) + SURCHARGE;
    }
    ...
}


ถึงแม้จะไม่จำเป็นจริงๆ แต่ผมชอบที่จะคอมเมนต์โค้ดที่กำลังจะแยกไว้ก่อน ด้วยวิธีนี้ ถ้าผมทำผิดพลาดและ Test ล้มเหลว ผมก็สามารถกลับไปใช้โค้ดเดิมได้อย่างง่ายดาย ทำให้ Test ผ่าน และลองใหม่ได้อีกครั้ง


ตัวอย่างที่ผมเพิ่งแสดงให้ดูเป็นเพียงวิธีหนึ่งในการทำ Extract Method เมื่อคุณมี Tests มันเป็นการดำเนินการที่ค่อนข้างง่ายและปลอดภัย ถ้าคุณมีเครื่องมือรีแฟกเตอร์ด้วยแล้ว ยิ่งง่ายเข้าไปอีก ทั้งหมดที่คุณต้องทำคือเลือกส่วนหนึ่งของเมธอดและเลือกคำสั่งจากเมนู เครื่องมือจะตรวจสอบว่าโค้ดนั้นสามารถแยกเป็นเมธอดได้หรือไม่ และจะถามชื่อเมธอดใหม่จากคุณ

Extract Method เป็นเทคนิคหลักสำหรับการทำงานกับ Legacy Code คุณสามารถใช้มันเพื่อแยกความซ้ำซ้อน แยกหน้าที่รับผิดชอบ และแบ่งเมธอดที่ยาวเกินไปออกเป็นส่วนย่อยๆ