การกลัวความเสี่ยง กับปัญหา defect ที่มีผลกระทบค่อนข้างมากในซอฟท์แวร์ขนาดใหญ่

เรื่องนึงที่หลาย ๆ คนกลัว กับการทำงานกับซอฟท์แวร์ขนาดใหญ่ก็คือ การกลัวความเสี่ยง กลัวที่จะต้องรับผิดชอบหากมีอะไรเกิดขึ้น

บางครั้งบางคราว เราอาจจะได้เจอกับ defect ที่มีผลค่อนข้างกว้าง ซึ่งอาจจะเป็นโค๊ดที่ใช้ร่วมกันหรืออาจจะเป็นถึงระดับโครงสร้างของตัวซอฟต์แวรนั้นเอง ซึ่งถ้าเราไปยุ่งกับมันก็อาจจะมีผลใหญ่โต ต้องทำ regression กันขนานใหญ่ หลาย ๆ คนเลือกที่จะใช้วิธีแก้ไขสถานการเฉพาะหน้า คือ แก้กันที่จุดที่เห็นโดยเลือกที่จะไม่แก้ที่ต้นตอของปัญหา

ในหลาย ๆ ครั้ง การแก้ไขสถานการณ์เฉพาะหน้าก็เป็นคำตอบที่ถูกต้อง โดยเฉพาะถ้ามันเป็นเรื่องเร่งด่วน อย่างเช่น defect ที่ทำให้ระบบล่มทั้งหมด ต้องกู้ให้กลับมาใช้งานได้ก่อนโดยไม่ต้องคำนึงว่าการแก้ไขนั้นจะเกิดปัญหาอะไรในภายหลัง

แต่ถ้าสักแต่แก้ไขเฉพาะจุดเล็ก ๆ โดยไม่มองภาพรวม อนาคตปัญหาใหญ่ที่เกิดจากการแก้ปัญหานั้นอาจจะย้อนกลับมาเล่นงานเราได้

ยกตัวอย่าง web application ตัวหนึ่ง ที่ส่วน frontend กับ backend นั้นคุยกันด้วย message ที่เป็น xml ปัญหาก็คือโค๊ดตัวโครงสร้างฝั่ง frontend นั้นมีการใช้การเข้ารหัส xml ที่ไม่เข้ากันกับส่วน backend ทำให้บางครั้งเกิดปัญหาว่าการถอดรหัสผิดพลาด เมื่อมีการรายงาน defect นี้เข้ามา โปรแกรมเมอร์คนที่ทำงานด้วยตัดสินใจว่า แทนที่เขาจะแก้ตัวโครงสร้าง ปรับให้มันตรงกัน เขาเลือกที่จะทำทำการเข้ารหัสซ้ำอีกครั้งหนึ่งบนหน้าเวปที่เขาทำงานด้วย เป็นการแก้ไขสถานการเฉพาะหน้า เพราะมีแค่หน้าเวปหน้านั้นหน้าเดียวที่ถูกแก้ไข ส่วนหน้าอื่นก็ยังคงผิดอยู่ เมื่อมีการรายงาน defect ลักษณะเดียวกันเข้ามา คนอื่นก็พากันใช้วิธีเดียวกัน ก็คือแก้เฉพาะหน้าที่เขาทำงานด้วย

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

ถ้าเกิดว่ามีการแก้ไข defect ตัวแรกที่ระดับโครงสร้างไปแล้ว เราก็ไม่ต้องมาตามแก้ในหน้าอื่น ๆ ทีหลัง (ซึ่งเกิดขึ้นแทบทุกหน้า) ใช่ไหมครับ ?

จากประสพการณ์ส่วนตัว defect ที่มีผลกระทบในวงกว้างนั้นไม่จำเป็นจะต้องเป็น defect ที่ซับซ้อนอะไร มักจะเกิดจากความประมาทของผู้เขียนโค๊ด และแก้ไขมันได้ไม่ยากนัก (เช่น ผมเจอ defect ที่เกิดจากการใช้ == แทน equals() ในภาษา java ค่อนข้างบ่อย) แต่เนื่องจากมันมีผลกระทบเยอะคนก็เลยกลัวการเทสต์กัน และสุดท้ายก็เลือกที่จะใช้วิธีแก้ไขเฉพาะหน้าไป

