ทำความรู้จักกับ Characterization Tests เพื่อความปลอดภัยก่อนแก้โค้ดเก่า
เวลาเราเข้ามาทำงานในบริษัทหรือรับโปรเจกต์ต่อจากคนอื่น สิ่งที่เจอแน่ๆ คือ Legacy Code (โค้ดเก่าที่ไม่มีใครกล้าแตะ) โค้ดพวกนี้มักจะซับซ้อน อ่านยาก และไม่มีคู่มือการใช้งานที่ชัดเจน หลายคนพอเห็นโค้ดแล้วอยากจะรื้อหรือปรับปรุงให้ดีขึ้นทันที แต่ความเสี่ยงที่น่ากลัวที่สุดคือการเผลอไปทำลายฟังก์ชันการทำงานบางอย่างที่โค้ดเดิมทำไว้โดยที่เราไม่รู้ตัว
Characterization Tests (การเขียนทดสอบเพื่อบันทึกพฤติกรรมปัจจุบันของโปรแกรม) คือเกราะป้องกันชั้นดีของเรา มันไม่ใช่การเขียนทดสอบว่าโปรแกรม "ควรทำอะไร" แต่เป็นการเขียนเพื่อบันทึกว่า "ตอนนี้มันทำอะไรอยู่" เปรียบเหมือนการถ่ายรูปบ้านเก่าก่อนที่คุณจะเริ่มทุบผนังเพื่อรีโนเวท ถ้าคุณไม่ถ่ายรูปไว้ คุณอาจจะเผลอไปทุบเสาที่ค้ำโครงสร้างบ้านโดยไม่ตั้งใจจนบ้านพังลงมาได้
การเขียนทดสอบแบบนี้จะช่วยให้เรากล้าแก้โค้ดมากขึ้น เพราะหากเราแก้แล้วผลลัพธ์เปลี่ยนไปจากที่เราเคยบันทึกไว้ ตัวทดสอบจะแจ้งเตือนทันที ทำให้เรามั่นใจได้ว่าโค้ดใหม่ของเรายังคงรักษาพฤติกรรมเดิมที่สำคัญไว้ได้ครบถ้วน นี่คือทักษะสำคัญที่โปรแกรมเมอร์มืออาชีพต้องมี เพื่อเปลี่ยนโค้ดที่น่ากลัวให้กลายเป็นโค้ดที่สะอาดและจัดการได้ง่ายโดยไม่ทำระบบพัง
เลือกจุดที่คุ้มค่าที่สุดในการเริ่มเขียนทดสอบ
อย่าพยายามเขียนทดสอบครอบคลุมทั้งโปรเจกต์ในคราวเดียว เพราะคุณจะท้อและเลิกทำไปก่อน ให้เริ่มเลือก Business Capability (ความสามารถของระบบทางธุรกิจ) มาหนึ่งอย่างที่คุณต้องเข้าไปแก้ไขจริงๆ เช่น การคำนวณส่วนลด หรือการสร้างใบแจ้งหนี้ วิธีนี้จะช่วยให้คุณเห็นภาพชัดเจนว่าส่วนไหนที่ห้ามพังเด็ดขาด
เมื่อเลือกจุดที่จะทำได้แล้ว ให้ลองไล่ดูว่ามันเริ่มต้นจากจุดไหนและส่งผลกระทบอะไรบ้าง เช่น เมื่อกดปุ่มสั่งซื้อ ระบบเรียกฟังก์ชันไหนก่อน แล้วไปบันทึกลงฐานข้อมูลอย่างไร การกำหนดขอบเขตให้แคบจะช่วยให้คุณโฟกัสได้ง่ายขึ้น แทนที่จะงมเข็มในมหาสมุทร คุณก็แค่โฟกัสว่า "ฟังก์ชันนี้รับค่าอะไรเข้ามา และต้องได้ผลลัพธ์อะไรออกไป"
จุดที่มือใหม่มักพลาดคือการพยายามทดสอบทุกบรรทัดจนเกินความจำเป็น ให้จำไว้ว่าเราเน้นที่ Observable Behavior (พฤติกรรมที่สังเกตเห็นได้จากภายนอก) เท่านั้น เช่น ค่าที่คืนกลับมา หรือการบันทึกข้อมูลลงฐานข้อมูล ส่วนการคำนวณภายในที่ไม่มีใครมองเห็นไม่จำเป็นต้องทดสอบทั้งหมดในคราวเดียว ให้เริ่มจากจุดที่ส่งผลกระทบต่อผู้ใช้งานมากที่สุดก่อนเสมอ
บันทึกพฤติกรรมปัจจุบันด้วยตัวอย่างโค้ด
เรามาลองดูตัวอย่างฟังก์ชันการคำนวณราคาที่ซับซ้อนกัน สมมติว่าคุณต้องแก้โค้ดนี้เพื่อเพิ่มฟีเจอร์ใหม่ แต่กลัวว่าเงื่อนไขเดิมจะพัง ก่อนจะแก้ เราต้องสร้างชุดทดสอบเพื่อ "ล็อค" พฤติกรรมเดิมไว้ก่อน นี่คือตัวอย่างการใช้ vitest (เครื่องมือทดสอบโค้ด) เพื่อตรวจสอบว่าฟังก์ชันคำนวณราคาทำงานถูกต้องตามกฎเดิมหรือไม่
// ตัวอย่างฟังก์ชันเดิมที่ต้องการตรวจสอบ
async function processOrder(order) {
let total = order.subtotal;
if (order.customer.type === "PREMIUM") {
total = total * 0.9; // ลดราคาให้ลูกค้า VIP 10%
}
return total;
}
// ชุดทดสอบเพื่อบันทึกพฤติกรรม
import { expect, it } from "vitest";
it("ควรลดราคาให้ลูกค้าพรีเมียม 10% ตามพฤติกรรมเดิม", async () => {
const order = { subtotal: 1000, customer: { type: "PREMIUM" } };
const result = await processOrder(order);
expect(result).toBe(900); // ต้องได้ 900 เสมอถ้าเป็นลูกค้าพรีเมียม
});
ในโค้ดนี้ เราสร้างตัวแปร order ที่ระบุเงื่อนไขเป็นลูกค้าพรีเมียม จากนั้นเรียกใช้ฟังก์ชัน processOrder เพื่อดูว่าได้ค่า 900 หรือไม่ บรรทัด expect(result).toBe(900) คือการยืนยันว่าผลลัพธ์ปัจจุบันคือ 900 ถ้าวันหนึ่งคุณแก้โค้ดแล้วผลลัพธ์กลายเป็นอย่างอื่น ทดสอบนี้จะขึ้นสีแดงเตือนคุณทันที
ผลลัพธ์ที่ควรเห็นคือการรันคำสั่งทดสอบผ่าน (Passed) ซึ่งเป็นการยืนยันว่า "ฉันเข้าใจพฤติกรรมเดิมแล้ว" หากคุณรันแล้วขึ้นสีแดง นั่นแปลว่าโค้ดปัจจุบันของคุณอาจมีพฤติกรรมที่คุณยังไม่เข้าใจ หรือฟังก์ชันที่คุณกำลังแก้ทำงานผิดพลาดตั้งแต่ก่อนเริ่มรีโนเวทแล้ว
จัดการกับผลกระทบข้างเคียงและระบบภายนอก
บ่อยครั้งที่โค้ดเก่าไม่ได้แค่คำนวณเลข แต่ยังต้องบันทึกลง Database (ฐานข้อมูล) หรือส่งอีเมล การทดสอบพฤติกรรมเหล่านี้เรียกว่า Side Effects (ผลกระทบข้างเคียงที่เกิดขึ้นนอกเหนือจากค่าที่คืนกลับมา) ซึ่งเราต้องใช้เทคนิคที่เรียกว่า Mocks (การจำลองการทำงานของระบบอื่น) เพื่อไม่ให้การทดสอบไปลบข้อมูลจริงหรือส่งอีเมลหาลูกค้าจริงๆ
การใช้ spyOn (เครื่องมือแอบดูการทำงานของฟังก์ชัน) จะช่วยให้คุณตรวจสอบได้ว่าโค้ดมีการเรียกใช้งานระบบภายนอกถูกต้องไหม เช่น ตรวจสอบว่าหลังจากคำนวณราคาแล้ว ระบบได้สั่งบันทึกข้อมูลลงฐานข้อมูลด้วยค่าที่ถูกต้องหรือไม่ นี่คือวิธีที่ปลอดภัยที่สุดในการทดสอบโค้ดที่มีความซับซ้อนสูงโดยไม่ต้องกังวลเรื่องข้อมูลพัง
มือใหม่มักจะกลัวการทดสอบระบบภายนอกจนไม่ยอมเขียนทดสอบเลย ซึ่งเป็นความคิดที่ผิดมาก ให้ลองมองว่าเราแค่ต้องการ "ดักฟัง" ว่าโค้ดของเราคุยกับระบบอื่นอย่างไร ถ้าเรามั่นใจว่ามันคุยกับฐานข้อมูลด้วยข้อมูลที่ถูกต้อง เราก็สามารถเปลี่ยนโค้ดภายในฟังก์ชันได้อย่างอิสระโดยไม่ต้องกังวลว่าข้อมูลจะหายไปไหน
ใช้ AI ช่วยทำความเข้าใจโค้ดที่ซับซ้อน
หากโค้ดมันซับซ้อนจนอ่านไม่ออก ให้ใช้ AI เช่น ChatGPT หรือ Claude ช่วยอธิบายโค้ดทีละบรรทัด แต่ต้องระวังว่าอย่าให้ AI เขียนชุดทดสอบให้โดยที่คุณไม่เข้าใจ เพราะเป้าหมายของเราคือการ เข้าใจพฤติกรรมที่แท้จริง ไม่ใช่แค่ให้ผ่านๆ ไป AI อาจจะเดาพฤติกรรมผิดได้หากโค้ดนั้นมีความซับซ้อนสูง
วิธีใช้ที่ถูกต้องคือให้ AI ช่วยสรุปว่าฟังก์ชันนี้รับค่าอะไร และคืนค่าอะไรออกมาบ้าง จากนั้นให้คุณเขียนชุดทดสอบด้วยตัวเองตามความเข้าใจนั้น หากผลการทดสอบออกมาตรงกับที่ AI วิเคราะห์ นั่นถือเป็นสัญญาณที่ดีว่าคุณเริ่มเข้าใจโครงสร้างของโค้ดนั้นแล้ว การมี AI เป็นผู้ช่วยจะช่วยลดเวลาในการแกะโค้ดไปได้มหาศาล
ข้อควรระวังคือ AI อาจจะสร้าง "พฤติกรรมที่ควรจะเป็น" แทนที่จะเป็น "พฤติกรรมที่เป็นอยู่จริงๆ" ซึ่งอาจทำให้คุณพลาดบั๊กบางอย่างที่ซ่อนอยู่ในโค้ดเก่าได้ ให้ยึดถือผลลัพธ์ที่โค้ดรันออกมาจริงเป็นหลักเสมอ อย่าเชื่อคำอธิบายของ AI จนกว่าคุณจะได้พิสูจน์ด้วยการเขียนทดสอบและรันมันด้วยตัวเอง
สรุป: เปลี่ยนโค้ดเก่าให้ปลอดภัยด้วยวินัยการทดสอบ
การทำ Characterization Tests ไม่ใช่เรื่องที่ต้องทำในวันเดียวจบ แต่มันคือการสะสมความเข้าใจเกี่ยวกับระบบไปเรื่อยๆ เริ่มจากฟังก์ชันเล็กๆ ที่คุณต้องแก้ แล้วค่อยๆ ขยายขอบเขตออกไป เมื่อคุณมีชุดทดสอบที่ครอบคลุมพฤติกรรมสำคัญแล้ว คุณจะรู้สึกมั่นใจขึ้นมากเวลาต้องเข้าไปแก้ไขโค้ดที่ดูเหมือนจะพังได้ตลอดเวลา
ลองจินตนาการว่าคุณต้องเปลี่ยนสูตรคำนวณภาษีในระบบบัญชีบริษัท ถ้าคุณไม่มีชุดทดสอบ คุณคงต้องนั่งลุ้นจนเหงื่อตก แต่ถ้าคุณมีชุดทดสอบที่บันทึกพฤติกรรมเก่าไว้แล้ว คุณแค่แก้โค้ดแล้วรันคำสั่ง npm test (คำสั่งสั่งให้เครื่องรันชุดทดสอบ) ถ้าทุกอย่างยังเป็นสีเขียว คุณก็ส่งงานได้อย่างสบายใจโดยไม่ต้องกลัวว่าจะโดนหัวหน้าเรียกไปคุยเพราะระบบพัง
จำไว้ว่าโค้ดที่ดีที่สุดไม่ใช่โค้ดที่เขียนขึ้นมาใหม่ทั้งหมด แต่เป็นโค้ดที่ผ่านการปรับปรุงอย่างปลอดภัยโดยยังรักษาพฤติกรรมที่ลูกค้าต้องการไว้ได้ครบถ้วน เริ่มต้นวันนี้ด้วยการเขียนทดสอบจุดเล็กๆ จุดเดียวในงานของคุณ แล้วคุณจะพบว่าการรีโนเวทโค้ดเก่าไม่ใช่เรื่องน่ากลัวอีกต่อไป ขอให้สนุกกับการทำให้โค้ดของคุณสะอาดขึ้นและมั่นใจขึ้นในทุกๆ วัน
ที่มา: How to Build Characterization Tests Before Refactoring Legacy Code — freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More