Git Branching Strategy จากประสบการณ์ของข้าพเจ้า

jo@sabotender
KBTG Life
Published in
14 min readJul 10, 2023
กิ่งก้านสาขาตามเวลาที่ผ่านไป (ภาพ: sabotender)

สวัสดีครับ วันนี้ผมมาเขียนเรียงความเฉพาะกิจที่ได้แรงบันดาลใจมาจากคำถามเกี่ยวกับเรื่อง Git Branching Strategy (แปลเป็นไทยก็คงประมาณกลยุทธ์ในการจัดการ Branches ของ Git) ที่ผมเองถูกถามหรือได้ยินบ่อย ๆ ในบริษัท เมื่อใดก็ตามที่คุณนึกอยากรู้เกี่ยวกับทฤษฎีของ Git Branching Strategy ว่ามีกี่แบบ แต่ละแบบมีหลักการอย่างไร ผมมั่นใจมากว่าคุณสามารถกระโจนเข้าสู่อินเทอร์เน็ตแล้วเลือกอ่าน เลือกดู และเลือกฟังได้เลย แต่พอถึงเวลานำไปใช้ ก็ยังมีคำถามเกิดขึ้นมากมาย บทความนี้จึงเป็นการแชร์ประสบการณ์ของผมเองภายใต้คอนเซ็ปต์ “สิ่งที่คุณควรรู้เกี่ยวกับ Git Branching Strategy เพื่อที่จะใช้มันให้สำเร็จ”

List of Contents

Prelude

มันมีคำหนึ่งที่ผมมักได้ยินคนระดับหัวหน้าทีมหรือผู้บริหารฝ่ายงานไอทีพูดอยู่บ่อย ๆ ถึงความสำคัญ หรือจะเรียกว่าบ่นอยู่เนือง ๆ เวลามีปัญหา นั่นก็คือคำว่า “Version Control” ผมรู้สึกว่าต้องมีสักครั้งในชีวิต Developer ของคุณ ที่คุณมีโอกาสเข้าไปอยู่ในวงไพบูลย์ของการโดนหัวหน้าบ่นเรื่อง Version Control หรือ Source Code Control ไม่ว่าจะเป็นการเอาโค้ดผิดเวอร์ชันไป Deploy บ้าง การรวมหรือ Merge โค้ดที่ผิดพลาด ทำให้บั๊กที่เคยแก้ไปแล้วย้อนกลับมาอีก หรือแม้แต่ทำโค้ดที่เพื่อนแก้นั้นหายวับไป บลาบลาบลา…

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

ย้อนไปเมื่อสิบกว่าปีก่อน ผมจำได้ลาง ๆ ถึงเครื่องมือที่มีชื่อประมาณว่า Visual SourceSafe ที่บังคับผมว่าถ้าผมอยากจะแก้ไขโค้ดในไฟล์ไหน ผมจะต้อง Check Out ไฟล์นั้นออกมาก่อน แล้วระหว่างที่ไฟล์นั้นถูก Checked Out ผมจะสามารถแก้ไขไฟล์นั้นได้คนเดียว แบบว่า VIP เลย เพื่อนฝูงจะไม่สามารถ Check Out ไฟล์นั้นได้ แล้วพอเราแก้ไขไฟล์เสร็จ เราก็กด Check In ไฟล์กลับไป คนอื่นก็จะทำการ Check Out ไฟล์นั้นได้ละ คอนเซ็ปต์แบบนี้คือการทำ Exclusive Lock เพื่อแก้ไข เหมือนเวลาเราเขียนโปรแกรมแล้วต้อง Lock ไฟล์หรือ Lock Data Record ก่อนที่จะทำการอัพเดตข้อมูล เพื่อไม่ให้เกิดการแย่งกันอัพเดตแล้วพังอย่างไงอย่างงั้น (เรียกเป็นศัพท์เทคนิคหน่อยก็ Mutual Exclusion) ถึงไม่ต้องบอก พวกเราก็รู้ได้เลยว่าวิธีนี้ทำให้ Developers ทำงานพร้อมกันได้ช้า เพราะถ้าเกิดต้องการแก้ไขไฟล์เดียวกันขึ้นมา มันจะต้องรอคนหนึ่งทำงานให้เสร็จก่อน อีกคนถึงจะทำงานต่อได้ ผมว่าแนวทางแบบนี้น่าจะสูญพันธุ์ไปแล้วในปัจจุบัน ไหมนะ? มองไปรอบตัวก็เห็นแต่ Git ทั้งนั้นเลย

สำหรับผม Git เป็น Collaboration Tool ที่ทำมากกว่า Version Control โดย Git สนับสนุนให้ Developers ทำงานร่วมกันได้อย่างคล่องตัวมากกว่าในอดีต ขณะที่ยังคงความปลอดภัยและมีความน่าเชื่อถือในระดับสูง ในปัจจุบันมีซอฟต์แวร์หรือแพลตฟอร์มหลากหลายยี่ห้อที่ช่วยให้ Developers ทำงานกับ Git ได้ง่ายและสะดวกขึ้น เช่น GitLab, GitHub, BitBucket ฯลฯ บางเจ้าอยู่บนพรม (On-Prem) บางเจ้าอยู่บนเมฆ (On-Cloud) แต่ทั้งหมดอยู่บนหลักการหรือแนวคิดเดียวกัน การทำงานกับ Git นั้นไม่ใช่แค่การ Push Codes หรือ Pull Codes เท่านั้น Developers จะต้องมีความเข้าใจในเรื่องของการ Branching ด้วย เป็นที่มาของคำว่า “Git Branching Strategy” นั่นเอง

Git Branching Strategy มีความสำคัญโดยตรงต่อการทำ Version Control บน Source Codes ที่ Developers ทำงานอยู่ และมีความสำคัญต่อเนื่องไปถึง Productivity ของทีม เราอาจจะเคยได้ยินชื่อ Git Branching Strategies ตัวดัง ๆ หรือที่เรียกว่าเป็น Best Practices เช่น Git Flow, GitHub Flow, GitLab Flow เป็นต้น

Best Practice สำหรับผมไม่ได้อยู่ที่ว่าเราใช้ Flow อะไร แต่อยู่ที่การทำอย่างไรให้ทุกคนในทีมเข้าใจ Flow เดียวกันอย่างแม่นยำ ปฏิบัติไปในแนวทางเดียวกัน เวลาพบปัญหาสามารถแก้ไขได้อย่างถูกต้องและฉับไว ตลอดจนสามารถคุยกันเพื่อปรับปรุง Flow เวลาที่ทีมทำงานติดขัดได้

ลองนึกภาพที่ Developer แต่ละคนต้องถามหัวหน้าหรือ Technical Lead ตลอดเวลาว่าเขาต้องสร้าง Branch ใหม่หรือไม่ ใช้ชื่อว่าอะไร โค้ดเสร็จแล้วต้อง Merge ไปที่ไหน จะให้ลบ Branch ทิ้งเลยมั้ย ใช้ Branch ไหนในการ Build ตอนนี้เจอ Conflict ให้ทำอย่างไร แล้ว Merge ผิดให้ทำอย่างไร ที่สำคัญเวลามี Critical Bug ที่ต้องแก้ด่วนเข้ามา หรือมี Bugs จาก Testers เข้ามาพร้อมกันให้ทำอย่างไร ถ้ามะรุมมะตุ้มอยู่อย่างนี้ การพัฒนาซอฟต์แวร์ของเราคงไม่คล่องตัวเท่าไหร่ ผมเองเคยพบเห็น Developer ที่ต้องหยุดงานพัฒนาของตัวเองเพื่อมานั่งแก้ไขปัญหาที่เกิดจากการใช้ Git อยู่เป็นวัน ๆ

Challenges

Image by macrovector on Freepik

ในกีฬาประเภททีม โค้ชจะต้องมีแผนการเล่น มีกฎกติกา ทุกคนต้องเข้าใจว่าแบบแผนและวิธีการเล่นเป็นอย่างไร แต่ละคนในทีมจะสื่อสารกัน จะเล่นร่วมกันอย่างไร สิ่งเหล่านี้จำเป็นต่อนักกีฬามืออาชีพอย่างไร Git Branching Strategy ก็จำเป็นต่อ Developers มืออาชีพอย่างนั้น

ย้อนวัยนึกถึงสมัยที่ผมเล่นกีฬา ตอนซ้อมอะไรก็ดูง่ายไปหมด พอลงสนามแข่งจริงนี่กลายเป็นหนังคนละเรื่อง มีแต่คนที่ซ้อมมาอย่างหนัก ฝึกฝนพื้นฐานมาอย่างแน่น ถึงจะเอาตัวรอดและส่งเสริมทีมให้ชนะได้ ตอนที่คุณอ่าน Git Branch Strategy ในอินเทอร์เน็ตแล้วคุณได้เลือกสักแบบเอามาใช้จริง คุณอาจจะรู้สึกเหมือนผมว่าตอนอ่านทฤษฎีมันก็เข้าใจง่ายนะ แต่เวลาเอามาใช้จริงกับทีมแล้วมันไม่ได้ราบรื่นสักเท่าไหร่