และที่จริง defect ในระดับโครงสร้างนั้นก็ไม่จำเป็นที่จะมีผลกระทบในวงกว้าง แค่ว่า defect มันเกิดในระดับนี้แล้วจะไม่แก้ไขที่ระดับนี้เพราะกลัวว่าจะเกิดผลกระทบมากนั้นเป็นความคิดที่ไม่ถูกต้องเสียทีเดียว

อยากจะขอแนะนำสักนิดหน่อย เกี่ยวกับการตัดสินใจว่าจะแก้ไขปัญหาในระดับไหน

1. ถ้าเป็นโค๊ดที่ยังไม่มีผลกระทบมากนักในปัจจุบัน แต่อาจจะมีแนวโน้มว่าจะมีในอนาคต หรือเป็นโค๊ดที่อยู่ในส่วนของโครงสร้างของซอฟท์แวร์ ให้ทำการแก้ไขไปเลย แล้วทำ unit test ให้ดี

2. ถ้าโค๊ดนั้นมีผลกระทบในวงกว้าง และมีการถูกใช้งานอยู่มากแล้ว เราไม่สามารถที่จะแก้ไขในจุดที่เป็นต้นเหตุได้ แต่เราสามารถที่จะสร้างทาง bypass ได้ นั่นก็คือการสร้าง function หรือ class ใหม่ที่มี functionality เหมือนกับตัวเดิม แต่ทำงานได้ถูกต้องตาม business  แล้วในจุดที่เป็นต้นเหตุก็เอาโค๊ดตัวนี้ไปใช้ จากนั้นให้ประกาศว่าโค๊ดตัวเก่านั้นอยู่ในสภาวะ deprecated ก็คือ จะไม่มีการรองรับโค๊ดตัวนี้แล้ว ให้ใช้โค๊ดตัวใหม่แทน เมื่อเกิด defect ที่เกิดจากโค๊ดตัวนี้ คนที่ทำการแก้ไขสามารถใช้โค๊ดตัวใหม่ที่ทำมาเพื่อแก้ไขปัญหานี้ได้โดยที่ไม่ต้องเสียเวลามาแก้ไขเองอีก

ในภาษา Java สามารถใส่ Annotation @Deprecated เพื่อระบุว่า โค๊ดส่วนนี้นั้นไม่ควรถูกเรียกใช้งานอีกในอนาคตได้ครับ

ประเด็นสำคัญของการตัดสินใจว่าจะแก้ไขปัญหาอย่างไร ไม่ใช่อยู่ที่ว่าโค๊ดนั้นอยู่ในส่วนของโครงสร้างหรือเปล่า หรือเป็นโค๊ดที่ใช้ร่วมกันหรือเปล่า แต่อยู่ที่ว่าผลกระทบนั้นกว้างมากขนาดไหน ดังนั้นการวิเคราะห์ผลกระทบ (หรือ impact analysis) เป็นสิ่งที่สำคัญมาก ถ้าเกิดเราทำตัวเป็นกระต่ายตื่นตูมโดยไม่ดูว่าจริง ๆ แล้วผลกระทบมันมากน้อยเพียงไหน ปัญหาใหญ่จะตามมาในอนาคตซึ่งเราอาจจะควบคุมไม่ได้อีกเลย แล้วจะกลายเป็นว่ามันจะกลายเป็นปัญหาที่ค้างอยู่ในระบบและแก้ไขไม่ได้ ซึ่งจะเป็นต้นเหตุของค่าใช้จ่ายมหาศาลที่จะถูกนำมาใช้เพื่อหลบเลี่ยงปัญหานี้

