- 1. ผลลัพธ์การเรียนรู้
- 2. หลักการและข้อควรระวัง
- 3. เตรียมเครื่องมือ
- 4. โครงสร้างโปรเจ็กต์
- 5. โจทย์ธุรกิจ: ระบบคำนวณยอดชำระ
- 6. ขั้นที่ 1 — Characterization Tests (Baseline)
- 7. ขั้นที่ 2 — Rename: เปลี่ยนชื่อให้สื่อความหมาย
- 8. ขั้นที่ 3 — Replace Magic Numbers with Named Constants
- 9. ขั้นที่ 4 — Extract Method: แยกความรับผิดชอบ
- 10. ขั้นที่ 5 — Guard Clauses และลดเงื่อนไขซ้ำ
- 11. โค้ดหลัง Refactoring (เฉลยฉบับเต็ม)
- 12. ตรวจสอบความถูกต้องด้วย Git
- 13. แบบฝึกหัดเพิ่มเติม
- 14. Rubric ประเมิน Workshop (100 คะแนน)
- 15. Checklist ก่อนส่งงาน
- 16. สรุป
- เอกสารอ้างอิง

เป้าหมาย: ฝึกปรับโครงสร้างโค้ดโดย ไม่เปลี่ยนพฤติกรรมที่สังเกตได้จากภายนอก (Observable Behavior) ใช้ Unit Tests ป้องกัน Regression และตรวจสอบผลหลังการปรับปรุงทุกขั้นตอน
ในโปรเจ็กต์ที่พัฒนาต่อเนื่องหลายปี เรามักพบเมธอดที่ซับซ้อน ตัวแปรชื่อคลุมเครือ เงื่อนไขซ้อนกัน และค่า Magic Numbers จำนวนมาก การ Refactoring ช่วยให้โค้ดเข้าใจง่ายขึ้น โดยแยกการแก้โครงสร้างออกจากการเปลี่ยน Business Rules
บทความนี้เป็น Hands-on Workshop ที่ทำตามได้จริง โดยใช้ตัวอย่างระบบคำนวณยอดชำระ (Checkout) ของร้านค้าออนไลน์ นักศึกษาจะได้เริ่มจากโค้ดที่ทำงานถูกต้องแต่มี Code Smells เขียนชุดทดสอบเพื่อบันทึกพฤติกรรม แล้วค่อย ๆ Refactor จนได้โค้ดที่ดูแลรักษาง่ายขึ้น
#1. ผลลัพธ์การเรียนรู้
หลังจบ Workshop ผู้เรียนจะสามารถ:
- อธิบายความแตกต่างระหว่าง Refactoring, Bug Fixing, Optimization และ Rewrite
- ระบุ Code Smells เช่น Poor Naming, Magic Numbers, Nested Conditionals และ Long Method
- เขียน Characterization Tests เพื่อเก็บพฤติกรรมของ Legacy Code
- ใช้เทคนิค Rename, Extract Method, Replace Magic Number with Named Constant และ Guard Clauses
- ทดสอบโค้ดก่อนและหลังด้วย JUnit Jupiter ผ่าน Maven
- ตรวจสอบ
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)
กฎธุรกิจเดิม:
- ยอดก่อนส่วนลด = ราคาต่อชิ้น × จำนวน
- สมาชิก GOLD ลด 15%; SILVER ลด 5%; ระดับอื่นไม่ลด
- ส่วนลดคำนวณด้วยจำนวนเต็ม (ปัดเศษทิ้งตามการหารของ Java)
- ค่าจัดส่งพิจารณาจากยอด หลังหักส่วนลด ไม่ใช่ยอดก่อนลด
- หากยอดหลังหักส่วนลด ตั้งแต่ 1,000 บาทขึ้นไป: ส่งปกติฟรี / ส่งด่วน 80 บาท
- หากยอดหลังหักส่วนลด ต่ำกว่า 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,000 บาทพอดี
- ยอดหลังลดต่ำกว่า 1,000 บาทเพียงเล็กน้อย
- สั่งสินค้า
quantity = 0และบันทึกพฤติกรรมปัจจุบัน 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 ในบทเรียนถัดไป
#เอกสารอ้างอิง
- Martin Fowler, Refactoring: Improving the Design of Existing Code
- Martin Fowler, Refactoring Catalog
- Refactoring.Guru, Refactoring Techniques
- JUnit, JUnit 5 User Guide
- Apache Maven, Maven Surefire Plugin — JUnit Platform
บทความและ Source Code สำหรับการเรียนรู้ เทคนิคที่แสดงเป็นตัวอย่างด้านโครงสร้าง ไม่ใช่กฎการเงินที่แนะนำให้ใช้ใน Production โดยไม่ตรวจสอบข้อกำหนดเพิ่มเติม