อุปสรรคหรือความท้าทายในเวลาที่ผมนำ Git Branching Strategy ไปใช้งาน

  • การสื่อสารให้ทุกคนเข้าใจถึงแบบแผนที่ตรงกัน และระหว่างที่ดำเนินการตามแบบแผนนั้น จะต้องมีการสื่อสารในกิจกรรมสำคัญที่จะเกิดขึ้นอย่างมีประสิทธิภาพ
  • สำหรับผม การที่มีฟีเจอร์ที่ต้องทำหลายฟีเจอร์เข้ามาพร้อม ๆ กันไม่ใช่ประเด็น แต่ประเด็นอยู่ที่แผนการ Release ซอฟต์แวร์ครับ เราต้องมีการจัดกลุ่มหรือชุดของฟีเจอร์หลายอันเข้าด้วยกัน เพื่อ Release ออกไปภายใต้เวอร์ชันเดียวกัน ในขณะที่แต่ละฟีเจอร์มีไทม์ไลน์ที่แตกต่างกัน ตั้งแต่เวลาที่เริ่มทำ ออกแบบ และทดสอบ บางฟีเจอร์พัฒนาเสร็จก่อนนานมาก แต่สุดท้ายต้องรอ Release พร้อมกับฟีเจอร์อื่น ๆ ในชุดของมัน
  • ถ้าเรามีช่วงเวลาการทดสอบนานมากกว่าจะ Release มันมีโอกาสมากที่ฟีเจอร์ของ Release ถัดไปจะพัฒนาเสร็จแล้วมีการขอ Merge เข้ามา เราต้องมีวิธีการจัดการกับฟีเจอร์ที่ไม่เกี่ยวข้องกับ Release ที่กำลังจะออก
  • ถ้าเรามีช่วงเวลาการทดสอบนานมากกว่าจะ Release ส่วนมากจะเป็นการทดสอบแบบ Manual Testing ยกตัวอย่างใน KBTG เราจะมีเฟส UAT และ Non-Functional Test (NFT) ที่มักจะใช้ระยะเวลานานและในบางครั้งแต่ละเฟสถูกรันพร้อมกันด้วย ซึ่งเราจะต้องมีวิธีบริหารจัดการกับ Bugs ที่เจอในแต่ละเฟส จนสุดท้าย Release ได้อย่างถูกต้องครบถ้วน
  • แม้ว่าใน Release หนึ่ง ๆ จะประกอบไปด้วยหลายฟีเจอร์ด้วยกัน แต่ผมพบว่าในบางทีมันจะมีความต้องการที่จะเทสแต่ละฟีเจอร์แยกกันก่อน แล้วสุดท้ายค่อยเอาทุกฟีเจอร์มารวมกันแล้วทดสอบอีกครั้งก่อนที่จะ Release
  • กับระบบงานที่ทำงานอยู่บน Production เรามีโอกาสพบ Bugs บน Production ที่ต้องรีบแก้ไขอย่างเร่งด่วน (Hot Fixes) ในขณะที่เรากำลังเตรียมที่จะขึ้น Release ถัดไป ทำให้เราต้องเปลี่ยนมาจัดการกับ Hot Fix ที่เข้ามาแทรกตัดหน้าแล้ว Deploy ไปก่อน เสร็จแล้วจึงกลับมาทำงานเดิมที่ค้างไว้ต่อ ซึ่งเราจะต้องไม่ลืมที่จะรวม Hot Fix ที่ Deployed ไปแล้วนี้เข้าไปใน Releases ที่ยังไม่ได้ Deploy ให้ถูกต้องด้วย
  • บางระบบไม่ค่อยได้ทำ Automated Test กัน หรืออีกนัยหนึ่ง เรายังพึ่งพา Manual Test กันอยู่มาก ซึ่งใช้เวลานานและใช้แรงคนจำนวนมากในการทดสอบ
  • เราไม่คุ้นเคยกับการทำ Feature Flags (บางที่เรียกว่า Feature Toggles) และบางทีมมีเหตุผลให้ไม่สามารถทำได้
  • Branching Strategy ต้องสัมพันธ์กับ DevOps Pipelines ซึ่งแตกต่างกันไปในแต่ละบริษัท ส่วนใน KBTG เราจะต้องคิดให้ดีว่าจะ Build Application จาก Branch ไหนไปใช้ที่ Environment ไหน ถ้าหาก Build จากหลาย Branches เราจะยังสามารถได้ Version ที่ถูกต้องหรือไม่
  • บางทีมมีความต้องการที่จะให้ Source Codes บน Git สะท้อนถึง Application Version ที่อยู่ในแต่ละ Environment เพราะมี Dev/Test Environment หลายชุดมาก ซึ่งรวมถึง Production Environment ด้วย
  • ฟ้าประทานคำสั่งที่ถือเป็นเด็ดขาดมา หลังจากที่เรา Merged Codes ไปแล้วว่าให้เอาฟีเจอร์นั้นออก หรือประทานคำสั่งปรับเปลี่ยนแผนงานให้เอา Release หน้ามาเป็น Release นี้ แล้วเอา Release นี้เลื่อนออกไปก่อน!

Classic Problems

Image by bedneyimages on Freepik

“รู้เขารู้เรา รบร้อยครั้ง ชนะร้อยครั้ง” สตีฟจ็อบส์ไม่ได้กล่าวไว้ แต่ผมว่าถ้าเราคาดการณ์ปัญหาก่อนล่วงหน้าได้ เราจะเป็นโดราเอมอน …เปล่า? เราจะสามารถผ่อนหนักให้เป็นเบา ผ่อนเบาให้หายไปได้ครับ เหมือนกับเรา “รู้โจทย์ก่อนเข้าห้องสอบ” ก็ไม่ปาน

Top 3 ของปัญหาในดวงใจเวลาผมเอา Git Branching Strategy ไปใช้มีดังต่อไปนี้

Misunderstanding / Miscommunication

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

Improper Handling of Code Conflicts

ถ้าคุณไม่ได้โค้ดอยู่คนเดียว Code Conflicts เป็นสิ่งที่คุณต้องได้เจอเข้าสักวันครับ รูปด้านล่างเป็นสัญลักษณ์คลาสสิกเวลา Git ต้องการให้คุณกระทำการที่เรียกว่า Resolve Conflicts (บาง IDE จะแสดงผลแตกต่างจากในรูปนะครับ) ซึ่งเกิดขึ้นจากการที่คุณและเพื่อนทำการแก้ไขโค้ดที่บรรทัดเดียวกันหรือบล็อกเดียวกัน หรือแก้ไขในตำแหน่งที่คาบเกี่ยวกัน แล้ว Git ไม่สามารถที่จะเลือกได้ว่าจะ Merge รวมกันอย่างไร

รูปภาพจากบทความ Merge Conflicts: What They Are and How to Deal with Them ของคุณ Tobias Günther

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

Inefficient Dealing with Unwanted Changes

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

แต่ What if…? อะไรก็เกิดขึ้นได้ โดยที่ไม่ต้องไปถึง Multiverse ไหน หากมีสาเหตุที่เราต้องเอา Changes ที่เรา Merged ไปแล้วออกล่ะ เราย้อนเวลายังไงดีนะ? ผมเองจะเริ่มขนลุกเพราะหวาดเสียวว่าการถอดเอา Changes มากมายหลายตำแหน่งออกไป มันจะทำได้ครบถ้วนหรือไม่ แล้วสุดท้ายจะมีอะไรพังหรือไม่ เพราะ Developer รู้ดีว่าตนไม่ได้แก้ไขงานคนเดียว งานก็ไม่ได้มีงานเดียว และถ้างานชุดนั้นยิ่งผ่านการ Resolve Code Conflicts มาด้วยแล้ว ๑!@#$!%#25+*฿7(#%@#$ … ไม่รู้จะอุทานเป็นภาษาอะไรดี

ถ้าเราเข้าใจการทำงานของ Git และมีโอกาสได้ทำซ้ำ ๆ มาจนชำนาญ ก็ไม่มีอะไรต้องกลัวให้มากมาย แต่เอาเข้าจริงนอกจากมันยากที่ทุกคนจะเข้าใจและทำได้ดีแล้ว สิ่งที่เราเสียแน่ ๆ ก็คือเสียเวลาครับ เราต้องใช้เวลากับการ Revert Changes แล้วเวลาเป็นสิ่งที่โปรเจคไม่ค่อยจะมีเสียด้วย ดังนั้นวิธีการที่มีประสิทธิภาพที่สุดในการจัดการกับความเปลี่ยนแปลงแบบนี้ คือการพยายามไม่ให้มันเปลี่ยนแปลงตั้งแต่แรกครับ นั่นคือการช่วยกันทำ Release Control การพัฒนาโดยใช้ Feature Flags และการทำ Automated Test อย่างที่ผมจะอธิบายในช่วงถัดไป

Success Factors

กิ่งก้านสาขาของ Red Hat Linux สมัย Y2K (ขอบคุณภาพจาก Wikipedia)

