info-workshop-refactoring-java-junit

เป้าหมาย: ฝึกปรับโครงสร้างโค้ดโดย ไม่เปลี่ยนพฤติกรรมที่สังเกตได้จากภายนอก (Observable Behavior) ใช้ Unit Tests ป้องกัน Regression และตรวจสอบผลหลังการปรับปรุงทุกขั้นตอน

ในโปรเจ็กต์ที่พัฒนาต่อเนื่องหลายปี เรามักพบเมธอดที่ซับซ้อน ตัวแปรชื่อคลุมเครือ เงื่อนไขซ้อนกัน และค่า Magic Numbers จำนวนมาก การ Refactoring ช่วยให้โค้ดเข้าใจง่ายขึ้น โดยแยกการแก้โครงสร้างออกจากการเปลี่ยน Business Rules

บทความนี้เป็น Hands-on Workshop ที่ทำตามได้จริง โดยใช้ตัวอย่างระบบคำนวณยอดชำระ (Checkout) ของร้านค้าออนไลน์ นักศึกษาจะได้เริ่มจากโค้ดที่ทำงานถูกต้องแต่มี Code Smells เขียนชุดทดสอบเพื่อบันทึกพฤติกรรม แล้วค่อย ๆ Refactor จนได้โค้ดที่ดูแลรักษาง่ายขึ้น

#1. ผลลัพธ์การเรียนรู้

หลังจบ Workshop ผู้เรียนจะสามารถ:

  1. อธิบายความแตกต่างระหว่าง Refactoring, Bug Fixing, Optimization และ Rewrite
  2. ระบุ Code Smells เช่น Poor Naming, Magic Numbers, Nested Conditionals และ Long Method
  3. เขียน Characterization Tests เพื่อเก็บพฤติกรรมของ Legacy Code
  4. ใช้เทคนิค Rename, Extract Method, Replace Magic Number with Named Constant และ Guard Clauses
  5. ทดสอบโค้ดก่อนและหลังด้วย JUnit Jupiter ผ่าน Maven
  6. ตรวจสอบ git diff และ Commit แบบ Incremental Refactoring

ระยะเวลาแนะนำ: 90–120 นาที แบ่งเป็นติดตั้ง/อธิบาย 15 นาที, baseline tests 20 นาที, refactor 40 นาที, ตรวจงานและแบบฝึกหัด 15–45 นาที

#2. หลักการและข้อควรระวัง

Refactoring คือการปรับโครงสร้างภายในโดยรักษาผลลัพธ์และพฤติกรรมเดิม ส่วน Bug Fixing เปลี่ยนพฤติกรรมเพื่อแก้ข้อผิดพลาด และ Performance Optimization มีวัตถุประสงค์หลักเพื่อปรับทรัพยากรหรือความเร็ว (แม้บางกรณีใช้เทคนิคคล้ายกัน)

กิจกรรม เปลี่ยนพฤติกรรมทางธุรกิจ? ตัวอย่าง
Refactoring ไม่ควรเปลี่ยน Extract Method, Rename Variable
Bug Fixing เปลี่ยนเพื่อให้ถูกต้อง แก้สูตรส่วนลดที่ผิด
Feature Development เปลี่ยนตามข้อกำหนดใหม่ เพิ่มระดับสมาชิก PLATINUM
Optimization มุ่งเปลี่ยนประสิทธิภาพ เปลี่ยนอัลกอริทึมให้ใช้เวลาน้อยลง

สำคัญ: การทดสอบผ่านไม่ได้พิสูจน์ว่าโค้ดทุกเส้นทางเหมือนเดิม 100% แต่เป็นหลักฐานลดความเสี่ยง เราต้องเลือกกรณีทดสอบที่ครอบคลุมกฎธุรกิจ จุดขอบ (Boundary) และข้อผิดพลาดสำคัญด้วย

#ตัวอย่าง Code Smells ใน Workshop

Code Smell อาการ เทคนิคที่ใช้
Poor Naming t, p, n, fast Rename Variable/Parameter
Long Method คำนวณหลายหน้าที่ในเมธอดเดียว Extract Method
Magic Numbers 15, 5, 1000, 80, 120, 40 Introduce Named Constants
Nested Conditionals if ซ้อนหลายระดับ Guard Clauses
Duplicate Condition ตรวจ afterDiscount >= 1000 สองครั้ง Consolidate Conditional Logic

