06 August 2017

เขียนโปรแกรมให้เป็น คิดกันอย่างไร? แก้ปัญหากันอย่างไร? [ตอนที่ 4]

Updated on

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

เปิดประเด็น

        "ฟีเจอร์นี้เขียนมาตั้งสัปดาห์นึงแล้ว ยังไม่เสร็จเลยครับ"

        "บั๊กนี้หนูนั่งแก้มาสองวันแล้ว ยังแก้ไม่ได้เลยค่ะ"

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

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

        "โห ผมนั่งงมมาเป็นสัปดาห์ แต่พี่แก้ไขได้ในไม่กี่วัน/กี่ชั่วโมง"

        ซึ่งเหตุการณ์แบบนี้ทำให้เจ้าของบล็อกรู้สึกว่ามันกลายเป็นหนึ่งปัญหายอดนิยมที่นักพัฒนาหลายๆคนประสบปัญหาอยู่แฮะ

        ...ระยะเวลายอดฮิตในการเสียเวลาไปกับปัญหาเหล่านี้คือ 2 วัน หรือไม่ก็ 1 สัปดาห์ ซึ่งเจ้าของบล็อกก็ไม่รู้เหมือนกันว่าเพราะอะไร...

เมื่อนักพัฒนาใช้เวลาแก้ปัญหาใดๆนานเกินไป

        ในการทำงานเป็นนักพัฒนาของเจ้าของบล็อกนั้น ระยะเวลาที่ใช้ในการทำงานนั้นเป็นสิ่งที่สำคัญมาก การใช้เวลาไปกับสิ่งใดก็ตามนานเกินที่ควรจะเป็นจึงถือว่าเป็นความผิดพลาดที่ส่งผลกระทบต่อการทำงานต่อไปเป็นอย่างมาก (ถึงแม้ว่าบางคนมองว่ามันเป็นเรื่องปกติก็ตาม)

         ยกตัวอย่างเช่น ใน 1 Sprint ของเจ้าของบล็อกมีสิ่งที่ต้องทำอยู่ 3 ฟีเจอร์


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


        กลายเป็นว่าใน Sprint นั้นทำเสร็จแค่ฟีเจอร์เดียว ฟีเจอร์ถัดไปก็ต้องทำต่อใน Sprint หน้า แถมฟีเจอร์ต่างๆที่รออยู่ใน Sprint ถัดไปก็ต้องเลื่อนตามๆกันไป และถ้าเป็นงานที่ต้องทำต่อเนื่อง มีการปล่อยฟีเจอร์ในทุกๆ Sprint ก็คงไม่แฮปปี้ซักเท่าไรถ้าแผนที่วางไว้ทั้งหมดเลื่อน เพียงเพราะว่ามีฟีเจอร์หนึ่งที่ใช้เวลาทำนานกว่าที่คาดไว้

        แล้วจะแก้ปัญหานี้ยังไงดีล่ะ?

เพราะอะไรถึงใช้เวลานานเกินไป?

        • ไม่เข้าใจในสิ่งที่ทำ
        • ไม่รู้วิธีที่ถูกต้อง
        • ไม่เข้าใจว่าปัญหาเกิดขึ้นเพราะอะไร

        ซึ่งสาเหตุเหล่านี้เป็นเรื่องปกติที่เกิดขึ้น เพราะเป็นเรื่องธรรมดาที่นักพัฒนาคนหนึ่งต้องเจอกับภารกิจที่ไม่อาจจะคาดเดาได้

        ถ้าถามว่าจะแก้ไขได้ยังไงก็คงตอบได้ไม่ยากว่า "ต้องเรียนรู้ให้พร้อมอยู่เสมอ" แต่ในความเป็นจริงมันก็ไม่ได้สวยหรูขนาดนั้นน่ะสิ เพราะส่วนมากงานที่ได้รับมอบหมายนั้นมักจะเป็นสิ่งที่ไม่สามารถคาดเดาได้ ยกตัวอย่างเช่น เจ้าของบล็อกเป็นนักพัฒนาในบริษัท Software House ที่รับงานจากลูกค้า ดังนั้นเจ้าของบล็อกจะคาดเดาไม่ได้เลยว่างานที่ลูกค้าต้องการนั้นจะมาในรูปแบบไหน

        จากที่เจ้าของบล็อกได้พบเจอ ปัญหาเกิดขึ้นเพราะการเอาโค้ดจากที่ใดก็ตาม (ไม่ว่าจะหาเจอด้วย Google หรือที่มีคนตอบไว้ใน StackOverflow) ไปใช้งานโดยที่ไม่ได้เข้าใจในโค้ดนั้นๆจริงๆ

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

        พอมารู้ตัวอีกทีก็เสียเวลาไปหลายวันแล้วนั่นเอง...

        แล้วต้องทำอย่างไรล่ะ?

