การรีแฟกเตอร์เป็นเทคนิคหลักในการปรับปรุงโค้ด หนังสืออ้างอิงหลักเกี่ยวกับการรีแฟกเตอร์คือหนังสือ 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 คุณสามารถใช้มันเพื่อแยกความซ้ำซ้อน แยกหน้าที่รับผิดชอบ และแบ่งเมธอดที่ยาวเกินไปออกเป็นส่วนย่อยๆ