#3. เตรียมเครื่องมือ

  • JDK 17 หรือใหม่กว่า
  • Apache Maven (แนะนำ 3.9+)
  • IDE เช่น IntelliJ IDEA, Eclipse หรือ VS Code + Java Extensions
  • Git สำหรับติดตามการเปลี่ยนแปลง

ตรวจสอบการติดตั้ง:

java -version
javac -version
mvn -version
git --version

เครื่องมือสำหรับรัน Test ใช้ JUnit Jupiter 5.12.2 และ Maven Surefire 3.5.3 (ล็อกเวอร์ชันไว้เพื่อให้ workshop ทำซ้ำได้; ไม่จำเป็นต้องใช้เวอร์ชันล่าสุด)

#4. โครงสร้างโปรเจ็กต์

refactoring-workshop/
├── pom.xml
└── src/
    ├── main/java/com/example/refactoring/
    │   └── OrderCalculator.java
    └── test/java/com/example/refactoring/
        └── OrderCalculatorTest.java

ดาวน์โหลดชุด Source Code ที่แนบกับบทความแล้วแตกไฟล์ หรือสร้างโปรเจ็กต์ Maven ใหม่ตามโครงสร้างข้างต้น

#4.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>refactoring-workshop</artifactId>
    <version>1.0.0</version>
    <properties>
        <maven.compiler.release>17</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>5.12.2</junit.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.14.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.5.3</version>
            </plugin>
        </plugins>
    </build>
</project>

Maven จะดาวน์โหลด Dependencies ในการรันครั้งแรก จึงต้องเชื่อมต่ออินเทอร์เน็ต และไม่ควรรันไฟล์ .java ของ JUnit โดยตรงด้วย java

#5. โจทย์ธุรกิจ: ระบบคำนวณยอดชำระ

ระบบรับค่า:

  • tier = GOLD, SILVER, STANDARD หรืออื่น ๆ
  • ราคาสินค้าต่อชิ้น หน่วย บาทเต็ม (int)
  • จำนวนสินค้า (int)
  • สถานะจัดส่งด่วน (boolean)

กฎธุรกิจเดิม:

  1. ยอดก่อนส่วนลด = ราคาต่อชิ้น × จำนวน
  2. สมาชิก GOLD ลด 15%; SILVER ลด 5%; ระดับอื่นไม่ลด
  3. ส่วนลดคำนวณด้วยจำนวนเต็ม (ปัดเศษทิ้งตามการหารของ Java)
  4. ค่าจัดส่งพิจารณาจากยอด หลังหักส่วนลด ไม่ใช่ยอดก่อนลด
  5. หากยอดหลังหักส่วนลด ตั้งแต่ 1,000 บาทขึ้นไป: ส่งปกติฟรี / ส่งด่วน 80 บาท
  6. หากยอดหลังหักส่วนลด ต่ำกว่า 1,000 บาท: ส่งปกติ 40 บาท / ส่งด่วน 120 บาท

เพื่อให้เห็นเทคนิค Refactoring ชัดเจน Workshop นี้ใช้ int กับจำนวนเต็มบาทตามระบบสมมติ ไม่ใช่การออกแบบระบบการเงินจริงที่สมบูรณ์ สำหรับ Production ควรพิจารณา Monetary Type/Minor Units, Validation, Overflow และกฎการปัดเศษอย่างเป็นทางการ แต่ อย่าเพิ่มหรือแก้พฤติกรรมเหล่านี้ระหว่าง Refactoring รอบนี้

#5.1 Legacy Code: ก่อนทำ Refactoring

สร้าง src/main/java/com/example/refactoring/OrderCalculator.java:

package com.example.refactoring;

public class OrderCalculator {

    // Legacy implementation: intentionally contains several Code Smells.
    public int calculateTotal(String t, int p, int n, boolean fast) {
        int total = p * n;
        int discount = 0;

        if ("GOLD".equals(t)) {
            discount = total * 15 / 100;
        } else {
            if ("SILVER".equals(t)) {
                discount = total * 5 / 100;
            }
        }

        int afterDiscount = total - discount;
        int shipping = 0;

        if (fast) {
            if (afterDiscount >= 1000) {
                shipping = 80;
            } else {
                shipping = 120;
            }
        } else {
            if (afterDiscount >= 1000) {
                shipping = 0;
            } else {
                shipping = 40;
            }
        }

        return afterDiscount + shipping;
    }
}