มาจำลองสถานการณ์กันเถอะ

ภารกิจฟีเจอร์ 101

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

ฟีเจอร์นั้นๆจะต้องมีโค้ดอะไรบ้าง

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

วิเคราะห์ Use Case ที่เป็นไปได้ทั้งหมด

        อันนี้ค่อนข้างสำคัญ ถึงแม้ว่าจะได้ Requirement และฟังบรีฟมาแล้วก็ตาม แต่ในความเป็นจริงมักจะพบว่า Use Case ที่ได้รับมามันไม่ครอบคลุมซักเท่าไรนัก (เป็นเรื่องปกติ) ดังนั้นเจ้าของบล็อกจึงต้องมานั่งคิดก่อนว่ามี Use Case อะไรบ้างที่สามารถเป็นไปในการทำงานนั้นๆ

หาข้อมูล หาโค้ด หรือหาตัวอย่างการทำงานที่จะเอามาใช้ในฟีเจอร์

        อ้าว งี้มันก็เหมือนเดิมนี่นา?

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

        แต่หัวใจสำคัญคือขั้นตอนต่อไปครับ

อย่ายึดติดกับวิธีคิดเดิมๆถ้ามันไม่สามารถแก้ปัญหาได้

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

        ดังนั้นถ้ารู้สึกว่าติดปัญหาที่ทำให้ไปต่อไม่ได้ และใช้เวลานานเกินไป ให้ล้มเลิกความคิดนั้นทันทีแล้วมองหาวิธีอื่นซะ จะได้ไม่เสียเวลา

        ทั้งนี้ทั้งนั้นวิธีคิดนั้นจะต้องถูกพิสูจน์ก่อนว่าเวิร์กหรือไม่ด้วยการทำ Proof of Concept

ทำ Proof of Concept (POC) ทุกครั้งเพื่อดูความเป็นไปได้

        นี่คือขั้นตอนสำคัญที่นักพัฒนาหลายๆคนมองข้ามไปและไม่ได้ทำเลย ทั้งๆที่การทำ Proof of Concept นั้นจะช่วยให้นักพัฒนามั่นใจได้ว่าสิ่งที่จะเอามาใช้งานนั้นตอบโจทย์จริงๆหรือไม่ โดยที่ใช้เวลาไม่นานมาก

         เมื่อไม่ได้ทำ Proof of Concept สิ่งที่เกิดขึ้นก็จะเป็นแบบนี้


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

         แล้วก็มาเจอทีหลังว่า "มันไม่เวิร์กแฮะ..."

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

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

สร้างโปรเจคแบบโง่ๆเพื่อทดสอบ

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

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

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

        • เรียกใช้งานใน Layout XML โดยตรง
        • เรียกใช้งานโดย Inflate ผ่านโค้ด Java
        • เรียกใช้งานใน Fragment
        • เรียกใช้งานใน View Pager
        • เรียกใช้งานใน List View
        • เรียกใช้งานใน Recycler View
        • ทั้งหมดนี้จะต้องสามารถหมุนหน้าจอ แล้วยังทำงานได้ถูกต้อง

        ซึ่งทั้งหมดนี้คือเงื่อนไขที่เป็นไปได้ในการเรียกใช้งาน Custom View ดังนั้นถ้าโค้ดดังกล่าวทำงานได้ในทุกๆเงื่อนไข นั่นก็หมายความว่า Custom View ตัวนี้สามารถนำไปใช้งานได้แบบสบายหายห่วงนั่นเอง

โค้ดพร้อมแล้ว เอาไปใช้งานได้เลย

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

ภารกิจแก้บั๊ก 101

        นอกเหนือจากการพัฒนาฟีเจอร์บางอย่างแล้ว การแก้บั๊กก็เป็นหนี่งในปัญหาหลักของการใช้เวลานานเกินที่คาดไว้เช่นกัน เพราะบ่อยครั้งที่แก้บั๊กบางอย่างไป แล้วดันเกิดบั๊กอีกตัวขึ้นมา (นี่บั๊กหรือไฮร้า)

บั๊กเกิดขึ้นที่ไหน และเพราะอะไร

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

