วิธีแก้ปัญหาชั่วคราว (อังกฤษ: workaround, kludge, hotfix) คือวิธีแก้ปัญหาชั่วคราวหรือไม่เหมาะสมที่สุดสำหรับปัญหาในโค้ดที่ทำงานได้ แต่ละเมิดหลักการของสถาปัตยกรรมที่สะอาด ความสามารถในการอ่าน หรือประสิทธิภาพ วิธีแก้ปัญหาชั่วคราวเป็นสิ่งที่หลีกเลี่ยงไม่ได้ในการพัฒนาจริง: กำหนดเวลา ความไม่เข้ากันของเวอร์ชัน โค้ดเดิม และพฤติกรรมที่ไม่มีการบันทึกของเฟรมเวิร์กทำให้นักพัฒนาต้องประนีประนอม ตามข้อมูลของ Martin Fowler (2025) ความแตกต่างหลักระหว่างวิธีแก้ปัญหาชั่วคราวที่สมเหตุสมผลและหนี้ทางเทคนิคคือการมีแผนในการกำจัดและการทำเครื่องหมายที่ชัดเจนในโค้ด
ประเด็นสำคัญ
วิธีแก้ปัญหาชั่วคราว เป็นคำสแลงสำหรับวิธีแก้ปัญหาซอฟต์แวร์ที่ถูกต้องตามหน้าที่แต่ไม่เหมาะสมที่สุดในทางเทคนิค โค้ดดังกล่าวทำงานได้ ผ่านการทดสอบ และ甚至ถึงขั้น production แต่การอ่านมันทำให้อยากเขียนทุกอย่างใหม่ตั้งแต่ต้น ในสภาพแวดล้อมที่พูดภาษาอังกฤษ มีการใช้คำว่า workaround, kludge (kluge), hack หรือ quick-and-dirty fix
คำนี้มาจากอุปมาอุปไมยในชีวิตประจำวัน: หากขาเก้าอี้หัก คุณสามารถใช้เทปพันมันได้ — เก้าอี้กลับมาใช้งานได้อีกครั้ง แต่การแก้ปัญหาชั่วคราวและไม่สวยงาม เช่นเดียวกันในการเขียนโปรแกรม: บักถูกแก้ไขด้วย hardcode, วิธีแก้ปัญหาชั่วคราวด้วย timeout หรือการอ้อมผ่าน API ที่ไม่มีการบันทึก โค้ดคอมไพล์ได้ แอปพลิเคชันไม่ล่ม แต่ไม่สามารถเรียกวิธีแก้ปัญหาว่ามีคุณภาพได้
ข้อแตกต่างที่สำคัญ: บัก — คือเมื่อโค้ดไม่ทำงานตามที่คาดหวัง วิธีแก้ปัญหาชั่วคราว — คือเมื่อโค้ดทำงานแต่ถูกออกแบบไม่ดี วิธีแก้ปัญหาชั่วคราวเป็นทางเลือกที่มีสติของนักพัฒนาเสมอ: “ฉันรู้ว่ามันไม่สวยงาม แต่ตอนนี้มันแก้ปัญหาได้”
ตามข้อมูลของ Stripe (2024) นักพัฒนาใช้เวลาเฉลี่ย 17 ชั่วโมงต่อสัปดาห์ ในการจัดการกับหนี้ทางเทคนิคและวิธีแก้ปัญหาชั่วคราว — เกือบครึ่งหนึ่งของเวลาทำงาน นี่คือการสูญเสียผลผลิตของทีมโดยตรง
สาเหตุแรกและหลักคือ กำหนดเวลา เมื่อเหลืออีกหนึ่งวันก่อนการเปิดตัวและบักที่สำคัญยังไม่ได้รับการแก้ไข ทีมเลือกวิธีแก้ปัญหาที่รวดเร็วแทนที่จะเป็นวิธีที่ถูกต้อง การ hardcode ค่า การปิดการตรวจสอบ การเพิ่ม sleep() — ตัวอย่างคลาสสิกของวิธีแก้ปัญหาชั่วคราวเนื่องจากกำหนดเวลา นักพัฒนาที่มีประสบการณ์มักจะทำเครื่องหมายสถานที่ดังกล่าวด้วย TODO หรือ FIXME
สาเหตุที่สองคือ ความไม่เข้ากันของ API ไลบรารีหรือเฟรมเวิร์กของบุคคลที่สามทำงานแตกต่างจากที่บันทึกไว้ เฟรมเวิร์กไม่ส่งออกคลาสที่ต้องการ เมธอดถูกทำเครื่องหมายว่าล้าสมัยและไม่มีทางเลือกอื่น นักพัฒนาถูกบังคับให้ใช้ reflection, API ภายในหรือวิธีเลี่ยง ใน Java นี่อาจเป็นการเข้าถึงผ่าน setAccessible(true); ใน Swift — @objc และ performSelector
สาเหตุที่สามคือ โค้ดเดิม (legacy) นักพัฒนาได้รับโครงการที่เขียนเมื่อ 5-10 ปีก่อนบนเฟรมเวิร์กรุ่นเก่า ไม่มีเวลาหรืองบประมาณในการเขียนโมดูลทั้งหมดใหม่ ดังนั้นฟังก์ชันใหม่จึงถูก “ติด” เข้ากับโค้ดเก่าผ่านวิธีแก้ปัญหาชั่วคราว ทีละชั้นจนสะสมมากมายจนโมดูลกลายเป็น “ก้อนโคลนขนาดใหญ่” (big ball of mud)
สาเหตุที่สี่คือ การขาดการทดสอบ การปรับโครงสร้างโดยไม่มีการทดสอบเป็นอันตราย: การเปลี่ยนแปลงสถาปัตยกรรมสามารถทำลายฟังก์ชันที่ทำงานอยู่ได้ เมื่อไม่มีการทดสอบ นักพัฒนาชอบเพิ่มวิธีแก้ปัญหาชั่วคราวบนโค้ดที่ทำงานอยู่มากกว่าเสี่ยงต่อความเสถียร ตาม Google Testing Blog (2024) ทีมที่ไม่มีการทดสอบใช้วิธีแก้ปัญหาชั่วคราวมากกว่า 3 เท่า
การจำแนกประเภทของวิธีแก้ปัญหาชั่วคราวช่วยให้ทีมเข้าใจว่าพวกเขากำลังจัดการกับหนี้ทางเทคนิคประเภทใดและเลือกกลยุทธ์การกำจัดที่ถูกต้อง มาดูประเภทหลักกัน
Hardcode — ประเภทที่พบบ่อยที่สุด แทนที่จะใช้การกำหนดค่า ทรัพยากร หรือพารามิเตอร์ จะใช้ค่าคงที่ในโค้ด ตัวอย่าง: URL เซิร์ฟเวอร์ที่ hardcode, หมดเวลา 5 วินาที, ขนาดฟอนต์ 16pt Hardcode ทำให้โค้ดไม่สามารถปรับขนาดได้และต้องคอมไพล์ใหม่ทุกครั้งที่มีการเปลี่ยนแปลง
คัดลอกและวาง — การทำสำเนาส่วนโค้ดด้วยการเปลี่ยนแปลงเล็กน้อยแทนที่จะแยกตรรกะร่วมกัน อาการคลาสสิก: มี 3 เมธอดที่คล้ายกันในโครงการที่แตกต่างกันหนึ่งบรรทัด การคัดลอกและวางช่วยเร่งการเขียนโค้ดในเวลาที่ทำงาน แต่ทำให้การบำรุงรักษาช้าลง 10 เท่าในอนาคต — ต้องแก้ไขใน 3 แห่งแทนที่จะเป็นที่เดียว
try-catch ว่างเปล่า — บล็อก catch ที่ไม่ทำอะไรเลยหรือเพียงบันทึกข้อผิดพลาดโดยไม่จัดการ วิธีแก้ปัญหาชั่วคราวนี้ “ปิดเสียง” ข้อยกเว้นแต่ไม่ได้แก้ไขสาเหตุ แอปพลิเคชันยังคงทำงาน แต่ข้อมูลอาจเสียหายและผู้ใช้อาจไม่ได้รับการตอบกลับ
Sleep ในโค้ด — Thread.sleep(500) หรือ DispatchQueue.main.asyncAfter เพื่อรอเมื่อควรมีเหตุการณ์หรือ callback โค้ดดังกล่าวไม่น่าเชื่อถือ: บนอุปกรณ์ช้า 500 ms อาจไม่เพียงพอ; บนอุปกรณ์เร็ว การหยุดชั่วคราวจะไม่จำเป็น ใช้ CountDownLatch, Semaphore หรือ async/await กับการกำหนดเวลาที่เหมาะสม
ธงความเข้ากันได้ — if-else แบบเรียงซ้อนที่ตรวจสอบเวอร์ชันระบบปฏิบัติการ รุ่นอุปกรณ์ หรือความพร้อมของคุณสมบัติ เมื่อมีธงมากกว่า 3-4 อัน โค้ดจะกลายเป็นสปาเก็ตตี้ วิธีแก้ไขคือรูปแบบ Strategy หรือ Feature Flags ผ่านการกำหนดค่า
นักพัฒนาหลายคนสับสนระหว่างวิธีแก้ปัญหาชั่วคราวและหนี้ทางเทคนิค ความแตกต่างอยู่ใน ขนาดและความตระหนัก วิธีแก้ปัญหาชั่วคราวเป็นวิธีแก้ปัญหาเฉพาะที่ (หนึ่งเมธอด หนึ่งคลาส) หนี้ทางเทคนิคเป็นปัญหาที่เป็นระบบซึ่งส่งผลต่อสถาปัตยกรรมของโมดูลหรือทั้งแอปพลิเคชัน
อุปมาอุปไมยของ Ward Cunningham (ผู้สร้างคำว่าหนี้ทางเทคนิค): หนี้ทางเทคนิคเหมือนการกู้เงินจากธนาคาร คุณเอาเงินตอนนี้เพื่อสร้างบ้านให้เร็วขึ้น แต่ later จ่ายดอกเบี้ย วิธีแก้ปัญหาชั่วคราว — เหมือนการตอกตะปูด้วยค้อนแทนที่จะใช้ปืนยิงตะปู: งานสำเร็จ แต่มีประสิทธิภาพน้อยกว่า
วิธีแก้ปัญหาชั่วคราวหนึ่งอย่างไม่สร้างหนี้ทางเทคนิค แต่ 50 วิธีในโมดูลเดียว = หนี้ทางสถาปัตยกรรม ดังนั้น กฎของทีม: วิธีแก้ปัญหาชั่วคราวแต่ละอย่างจะถูกบันทึกในการตรวจสอบโค้ดหรือระบบติดตามงาน และทีมตรวจสอบวิธีแก้ปัญหาชั่วคราวที่สะสมอย่างสม่ำเสมอ (ครั้งต่อ sprint)
ตาม Spotify Engineering (2023) ทีมที่ติดตามวิธีแก้ปัญหาชั่วคราวในโค้ด (ผ่านป้ายกำกับ TODO พิเศษหรือคำอธิบายประกอบแบบกำหนดเอง) ลดเวลาในการปรับโครงสร้างลง 30% — เพราะพวกเขาไม่เสียเวลาหลายชั่วโมงในการค้นหาจุดที่มีปัญหา
ขั้นตอนแรก — การสำรวจ ค้นหาในฐานโค้ดสำหรับคำสำคัญ: TODO, FIXME, HACK, WORKAROUND, KLUDGE IDE สมัยใหม่จะเน้นด้วยสีที่แตกต่าง GitHub ยังแสดง TODO ในอินเทอร์เฟซ Pull Request ทำรายการวิธีแก้ปัญหาชั่วคราวทั้งหมดพร้อมลำดับความสำคัญ
ขั้นตอนที่สอง — การจัดลำดับความสำคัญ ไม่ใช่วิธีแก้ปัญหาชั่วคราวทั้งหมดที่ต้องแก้ไขทันที ลำดับความสำคัญ = ความถี่ของการเปลี่ยนแปลงในไฟล์ × ความสำคัญ หากไฟล์เปลี่ยนปีละ 2 ครั้ง วิธีแก้ปัญหาชั่วคราวสามารถรอได้ หากโมดูลถูกแตะในทุก sprint — วิธีแก้ปัญหาชั่วคราวควรแก้ไขก่อน
ขั้นตอนที่สาม — การปรับโครงสร้างพร้อมการทดสอบ อย่าปรับโครงสร้างวิธีแก้ปัญหาชั่วคราวโดยไม่มีการทดสอบ ก่อนอื่นเขียนการทดสอบที่ตรวจสอบพฤติกรรมปัจจุบัน (กับวิธีแก้ปัญหาชั่วคราว) จากนั้นปรับโครงสร้าง จากนั้นตรวจสอบให้แน่ใจว่าการทดสอบผ่าน หากไม่มีสิ่งนี้ การปรับโครงสร้างวิธีแก้ปัญหาชั่วคราวอาจทำลายฟังก์ชันที่มันถูกเขียนขึ้นมา
// Before: URL workaround แบบ hardcode
fun getApiUrl(): String {
return "https://api.example.com/v2"
}
// After: การกำหนดค่าผ่าน BuildConfig
fun getApiUrl(): String {
return BuildConfig.API_BASE_URL
}
ขั้นตอนที่สี่ — ระบบอัตโนมัติ ตั้งค่า linter ที่ห้ามรูปแบบวิธีแก้ปัญหาชั่วคราวบางอย่าง ตัวอย่างเช่น Detekt สำหรับ Kotlin สามารถตรวจสอบการไม่มี Thread.sleep() ในโค้ด production, ESLint สามารถห้าม console.log ในโครงการ สิ่งนี้ป้องกันการเกิดวิธีแก้ปัญหาชั่วคราวใหม่ประเภทเดียวกัน
แม้จะมีความหมายเชิงลบของคำ วิธีแก้ปัญหาชั่วคราวสามารถเป็นวิธีแก้ปัญหาที่ สมเหตุสมผล เงื่อนไขหลัก: วิธีแก้ปัญหาชั่วคราวเป็นชั่วคราว ถูกทำเครื่องหมายอย่างชัดเจน และมีแผนการแทนที่ ในโค้ด production ของทุกโครงการใหญ่ มีวิธีแก้ปัญหาชั่วคราวที่สมเหตุสมผลหลายร้อยวิธี
สถานการณ์ที่ 1: hotfix ใน production บักที่สำคัญส่งผลกระทบต่อผู้ใช้ทั้งหมด ทีมต้องการการแก้ไขภายในหนึ่งชั่วโมง วิธีการที่ถูกต้อง: แก้บักด้วยวิธีใดก็ได้ ปรับใช้ hotfix จากนั้น ในวันถัดไป เขียนวิธีแก้ปัญหาที่เหมาะสมและปิด ticket hotfix เป็นวิธีแก้ปัญหาชั่วคราวที่สมเหตุสมผลถ้ามันอยู่ไม่เกิน 48 ชั่วโมง
สถานการณ์ที่ 2: รอเวอร์ชันใหม่ของไลบรารี เฟรมเวิร์กมีบักที่แก้ไขใน master แล้ว แต่การวางจำหน่ายจะออกใน 2 สัปดาห์ แทนที่จะเขียนโค้ดเลี่ยงที่ซับซ้อน ทีมเพิ่มวิธีแก้ปัญหาชั่วคราวพร้อมหมายเหตุ “REMOVE after library 3.2” เมื่อ 3.2 วางจำหน่าย วิธีแก้ปัญหาชั่วคราวจะถูกลบออก
สถานการณ์ที่ 3: การปิดสตาร์ทอัพหรือ MVP ในขั้นตอน MVP ความเร็วสำคัญกว่าสถาปัตยกรรม วิธีแก้ปัญหาชั่วคราวในช่วงเริ่มต้นเป็นเรื่องปกติ ปัญหาเกิดขึ้นเมื่อสตาร์ทอัพไม่เปลี่ยนเป็นผลิตภัณฑ์ แต่วิธีแก้ปัญหาชั่วคราวยังคงอยู่ คำแนะนำ: หลังรอบการระดมทุน จัดสรร sprint เพื่อชำระหนี้ทางเทคนิคที่สำคัญ
หลักการสำคัญ: “โค้ดเดิมคือโค้ดที่ไม่มีการทดสอบ” (Michael Feathers) หากวิธีแก้ปัญหาชั่วคราวถูกครอบคลุมโดยการทดสอบและบันทึกอย่างชัดเจน — มันสามารถจัดการได้ หากมันถูกทิ้งไว้โดยไม่มีความคิดเห็นเป็นเวลา 2 ปีในโมดูลที่ถูกลืม — มันไม่ใช่วิธีแก้ปัญหาชั่วคราวอีกต่อไป แต่เป็นปัญหาทางสถาปัตยกรรม
คำถามที่พบบ่อย
บัก — โค้ดไม่ทำงานตามที่คาดหวัง วิธีแก้ปัญหาชั่วคราว — โค้ดทำงานแต่เขียนอย่างไม่เหมาะสม วิธีแก้ปัญหาชั่วคราวเป็น decision ที่มีสติของนักพัฒนาเสมอ บักมักเป็นความผิดพลาดโดยไม่รู้ตัว
ใช้ // TODO: refactor — ... หรือคำอธิบายประกอบ @Workaround แบบกำหนดเองพร้อมฟิลด์: เหตุผล วันที่ ผู้รับผิดชอบ กำหนดเวลาลบ หลีกเลี่ยง // HACK เปล่าโดยไม่มีคำอธิบาย
ถ้าโมดูลไม่เปลี่ยนแปลงและวิธีแก้ปัญหาชั่วคราวคงที่ — ไม่จำเป็น การปรับโครงสร้างโดยไม่มีเหตุผลเพิ่มความเสี่ยงของการถดถอย แก้ไขเฉพาะวิธีแก้ปัญหาชั่วคราวที่ขัดขวางการเพิ่มฟังก์ชันใหม่เท่านั้น
เปรียบเทียบเวลา: “ปัจจุบันเราใช้เวลา 4 ชั่วโมงในการทดสอบด้วยตนเองเนื่องจากวิธีแก้ปัญหาชั่วคราวเหล่านี้ การปรับโครงสร้างจะใช้เวลา 8 ชั่วโมงและลดเวลาเหลือ 30 นาที ผลตอบแทนจากการลงทุน — 2 sprints” พูดในภาษา ความเร็วและเงิน ไม่ใช่สถาปัตยกรรมที่สะอาด
ค้นหา TODO, FIXME, HACK, WORKAROUND ผ่าน grep ทั่วโครงการ วิเคราะห์เมธอดที่ยาวกว่า 100 บรรทัดและคลาสที่มีการพึ่งพามากกว่า 5 รายการ ใช้ linter ที่มีกฎแบบกำหนดเองสำหรับการตรวจจับอัตโนมัติ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม