Copy Folder Versioning
ทำ version ด้วยการก๊อป folder แทนการใช้ version control
ปัญหาคืออะไร
หัวข้อที่มีชื่อว่า “ปัญหาคืออะไร”Copy Folder Versioning คือการ “ทำ version” source code ด้วยวิธีที่ตรงไปตรงมาที่สุดเท่าที่จะนึกออก — เมื่อจะแก้ code ให้ ก๊อปทั้ง folder project ไปตั้งชื่อใหม่ก่อน แล้วค่อยแก้ใน folder ที่ก๊อปมา ผลลัพธ์ที่ตามมาแทบทุกครั้งคือกอง folder ที่มีชื่อประมาณ MyApp, MyApp_v2, MyApp_v2_fix, MyApp_v2_final, MyApp_v2_final_FINAL, MyApp_2026-03-12_backup เรียงกันอยู่บน disk โดยไม่มีเครื่องบ่งชี้ที่เชื่อถือได้ว่าอันไหนคือ “version ล่าสุดที่ deploy จริง”
นี่ไม่ใช่แค่เรื่องความไม่เป็นระเบียบ แต่คือการไม่มี version history เลย — ไม่มีบันทึกว่าใครแก้อะไร เมื่อไร เพราะอะไร และไม่มี diff ให้ดูว่า file ไหนเปลี่ยนไประหว่าง2 folder Wikipedia อธิบายวิธีทำ version แบบนี้ไว้ตรงประเด็นว่า “แม้จะใช้งานได้ แต่ไม่มีประสิทธิภาพ เพราะต้องดูแลสำเนาโปรแกรมที่แทบเหมือนกันจำนวนมาก ต้องอาศัยวินัยของนักพัฒนาอย่างมาก และมักนำไปสู่ความผิดพลาด” ก่อนที่ระบบ version control อย่าง Git หรือ Subversion จะเป็นมาตรฐาน วิธีนี้คือสิ่งที่ทีมจำนวนมากทำกันจริง ๆ — และปัจจุบันก็ยังพบได้ในทีมที่ไม่เคยได้รับการสอนเรื่อง source control อย่างจริงจัง เช่น ทีม data/analytics ขนาดเล็ก, project เดี่ยว, หรือสภาพแวดล้อมที่ทำงานผ่าน network share/FTP เป็นหลัก
ทำไมถึงดูน่าใช้
หัวข้อที่มีชื่อว่า “ทำไมถึงดูน่าใช้”ก๊อป folder เป็นสิ่งล่อใจเพราะมันมีต้นทุนเริ่มต้นเท่ากับศูนย์ และให้ความรู้สึกปลอดภัยทันที:
- ไม่ต้องเรียนรู้เครื่องมือใหม่ — ทุกคนที่ใช้คอมพิวเตอร์เป็นอยู่แล้วรู้วิธีก๊อป folder ไม่ต้องเข้าใจ commit, branch, merge หรือ conflict resolution
- ไม่ต้องตั้งค่าอะไรเลย — ไม่มี repository ต้อง init ไม่มี remote ต้อง config ไม่ต้องพึ่ง network หรือ server ใด ๆ
- ให้ความรู้สึก “ปลอดภัยไว้ก่อน” — ก่อนแก้ code ที่เสี่ยง การมีสำเนาเต็มของทั้ง project วางอยู่เฉย ๆ ให้ความอุ่นใจแบบที่จับต้องได้ ต่างจากแนวคิด commit ที่เป็นนามธรรมกว่า
- ทำงานได้กับทุก file ไม่ใช่แค่ text — ต่างจาก diff-based VCS ที่เหมาะกับ file ข้อความ การก๊อป folder ทำงานได้เหมือนกันไม่ว่าจะเป็น binary, file config ขนาดใหญ่ หรือ asset ที่ diff ไม่ได้อยู่แล้ว
- เร็วสำหรับงานเดี่ยวขนาดเล็ก — script ทดลองสั้น ๆ หรืองาน one-off ที่ไม่คิดจะย้อนดูประวัติในอนาคต การก๊อป folder ก็ดูสมเหตุสมผลในทางปฏิบัติ
ปัญหาคือเหตุผลเหล่านี้ใช้ได้เฉพาะตอนที่ project เล็กมาก มีคนแก้คนเดียว และไม่มีใครต้องย้อนดูว่าเกิดอะไรขึ้นในอดีต ซึ่งไม่ใช่สถานการณ์ของซอฟต์แวร์ส่วนใหญ่ที่มีอายุยืนกว่าหนึ่งวัน
ทำไมถึงเป็นปัญหา
หัวข้อที่มีชื่อว่า “ทำไมถึงเป็นปัญหา”เมื่อ project โตขึ้นหรือมีคนทำงานร่วมกันมากกว่าหนึ่งคน Copy Folder Versioning จะพังในหลายมิติพร้อมกัน:
- ไม่มี diff และไม่มี blame — ไม่มีทางรู้ว่า file ไหนเปลี่ยน บรรทัดไหนเปลี่ยน ใครเป็นคนเปลี่ยน หรือทำไมถึงเปลี่ยน ต้องเปิด2 folder เทียบกันเองด้วยตา ซึ่งทำไม่ไหวเมื่อ file มีเป็นร้อย
- ไม่มี “ความจริงหนึ่งเดียว” (single source of truth) — เมื่อมี folder หลายสิบอันชื่อคล้ายกัน ไม่มีใครมั่นใจได้ 100% ว่าอันไหนคือของจริงที่ deploy อยู่บน production การ debug ปัญหาที่เกิดเฉพาะบาง version จึงแทบเป็นไปไม่ได้
- collision เงียบเมื่อมีหลายคนทำงานพร้อมกัน — ไม่มีกลไก merge หรือ conflict detection ถ้าสองคนก๊อป folder ไปแก้พร้อมกัน แล้วเอากลับมารวมเอง งานของฝ่ายหนึ่งมักถูกเขียนทับแบบไม่มีใครรู้ตัว
- disk และ backup บวมโดยไม่จำเป็น — แต่ละสำเนามักพ่วง build artifact,
bin/,obj/,node_modules/ไปด้วยเต็ม ๆ ทำให้พื้นที่หายไปเร็วกว่าที่ควร ทั้งที่ diff-based VCS เก็บแค่ส่วนต่างจริง ๆ - ย้อนกลับ (rollback) ไม่ได้อย่างน่าเชื่อถือ — ถ้า production พัง ไม่มีคำสั่งเดียวที่รับประกันว่าจะได้กลับไปเป๊ะ ๆ ในสถานะก่อนหน้า ต้องเดาว่า folder สำรองอันไหนคือของจริง
- ไม่มี audit trail — ในองค์กรที่ต้องผ่าน compliance หรือ security review การไม่มีประวัติการเปลี่ยนแปลงที่ตรวจสอบได้คือความเสี่ยงจริง ไม่ใช่แค่ความไม่สะดวก
มองในมุมกว้างขึ้น นี่คือรูปแบบเดียวกับ Duplicate Code แต่ขยายสเกลจากระดับ function ไปเป็นระดับ “ทั้ง project” SourceMaking อธิบาย Cut-and-Paste Programming ไว้ว่าเป็น “รูปแบบการใช้ซ้ำที่ทำให้ต้นทุนบำรุงรักษาสูงเกินจำเป็น เพราะ bug ถูกก็อปกระจายไปทั่วระบบ” — Copy Folder Versioning ก็สร้างปัญหาแบบเดียวกัน เพียงแต่สิ่งที่ถูกก๊อปคือทั้ง project ไม่ใช่แค่ block code
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”สมมติทีมหนึ่งไม่มี Git และเขียนเครื่องมือช่วยของตัวเองสำหรับ “สำรอง version” ก่อนแก้ code ทุกครั้ง — เครื่องมือนี้ทำงานถูกต้องตามที่ตั้งใจ แต่มันคือการฝัง antipattern ไว้ในกระบวนการทำงานโดยไม่รู้ตัว
// แอนตี้แพทเทิร์น: เครื่องมือ "สำรอง version" ที่ก๊อปทั้ง folder แทนการ commitpublic static class BackupHelper{ public static void SaveVersionBeforeEdit(string projectPath) { // ก๊อปทั้ง folder ไปตั้งชื่อใหม่ด้วย timestamp // ไม่มี message อธิบายเหตุผล ไม่มีการบันทึกว่าใครเป็นคนเรียกใช้ var backupName = $"{projectPath}_backup_{DateTime.Now:yyyyMMdd_HHmmss}"; CopyDirectory(projectPath, backupName); // ผลลัพธ์ที่สะสมใน folder ผู้ใช้: // MyApp, MyApp_backup_20260310_1032, MyApp_backup_20260312_0915_final, // MyApp_backup_20260312_0915_final_v2 ... ไม่มีใครกล้าลบอันไหนทิ้ง }
private static void CopyDirectory(string source, string destination) { Directory.CreateDirectory(destination); foreach (var file in Directory.GetFiles(source, "*", SearchOption.AllDirectories)) { // ก๊อปทุก file แบบเต็ม รวมถึง bin, obj และ dependency ที่ build ใหม่ได้อยู่แล้ว var target = file.Replace(source, destination); Directory.CreateDirectory(Path.GetDirectoryName(target)!); File.Copy(file, target, overwrite: true); } }}code ชิ้นนี้ทำงานได้ไม่มี bug แต่มันคือปัญหาเชิงกระบวนการ ไม่ใช่เชิงไวยากรณ์ — เมื่อสมาชิกในทีมสองคนเรียก SaveVersionBeforeEdit ใกล้เวลากันแล้วแก้คนละจุด ไม่มีทางรวมงานทั้งสองเข้าด้วยกันได้โดยไม่เสี่ยงเขียนทับกันเอง และเมื่อ production มีปัญหา ก็ไม่มีทางรู้แน่ชัดว่า folder backup อันไหนตรงกับสิ่งที่ deploy อยู่จริง
แผนภาพด้านล่างเทียบสองแนวทางเมื่อเกิดการแก้ code หนึ่งครั้ง:
flowchart LR
A[แก้ code เสร็จหนึ่งจุด] --> B{เลือกวิธีทำ version}
B -->|Copy Folder| C[ก๊อปทั้ง folder ตั้งชื่อใหม่]
C --> D[ไม่มี diff ไม่มีผู้เขียน เดาไม่ได้ว่าอันล่าสุดคืออันไหน]
B -->|Git commit| E[commit พร้อมข้อความและผู้เขียน]
E --> F[มี diff ประวัติครบ ย้อนกลับได้ด้วยคำสั่งเดียว]
ทางแก้และการ refactor
หัวข้อที่มีชื่อว่า “ทางแก้และการ refactor”ทางแก้ไม่ซับซ้อน: ใช้ version control ที่เหมาะสม (Git เป็นมาตรฐานปัจจุบัน) ซึ่งแก้ทุกข้อที่ Copy Folder Versioning ทำไม่ได้ในคราวเดียว — เก็บ diff จริง ไม่ใช่สำเนาเต็ม, บันทึกผู้เขียนและเวลาอัตโนมัติ, รองรับ merge เมื่อหลายคนทำงานพร้อมกัน, และย้อนกลับได้ด้วยคำสั่งเดียว
แนวทางปฏิบัติที่ทำให้การย้ายจาก Copy Folder Versioning ไปสู่ Git ได้ผลจริง:
- commit เล็กและบ่อย พร้อมข้อความที่อธิบายเหตุผล ไม่ใช่แค่ “อะไร” — ดู The 50/72 Rule of Git สำหรับรูปแบบ commit message ที่อ่านง่ายในระยะยาว
- ใช้
.gitignoreกันไม่ให้ build artifact อย่างbin/,obj/,node_modules/เข้าไปพอง repository เหมือนที่เคยพอง folder backup - เปิดให้ทุกคนแก้ code ได้ทุกจุดเมื่อจำเป็น ผ่านแนวทาง Collective Code Ownership แทนที่จะให้แต่ละคนหวง folder สำเนาของตัวเองไว้คนเดียวเพราะกลัวชนกัน
- refactor ทีละก้าวเล็ก ๆ ผ่าน Refactoring โดยพึ่ง commit history เป็นตาข่ายนิรภัยแทนการก๊อป folder ไว้ “เผื่อพัง”
- ถ้ามีกอง folder เก่าสะสมอยู่แล้ว ให้เลือก version ล่าสุดที่มั่นใจว่าตรงกับ production มากที่สุด,
git initเริ่มต้นจากตรงนั้น, แล้วค่อยเก็บ folder เก่าไว้เป็น archive แบบอ่านอย่างเดียว ไม่ใช่ที่ทำงานต่อ
หลักการที่อยู่เบื้องหลังคือแนวคิดเดียวกับ Don’t Repeat Yourself — ความรู้ (ในที่นี้คือ “code ล่าสุดคืออะไร”) ควรมีแหล่งความจริงเดียวที่ชัดเจน ไม่ใช่กระจายอยู่ในสำเนาหลายสิบชุดที่ไม่มีใครมั่นใจว่าอันไหนถูกต้อง
// refactor: แทนที่จะก๊อป folder ให้ commit เข้า Git ทุกครั้งที่มีการเปลี่ยนแปลงที่มีความหมายusing LibGit2Sharp;
public static class VersionControlHelper{ public static void CommitSnapshot(string repoPath, string message, Signature author) { using var repo = new Repository(repoPath);
// stage การเปลี่ยนแปลงทั้งหมด เหมือนก๊อป folder แต่เก็บเป็น diff ไม่ใช่สำเนาเต็ม Commands.Stage(repo, "*");
// ทุก commit มีผู้เขียน เวลา และข้อความอธิบาย ตาม 50/72 rule repo.Commit(message, author, author); // ย้อนดูได้ด้วย git log, ย้อนกลับได้ด้วย git revert หรือ git checkout // ไม่ต้องเดาว่า folder ไหนคือ version ล่าสุด }}ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”- The 50/72 Rule of Git
- Collective Code Ownership
- Refactoring
- Don’t Repeat Yourself
- Duplicate Code
- Copy-Paste Programming