ทดสอบว่าบั๊กเกิดจากสิ่งที่คิดไว้จริงๆหรือไม่

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

        และสาเหตุในการเกิดบั๊กดังกล่าวก็เป็นเสมือน Use Case หนึ่งที่ถูกมองข้ามไป ดังนั้นจงอย่าลืมว่าโค้ดที่เกิดบั๊กนั้นมี Use Case เดิมคืออะไรบ้าง แล้วเพิ่มสาเหตุของบั๊กเข้าไปใน Use Case ดังกล่าวด้วย เพื่อทดสอบให้ครบทุก Use Case ในภายหลัง จะได้ไม่เผลอแก้บั๊กแล้วผุดบั๊กตัวอื่นขึ้นมาโดยไม่ได้ตั้งใจ

        สำหรับการหาสาเหตุก็เหมือนการหาเบาะแสหลักฐานน่ะแหละ ให้เริ่มจากสิ่งที่ใกล้ตัวบั๊กนั้นมากที่สุดก่อน แล้วค่อยไล่ทดสอบไปเรื่อยๆเพื่อหาว่าจริงๆแล้วเกิดจากตรงไหนกันแน่

         ยกตัวอย่างเช่น เจ้าของบล็อกเขียนแอปฯตัวหนึ่งที่มีการส่งข้อมูลไปที่ Server แล้วอยู่มาวันหนึ่งมีการแจ้งมาว่ามีบั๊กเกิดขึ้นที่ฝั่ง Server เพราะข้อมูลที่ได้รับนั้นไม่ถูกต้องตามที่กำหนดไว้

        ดังนั้นเจ้าของบล็อกจะต้องพิสูจน์ก่อนว่าบั๊กนั้นเกิดจาก Server เอาค่าไปทำอะไรแล้วผิดเองหรือฝั่งแอปฯส่งข้อมูลไปผิดตั้งแต่แรกกันแน่ เพื่อที่จะได้ไม่เสียเวลาไล่หาบั๊กผิดที่ ดังนั้นก็จะเริ่มจากเช็คก่อนเลยว่าฝั่ง Server ตอนรับค่าไปนั้นถูกต้องหรือไม่ จากนั้นก็เช็คต่อที่ฝั่งแอปฯในตอนที่ส่งค่าไปให้ Server นั้นมีค่าถูกต้องหรือไม่ จากนั้นก็จะไล่ไปเรื่อยๆตั้งแต่คำสั่งส่งค่าไปให้ Server, คำสั่งต่างๆที่มีผลต่อค่าดังกล่าว ไปจนถึงการ Input ข้อมูลจาก User ในตอนใช้งาน

        และที่สำคัญ ให้นึกถึงสาเหตุของบั๊กให้ที่เกิดขึ้นได้ให้ครบทุกกรณีเสมอ แล้วไล่ทดสอบให้หมดซะ ถึงแม้ว่าบางกรณีจะฟังดูไม่น่าจะเกิดขึ้นได้ก็ตาม เพราะเจ้าของบล็อกเคยมีความคิดประมาณว่า "มันปัญญาอ่อนมาก มันไม่น่าจะเกิดขึ้นหรอก" แต่ก็มาพบทีหลังว่าบั๊กนั้นเกิดขึ้นเพราะสาเหตุปัญญาอ่อนนั้นจริงๆ

แก้ไขโค้ดเพื่อแก้บั๊กซะ

        จะเขียนโค้ดเพื่อแก้ไขเองหรือจะค้นหาตาม Google ก็แล้วแต่เถอะ

ทำ Proof of Concept กับโค้ดที่มีการแก้ไขด้วย (ถ้าเป็นไปได้)

        เพื่อให้มั่นใจว่าการแก้ไขโค้ดของเจ้าของบล็อกนั้นถูกต้อง ไม่กระทบต่อการทำงานใน Use Case อื่นๆ (ที่มาของแก้บั๊กแล้วงอกเป็นบั๊กตัวอื่น)

แก้บั๊กเสร็จเรียบร้อย

        ปิดงานได้! กลับบ้านนอนกันเถอะ

สรุป

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

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

        ถ้าสังเกตขั้นตอนและวิธีคิดในบทความนี้ดีๆก็จะพบว่ามันสอดคล้องกับ "การเขียนโปรแกรมที่ดีนั้น ควรมีการเขียนเทสด้วย" เพราะการเขียนเทสก็เสมือน Proof of Concept ของโค้ดนั่นเอง

        เจ้าของบล็อกจะถือว่าเมื่อใดก็ตามที่คิดอะไรบางอย่างแล้วไปต่อไม่ได้หรือแก้ไขปัญหาไม่ได้ซักที และใช้เวลานานเกิน 6 ชั่วโมง ก็จะถือว่าวิธีนั้นใช้ไม่ได้ทันที (เพราะถ้าเจ้าของบล็อกมาถูกทาง มันไม่ควรจะเสียเวลานานขนาดนั้น) ก็จะล้มเลิกความคิดนั้นทันทีแล้วมองหาวิธีอื่นที่แตกต่างกันออกไป

         สุดท้ายนี้ ก็หวังว่าจะได้ยินคำพูดที่ว่า "นั่งแก้บั๊กมาสองวันแล้ว ยังแก้ไม่ได้เลย" ลดน้อยลงนะครับ