ลองอ่านโค้ดนี้ จะพบว่าแม้ทำงานได้ แต่ชื่อพารามิเตอร์ไม่บอกความหมาย เมธอดรวมหลายขั้นตอน มีตัวเลขไม่มีชื่อ และเงื่อนไขจัดส่งซ้ำกัน

#6. ขั้นที่ 1 — Characterization Tests (Baseline)

อย่าเริ่มเปลี่ยนโค้ดทันที ให้บันทึกผลลัพธ์ที่ระบบเดิมทำอยู่ก่อน แม้บางพฤติกรรมจะดูไม่สมบูรณ์ เช่น กรณี null หรือการปัดเศษจำนวนเต็ม ก็ต้องแยกประเด็นไว้สำหรับงาน Bug Fix/Change Request ในภายหลัง

สร้าง src/test/java/com/example/refactoring/OrderCalculatorTest.java:

package com.example.refactoring;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

class OrderCalculatorTest {
    private final OrderCalculator calculator = new OrderCalculator();

    @ParameterizedTest(name = "tier={0}, unit={1}, qty={2}, express={3} => {4}")
    @CsvSource({
            "GOLD, 400, 2, false, 720",
            "SILVER, 500, 2, false, 990",
            "STANDARD, 400, 3, false, 1200",
            "STANDARD, 200, 1, true, 320",
            "GOLD, 1000, 2, true, 1780",
            "UNKNOWN, 100, 1, false, 140",
            "STANDARD, 1000, 1, false, 1000",
            "GOLD, 199, 1, false, 210",
            "SILVER, 19, 1, false, 59",
            "STANDARD, 1000, 1, true, 1080",
            "SILVER, 1000, 1, true, 1070",
            "GOLD, 1200, 1, true, 1100"
    })
    void preservesExistingPrices(String tier, int unitPriceBaht, int quantity,
                                boolean expressShipping, int expectedTotal) {
        assertEquals(expectedTotal,
                calculator.calculateTotal(tier, unitPriceBaht, quantity, expressShipping));
    }

    @Test
    void nullTierBehavesLikeUnrecognizedTier() {
        assertEquals(140, calculator.calculateTotal(null, 100, 1, false));
    }
}

รัน:

mvn clean test

ผลลัพธ์ที่คาดหวังเมื่อ Environment ถูกต้อง:

Tests run: 13, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

ตัวเลข 13 มาจาก Parameterized Test 12 กรณี และ Test แยกสำหรับ null 1 กรณี

ตัวอย่างการคำนวณที่ Test ควรยืนยัน:

กรณี คิดยอดหลังลด ค่าจัดส่ง ยอดรวม
GOLD, 400 × 2, ปกติ 680 40 720
SILVER, 500 × 2, ปกติ 950 40 990
STANDARD, 400 × 3, ปกติ 1,200 0 1,200
STANDARD, 200 × 1, ด่วน 200 120 320
GOLD, 1,000 × 2, ด่วน 1,700 80 1,780
SILVER, 19 × 1, ปกติ 19 (ส่วนลดปัดทิ้งเป็น 0) 40 59

Checkpoint 1: ต้องได้สีเขียวก่อนเริ่ม Refactor หาก Test ล้มเหลวตั้งแต่ต้น ให้ตรวจ Business Rules, ค่าคาดหวัง, Imports และโครงสร้างแพ็กเกจ ไม่แก้ Production Code เพื่อให้ Test ผ่านโดยไม่มีหลักฐานรองรับ

#7. ขั้นที่ 2 — Rename: เปลี่ยนชื่อให้สื่อความหมาย

ใน IDE ให้ทำ Rename Refactoring ที่พารามิเตอร์ในเมธอด calculateTotal ดังนี้:

ก่อน หลัง ความหมาย
t tier ระดับสมาชิก
p unitPriceBaht ราคาต่อชิ้น
n quantity จำนวนสินค้า
fast expressShipping ส่งด่วนหรือไม่
total subtotal ยอดก่อนส่วนลด
afterDiscount discountedTotal ยอดหลังส่วนลด

ผลที่คาดหวัง: โค้ดเข้าใจง่ายขึ้น โดยไม่เปลี่ยน Signature ที่ระดับชนิดข้อมูลหรือเมธอดที่ผู้เรียกใช้งานต้องเรียก

