โค้ดที่เราไม่ได้เขียน เขียนขึ้นมาทำไมนะ
วันก่อนครับ เพื่อนร่วมงานผมบอกว่า PR ผมอ่านยากชิบหาย
วันก่อนครับ เพื่อนร่วมงานผมบอกว่า PR ผมอ่านยากชิบหาย
แม่งจริง เพราะว่าฟีเจอร์ที่ผมเปิด PR ไปนั้น ถ้าเป็นสมัยก่อนผมน่าจะใช้เวลาทั้งอาทิตย์แต่รอบนี้คือผมใช้เวลาแค่บ่ายวันเดียวกับ Agent ก็เสร็จ ผมยัดทุกอย่างลง PR ด้วยความภาคภูมิใจกับความสามารถในการคุม Agent ของผมทั้ง Spec ทั้ง Implementation เป็นร้อยๆ บรรทัด
พอส่งให้รีวิว เพื่อนร่วมงานผมบอกว่า มันลำบากมากในการทำความเข้าใจโค้ด หรือไล่โค้ดอย่างเป็นระบบ เพราะว่ามันจะมี Spec หรือคำอธิบายที่ Agent เติมแต่งขึ้นมานอกจากโค้ดมาทำให้ไขว้เขว มาขัด Flow ในการทำความเข้าใจอยู่เรื่อยๆ
สิ่งที่พังจริงๆ
หลังจากผ่านมาซักพักใหญ่ ผมว่าผมเริ่มเข้าใจละว่ามันเกิดอะไรขึ้น และผมสามารถพูดได้เต็มปากว่า
ต้นทุนในการผลิตโค้ดมันลดลงจนแทบไม่เหลือ แต่ต้นทุนในการทำความเข้าใจมันไม่ได้ถูกลงเลย
ทุกๆ Practice ที่เราถูกสอนกันมา ถูกสร้างมาสำหรับโลกที่ “การเขียนโปรแกรม” คือส่วนที่แพงที่สุด ถูกสร้างจากสมมติฐานว่าโค้ดเกิดขึ้นได้เพราะผ่านการคิดมาแล้ว ในอดีตการคิดนั้นถูกส่งต่อผ่าน Code Review ที่ซึ่งคนในทีมจะได้ถ่ายทอดการเรียนรู้ให้กัน แต่พอปริมาณโค้ดที่ผลิตมีมากขึ้นเกินกว่าที่คนๆ หนึ่งจะรับไหว สิ่งนั้นแหละที่ทำให้เกิดช่องว่างทางการเรียนรู้ และมันขยายใหญ่ขึ้นมากเรื่อยๆ
สิ่งที่ผมลองทำ
พอผมรู้ตัวว่า PR ผมมันปนกันระหว่าง Spec กับ Implementation การทดลองแรกผมเลยลองแยก Spec ออกมาเป็น PR หนึ่ง ตามด้วย Implementation ออกมาเป็นอีก PR หนึ่งที่ Stack อยู่บน PR แรกอีกที ถ้าเพื่อนร่วมงานผมไม่สนว่า ผมสั่ง Agent ผ่าน Spec ยังไงบ้าง เค้าก็แค่ Skip PR แรกไปดูโค้ดเองได้เลย
ซึ่งเหมือนจะเวิร์ค แต่ก็สร้างปัญหาใหม่ตามมาทันทีเพราะ Stack PRs ต้อง Merge ตามลำดับ ทำให้ผมต้องจัดการ Dependency ในการ Merge เพิ่มมาอีกต่อหนึ่งด้วย และก็ต้องอธิบายเพิ่มด้วยว่า PR อันนี้ต้องเข้าก่อนอันนี้นะ แทนที่จะแค่โฟกัสกับสิ่งที่สำคัญที่สุดจริงๆ ซึ่งก็คือ Implementation นั้นแหละ
แต่ในความซับซ้อน มันก็มีข้อดีอยู่นะ เพราะหลังจากทำอย่างนี้ได้ซักพัก ผมก็เริ่มรู้ตัวว่าสิ่งที่ควรแก้จริงๆ ไม่ใช่ Workflow ในการ Merge แต่คือความเข้าใจว่าเรามี Spec เอาไว้ทำไม
ผมใช้ Spec เพื่อที่จะสร้าง Implementation เวอร์ชั่นแรก แล้วหลังจากนั้นค่อย Refactor ตามมา ตัว Spec จะถูกใช้เป็น Reference ของ Logic แต่จะไม่เคยถูกอัพเดทตามมาเลย พอถึงจุดที่ผม Refactor จนพอใจแล้ว ถึงแม้ Business Logic (What) จะยังเหมือนเดิมอยู่แต่วิธีการที่ Implement (How) มันต่างจากที่อธิบายไว้ใน Spec ตอนแรกไปแล้วครับ
งั้นก็อัพเดท Spec หลัง Refactor ด้วยสิ ผมลองละครับและมันไม่เวิร์ค เพราะ Spec ถูกเขียนออกมา “ก่อนที่เราจะรู้อะไร หลายๆ อย่าง”
เราไม่มีทางที่จะออกแบบ Software ได้อย่างสมบูรณ์แบบก่อนที่จะ Implement ไม่ว่าเราจะพยายามมาแค่ไหนก็ตาม เพราะว่าความรู้บางอย่างจะเกิดขึ้นได้ในระหว่างที่เรากำลังสร้างมันออกมาเท่านั้น หลายครั้งที่เรากำลังเขียนโปรแกรมอยู่ก็นึกวิธีการที่ดีกว่าได้ออกและ Agent ไม่สามารถทำสิ่งนี้ได้ ไม่ใช่เพราะว่ามันจำ Context ไม่ได้เก่ง แต่เพราะมันไม่ได้เห็นภาพใหญ่ของงานที่คุณกำลังทำอยู่ และ “คุณไม่เคยบอกมัน เพราะคุณไม่รู้ตัวด้วยซ้ำว่าต้องบอก”
เพราะฉะนั้นเนี่ยในมุมมองผม Spec มันได้ทำงานตอบโจทย์ของมันแล้วตั้งแต่เราสั่งมัน Implement จนเสร็จเป็นเวอร์ชั่นแรก เวอร์ชั่นที่เราจะได้เล่น ได้แกะ ได้แงะดูว่าเป็นอย่างที่เราต้องการมั้ย และทุกอย่างที่เราเรียนรู้หลังจากจุดๆ นี้มันเกิดขึ้นกับเรา และการที่เราต้องกลับไปถ่ายทอดความรู้นี้กลับไปที่ Spec ทำให้เราต้องดูแล knowledge ถึง 2 ที่ซึ่งก็จะผิดทันที ถ้าเรา Refactor ต่อไป
พอถึงจุดนี้เนี่ยผมโยน Spec ทิ้งละครับ สิ่งที่เหลืออยู่คือ Business logic ที่ถูก Implement ลงไปอยู่ในโค้ดเรียบร้อยแล้วและได้ถูก Refactor มาถึงจุดที่ผมสามารถอธิบายทุกส่วนของมันได้
ผมไม่ได้บอกว่าวิธีนี้ดีที่สุดนะ แต่อย่างน้อยโค้ดมันก็เล็กลง ถึงจุดที่ผมสามารถรับผิดชอบมันได้และไม่ต้องให้ใครไปอ่าน Spec ถึงจะเข้าใจมันครับ
แต่ถ้าสังเกตุดีๆ สุดท้าย ผมก็ไม่ได้แก้ปัญหาคอขวดของ Code Review นะ แต่สิ่งที่ผมทำจริงๆ คือ ทำงานหนักขึ้นก่อนจะส่งโค้ดให้เพื่อนร่วมงาน Review เพื่อที่จะมั่นใจได้ว่า โค้ดที่ต้อง Review จะถูกจัดสรรให้มีขนาดที่เล็กพอที่คนๆ หนึ่งจะเข้าใจได้โดยที่ไม่ใช้พลังเยอะไป
นั่นแหละครับคือสิ่งที่ผมอยากจะสื่อ เราต้องคิดถึงคนที่ดูแลโค้ดด้วยกันด้วย ถ้าเราไม่ทำมันก่อนที่จะเปิด PR มันคือการที่เราบังคับให้เพื่อนร่วมงานทำสิ่งนั้นแทนเรากับโค้ดที่เค้าไม่คุ้นเคยด้วยซ้ำ
อีกครึ่งหนึ่งของปัญหา
ในอีกสถานการณ์หนึ่ง ผมกำลัง Pair กับเพื่อนร่วมงานผมอยู่กับฟีเจอร์ที่เค้าสร้างด้วย Agent ตัวฟีเจอร์ถูกสร้างมาอย่างละเอียดครอบคลุมทุก edge case ที่ควรจะเป็นไม่มีอะไรขาด
ผมเลยเริ่มถามคำถาม
- อันนี้เราต้องการจริง ๆ เหรอ?
- เคสนี้มันจะเกิดขึ้นจริงไหม?
- ทำให้ง่ายกว่านี้ได้ไหม?
- ใช้ X แทน Y ได้หรือเปล่า?
สังเกตุว่ามันไม่ใช่คำถามที่ยากเลย แต่พอได้ดูโค้ดที่เกิดขึ้น ผมคิดอะไรขึ้นได้อย่างนึงกับวิธีการที่เราอ่านโค้ดกันในตอนนี้
ทุกครั้งที่มีโค้ดถูกผลิตมากขึ้นเกินกว่าจุดที่เราสามารถจะทำความเข้าใจได้ เราจะหยุด Evaluate มันและจะเริ่มปรับโหมดสมองเป็น accept / reject ตัว Agent อาจจะมาปิด gap ของ requirements ได้ทุกๆ branch condition ดูก็รู้ว่าถูกต้อง ในใจเราก็คิดว่า เยี่ยม ดีครับผม เหมาะสมครับ Agent ไม่มีการหยุดคิดเพราะว่าตอนนี้ Input ที่เราต้องตัดสินใจมันมากกว่าเกินกว่าที่สมองเราจะรับได้แล้ว
มันเลยกลับมาที่หลักการง่ายๆ 2 อย่างที่ผมยึดเป็นหลักเวลาทำงานกับโค้ดที่ถูก Agent สร้างขึ้นมา
- เขียนขึ้นมาทำไมนะ ยึดกับสิ่งที่เรารู้เป็นหลัก หลายครั้งที่เราไม่จำเป็นต้องเขียน Edge case ให้ครอบคลุมขนาดนั้นก็ได้ ปล่อยให้มันระเบิดไปแล้วค่อยแก้ถ้ามันเกิดขึ้นจริง สำคัญคือ เราไม่จำเป็นต้องทำถูกตั้งแต่ครั้งแรก แต่พูดอย่างงี้ก็สุ่มเสี่ยง เอาเป็นว่า ถ้าไม่ใช่ระบบที่สำคัญอย่างการเงิน หรือข้อมูลอาจจะกู้กลับมาไม่ได้ ปล่อยให้มันแตกเลยครับ
- เขียนให้ง่ายกว่านี้ได้มั้ย การเขียนโค้ดให้มันน้อยลง แปลว่าสิ่งที่เราต้องทำความเข้าใจก็น้อยลงไปด้วย และทำให้เรามีพื้นที่ในสมองเพิ่มมากขึ้นที่จะคิดไปไกลกว่า ของที่เราเห็นอยู่ตรงหน้า หลายครั้งที่เราบอกไม่ได้หรอกว่า Abstraction ที่เราเขียนมามันดีหรือยัง แต่ถ้าเราสั่งให้มันลองทำให้สั้นลง ง่ายลงซักร้อยครั้ง คุณก็จะเริ่มรู้ตัวเองว่าชอบโค้ดแบบไหน
บ่ายวันนั้นเราน่าจะตัดโค้ดกันไปเยอะระดับหนึ่ง และพอถึงจุดที่พอใจแล้วทั้งผมกับเพื่อนร่วมงานก็เข้าใจทั้งตัวฟีเจอร์และเหตุผลว่าทำไมต้องเขียนแบบนั้นกันทั้งคู่
แต่ว่า Model มันเก่งขึ้นทุกวันนะ
ผมยอมรับ เพราะมันก็เก่งขึ้นทุกวันจริงๆ วันนึงโค้ดที่มันผลิตออกมาก็จะดีขึ้น แล้วบล็อกนี้ก็จะไม่ต่างอะไรกับคน gen y กำลังบ่นคน gen z แต่เอาจริงๆ มันก็ถูกครึ่งนึง หลายเรื่องที่เราต้องคิดเยอะๆ เวลาเขียนโค้ดเดี๋ยวนี้เราก็คิดน้อยลงแล้วอย่างพวก Syntax หรือ Semantic อันนี้ Agent มันเก่งจริง
และถึงแม้ Model มันจะเก่งขึ้นทุกปี แต่มันก็ไม่ได้ทำให้ ตัวเรา เก่งขึ้นตามมันไปด้วย มันอาจจะผลิตโค้ดได้เก่งมากกว่าเรา แต่อย่าลืมว่ามันก็หยุดทำงานได้เหมือนกันหรือใกล้ตัวหน่อยคือ token หมด พอถึงจุดที่เราต้องอยู่กับตัวเองคำถามคือ เรายังทำงานได้อยู่มั้ย
นั่นแหละครับคือราคาที่เราต้องจ่ายจริงๆ ถ้ายังอยู่กับ Agent เพราะฉะนั้นถามตัวเองดังๆ ตลอดครับว่าเรายังอยู่ได้โดยไม่มีมันมั้ย
สรุป
ตอนที่ผมยังเป็น Junior อยู่ Lead ผมเคยสอนไว้ว่า แม้แต่ ช่องว่างระหว่างบรรทัดก็สามารถเล่าเรื่องราวได้ เพราะว่าสิ่งที่เราเขียน เราเขียนเพื่อให้คนอื่นมาอ่านต่อ แล้วพอมามองในยุคนี้ คำสอนนี้ยิ่งสำคัญมากขึ้นไปอีก เพราะว่าคนอื่นนั้น ไม่ใช่แค่เพื่อนร่วมงาน แต่อาจจะเป็นเราในอีก 6 เดือนข้างหน้า หรือแม้แต่ Agent ที่เราสั่งเพื่อที่จะให้มันแก้บางอย่าง
สุดท้ายแล้ว Spec จะ Obsolete ตัว Context จะเต็ม Session หมดอายุ แต่อะไรก็ตามที่เราเรียนรู้ระหว่างที่ปรับโค้ดจะอยู่กับเราตลอดไปและจะถูกส่งต่อไปยังคนที่มาอ่านต่อถ้าเค้าถามเกี่ยวกับโค้ดนั้น
โค้ดที่ถูกสร้างด้วย Agent จะยังอยู่ต่อไปครับ แต่เราสามารถอยู่ร่วมกับมันได้ถามตัวเองดังๆ เขียนมันขึ้นมาทำไมนะ หรือ เขียนให้มันง่ายกว่านี้ได้มั้ย เป็นคนที่คอยคุมมันแทนที่จะให้มันคุมเรา เพราะมนุษย์เราไม่สมบูรณ์แบบครับ เรามีข้อจำกัด เราทำพลาด แต่นั่นก็ทำให้เราได้เรียนรู้ด้วย และสิ่งที่เราเรียนรู้นี่แหละที่ไปได้ไกลเกินกว่า Token limit และส่งต่อไปกับคนอื่นได้ด้วย สวัสดีครับ