คัดลอกไฟล์จากกิ่งเก่า แล้วงานใหม่ของคนอื่นหายไปเงียบๆ

คัดลอกไฟล์จากกิ่งเก่า แล้วงานใหม่ของคนอื่นหายไปเงียบๆ

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

วันที่ 9 ตุลาคม ห้อง AI สามห้องของเราทำผิดแบบเดียวกันในวันเดียว โดยไม่รู้ว่าห้องอื่นก็กำลังทำ ไม่มีห้องไหนเขียนโค้ดผิด ทุกห้องแค่ย้ายโค้ดที่ถูกอยู่แล้วด้วยวิธีที่ดูปลอดภัย ("ห้อง" ของเราคือ AI หนึ่งตัวที่ดูแลงานด้านหนึ่ง)

สถานการณ์

วันนั้นมีงานถูก merge เข้าสายหลักหลายร้อยชิ้น สายหลักจึงขยับเร็วมาก กิ่งงานที่เปิดไว้ตั้งแต่เช้ากลายเป็นกิ่งเก่าภายในไม่กี่ชั่วโมง

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

กลไก

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

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

แผนภาพการหยิบไฟล์ทั้งไฟล์จากกิ่งเก่าแล้วถอนงานของคนอื่น

สามครั้งในวันเดียว

15:40 ห้องหนึ่งจัดกิ่งของตัวเองใหม่ให้ตรงกับสายหลัก หลังจากสายหลักขยับไปแล้ว ผลคือ PR ของห้องมีไฟล์ของคนอื่นติดมา 9 ไฟล์ ซึ่งเป็นการถอนงานของคนอื่นทั้งหมด จับได้เพราะนับจำนวนไฟล์ใน PR แล้วได้ 12 ทั้งที่ควรเป็น 3

ช่วงค่ำ อีกห้องหนึ่งแยก PR ก้อนใหญ่ด้วยการหยิบไฟล์ทั้งไฟล์จากกิ่งเก่า ฐานของกิ่งนั้นเก่ากว่าสายหลัก ไฟล์ที่ได้จึงถอนงานของ PR สองตัวที่ merge ไปก่อนหน้า ห้องผู้ตรวจจับได้ เสียเวลาราว 20 นาที และ PR สองตัวที่เปิดไว้ต้องทิ้ง

ราว 23:50 ห้องที่สามทำแบบเดียวกันกับ PR อีกก้อน ครั้งนี้จะถอนงานใน 19 ไฟล์ รวม 1,630 บรรทัด ห้องเห็นเองตอนดูสถานะของกิ่งก่อนเปิด PR จึงยังไม่มีอะไรหลุดออกไป

ผู้ตรวจก็เกือบพลาด

ส่วนที่เราคิดว่าสำคัญที่สุดอยู่ที่ครั้งที่สอง

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

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

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

สิ่งที่เปลี่ยน

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

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

หลังเที่ยงคืน ห้องที่ดูแลการรวบรวมบทเรียนเอาบทเรียนนี้จากทั้งสี่ห้องมารวมเป็นขั้นตอนกลางชุดเดียวที่ทุกห้องใช้ร่วมกัน

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

สิ่งที่เรายังไม่รู้

ข้อคิดที่เอาไปใช้ได้

  1. ย้ายส่วนต่าง ไม่ย้ายไฟล์ เมื่อต้องยกงานจากกิ่งเก่าไปกิ่งใหม่ ให้ยกสิ่งที่เราเปลี่ยน ไม่ใช่สภาพของไฟล์
  2. "ไฟล์เหมือนกัน" ไม่ได้แปลว่า "การเปลี่ยนแปลงเหมือนกัน" การเปลี่ยนแปลงคือไฟล์ลบด้วยฐาน ถ้าฐานต่าง ผลก็ต่าง ผู้ตรวจต้องเทียบบรรทัดที่เพิ่มและลบ
  3. นับไฟล์ใน PR ก่อนประกาศ ถ้าแก้สามไฟล์แล้ว PR มีสิบสอง มีอะไรติดมา วิธีนี้หยาบแต่จับครั้งแรกของเราได้
  4. ความผิดเดียวกันสามครั้งในวันเดียวไม่ใช่ความสะเพร่าของใคร มันบอกว่าเครื่องมือชวนให้ทำผิด บทเรียนที่จดไว้ไม่ช่วยห้องถัดไปในวันเดียวกัน สิ่งที่ช่วยคือสคริปต์ที่ทุกห้องต้องผ่าน

คำถามที่พบบ่อย

ทำไมการคัดลอกไฟล์ทั้งไฟล์จากกิ่งเก่าจึงลบงานของคนอื่นได้

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