การบอกว่า Developers ต้องรับผิดชอบเรื่อง Source Code Version Control เพียงผู้เดียว ผมว่าออกจะโหดร้ายอยู่นิดหน่อยครับ ประสบการณ์สอนผมว่าสมาชิกของทีมที่ทำงานในบทบาทอื่น ๆ ก็มีส่วนช่วยให้ Version Control มีคุณภาพมากขึ้นได้ด้วยปัจจัยดังต่อไปนี้ครับ

  1. Release Control: ว่าด้วยการควบคุมฟีเจอร์ในแต่ละ Release ให้นิ่งมากที่สุด รวมไปถึงวันเวลาที่จะ Release ก็ควรต้องนิ่งด้วย การเปลี่ยนฟีเจอร์สลับไปมา เช่น ตอนแรกมีแผนที่จะปล่อย v1.0.0 ในเดือนมีนาคม โดยมีฟีเจอร์ A, B, C และจะปล่อย v1.1.0 ที่มีฟีเจอร์ D, E, F ต่อในเดือนเมษายน ทุกคนในทีมก็ต้องพยายามรักษาแผนแบบนี้ให้ได้มากที่สุด ถ้าทำ ๆ ไป จู่ ๆ เกิดมีความต้องการให้ v1.0.0 ปล่อยฟีเจอร์ A, B, E แทนจะเริ่มยุ่งละ ขึ้นกับว่ามาเปลี่ยนเอาตอนไหน หรือหนักสุดผมเคยเจอ เคสประมาณว่าจะเอาฟีเจอร์ทั้งยวงของ v1.1.0 สลับมาเป็น v1.0.0 และปล่อยออกไปก่อน แบบนี้งานงอกเป็นถั่วเลย สำหรับประเด็นนี้ PO หรือ Product Owner มีบทบาทมากครับ ผมเข้าใจว่าบางทีธุรกิจมันจำเป็นต้องเปลี่ยนแปลง แต่ยังไงก็ควรพยายามควมคุมฟีเจอร์และแผนของแต่ละ Release กันให้ดีที่สุดครับ Trade-off ร่วมกันให้ดี ยังไง Release ช้าหน่อย ก็ยังดีกว่า Release เร็วแล้วมีปัญหาครับ
  2. Release Notes: การทำ Release Notes ที่ดีและเป็นระบบจะมีส่วนช่วยในการทำ Release Control และการวางแผนงานของทีม แต่อันนี้แปลกที่ผมไม่ค่อยเห็นทีมที่พัฒนาซอฟต์แวร์ที่ใช้กันภายในบริษัททำสักเท่าไหร่ จะเห็นก็แต่ซอฟต์แวร์ที่ต้อง Release ออกไปภายนอกถึงจะมี น่าคิดเหมือนกัน
  3. Reduce Lead Time to Production: ผมหมายถึงช่วงเวลาตั้งแต่เราได้งานมาพัฒนาจนไปถึงเรา Deployed งานนั้นเสร็จสิ้นบน Production หากเราลดระยะเวลาลงได้ นั่นคือถ้าสามารถเข็นงานออกมา Deploy ให้เร็วขึ้นได้มากขึ้นเท่าไหร่ โอกาสที่จะเกิดการเปลี่ยนแปลงฟีเจอร์หรือเปลี่ยนแผนงานก็ลดน้อยลงเท่านั้น
  4. Apply Feature Flags (aka. Feature Toggle): คือการออกแบบและพัฒนาให้ระบบงานของเราสามารถเปิดและปิดการทำงานของฟีเจอร์ต่าง ๆ ได้โดยไม่ต้องแก้โค้ด ปัจจุบันมีเครื่องมือสำหรับทำ Feature Flags มากมาย หรือเราอาจจะออกแบบเองง่าย ๆ โดยใช้ Configuration File เก็บ Flags On/Off หรือ Yes/No แล้วเขียนโค้ดไปอ่านค่ามาใช้ควบคุมการทำงานของฟีเจอร์ก็ได้เหมือนกัน การใช้ Feature Flags จะช่วยให้เราไม่ต้องทำการ Revert Changes ซึ่งมีความเสี่ยงได้
  5. Strong Test Automation: หลายคนไม่เคยคิดว่าการ Test นั้นมีประโยชน์ในแง่ของการทำ Source Code Version Control ด้วย การรันเทสได้อย่างรวดเร็วและครอบคลุมในหลายระดับจะทำให้เรามีความมั่นใจในวันที่เราต้อง Resolve Code Conflicts ต้อง Revert Changes หรือต้องเล่นท่ายากอื่น ๆ นอกจากนี้การรันเทสได้อย่างรวดเร็วยังเป็นการลด Lead Time to Production ได้อย่างชะงัดนักแล
  6. Efficient Code Review: หากเราไม่สามารถทำทั้งหมดที่กล่าวข้างบนได้ ผมว่าอย่างน้อยเราต้องทำ Code Review เป็นคำตอบสุดท้าย แต่อยากจะเน้นนิดนึงว่า Efficient ไม่ได้แปลว่าต้องทำให้บ่อยเข้าว่าครับ ทำบ่อยแต่ไม่อร่อยสู้ทำน้อยครั้งแต่มีคุณภาพไม่ได้ครับ

Which Strategy do I Use?

หลังจากยุคของ SourceSafe ผมก็เปลี่ยนมาใช้ SVN (Subversion) อยู่พักนึง แล้วสุดท้ายก็มาลงเอยที่ Git ไม่รู้กี่ปีแล้ว แต่จนถึงตอนนี้ ผมมี Strategies ที่ใช้เป็นหลักอยู่แค่ 2 แบบ แบบที่ลองผิดลองถูกไม่นับนะครับ สองแบบนี้เพียงพอต่องานที่ผมทำ พอได้ปรับจนลงตัวแล้วไม่ค่อยมีปัญหา ทุกคนแฮปปี้ บางทีทำงานกับหลายระบบแล้วใช้ Strategies ต่างกันก็พาลทำให้สับสนได้

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

ปัจจัยหลักที่ผมใช้ในการเลือกว่าจะใช้ Strategy แบบไหนมี 2 อย่าง

  • ความสามารถของทีมในการ Control Release ของซอฟต์แวร์นั้น
    Release เป็นสิ่งที่ต้อง Control ให้ดีครับไม่ว่า Lead Time to Deployment ของเราจะน้อยแค่ไหนก็ตาม โดยสิ่งที่ต้องควบคุมให้ดีคือ (1) ควบคุมให้เกิดการคอนเฟิร์ม Release ในเวลาที่เหมาะสม คือให้การคอนเฟิร์มเกิดขึ้นเร็วเพียงพอที่จะเหลือเวลาให้ทีมได้วางแผนทำงาน ไม่ใช่ว่าทำงานผ่านไปเดือนนึงไม่คอนเฟิร์มสักที แต่มาคอนเฟิร์มเอาก่อนส่งสองวัน คอนเฟิร์มแบบนี้ให้โทษมากกว่าประโยชน์ครับ ทีมงานจะรู้สึกเหมือนต้องมานั่งลุ้นหวยที่พอถูกรางวัลแล้วไม่ดีใจเท่าไหร่ (2) ควบคุมฟีเจอร์ในแต่ละ Release และวันเวลาที่จะ Release ไม่ให้เปลี่ยนแปลงภายหลังจากที่คอนเฟิร์มกันเรียบร้อยแล้ว (อันนี้เป็นหนึ่งในเรื่องน่าเศร้าในชีวิตการทำงานอันแสนสนุกของ Developers ที่เรามักจะพบว่าถึงคอนเฟิร์มแล้วก็เปลี่ยนได้อยู่ดี ต่อให้จับ Sign-off สุดท้ายก็ยังเปลี่ยนได้ ยังไงเรามาพยายามกันให้ถึงที่สุดนะครับ)
  • ความสามารถของทีมในการบริหาร Workload ทั้งหมด
    ทีมจะทำงานหนักหรือไม่ ประกอบด้วยปัจจัยหลัก ๆ คือปริมาณงานที่เข้ามา ความเร่งด่วนของงาน จำนวนคนที่มี และทักษะของคนที่มี ผมจะลองถามตัวเองง่าย ๆ ว่าถ้ามีสักคนหนึ่งในทีมเจอ Code Conflicts หรือมีความจำเป็นที่จะต้องมานั่งปลีกวิเวกต่อจิ๊กซอว์เพื่อให้ได้ Source Code Version ที่ต้องการ โดยคนผู้นั้นต้องหยุดงานทั้งหมดที่เขาถืออยู่แล้วมานั่งทำ ซึ่งอาจจะใช้เวลาเป็นหลักชั่วโมง ทีมจะส่งงานทันหรือไม่ หรือจะต้องทำงานล่วงเวลากันมากแค่ไหน คำตอบที่ได้จะส่งผลในการเลือก Strategy และวิธีปฏิบัติครับ โดย Strategy แบบหนึ่งอาจจะมีขั้นตอนวิธีปฏิบัติเยอะหน่อย แต่จะช่วยลดโอกาสในการเกิด Code Conflicts และลดโอกาสที่เราจะต้องมา Revert Changes ซึ่งสิ่งเหล่านี้จะเกิดแบบกะทันหันและกระทบแผนงานเราทันที ส่วน Strategy อีกแบบหนึ่งอาจจะมีขั้นตอนน้อยกว่า แต่อาจจะเกิด Conflicts ได้บ่อยกว่า หรือแย่หน่อยก็ต้อง Revert Changes ซึ่งถ้าจะใช้แบบนี้ทีมก็ควรมีเวลาเพียงพอที่จะจัดการ

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