รันและตรวจ Diff:

mvn test
git diff -- src/main/java/com/example/refactoring/OrderCalculator.java

#8. ขั้นที่ 3 — Replace Magic Numbers with Named Constants

กำหนดค่าคงที่ในระดับคลาส แทนการฝังตัวเลขที่ไม่มีชื่อ เช่น:

private static final int GOLD_DISCOUNT_PERCENT = 15;
private static final int SILVER_DISCOUNT_PERCENT = 5;
private static final int SHIPPING_THRESHOLD_BAHT = 1000;
private static final int EXPRESS_SHIPPING_ABOVE_THRESHOLD_BAHT = 80;
private static final int EXPRESS_SHIPPING_BELOW_THRESHOLD_BAHT = 120;
private static final int STANDARD_SHIPPING_ABOVE_THRESHOLD_BAHT = 0;
private static final int STANDARD_SHIPPING_BELOW_THRESHOLD_BAHT = 40;

เปลี่ยนเฉพาะ ความหมายของค่า ไม่ใช่สูตร เช่น คงลำดับการคำนวณ subtotal * 15 / 100 เพื่อรักษาพฤติกรรมการคำนวณด้วยจำนวนเต็มไว้

Checkpoint 2: mvn test ผ่านทุกกรณี

#9. ขั้นที่ 4 — Extract Method: แยกความรับผิดชอบ

แบ่งเมธอดเดิมออกเป็น 3 ส่วน:

  • calculateSubtotal(...) — คำนวณยอดก่อนลด
  • calculateDiscount(...) — คำนวณส่วนลดตามระดับสมาชิก
  • calculateShipping(...) — คำนวณค่าจัดส่งจากยอดหลังลด

ภาพรวมของเมธอดหลักที่ควรได้:

public int calculateTotal(String tier, int unitPriceBaht, int quantity,
                          boolean expressShipping) {
    int subtotal = calculateSubtotal(unitPriceBaht, quantity);
    int discount = calculateDiscount(tier, subtotal);
    int discountedTotal = subtotal - discount;
    int shipping = calculateShipping(discountedTotal, expressShipping);
    return discountedTotal + shipping;
}

สังเกตว่า Method หลักอ่านคล้ายลำดับ Business Workflow และแยกรายละเอียดออกไปไว้ที่ Helper Methods

ข้อควรระวัง: ตัวแปร discountedTotal ต้องส่งให้เมธอดค่าจัดส่ง ไม่ใช่ subtotal มิฉะนั้นจะทำให้พฤติกรรมเปลี่ยนในกรณีใกล้ 1,000 บาท

Checkpoint 3: รัน mvn test หลัง Extract แต่ละเมธอด และตรวจสอบทุกครั้งว่าผลยังตรงกับ Baseline

#10. ขั้นที่ 5 — Guard Clauses และลดเงื่อนไขซ้ำ

สำหรับการคำนวณค่าจัดส่ง สามารถตรวจเงื่อนไข Threshold ครั้งเดียว แล้ว Return ผลลัพธ์ตามประเภทการจัดส่งได้ทันที:

private int calculateShipping(int discountedTotal, boolean expressShipping) {
    if (discountedTotal >= SHIPPING_THRESHOLD_BAHT) {
        return expressShipping
                ? EXPRESS_SHIPPING_ABOVE_THRESHOLD_BAHT
                : STANDARD_SHIPPING_ABOVE_THRESHOLD_BAHT;
    }
    return expressShipping
            ? EXPRESS_SHIPPING_BELOW_THRESHOLD_BAHT
            : STANDARD_SHIPPING_BELOW_THRESHOLD_BAHT;
}

โครงสร้างนี้ใช้การคืนค่าเร็ว (Early Return) เพื่อลดการซ้อนเงื่อนไข และใช้ค่าคงที่ที่ตั้งชื่อแล้ว การใช้ Conditional Expression (?:) สั้น ๆ สำหรับเลือกสองค่าช่วยให้กระชับโดยยังอ่านง่าย

Checkpoint 4: Test ผ่าน และไม่มีการเปลี่ยน Business Rules

#11. โค้ดหลัง Refactoring (เฉลยฉบับเต็ม)

package com.example.refactoring;

