ข้าม​ไป​ยัง​เนื้อหา

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 แทนการ commit
public 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 ประวัติครบ ย้อนกลับได้ด้วยคำสั่งเดียว]

ทาง​แก้​ไม่​ซับซ้อน: ใช้ 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 ล่าสุด
}
}