ส่วนถามว่าทำไมเมื่อมีผลกระทบในวงที่ไม่กว้างนัก เราถึงควรจะแก้ไขปัญหาที่ต้นเหตุไปเลย ผมจะยกตัวอย่างปัญหาหนึ่งที่เคยเจอกับตัวเอง ก็คือ โปรดักท์ตัวหนึ่งของผมเป็น C++ และเป็นโปรดักท์ทางการเงิน มีโค๊ดบางส่วนที่เกี่ยวข้องกับการคำนวน และการคำนวนตรงนี้โปรแกรมเมอร์คนที่ทำคนแรกเลือกที่จะใช้ floating point ใน C++ ในภายหลังได้เจอปัญหาเรื่อง precision lost ทำให้การเทียบค่าโดยตรงด้วยการใช้ == กับค่าคงตัวนั้นใช้ไม่ได้ คนที่แก้ไขปัญหานี้เลือกที่จะใช้การเทียบค่าโดยการนำสองค่ามาลบกันแล้วดูว่ามีความแตกต่างมากกว่าช่วงที่รับได้หรือไม่ ในภายหลังมีการพัฒนาโปรดักท์ตัวนี้มากขึ้น มีการคำนวนในหน้าอื่น ๆ เพิ่มขึ้นมา ทุกคนเลือกที่จะใช้วิธีเดียวกันในการแก้ปัญหา แล้วก็พบว่ามันแก้ไขปัญได้ไม่ยั่งยืน ปัญหาเกิดจากว่า floating point นั้นมีปัญหามากกว่าแค่เรื่อง precision lost (มีเรื่องการแสดงผลที่คาดเดาไม่ได้อีกด้วย) เมื่อจะแก้ปัญหาที่ต้นตอ ก็คือการนำทศนิยมตัวอื่นมาใช้แทน floating point ก็สายไปเสียแล้วเพราะว่ามีอีกหลายสิบหน้าที่ยังใช้ floating point อยู่ ก็ได้แต่คอยใช้วิธี bypass ปัญหาทีละจุด ๆ ไป ซึ่งถ้าเกิดว่าเราแก้ไขปัญหานี้ตั้งแต่ขั้นต้นเราก็คงไม่เจอปัญหานี้ในภายหลัง

การทำ bypass ก็เหมือนกับการทำ bypass เส้นเลือดนั่นล่ะครับ ปัญหาจริง ๆ เกิดจากการที่เส้นเลือดตีบ ทำให้เลือดไหลเวียนได้น้อยลง ปัญหานี้เกิดจากการบริโภคไขมันมากเกินไป ถ้าเราเลือกที่จะลดการตีบตันโดยการออกกำลังกายตั้งแต่ต้นปัญหานี้ก็จะไม่เกิดขึ้นอีก แต่ถ้าทิ้งเอาไว้นานเกินไป จนถึงขั้นที่ต้องทำการผ่าตัดทำ bypass หลังจากที่ทำ bypass อีกสักพักการตีบตันมันก็จะเกิดขึ้นอีก แล้วก็ต้องทำ bypasss อีก ไปเรื่อย ๆ เหมือนแมวจับหนู แต่อย่างน้อยเราก็มีวิธี bypass ให้กับหมอคนอื่น ๆ แล้ว ไม่ต้องให้เขาค้นหาวิธีรักษาใหม่ ๆ อีก และในกรณีของโปรแกรมเมอร์อย่างเรา เราสามารถนำเอาท่อบายพาสมาใช้งานได้เลย

พูดโดยสรุปก็คือ เมื่อเราพบ defect ที่อยู่ในโค๊ดที่อยู่ในชุดใช้งานร่วมกัน หรืออยู่ในส่วนของโครงสร้าง ให้ลองวิเคราะห์ถึงผลกระทบก่อน อย่าไปแก้ไขสถานการณ์เฉพาะหน้า เมื่อพบว่าผลกระทบมีอยู่ไม่มากให้ทำการแก้ไขในจุดนั้นไปเลย แต่ถ้าผลกระทบมีมาก ให้เตรียมโค๊ดใหม่เป็นท่อ bypass แล้วแก้ไขโค๊ดที่เรียกใช้โค๊ดที่มีปัญหานั้นมาใช้ท่อ bypass แทน จะดีกว่า อย่าไปแก้ไขโดยการเขียนโค๊ดใหม่เฉพาะส่วน เพราะเมื่อเกิดปัญหาขึ้นอีกคนอื่นก็ต้องมาเสียเวลาหาวิธีแก้ใหม่ ซึ่งก็คงจะไม่ต่างจากอันที่เคยเกิดขึ้นก่อนหน้าเท่าไหร่นัก

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *

This site uses Akismet to reduce spam. Learn how your comment data is processed.