public class OrderCalculator {
    private static final int GOLD_DISCOUNT_PERCENT = 15;
    private static final int SILVER_DISCOUNT_PERCENT = 5;
    private static final int SHIPPING_THRESHOLD_BAHT = 1000;
    private static final int EXPRESS_SHIPPING_ABOVE_THRESHOLD_BAHT = 80;
    private static final int EXPRESS_SHIPPING_BELOW_THRESHOLD_BAHT = 120;
    private static final int STANDARD_SHIPPING_ABOVE_THRESHOLD_BAHT = 0;
    private static final int STANDARD_SHIPPING_BELOW_THRESHOLD_BAHT = 40;

    public int calculateTotal(String tier, int unitPriceBaht, int quantity,
                              boolean expressShipping) {
        int subtotal = calculateSubtotal(unitPriceBaht, quantity);
        int discount = calculateDiscount(tier, subtotal);
        int discountedTotal = subtotal - discount;
        int shipping = calculateShipping(discountedTotal, expressShipping);
        return discountedTotal + shipping;
    }

    private int calculateSubtotal(int unitPriceBaht, int quantity) {
        return unitPriceBaht * quantity;
    }

    private int calculateDiscount(String tier, int subtotal) {
        if ("GOLD".equals(tier)) {
            return subtotal * GOLD_DISCOUNT_PERCENT / 100;
        }
        if ("SILVER".equals(tier)) {
            return subtotal * SILVER_DISCOUNT_PERCENT / 100;
        }
        return 0;
    }

    private int calculateShipping(int discountedTotal, boolean expressShipping) {
        if (discountedTotal >= SHIPPING_THRESHOLD_BAHT) {
            return expressShipping
                    ? EXPRESS_SHIPPING_ABOVE_THRESHOLD_BAHT
                    : STANDARD_SHIPPING_ABOVE_THRESHOLD_BAHT;
        }
        return expressShipping
                ? EXPRESS_SHIPPING_BELOW_THRESHOLD_BAHT
                : STANDARD_SHIPPING_BELOW_THRESHOLD_BAHT;
    }
}

โค้ดหลัง Refactor ยังมี Public API calculateTotal(String, int, int, boolean) เดิม คงสูตร/ค่าจัดส่ง/การปัดเศษ/การจัดการระดับสมาชิกที่ไม่รู้จักและ null เช่นเดิม แต่การอ่านและแยกความรับผิดชอบชัดเจนกว่า

หากใช้ไฟล์เฉลยจาก ZIP:

macOS / Linux

cp solutions/OrderCalculator.java src/main/java/com/example/refactoring/OrderCalculator.java
mvn clean test

Windows PowerShell

Copy-Item solutions/OrderCalculator.java src/main/java/com/example/refactoring/OrderCalculator.java -Force
mvn clean test

#12. ตรวจสอบความถูกต้องด้วย Git

สร้าง Baseline Commit ก่อนเปลี่ยนโค้ด (แนะนำให้ทำหลังเพิ่ม Test แล้ว):

git init
git add .
git commit -m "test: characterize checkout behavior"

หลังทำ Refactor ทีละช่วง:

mvn test
git diff --check
git diff
git add src/main/java/com/example/refactoring/OrderCalculator.java
git commit -m "refactor: extract checkout pricing rules"

ข้อแนะนำ: หาก git commit แจ้งว่าไม่มีการตั้งค่า Identity ให้ตั้งค่า user.name และ user.email สำหรับ Git repository ก่อน อย่าสับสนระหว่าง Commit ของการเพิ่ม Test กับ Commit ของการ Refactor

#ขั้นตอนการทำงาน

flowchart TD
    A[ตรวจพบ Code Smells] --> B[บันทึกพฤติกรรมเดิมด้วย Characterization Tests]
    B --> C{Baseline ผ่าน?}
    C -- ไม่ผ่าน --> D[วิเคราะห์สาเหตุและยืนยันผลเดิม]
    D --> B
    C -- ผ่าน --> E[Refactor ทีละจุด]
    E --> F[รัน Tests และตรวจ Git Diff]
    F --> G{ผลทดสอบผ่าน?}
    G -- ไม่ผ่าน --> H[ย้อนกลับการเปลี่ยนล่าสุดหรือแก้ให้คงพฤติกรรม]
    H --> E
    G -- ผ่าน --> I[Commit และทำซ้ำหากจำเป็น]

#13. แบบฝึกหัดเพิ่มเติม

Exercise A — Boundary Testing (ระดับพื้นฐาน) เพิ่ม Tests โดยไม่เปลี่ยน Production Code สำหรับ:

  1. ยอดหลังลดเท่ากับ 1,000 บาทพอดี
  2. ยอดหลังลดต่ำกว่า 1,000 บาทเพียงเล็กน้อย
  3. สั่งสินค้า quantity = 0 และบันทึกพฤติกรรมปัจจุบัน
  4. tier = null และ tier = "UNKNOWN"

Exercise B — Improve Readability (ระดับกลาง) ทดลองนำ calculateDiscount ไปแยกเป็นคลาส DiscountPolicy แต่คง calculateTotal(...) เดิมเป็น Facade แล้วเขียน Tests ให้ครบ

Exercise C — Design Decision (ระดับสูง) พิจารณาว่าควรใช้ enum CustomerTier หรือ record OrderRequest หรือไม่ และเขียนเหตุผลว่าทำไมการเปลี่ยนชนิด Input ของ Public API อาจเป็น API Change ไม่ใช่ Refactoring ที่ปลอดภัยโดยอัตโนมัติ

Exercise D — Production Readiness (ระดับสูง) ออกแบบรายการงานแยกสำหรับ Input Validation, Overflow Protection, กฎการปัดเศษของเงินจริง และการใช้ BigDecimal พร้อมอธิบายว่าเรื่องใดเปลี่ยน Behavior และควรแยก PR

#14. Rubric ประเมิน Workshop (100 คะแนน)

เกณฑ์ คะแนน
Tests ครอบคลุมส่วนลด ค่าจัดส่ง Boundary และ Null 30
Refactor ได้โดยไม่เปลี่ยนพฤติกรรมเดิม 30
ชื่อและโครงสร้าง Method/Constant สื่อความหมาย 20
Commit/git diff ตรวจสอบย้อนหลังได้ 10
อธิบาย Trade-off และข้อจำกัดของการออกแบบได้ 10
รวม 100

#15. Checklist ก่อนส่งงาน

  • [ ] mvn clean test ผ่านก่อน Refactoring
  • [ ] มี Test สำหรับ GOLD / SILVER / STANDARD / UNKNOWN / null
  • [ ] มี Test ครอบคลุม Threshold 1,000 บาทและการปัดเศษจำนวนเต็ม
  • [ ] ใช้ Named Constants แทน Magic Numbers ที่มีความหมายทางธุรกิจ
  • [ ] แยก Method ตามความรับผิดชอบ
  • [ ] ลด Nested Conditionals และตรวจซ้ำของเงื่อนไข
  • [ ] ไม่แก้กฎส่วนลดหรือค่าจัดส่งโดยไม่ได้ตั้งใจ
  • [ ] Public Method Signature ยังเหมือนเดิม
  • [ ] ตรวจสอบ git diff --check และ Commit
  • [ ] อธิบายได้ว่าเหตุใด Tests จึงช่วยลดความเสี่ยง แต่ไม่รับประกันความเท่าเทียมในทุกกรณี

#16. สรุป

Refactoring ที่ดีไม่ได้หมายถึงการลดจำนวนบรรทัดให้สั้นที่สุด แต่เป็นการทำให้โค้ด เข้าใจง่าย เปลี่ยนได้อย่างมั่นใจ และทดสอบได้ กระบวนการสำคัญคือ:

บันทึกพฤติกรรมเดิม → Refactor ทีละจุด → รันทดสอบ → ตรวจ Diff → Commit

Workshop นี้เหมาะสำหรับรายวิชา Software Engineering, Software Testing, Object-Oriented Programming และหัวข้อ Clean Code / Technical Debt โดยสามารถขยายไปสู่ Design Patterns, CI/CD Quality Gates และ Static Analysis ในบทเรียนถัดไป

#เอกสารอ้างอิง

  1. Martin Fowler, Refactoring: Improving the Design of Existing Code
  2. Martin Fowler, Refactoring Catalog
  3. Refactoring.Guru, Refactoring Techniques
  4. JUnit, JUnit 5 User Guide
  5. Apache Maven, Maven Surefire Plugin — JUnit Platform

บทความและ Source Code สำหรับการเรียนรู้ เทคนิคที่แสดงเป็นตัวอย่างด้านโครงสร้าง ไม่ใช่กฎการเงินที่แนะนำให้ใช้ใน Production โดยไม่ตรวจสอบข้อกำหนดเพิ่มเติม