(#1) I Use Trunk-based Development

เปิดมาแบบแรกก็ออกจะย้อนแย้งหน่อย เพราะ Trunk-based Development มันไม่ได้สนับสนุนให้สร้าง Branches สักเท่าไหร่ แต่ผมเอามาเขียนไว้ในเรื่อง Git Branch Strategy ฮ่า ๆๆ

ผมใช้ Trunk-based Development กับระบบงานที่ทีมสามารถทำ Release Control ได้แม่นยำ พูดง่าย ๆ ว่างานที่ทำนั้นมี Roadmap ชัดเจนว่าจะออกฟีเจอร์ไหน เมื่อไหร่ เมื่อตกลงกันแล้วมีการเปลี่ยนแปลงน้อยถึงน้อยมาก และอีกด้านคือ Workload ของทีมงานต้องไม่หนาแน่นมาก ไม่ได้ถืองานด่วนจานร้อนไว้ตลอดเวลา หรือไม่มีซีเรียสบั๊กเข้ามาอย่างต่อเนื่อง พอจะมีเวลาพักหายใจหายคอบ้างไรบ้าง ไม่ได้ทำงานล่วงเวลาจนเป็นสภาวะปกติ

รูปแสดงคอนเซปต์ Trunk-based Development จาก Google Cloud

กฎกติกาเมื่อผมใช้ Trunk-based Development

  • คุณ Push Codes ไปที่ Trunk ได้โดยตรง แต่ถ้ามีปัญหาที่เกิดจากการที่คุณ Push Codes นั้น คุณต้องมาแก้ทันที
  • Push Codes ให้บ่อย บ่อยแค่ไหนนั้นไม่มีคำตอบตายตัว คุณควร Push Code ในระดับที่เป็น Minimum Workable Unit คือหน่วยที่เล็กที่สุดที่ Push เข้ามาแล้วทำงานได้และที่สำคัญเพื่อนคนอื่นอ่านเข้าใจ
  • ก่อน Push Codes ภาคบังคับคือให้รัน Unit Test ก่อน ส่วน Automated Test ในเลเวลอื่น ๆ แล้วแต่สะดวก
  • หลัง Push Codes เข้า Trunk ให้ DevOps Pipeline ทำงานทันทีโดยให้รันเทส ทำ Code Scanning ถ้าผ่านหมดก็ Build Deployment Package ต่อ ในกรณีที่มีปัญหาเกิดขึ้น คน Push Codes ต้องมาแก้ไขทันที
  • พวกเราจะไม่ทำ Code Review ทุกครั้งที่ Push Codes เพราะมันจะบ่อยเกินจำเป็น ให้เราเน้นการ Pair Programming ระหว่างการเขียนโค้ด แล้วไปจัด Code Review เมื่อ Feature หนึ่งเสร็จสมบูรณ์ หรือถ้าฟีเจอร์มีขนาดเล็กก็รวม ๆ กันแล้วค่อย Review Codes กันอย่างจริงจังครั้งหนึ่ง
  • เมื่อทีมได้พัฒนาฟีเจอร์ของ Release หนึ่งเสร็จทั้งหมดแล้ว และ Build-Test-Deploy ผ่าน ให้ทำการ Tag เป็น Release Candidate (RC) Version และจัดทำ Release Notes ให้เรียบร้อย
  • ข้อนี้สำคัญ ถ้าจำเป็นจะต้องมีโค้ดของฟีเจอร์อื่นที่ไม่อยู่ใน Release นี้ติดไปใน Trunk (ซึ่งเป็นเรื่องปกติเพราะบางคนอาจจะทำงานของ Release หน้าอยู่) ทีมจะต้องคุยกันก่อนว่าจะซ่อนฟีเจอร์เหล่านั้นอย่างไร (ตรงนี้มีหลายเทคนิคครับ แบบระบบหนึ่งที่ผมทำมี Web Front-end ก็จะซ่อนง่ายหน่อย แค่ไปซ่อนปุ่มหรือเมนูที่หน้าเว็บไม่ให้ Users กดได้ก็จบ — แต่ถ้าเป็นเว็บบนอินเตอร์เน็ตจะทำง่าย ๆ แบบนี้ไม่ได้นะครับ มันไม่ปลอดภัย ^^)
  • เมื่อมีบั๊กที่พบจากการทดสอบ ก็แก้ไขลงที่ Trunk โดยใช้หลักการเดียวกับฟีเจอร์ที่กล่าวมาครับ เน้นว่าถ้า Tag เป็น RC Version ใหม่ (Bug Fixed Version) เมื่อไหร่ อย่าลืมอัพเดต Release Notes ด้วย
  • กรณีบั๊กจาก Production ถ้าไม่ต้องแก้แบบเร่งด่วน ก็แก้เหมือนทำฟีเจอร์ปกติลงพร้อม Release ถัดไปได้เลย แต่ถ้าต้องแก้แบบเร่งด่วน (Hot Fixes) มีให้เลือก 2 ท่า ท่าแรกใช้เมื่อ Trunk สะอาด คือไม่มี Feature ที่ทำค้างไว้ หรือถ้ามีก็มีการทำ Feature Flags ปิดไว้แล้ว กรณีนี้สามารถแก้ลง Trunk แล้ว Build-Test-Deploy ได้เลย อย่าลืม Tag และอัพเดต Release Notes เหมือนเดิม
  • ในกรณีที่ต้องทำ Hot Fixes แต่ Trunk ไม่คลีน ให้สร้าง Branch จาก Tag ของ Production Version มาแก้บั๊กแล้ว Build-Test-Deploy จาก Branch นั้นไปเลย เพื่อจะได้ไม่ต้องกังวลกับฟีเจอร์อื่นที่ติดปนอยู่ใน Trunk เสร็จแล้วค่อย Merge Hot Fix Branch นั้นกลับเข้ามาที่ Trunk กับอัพเดต Release Notes ให้ดีเป็นอันปิดงาน

ถามต่อว่าเวลาใช้ Trunk-based Development แล้วสร้าง Feature Branch ได้ไหม สำหรับผมก็จะตอบว่าสร้างได้ครับไม่ผิด ผมก็มีทำบ้างกับงานที่สำคัญมาก ๆ และอยากจะทำ Code Review ให้เป็นกิจลักษณะ แต่คือนาน ๆ จะทำสักทีนึงครับ เพราะถ้าสร้าง Branches บ่อย ๆ มันก็ไม่เหมือน Trunk-based แล้วนะ

ถ้าสังเกตในรูปด้านบนที่ผมยืมของ Google Cloud มาแปะ จะเห็นว่ามีการทำ Release Branches ใน Trunk-based Development ด้วย รูปแบบนี้ใช้สำหรับซอฟต์แวร์ที่มีการ Releases ในลักษณะที่เป็น Long-Term Support (LTS) Version ที่แต่ละ Major Release มี Maintenance Roadmap ของตัวเอง ซึ่งไม่รู้ว่าเป็นโชคดีหรือโชคร้ายที่ผมไม่เคยมีประสบการณ์ทำซอฟต์แวร์ที่มีการ Release ในลักษณะแบบนี้เลย

(#2) I Use GitLab Flow + Production Branch

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

ภาพด้านล่างนี้เป็นภาพจากบทความของคุณ Alex Chiraples ซึ่งเขาได้อธิบายถึง Flow ที่เขาใช้ในการทำงานที่มีพื้นฐานมาจากการใช้ GitLab Flow ร่วมกับ Production Branch แล้วเขาก็ตั้งชื่อว่า GitRed Flow (ผมเดาเอาเองว่าคำว่า Red ใน GitRed สื่อถึงชื่อบริษัทที่เขาทำงานอยู่) แวบแรกที่ผมไปอ่านเจอผมก็ อุ้ย… ทำไมมันคล้ายกับที่ข้าพเจ้าใช้เลยเนี่ย แต่ที่ผมจะอธิบายต่อไปนี้ ผมจะไม่เรียกมันว่า GitRed Flow นะครับ เพราะเขาไม่ได้อธิบายในรายละเอียดลึก ๆ ว่าเขาใช้มันอย่างไร มันอาจจะไม่เหมือนกับวิธีปฏิบัติของผมก็ได้ และผมก็ไม่คิดอยากจะตั้งชื่อใหม่ด้วย ขอเรียกว่าเป็นการประยุกต์ใช้ GitLab Flow ในสไตล์ผมละกัน

ภาพแสดง GitRed Flow จากบทความของคุณ Alex Chiraples

กฎกติกาเมื่อผมใช้ GitLab Flow

  • ไม่ว่าคุณจะทำอะไรก็ตาม (ยกเว้น Hot Fixes ให้อ่านข้อด้านล่าง) ให้สร้าง Branch ใหม่จาก Main Branch ตาม Naming Conventions ที่ตกลงไว้ร่วมกัน แล้วให้ลบ Branch นั้นทิ้งไปเลยเมื่อการ Merge เสร็จสมบูรณ์ โดยชื่อ Branch ที่ถูกลบทิ้งไปจะเอากลับมา Reuse ใหม่ได้
  • โฟกัสงานทีละ Release หมายถึงให้เรียงลำดับการทำงานฟีเจอร์หรือของที่จะส่งมอบใน Release ที่จะมาถึงหรือใกล้ตัวที่สุดก่อน คุณสามารถทำงานของ Release หน้าได้ แต่ถ้าไม่จำเป็นเราจะไม่ Merge Feature ของ Release หน้าลง Main Branch อย่างน้อยจนกว่า Release Candidate (RC) Version ของ Release นี้จะออก (อ่านความหมายของ RC ได้ในข้อถัดไป) แต่ถ้าจำเป็นต้อง Merge Feature ของ Release หน้าเข้ามา จะต้องควบคุมโดย Feature Flags แล้วปิดฟีเจอร์เหล่านั้นยังไม่ให้ทำงาน
  • ก่อน Merge ภาคบังคับคือให้รัน Unit Test ให้ผ่านก่อน ส่วน Automated Test ในเลเวลอื่น ๆ แล้วแต่สะดวก
  • เมื่อทีมทำการ Merge โค้ด ไม่ว่าจะเป็น Feature, Bug Fix, หรือ Hot Fix เข้า Main Branch จนครบตามที่ตกลงกันว่า Release นี้จะมีอะไรบ้าง ให้ทำการรัน Unit Test และ Build ให้ผ่าน จากนั้นทำการ Tag Version ทันที เราจะเรียก Tagged Version นั้นเป็น Release Candidate (RC) Version ซึ่งเป็นการสื่อว่า Version นี้มีโอกาสที่จะถูกเอาไป Deploy บน Production ได้
  • ก่อนจะ Tag ให้สื่อสารกับสมาชิกคนอื่นให้ดี เพื่อป้องกันการ Merge ของที่ไม่ต้องการตัดหน้าเข้าไปโดยไม่ได้ตั้งใจ และหลังจาก Tag ให้สื่อสารกับสมาชิกของทีมให้ดีว่ามี RC Version ใดเกิดขึ้นแล้ว
  • ใช้มาตรการ 2 ชั้นสำหรับควบคุมและป้องกันการ Merge ของที่ไม่ต้องการเข้าไปใน Release ชั้นแรกคือการกำหนดให้ Main Branch และ Production Branch เป็น Protected Branches ส่วนชั้นที่สองคือคนที่ทำการอนุมัติ Merge Request (MR) จะต้องสื่อสารกับทีมให้แน่ใจก่อนว่าเป็นการ Merge ที่ถูกต้องครบถ้วนตามหลักการที่กล่าวมาข้างต้น
  • เมื่อได้ Tagged RC Version ที่ Main Branch แล้ว จะมีเวลาให้ “รอ” นิดหน่อยเผื่อมีการเปลี่ยนใจเพิ่มโค้ดเข้ามาอีก ถ้ามีมาเพิ่มก็ให้ Tag เป็น RC Version ใหม่ แต่ถ้านิ่งแล้ว พร้อมจะเดินหน้าสู่ Production ก็ให้ทำ MR จาก Main Branch ไปที่ Production Branch (จริง ๆ แล้วเราไม่ต้องมี Production Branch ก็ได้ครับ นั่นคือเราสามารถ Build RC Version จาก Main Branch เพื่อนำไปทดสอบแล้ว Deploy ไปที่ Production ได้เลย แต่สำหรับผมเลือกที่จะมี Production Branch ซ้อนไว้อีกชั้นหนึ่ง เพื่อให้ได้มาซึ่งความยืดหยุ่นในการ Release ที่มากขึ้นในกรณีที่ต้องทำ Bug Fixes หรือ Hot Fixes ที่จะอธิบายหลังจากนี้ครับ ซึ่งถ้าเรามีแต่ Main Branch แล้วงานเราเยอะมาก ๆ เราจะควบคุมการ Merge ได้ยาก การหาจุดที่คลีนสำหรับ Build Application Package ก็จะยากตามไปด้วย)
  • พอ Merged Tagged RC Version จาก Main Branch เข้ามาที่ Production Branch แล้วให้รัน Automated Test และ Build Application Package จาก Production Branch นี้ไปใช้ในกระบวนการต่อ ๆ ไป โดยให้ Tag ไว้เป็น RC Version เดียวกับของต้นทางด้วย (Production Branch มีข้อดีก็คือมันจะคลีนกว่า Main Branch ครับ การที่เรา Build และ Tag ไว้บน Production Branch จะการันตีว่าเราจะมี Source Codes ที่นิ่งและ Sync กับ Application Package ที่ใช้งานบน Production อยู่เสมอ แล้วถ้าหาก Production Branch ของเรามีอันเป็นไป เราก็จะยังมี Tag อยู่ที่ Main Branch สำรองไว้อีกชั้นหนึ่ง — โดยผมจะมอบหมายให้มีเพียงไม่กี่คนในทีมเท่านั้นที่สามารถทำงานกับ Production Branch ได้ ส่วน Developers คนอื่นที่เหลือจะยุ่งกับแค่ Main Branch อย่างเดียว เพื่อป้องกันความสับสน)
  • Application Package ที่ Built ออกไปจาก Production Branch อาจจะต้องไปผ่านกระบวนการ Manual Testing ที่กินเวลานานกว่าจะได้ Deploy ณ Production ยกตัวอย่างเช่น User Acceptance Test (UAT) เป็นต้น ในระหว่างนั้นถ้าไม่มีบั๊กหรือความจำเป็นอื่น ๆ ที่ทำให้ต้องมีการแก้ไขโค้ด เราจะใช้ Application Package นั้น Deploy ไปที่ Production เลยโดยไม่มีการ Build ใหม่ เพื่อการันตีว่าตัว Application Package ที่ผ่านการทดสอบมาอย่างโชกโชนนั้นเป็นตัวเดียวกันกับที่จะไปทำงานอยู่บน Production ครับ แต่ถ้ามีความจำเป็นต้องแก้ไขโค้ดขึ้นมา ก็จะมีวิธีการจัดการแตกต่างกันซึ่งจะได้กล่าวต่อไป
  • การจัดการกับกรณี Hot Fixes ซึ่งหมายถึงการเจอ Bug บน Production แล้วต้องรีบแก้ไขด่วน ซึ่งในภาพด้านบนจะแสดงด้วยเส้นสีแดงครับ วิธีการคือให้สร้าง Branch ใหม่ (จะเรียกว่า Hot Fix Branch) จาก Tag บน Production Branch ออกมาเพื่อแก้บั๊ก แก้เสร็จแล้วให้เช็คว่า Production Branch นั้นคลีนหรือไม่ คำว่าคลีนคือมี Tagged RC Version ที่ยังไม่ได้ Release ค้างอยู่มั้ย ถ้ามีค้างอยู่ ให้ Build-Test-Deploy จาก Hot Fix Branch ไปเลย แต่ถ้าไม่มีของค้างอยู่ ก็ให้ทำ MR จาก Hot Fix Branch กลับไปที่ Production Branch จากนั้นค่อย Tag เป็น RC Version ใหม่แล้วทำการ Deploy ท้ายสุดอย่าลืม MR Hot Fix Branch ไปที่ Main Branch ด้วย จุดนี้สำคัญมาก เพราะถ้าคุณลืม บั๊กนั้นจะกลับมาใหม่ใน Release หน้า

แวะทำความเข้าใจ Scenario ที่มักทำให้เกิดปัญหา

Scenario เจ้าปัญหาคือ Bug Fixing ซึ่งหมายถึงการที่เราพบ Bugs ระหว่างการทดสอบแล้วต้องแก้ไขให้เสร็จก่อนที่จะ Release จากประสบการณ์ผมเอง ความยุ่งยากจะเกิดขึ้นในกรณีที่เราต้องเริ่มพัฒนาฟีเจอร์ของ Release ถัดไปในขณะที่ Release ปัจจุบันเรายังติดอยู่ในเฟสเทสและมีบั๊กที่ต้องแก้เต็มไปหมด จริง ๆ เราสามารถคิดได้ว่า Bug Fix ก็คือ Feature ที่เราไม่ได้วางแผนไว้ล่วงหน้าว่ามันจะเข้ามาอยู่ใน Release นี้ ไม่รู้ว่ามันจะมีจำนวนมากหรือน้อย ไม่รู้ว่าจะเข้ามาวันไหน แต่เราน่าจะรู้ได้ว่ามันจะมีเข้ามาแน่ ๆ ใช่ไหมครับ ^^

ดังนั้นตามหลักการแล้ว เราก็สามารถใช้ Flow แบบเดียวกับ Feature จัดการ Bug Fix ได้โดยการสร้าง Branch ใหม่ออกมาจาก Tag ที่ Main Branch เพื่อทำการแก้บั๊ก แก้เสร็จแล้วก็ Merge กลับเข้าไปที่ Main Branch เหมือนเดิม แต่ปัญหามันจะเกิดเมื่อโค้ดใน Main Branch ก้าวล้ำหน้า Release ปัจจุบัน (ที่ยังเทสอยู่) ไปเยอะ เช่น มีโค้ดของฟีเจอร์ใน Release หน้าเข้ามาปน (ซึ่งผมจะเรียกสถานการณ์นี้ว่า “Main Branch ไม่คลีน” ครับ) ต่อให้เราใช้ Feature Flags ปิดการทำงานของฟีเจอร์ใน Release หน้าไว้ทั้งหมดแล้ว มันก็จะยังมีความเสี่ยงเหลืออยู่อีกนิดหนึ่ง คือความเสี่ยงที่เราจะเจอบั๊กใหม่ที่เป็นผลข้างเคียงจากการที่เรามีโค้ดของ Feature ใน Release หน้าปนเข้าไปเพิ่ม (สถานการณ์แบบนี้ผมเรียกว่า “Code Base ที่ใช้ในการ Build Application Package ไม่เท่าเดิม”)

  • สรุปการจัดการกับกรณี Bug Fixing
    (1) จัดหมวดหมู่ Bugs ที่จำเป็นต้อง Fix ใน Release นี้มาแก้เท่านั้น ส่วนที่เหลือที่ไม่จำเป็นก็ไปรวมไว้กับ Release ถัดไป
    (2) ถ้าโค้ดใน Main Branch คลีน คือไม่มีฟีเจอร์ของ Release อื่นมาปน สามารถใช้ Flow เดียวกับ Feature Branch ได้เลย แต่เราจะเรียก Branch ที่แตกออกมาเพื่อทำ Bug Fix ว่าเป็น Bug Fix Branch
    (3) ถ้าโค้ดใน Main Branch ไม่คลีน แล้วมีบั๊กที่ต้องแก้ด่วนเพื่อรีเทสทันที สามารถใช้ท่าเดียวกับการทำ Hot Fix ที่กล่าวมาข้างต้นได้
    (4) ถ้าโค้ดใน Main Branch ไม่คลีน แล้วต้องการรวมแก้ไขบั๊กหลาย ๆ ตัวก่อนจะส่งไปรีเทส ให้ใช้เทคนิคการทำ Integration Branch (ผมอธิบายอยู่ในหัวข้อ Add-on Strategies)
    (5) ถ้าสุดท้ายแล้วไม่สามารถหลีกเลี่ยงการปนกันของโค้ดที่ไม่ต้องการได้ ให้ทำ Feature Flags เพิ่มเข้าไปแล้วปิดการทำงานของส่วนที่ไม่ต้องการ หรือไม่ก็ต้อง Revert Codes ส่วนที่ไม่ต้องการออก กรณีนี้ต้อง Trade-off ดูว่าแบบไหนง่ายและความเสี่ยงน้อยกว่ากัน
  • สุดท้ายคือกรณีฟ้าประทานคำสั่งเปลี่ยนแปลงการ Release มาหลังจากที่คุณ Merged Codes ไปเรียบร้อยแล้ว ถ้าคุณโชคดีมีทำ Feature Flags ไว้ คุณก็ไป Turn off ฟีเจอร์ที่ไม่ต้องการได้เลย แต่ถ้าคุณไม่ได้ทำ Feature Flags ไว้ คุณมีสองทางเลือก ทางเลือกแรกคือไปทำ Feature Flags เพิ่มเข้าไป อีกทางเลือกหนึ่งก็คือการ Undo สิ่งที่คุณ Merged ไปนั่นแหละ และเมื่อคุณจัดการต่อจิ๊กซอว์ของ Release ตามที่ฟ้าประทานได้แล้ว คุณก็สามารถ Build-Test-Deploy ตาม Flow ปกติที่กล่าวมาข้างต้นต่อไป

How to Undo Things

หลายสิ่งในชีวิตจริงเรา Undo ไม่ได้ แต่ไม่ใช่สำหรับ Git ครับ ในบทความนี้ผมคงไม่ได้อธิบายการ Undo แบบ Step-by-Step เพราะมันไม่ใช่จุดประสงค์หลักของบทความนี้ ผมเลยจะเขียนเป็นหลักการเบื้องต้นเพื่อให้นำไปใช้ศึกษาต่อไปครับ

คอนเซปต์การทำงานทั่วไปของ Git

วิธีการ Undo จะแตกต่างกันไปขึ้นกับตำแหน่งของสิ่งที่เราต้องการ Undo ครับ (ดูรูปด้านบนประกอบ)

Undo Local Unstaged Changes

กรณีนี้คือเราแค่แก้โค้ดในไฟล์ไปเฉย ๆ ยังไม่ได้ใช้คำสั่งอะไรเลยแม้แต่ git add (ตามภาพของที่เราต้องการ Undo จะอยู่ที่เส้น Working Directory) ให้เราใช้คำสั่ง git restore ตามด้วยชื่อไฟล์ครับ คำสั่งนี้มันจะไปเอาโค้ดที่เป็น Commit ล่าสุดมาทับให้

Undo Local Staged Changes

กรณีนี้เขยิบมาอีกหน่อย คือหากเราใช้ git add ไปแล้วแต่ยังไม่ได้ Commit (อยู่ที่เส้น Staging Area ในภาพ) ให้เราใช้คำสั่ง git restore --staged filenameแล้วไฟล์ของเราจะกลับไปอยู่ในสถานะ Unstaged

Undo Local Committed Changes

กรณีถัดมาคือ ถ้าเราใช้คำสั่ง git commit ลง Local Repository ไปแล้ว แต่ยังไม่ได้ Push (กรณีนี้ของอยู่ที่เส้น Local Repository) ให้ใช้คำสั่ง git reset ตามด้วยเลข Commit เพื่อ Undo ครับ โดยการทำงานของคำสั่งนี้จะพิเศษนิดนึงตรงที่จะไม่ได้ไปลบ Commit ที่เราระบุเลขไป แต่จะไปลบ Commit อื่นที่มาทีหลัง Commit ที่เราระบุ และคำสั่งนี้มันจะมีโหมดการทำงานแบบ Soft และแบบ Hard ด้วยครับ โดยความต่างคือแบบ Soft นั้น Changes ของ Commits ที่ถูกลบไปจาก Local Repository จะยังอยู่ในเครื่องของเรา เราสามารถแก้ไขเพิ่มเติมแล้ว Commit เข้าไปใหม่ได้ แต่ถ้าเป็นแบบ Hard มันจะหายเกลี้ยงเลยครับ ทั้ง Commits ใน Local Repository และ Changes ในเครื่องเรา ข้อควรระวังอีกอย่างหนึ่งของการใช้คำสั่งนี้ก็คือมันไม่สนใจว่าเป็น Commits ของใครนะครับ ถ้าตรงเงื่อนไขก็โดนหมด

Git มีวิธีสำหรับ Recover ไฟล์ที่ถูกคำสั่ง Hard Reset ลบไปด้วยนะครับ ผมเองไม่เคยมีประสบการณ์ที่ต้องใช้มันเลย ถ้าใครมีความจำเป็นต้องกู้ไฟล์แล้วไฟล์นั้นอยู่ในสถานะ Committed ไปแล้วก็ลองไปดูคำสั่ง git checkoutหรือ git reflog นะครับ แต่ถ้าไฟล์ที่ถูกลบไปยังไม่ถูก Committed แต่แค่ Staged ไว้เฉย ๆ ให้ลองดูคำสั่ง git fsck ครับ ส่วนไฟล์พวกที่อยู่ในสถานะ Unstaged แล้วถูก Hard Reset ไปนี่ ผมไม่เคยอ่านเจอว่ามีคำสั่งไหนกู้ได้เหมือนกัน อาจจะต้องไปกู้ไฟล์ในเลเวลของฮาร์ดดิสก์แทน

Undo Remote Committed Changes

ในสามกรณีที่ผ่านมา เป็นการ Undo สิ่งที่อยู่ในเครื่องของเรา (Local) เท่านั้น แต่กรณีนี้คือกรณีที่เราใช้คำสั่ง git push เพื่อ Commit การเปลี่ยนแปลงของเราไปที่ Remote แล้ว (ในรูปแสดงด้วยเส้น Remote Repository) และแน่นอนว่าสมาชิกในทีมคนอื่นก็จะสามารถเห็น Commit นี้ได้ แบบนี้เราจะใช้คำสั่ง git revert ตามด้วยเลข Commit

การทำงานของคำสั่ง Revert จะไม่เหมือนกับคำสั่ง Reset

  • ถ้าเราใช้คำสั่ง Reset CommitID โค้ดเราจะกลับไปสู่สถานะ “ที่” CommitID นั้น แต่ถ้าเราใช้คำสั่ง Revert CommitID โค้ดเราจะกลับไปสู่สถานะ “ก่อน” CommitID นั้นครับ ซึ่งแปลว่าตัว CommitID ที่เราสั่งจะถูก Reverted ไปด้วย
  • คำสั่ง Revert จะไม่ลบ Commits ของเราไปแบบดื้อ ๆ เหมือนกับคำสั่ง Reset แต่มันจะสร้าง Commit อันใหม่เข้าไปต่อท้าย สมมติเรามี Commits เรียงกันแบบนี้ A -> B -> C แล้วเราสั่ง Revert B เราจะได้ผลลัพธ์เป็น Commits แบบนี้ครับ A -> B -> C -> D (จะเห็นว่าไม่มี Commits ไหนหายไปครับ แต่จะมี D เพิ่มเข้ามาแทน โดย D คือ Commit ที่สร้างโดยคำสั่ง Revert ซึ่งมันคือการแก้ไขโค้ดเราโดยย้อนกลับ Changes ที่เกิดจาก B และ C ดังนั้นเราก็จะเห็นโค้ดเราอยู่ในสถานะ A) แต่ถ้าเราสั่ง Reset B มันจะไปลบ Commits C กับ D ทิ้งไป แล้วเราจะเหลือแค่ Commits: A -> B ครับ
  • ถ้าเราทำการ Revert แล้วเปลี่ยนใจอยากเอาโค้ดกลับมา เราไม่จำเป็นต้องเขียนโค้ดส่วนที่ถูก Revert ใหม่กลับเข้าไปนะครับ เราใช้วิธี Revert การ Revert ครับ (ผมไม่ได้พิมพ์เบิ้ลนะ)

สิ่งที่ต้องตระหนักเพิ่มเติมเกี่ยวกับการใช้คำสั่ง Revert ที่เป็นงาน Remote คือใน Remote Repository มันจะมี Commits ของเพื่อน ๆ เราอยู่ด้วยนะครับ และอีกอย่างคือการ Revert นั้นมีโอกาสที่จะทำให้เกิด Code Conflicts ที่ต้องตามแก้กันต่อ ซึ่งคนแก้อาจจะไม่ใช่แค่เราครับ เพื่อนคนอื่นที่ Pull Codes ลงมาก็มีสิทธิโดนไปด้วย ขึ้นอยู่กับว่าแต่ละคนแก้ไขงานอะไรอยู่ ก่อนที่จะ Revert จึงควรที่จะสื่อสารกับเพื่อนร่วมทีมกันให้ดี

Add-on Strategies

Environment Branches, Release Branches และ Integration Branches ผมเรียกว่าเป็นท่าเสริมความยาก… เอ้ย ท่าเสริมประสิทธิภาพ ผมเอามันมาประยุกต์ใช้ร่วมกับ Strategy หลัก

Environment Branches

Environment Branches คือการสร้าง Long-Lived Branches ให้กับแต่ละ Environment ของเรา หรือจะประยุกต์สร้างตามขั้นตอนในการทำงาน เช่น DEV, SIT, UAT ก็ได้ ส่วนในภาพด้านล่างจะแสดงเป็น Staging, Pre-Production และ Production เป็นต้น การใช้ Environment Branches ช่วยในการระบุ Source Code Version ที่ใช้อยู่ในแต่ละ Environment หรือแต่ละขั้นตอนการทำงาน กับช่วยให้เรามั่นใจว่า Source Code แต่ละ Version จะต้องผ่านแต่ละ Environment หรือผ่านขั้นตอนการทำงานมาอย่างถูกต้องครบถ้วนตามลำดับ Production Branch ที่ผมใช้ร่วมกับ GitLab Flow ก็เป็นตัวอย่างหนึ่งของ Environment Branch นี่แหละ

Environment Branches with GitLab Flow

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

ที่ KBTG เราใช้ DevOps Pipelines ในการ Deploy ของลงไปที่ Environment ต่าง ๆ ครับ แต่เรามีหลักการว่าเราจะ Build Application Package Version หนึ่ง ๆ เพียงครั้งเดียวแล้วใช้กับทุก Environments ไปตลอดยัน Production ครับ ถ้าระหว่างทางมีบั๊กที่ต้องแก้ไขก็ต้อง Build ออกเป็น Version ใหม่ โดย Pipelines ในการทำงานก็จะเป็นเหมือนเดิมเป๊ะ พอเป็นแบบนี้ ผมเลยรู้สึกว่าใช้แค่ Production Branch อันเดียวก็พอเพียงต่อการทำงาน ไม่ต้องสร้าง Environment Branch อื่นเพิ่มเติม

การใช้ Environment Branches มักทำให้ Flow ในการแก้บั๊กของทีมซับซ้อนขึ้น เพราะเราต้องแทร็กให้ดีว่าบั๊กเกิดขึ้นที่ Environment ไหน โค้ดที่เป็น Bug Fix จะ MR จากไหนไปไหน จะ Tag อย่างไร รวมไปถึงเวลามี Hot Fix ที่ต้องแก้ด่วนจาก Production จะต้องตามมา Merge เข้าที่ Environment Branches กี่อันบ้าง ยิ่ง Branches เยอะเท่าไร กติกายิ่งซับซ้อนเท่านั้น สมาชิกในทีมสับสนได้ง่าย โอกาสพลาดก็มีสูง

Release Branches

ถ้าเรามีการ Release ซอฟต์แวร์ในแบบ Long-Term Support นั่นคือคุณต้อง Maintenance Release หลายตัวในระยะยาวพร้อม ๆ กัน สำหรับตัวอย่างให้ลองนึกถึง Microsoft Windows ก็ได้ครับ ตอนที่ Windows ออกเวอร์ชันใหม่มา Microsoft ก็ยังต้อง Support เวอร์ชันเก่าอยู่ อย่างเช่น ต้องแก้บั๊ก ออก Patches บางครั้งก็มี Features ใหม่ด้วย ฯลฯ ลักษณะแบบนี้การทำ Release Branches จะเหมาะมากครับ

Release Branches from GitLab

สิ่งที่ต้องให้ความสำคัญเมื่อนำคอนเซปต์ Release Branches มาใช้คือการ Merge โค้ดเข้าในแต่ละ Release Branch ที่มีอยู่ครับ โดยหลายที่เขาจะแนะนำเทคนิคที่เรียกว่า “Upstream First” ซึ่งหมายถึง ให้เราเอาของเข้าไปที่ Main Branch ก่อน แล้วทำ MR หรือ Cherry-Pick ต่อไปยัง Release Branch แต่ละอันตามลำดับ แต่ถ้ามันของบางอย่างที่เฉพาะเจาะจงกับ Release อันใดอันหนึ่ง ก็สามารถทำบน Release Branch อันนั้นได้เลย ส่วนพวก Bug Fix หรือ Hot Fix จะซับซ้อนขึ้นมาหน่อย เราต้องวิเคราะห์ให้ดีว่าจะ MR ไปที่ Release Branches ใดบ้าง เพราะแต่ละ Release อาจจะไม่ได้มีบั๊กแบบเดียวกัน

Integration Branches

ผมเอาคอนเซ็ปต์ของ Integration Branches มาใช้ในลักษณะที่เป็น Short-lived Branches เพื่อตอบโจทย์ Scenarios ที่ค่อนข้างหลากหลาย

  • เพื่อ Merge Feature Branches มากกว่าหนึ่งอันเข้ามาเพื่อทดสอบร่วมกันเป็นการชั่วคราว ในการทำงานเราอาจจะเจอว่า Feature Branches หรือ Bug Fixes บางอันมัน Make Sense กว่าที่จะนำมาทดสอบร่วมกัน
  • เพื่อ Experiment ว่าควรเลือก Features ใดบ้างเข้ามารวมเป็น Release เดียวกัน
  • เพื่อให้ได้ความยืดหยุ่นในสถานการณ์ที่ PO หรือทีมไม่สามารถตัดสินใจล่วงหน้าได้ว่าใน Release นี้จะมีฟีเจอร์อะไรบ้าง หรือในกรณีที่ PO มีพฤติกรรมชอบสลับสับเปลี่ยน Features ใน Release ไปมา หรือทีมเล็งเห็นว่ามีความเป็นไปได้สูงที่จะไม่สามารถ Control Release นี้ได้ ในกรณีแบบนี้ การที่เรา Merge Codes เข้า Main Branch ไปเลยอาจจะทำให้เราต้องมา Revert Changes ไปมา ซึ่งไม่เป็นผลดีนัก การมี Integration Branch ที่รวมเอา Feature Branches เข้ามาแบบชั่วคราวก่อน ถ้าเกิดมีการเปลี่ยนใจ เราก็ไปสร้าง Integration Branch อันใหม่แล้วลบอันเก่าออก ก็สามารถช่วยให้เราไม่ต้องไป Revert Changes ที่ Main Branch ได้ แล้วสุดท้ายเมื่อได้รับการยืนยันว่าไม่เปลี่ยนแน่แล้ว เราก็เอา Integration Branch นั้นไป Merge เข้า Main Branch ต่อไป
  • เพื่อรองรับการทำ Bug Fixing ระหว่างเฟส Manual Test ที่นานมาก ๆ ในขณะเดียวกันทีมต้องพัฒนาฟีเจอร์ของ Release หน้าไปพร้อม ๆ กัน (ดูภาพประกอบด้านล่าง) Integration Branch (เส้นสีเหลือง) จะมาช่วยรองรับการ Fix Bugs (สีแดง) ระหว่างที่โค้ดของฟีเจอร์ใหม่นั้นเราให้ Merge เข้าสู่ Main Branch ส่วนการ Fix Bugs บน Integration Branch นั้นทำได้ทั้งแบบแก้โค้ดเข้าไปโดยตรง หรือจะสร้างเป็น Bug Fix Branch (เส้นประสีแดง) เพื่อแก้บั๊กแล้ว Merge ไปที่ Integration Branch ก็ได้เช่นกัน สิ่งสำคัญที่ต้องเน้นย้ำในกรณีนี้ก็คืออย่าลืม Merge Integration Branch กลับเข้าไปที่ Main Branch นะครับ ไม่งั้น Release หน้าคุณจะไม่เหลือ Bug Fixes เลยสักกะตัว
การประยุกต์ใช้ Integration Branch เพื่อรองรับการทำ Bug Fixing

ถ้าใครคุ้นเคยกับ Git Flow จะเห็นว่าผมประยุกต์เอา “Release Branch ของ Git Flow” มาใช้เป็น Integration Branch ใน GitLab Flow ที่ผมใช้อยู่ ซึ่งผมก็ไม่แน่ใจเหมือนกันว่ามันไปตรงหรือไปแย้งกับตำราไหนหรือเปล่านะครับ :P

ข้อเสียหรือข้อพึงระวังของการใช้ Integration Branches ก็มีดังนี้ครับ

  • ถ้าเราสร้าง Short-Lived Branches อะไรก็ตามไว้เยอะจะสับสนได้ง่าย
  • อย่างที่ได้อธิบายไปว่า Integration Branches สำหรับผมเป็น Short-Lived Branches ครับ ซึ่งปกติเราควรจะหลีกเลี่ยงการ Build Application Package จาก Short-Lived Branches ไปใช้ในงาน Manual อะไรสักอย่างนาน ๆ (เช่น Manual Test เป็นต้น) ประเด็นที่ผมเจอบ่อยก็คือ พอถึงจุดที่เราต้อง Merge Short-Lived Branches พวกนี้เข้าไปที่ Main Branch หรือ Production Branch ที่เป็น Long-Lived Branch จะเกิดคำถามประมาณ “เราต้องเทสใหม่หรือไม่นะ” ที่เกิดคำถามแบบนี้ก็เพราะว่างาน Manual ที่ทำไปแล้วมักจะใช้กำลังคนเยอะและใช้เวลานาน ถ้าต้องทำใหม่ก็เหนื่อยน่ะสิครับ ถ้าคุณกำลังจะเดินเข้าสู่สถานการณ์แบบนี้ ผมแนะนำว่าคิดให้ดี ๆ ก่อนครับ ^^ เราลองสมมติสถานการณ์เทียบกับภาพด้านบนกันหน่อย สมมติว่าเราเผอิญมีการ Build Application Package จาก Branch integration_1 ไปทำการทดสอบว่า Bugs ทุกตัวได้รับการแก้ไขหมดแล้ว กว่าจะเทสผ่านหมดใช้เวลาสองสัปดาห์ ต่อมาเราต้อง Merge Integration Branch นี้เข้าไปที่ Main Branch และ Tag เป็น RC v1.4.1 คำถามที่เกิดขึ้นคือ “เราต้องเทสบั๊กเหล่านั้นกับ v1.4.1 อีกรอบไหมนะ?” ถ้าคำตอบคือต้องเทส แสดงว่าเราก็จะเสียเวลาไปอีกสองสัปดาห์ใช่ไหมครับ! เราลองมาว่าตามทฤษฎีกันหน่อย ถ้าเราสังเกต Timeline ของ Production Branch จากจุด v1.4.0 ที่เราแตก Integration Branch ใหม่ออกมา มาถึงจุด v1.4.1 มันไม่มีการเปลี่ยนแปลงอื่นใดเกิดขึ้นกับ Production Branch เลย ดังนั้นเราก็ไม่ควรจะต้องเทสใหม่ครับ เวลา Merge Codes จาก Integration Branch เข้ามาที่ Production Branch โค้ดมันก็จะเข้ามารวมอย่างตรงไปตรงมาแบบไม่มี Conflicts อย่างไรก็ตามในทางปฏิบัติผมจะรัน Automated Test หลัง Merge นะ เพื่อความมั่นใจ ^^ ลักษณะประมาณนี้จะไปคล้ายกับหลักการของ GitHub Flow ที่ Build-Test-Deploy จบบน Feature Branch เลยแล้วค่อยมา Merge ทีหลังครับ
ภาพแสดง GitHub Flow แบบทั่วไป

ผมเห็นบางทีมมีความปวดศีรษะกับการเปลี่ยนแผน Release ของ PO อยู่ไม่น้อย ผมเองคิดว่าไม่มี Strategy ไหนที่แก้ปัญหาการเปลี่ยนใจหลังจากที่คุณ Merged ของไปแล้วได้อย่างเพอร์เฟ็ค แต่เมื่อมันเกิดขึ้นเราก็ควรจะจัดการได้อย่าง Professional ครับ เราต้อง Revert อย่างรอบคอบและแนะนำได้ว่าควร Re-Test ตรงไหนอย่างไร ชีวิตคุณจะดีขึ้นมากถ้าคุณสามารถใช้ Feature Flags (คุณจะไม่ต้อง Revert) และมี Test Automation (ช่วยให้การเทสของคุณเร็วขึ้น) ไว้ข้างกาย

หากคุณไม่ได้ทำ Feature Flag และ Test Automation ที่ดีเพียงพอ ทางที่จะบรรเทาปัญหาได้ก็คือคุณต้องมีจุดเวลาที่ตกลงร่วมกันว่าผ่านจุดนี้แล้วต้องไม่เปลี่ยนใจครับ มันคือจุดที่คุณ Merged ของและ Tag RC Version เพราะถ้าเกิดการเปลี่ยนใจหลังจากนี้ มันจะมีความเสี่ยงจากการ Revert และมี Effort ที่เพิ่มขึ้นจากการ Re-Test ซึ่งไม่ใช่เฉพาะ Developers แต่ทุกคนในทีมต้องมีความเข้าใจในจุดนี้ร่วมกัน

Tips & Techniques

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

ความมีระเบียบเป็นสิ่งสวยงาม (ภาพ: rare-gallery.com)
  • ทีมที่ทำงานในแบบ Agile อยู่ Git Branching Strategy ควรเป็นหนึ่งในหัวข้อของ Retrospective ของคุณ ผมย้ำอีกครั้งว่าแผนการเล่น (Strategy) ส่งผลต่อความคล่องตัวในการทำงานของทีม ถ้าคุณไม่ได้ใช้ Agile คุณก็ควรจะจัดให้ทีมมีเวลาหารือกันเพื่อปรับปรุงแผนการเล่นของคุณให้ดีขึ้นอย่างต่อเนื่อง (Continuous Improvement)
  • อย่างที่ได้บอกไปว่า Git Branch Strategy เปรียบเสมือนแผนการเล่นของนักกีฬามืออาชีพ เราต้องจัดทำแผนการเล่น หรือกฎกติกาออกมาให้ชัดเจน เพื่อให้สะดวกต่อการอ้างอิง และศึกษาทำความเข้าใจของทุกคน โดยเฉพาะสมาชิกใหม่ที่จะเข้ามาร่วมทีม
  • แผนการเล่นที่ดีที่สุด คือแผนการเล่นที่ทุกคนเข้าใจและปฏิบัติตามได้
  • อย่าลืมจัดทำ Naming Conventions สำหรับชื่อ Branches ด้วย บางทีเขียนโค้ดแค่แป๊บเดียว แต่เสียเวลานั่งคิดชื่อนานมาก
  • เมื่อใดที่คุณแตก Task ใหญ่ออกเป็น Task ย่อย ๆ ให้มีขนาดที่เหมาะสมได้ เมื่อนั้นคุณจะสัมผัสได้ถึงความคล่องตัวในการทำงานของทีมที่แตกต่าง ขนาดของ Task จะสะท้อนไปถึงขนาดของ Branch และ Branch ที่ใหญ่เกินไปก็จะส่งผลต่อไปที่ขนาดและจำนวนของ Commits ที่เกิดขึ้น รวมไปถึงอายุของ Branch นั้นก็จะอยู่ยาวนานขึ้น (แปลว่าพัฒนาไม่เสร็จสักที) ความซับซ้อนในการจัดการมีมากขึ้น ความคล่องตัวก็จะลดลง
  • GitLab มีฟีเจอร์ Protected Branches เพื่อควบคุมจำกัดบุคคลที่สามารถ Merge และ Push Codes เข้าสู่ Branch ซึ่งปกติผมก็จะกำหนดให้ Long-Lived Branches เป็น Protected Branch เพื่อป้องกันการ Push Codes เข้าไปโดยตรง แต่ต้องส่งเป็น MR เข้ามาแทน
  • GitLab มีคำว่า Default Branches ซึ่งหมายถึง Branch ที่ถูกสร้างมาเป็น Branch แรกเวลาที่เราสร้าง Project และมักจะถูกใช้เป็นค่า Default เวลาเราทำกิจกรรมที่เกี่ยวกับ Branch ซึ่ง Default Branch จะมีลักษณะพิเศษหลัก ๆ คือเป็น Protected Branch และไม่สามารถลบทิ้งได้ โดยปกติมันจะชื่อ “Main” นั่นเอง
  • ใช้คำสั่ง Stash ให้เป็นครับ มีประโยชน์ในหลายสถานการณ์มาก ใครยังไม่รู้จัก ลองศึกษาดูนะครับ
  • คนมักคุ้นเคยกับการแตก Branch ใหม่ออกมาจาก Branch เก่า แต่เราสามารถสร้าง Branch ใหม่ออกมาจาก Tag ได้นะครับ (ผมชอบเป็นการส่วนตัวด้วย เพราะ Tag มันนิ่งดีครับ ^^)
  • การ Merge Branch จะมีผลกับทุก Commits ที่อยู่ใน Branch นั้น แต่เราต้องการ Merge แค่ Commit เดียวที่ต้องการ เราจะเรียกว่า Cherry Pick ซึ่งเวลาใช้ต้องระวังมาก ๆ เรื่องการ Pick ให้ถูก Commit และการ Pick มาให้ครบ
  • ในบางครั้งที่เราต้องการหาของ จำนวน Commits ที่มีเยอะเกินไปก็ทำให้เราหาสิ่งที่ต้องการได้ยาก ใช้เวลานาน และมีโอกาสที่จะผิดพลาดมากขึ้น GitLab มีฟีเจอร์ Squashing Commits ที่เป็นการรวม Commits ย่อย ๆ เข้ามาเป็น Commit ใหญ่อันเดียว ซึ่งจะช่วยแก้ปัญหาการหยิบ Commits ไม่ครบได้ และทำให้การ Revert นั้นง่ายและเร็วขึ้นเพราะจำนวน Commits ก็จะน้อยลง แต่เวลาใช้ก็ควรระวังด้วยครับ เพราะมันเป็นดาบสองคม การรวมหลาย Commits เข้าด้วยกันให้เหลืออันเดียวก็ทำให้เราเข้าใจการเปลี่ยนแปลงที่เกิดขึ้นกับโค้ดได้ยากเช่นกัน
  • Squash ใช้กับการ Merge ได้ด้วยครับ โดย GitLab จะเรียกว่า Squash and Merge หรือ Squash Merge ซึ่งการทำงานของมันจะ Squash เอา Commits ทั้งหมดที่อยู่ใน Branch ต้นทางรวมเป็น Commit ใหญ่อันเดียวไปเข้าที่ Branch ปลายทางครับ โดยวิธีนี้จะแตกต่างจากการทำ Merge แบบปกติ ตรงที่ Squash Merge จะทำให้ History ของ Branch ปลายทางเป็น Linear แบบไม่มีความสัมพันธ์กับ Branch ต้นทางเลยครับ ส่วนตัวผมเองชอบอะไรที่ Trace ง่ายหน่อย ดังนั้นผมจะใช้วิธีนี้กับการ Merge Branches ที่ Scope งานเล็กแต่ชัดเจน มีหลาย Commits แต่ควรรวมเข้ามาอยู่ด้วยกัน เพราะมีจุดประสงค์เดียวกันหรือต้องใช้งานร่วมกัน เช่นพวก Bug Fixes หรือ Hot Fixes เท่านั้น
  • เวลาทำการ Merge ผมจะนิยมทำแบบ -no-ff (no fast forward) และหลีกเลี่ยงการใช้คำสั่ง rebase ในการ Merge Codes โดยถ้าจำเป็นต้องใช้ให้ใช้อย่างระมัดระวังมาก ๆ
  • หลายคนสร้าง MR ก็ต่อเมื่อทำงานบน Feature Branch เสร็จหมดแล้ว แต่จริง ๆ แล้วเราสามารถสร้าง MR และใช้ประโยชน์จากมันได้ตั้งแต่เริ่มงานเลยครับ โดยหลังจากที่เราสร้าง Branch ใหม่ ให้สร้าง MR ได้เลยโดยยังไม่ต้องใส่ชื่อ Reviewers เพื่อสื่อว่ายังไม่ถึงเวลาที่จะ Merge Branch นี้ แต่เราจะใช้ MR นี้เป็นสถานที่ปรึกษากันในทีมคล้าย ๆ กับการ Pair Programming ครับ เราสามารถพิมพ์ถามหรือขอความเห็นจากเพื่อนเกี่ยวกับโค้ดแต่ละบรรทัดได้เลยเหมือนตอนเราทำ Code Review โดยพิมพ์แท็กเพื่อนด้วยเครื่องหมาย @ ตามด้วยชื่อ Account ของเพื่อน พองานเสร็จเราค่อย Assign Reviewers แล้วดำเนินการต่อไปตามปกติ GitLab จะเรียกฟีเจอร์นี้ว่า Draft Merge Request
  • GitLab มีความสามารถในการเชื่อมต่อกับซอฟต์แวร์ประเภท Issue Tracker ด้วย แบบผมใช้ Jira อยู่ ผมก็สามารถปรับแต่งให้มันรู้จักกันได้ ประโยชน์หลักคือเราจะเห็นความเชื่อมโยงระหว่างงานที่ทำ (Issue) กับโค้ดที่เกี่ยวข้อง ที่เด็ดขึ้นไปอีกคือเราจะสามารถสั่งงานข้ามกันได้ เช่น เราจะสามารถสร้าง Feature Branch จาก Jira Issue หรือในทางกลับกัน เราสามารถปิด Jira Issue ได้เมื่อ MR เราได้รับการ Approved เป็นต้น นอกจากนี้ผมแนะนำให้เชื่อมต่อ IDE เข้ากับทั้ง GitLab และ Jira ด้วย เพราะ Developer เราเขียนโค้ดบน IDE เป็นหลัก การเชื่อมต่อของพวกนี้เข้าด้วยกันจะทำให้เราสามารถทำงานกับเครื่องมืออื่น ๆ ผ่านทาง IDE โปรแกรมเดียวได้ทั้งหมด พอเริ่มชินแล้วผมรู้สึกได้เลยว่ามันเพิ่ม Productivity ได้มาก และทำให้การทำงานสนุกขึ้น

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

Happy Git Branching!

--

--

jo@sabotender
KBTG Life

principal DEVelopment eXcellence engineer — DEVX@KBTG / Full-time Daddy / Console Gamer & Gunpla Collector