Toggle for side bar
Logo

Semantic Versioning เลขเวอร์ชันแบบนี้มีความหมายอย่างไร

updated at: 26 Nov 2023

บล็อกนี้ไม่ได้จะมาตีเลขหวยนะครับ บอกเผื่อไว้ก่อน!

Semantic Versioning มันคืออะไร

"0.1.12", "1.6.3", "2.0.0-alpha.1", "2.0.0-beta.1.5.1+12553"... หลาย ๆคนคงเคยเห็นเลขเวอร์ชันหน้าตาประมาณนี้กันมาอยู่แล้ว แต่บางคนก็อาจจะไม่รู้ว่าทำไมมันถึงต้องเขียนแบบนั้น มันหมายความว่าอย่างไร ทำไมเขียนแค่ตัวเลขอย่างเดียวเช่น เวอร์ชัน 1, เวอร์ชัน 25, เวอร์ชัน 36 ฯลฯ ถึงไม่เพียงพอ ตัวเลขหน้าตาแบบนี้ มันเรียกว่า "Semantic Versioning" ซึ่งเป็นชุดตัวเลขและตัวอักษรที่หากเราเข้าใจมัน เราก็จะเข้าใจได้ว่า เวอร์ชันนั้น ๆเป็นเวอร์ชันที่มีการอัพเดทในลักษณะใด ทำให้เราสามารถเลือกใช้งานหรืออัพเดทซอฟต์แวร์ได้อย่างถูกต้อง รวมถึงว่าหากเราจะทำซอฟต์แวร์ขึ้นมาให้ผู้พัฒนาคนอื่นได้ใช้งาน เราก็ควรที่จะต้องเขียนเวอร์ชันให้ถูกต้องตามมาตรฐาน

โครงสร้างของ Semantic Versioning นั้นจะประกอบไปด้วย

  1. Major version: เป็นตัวเลขที่บอกถึงเวอร์ชันหลักของซอฟต์แวร์ เลขนี้จะเพิ่มขึ้นเวลาที่มีการอัพเดทใหญ่ อาจจะมีการยกเลิกหรือปรับวิธีการใช้งาน API บางอย่างของ major version ก่อนหน้า ดังนั้นในฐานะผู้ใช้งานซอฟต์แวร์นั้น ก็มักจะต้องตรวจสอบและทำการอัพเดทซอฟต์แวร์ของตัวเองตามไปด้วยเวลามีตัวไหนทำการอัพเดท major version
  2. Minor version: เลขนี้จะเพิ่มขึ้นเมื่อซอฟต์แวร์มีการเพิ่มหรือปรับอะไรบางอย่างโดยจะต้องเป็น "Backward Compatible Update" หรือก็คือเป็นการอัพเดทที่จะไม่ไปแก้ไขสิ่งที่มีอยู่เดิมที่จะทำให้มันเปลี่ยนวิธีการเรียกใช้งานไป เช่น จากเดิม React มี API ที่ชื่อว่า useState ให้ใช้ แต่พอมันทำการอัพเดท minor version มันดันไปเปลี่ยนชื่อเป็น useStateJa แบบนี้ก็จะไม่เป็น Backward Compatible Update
  3. Patch version: เมื่อซอฟต์แวร์มีปัญหา (เจอบัคนั่นแหละ) เลขนี้ก็จะเพิ่มขึ้นหากมีการอัพเดทเพื่อแก้ปัญหานั้น โดยที่จะต้องเป็น "Backward Compatible Update"
  4. Pre-release version: เป็นชุดตัวเลขหรือตัวอักษรที่บอกว่าเวอร์ชันนั้น ๆมันเป็นเวอร์ชันที่ยังไม่ stable สามารถถูกปรับเปลี่ยนได้ในเวอร์ชันถัด ๆไป ส่วนใหญ่แล้วมักจะมีคำว่า "alpha", "beta" หรืออื่น ๆ อยู่ด้วย
  5. Build metadata: เป็นชุดตัวเลขหรือตัวอักษรที่มักใช้ในการบอกข้อมูลของการ build ซึ่งจะไม่เกี่ยวข้องกับเวอร์ชัน ดังนั้นอาจจะมองว่ามันเป็นโน้ตเฉย ๆก็ได้
  • ผมอาจจะไม่ได้พูดถึงกฎหรือข้อกำหนดบางอย่างเพราะไม่ต้องการให้มีลายละเอียดเยอะจนเกินไปจนทำให้อ่านเข้าใจยาก แต่หากใครสนใจอ่านแบบเต็ม ๆ สามารถอ่านได้ ที่นี่

โครงสร้างของ Semantic Versioning เป็นอย่างไร

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

  1. [REQUIRED] สำหรับ major, minor และ patch versions นั้นจะเป็นส่วนที่ขึ้นก่อน ซ้ายสุดคือ major ตรงกลางคือ minor และขวาสุดคือ patch โดยทั้งสามประเภทจะถูกคั่นด้วยเครื่องหมาย "." เช่น 1.0.3 จะหมายความว่า เวอร์ชันนี้มี major version = 1, minor version = 0 และ patch version = 3
  2. [OPTIONAL] สำหรับ pre-release version นั้นจะต่อจาก patch version โดยจะคั่นด้วยเครื่องหมาย "-" ซึ่งจะเป็นชุดตัวเลขหรือตัวอักษรหรือเครื่องหมาย "-" ก็ได้ และสามารถมีเครื่องหมาย "." คั่นได้ด้วย เช่น 2.0.0-alpha, 2.0.0-beta1, 2.0.0-beta20, 3.3.0-beta.1.0.0, 3.3.0-beta-1.0.0
  3. [OPTIONAL] สำหรับ build metadata นั้นจะเป็นชุดตัวเลขหรือตัวอักษร ที่จะต่อจาก pre-release version อีกที แต่ถ้าหากไม่มี pre-release version ก็จะต่อจาก patch version โดยจะมีเครื่องหมาย "+" คั่น เช่น 1.0.0-rc+2340123Ad23, 2.0.0+12345

*สำหรับ pre-release version ที่มีเครื่องหมาย "." คั่นนั้น จะเรียกพวกตัวเลขหรือตัวอักษรที่ถูกคั่นว่า "Identifier" เช่น 1.0.0-alpha-1.3.0 จะมี Identifier ของ pre-release version ได้แก่ alpha-1, 3 และ 0

สามารถอ่านกฎฉบับเต็มได้ ที่นี่

ลำดับของ Semantic Versioning

แน่นอนว่าเมื่อเป็นเรื่องของเวอร์ชัน มันต้องมีลำดับก่อนหลังของแต่ละเวอร์ชัน สำหรับ Semantic Versioning นั้นจะมีวิธีการเรียงลำดับดังนี้

  1. Major, minor, patch และ pre-release versions เท่านั้นที่จะถูกนำมาคิดลำดับ สำหรับ build metadata จะไม่ถูกนำมาคิด
  2. ลำดับจะถูกกำหนดด้วยวิธีการเรียงลำดับของตัวเลข โดยจะเปรียบเทียบเลขในประเภทเดียวกัน เช่น 1.0.0 < 2.0.0, 1.3.0 < 2.1.0 < 1.2.333 < 1.3.0 และหากมีเลขในประเภทเดียวกันตรงกัน ก็จะเรียงลำดับจากประเภทของเวอร์ชันเดียวกันที่อยู่ถัดไปด้านขวา เช่น 1.1.0 < 1.2.0, 2.4.0 < 2.4.1
  3. pre-release version จะมีลำดับที่น้อยกว่าเสมอ เช่น 1.0.0-alpha < 1.0.0 (หลาย ๆคนอาจจะสงสัยว่า แล้วถ้าเป็น 1.0.0 กับ 2.0.0-alpha หละ ลำดับจะเป็นยังไง สำหรับเคสนี้ อยากให้อดทนรอไว้ก่อนนะ เดี๋ยวจะไปอธิบายในด้านล่าง)
  4. เมื่อเลข Major, minor และ patch versions เท่ากันและมี pre-release version วิธีเรียงลำดับของ pre-release version จะเป็นดังนี้
    1. identifier ของ pre-release version ที่ประกอบไปด้วยตัวเลขเพียงอย่างเดียว จะเรียงลำดับตามตัวเลข
    2. identifier ของ pre-release version ที่ประกอบไปด้วยตัวอักษรหรือเครื่องหมาย "-" จะถูกเรียงลำดับตามหลักพจนานุกรมด้วยลำดับของ ASCII
    3. identifier ที่เป็นตัวเลขจะมีลำดับต่ำกว่า identifier ที่ไม่ใช่ตัวเลข
    4. pre-release version ที่มี identifier มากกว่า จะมีลำดับที่สูงกว่า เมื่อ identifier ในลำดับก่อนหน้าเหมือนกัน

ตัวอย่างการเรียงลำดับ: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
*Note: จะมีเวอร์ชันอยู่รูปแบบหนึ่งซึ่งมักจะใช้กับซอฟต์แวร์ที่เพิ่งเปิดให้ใช้งาน ในช่วงนี้ซอฟต์แวร์จะมีการอัพเดทค่อนข้างบ่อย อาจจะมีการเปลี่ยนแปลงระหว่างเวอร์ชันค่อนข้างมาก เขาก็จะใช้เวอร์ชันเป็น "0.x.x" เพื่อบอกว่าซอฟต์แวร์ตัวนั้น ๆยังอยู่ในสถานะเริ่มต้น อาจจะยังไม่เสถียรและยังไม่แนะนำให้ใช้กับ production... (Fun Fact: React Native ที่คนใช้เขียนแอพขึ้น production มากันเต็มไปหมด จริง ๆแล้วยังเป็นเวอร์ชัน 0.x.x อยู่ 😂😂😂)

สามารถอ่านกฎการเรียงลำดับจากต้นฉบับได้ ที่นี่

การกำหนดเวอร์ชันให้แพ็คเกจที่จะใช้

พวก Package Managers หลาย ๆเจ้านั้นก็ได้มีการทำ Semantic Versioning มาใช้ ซึ่งเมื่อเราใช้งานเราสามารถกำหนดได้ว่าแพ็จเกจไหนจะเป็นเวอร์ชันอะไรตามหลักของ Semantic Versioning ผมจะขอยกตัวอย่างจาก NPM ซึ่งน่าจะเป็นตัวที่ค่อนข้างนิยมและหลาย ๆคนน่าจะเคยใช้งานกัน

"dependencies": {
    "my_dep_1": "1.0.0",
    "my_dep_2": "~2.2.0",
    "my_dep_3": "^2.2.0",
    "my_dep_4": "~3.0.0-alpha.1.1.0",
    "my_dep_5": "^4.0.0-beta.1.0.4"
},

จากตัวอย่างข้างบน จะเห็นว่าบางแพ็กเกจมันมีสัญลักษณ์ caret (^) หรือ tilde (~) อยู่ข้างหน้าเวอร์ชัน

  • Caret (^): เป็นการบอกว่า ให้ NPM ทำการติดตั้ง minor/patch version ที่มากที่สุด ณ ขณะนั้นเมื่อทำการสั่ง npm install เช่น
    "dependencies": {
        "my_dep_3": "^2.2.0"
    }
    
    เมื่อ my_dep_3 มีการ publish เวอร์ชัน "2.2.1" หรือ "2.3.0" มา npm ก็จะทำการติดตั้งเวอร์ชันพวกนั้นให้เราเมื่อเราทำการสั่ง npm install
  • Tilde (~): เป็นการบอกว่า ให้ NPM ทำการติดตั้ง patch version ที่มากที่สุด ณ ขณะนั้นเมื่อทำการสั่ง npm install เช่น
    "dependencies": {
        "my_dep_2": "~2.2.0"
    }
    
    เมื่อ my_dep_2 มีการ publish เวอร์ชัน "2.2.1" มา npm ก็จะทำการติดตั้งเวอร์ชันพวกนั้นให้เราเมื่อเราทำการสั่ง npm install แต่ถ้ามีการ publish เวอร์ชัน "2.3.0" มา มันก็จะไม่สนใจ เพราะ tilde จะสนใจแค่ patch version เท่านั้น

*เสริม: สำหรับ pre-release version นั้น มันจะไม่ถูกติดตั้งมาให้อัตโนมัติ เช่น

"dependencies": {
    "my_dep_2": "~2.2.0",
    "my_dep_3": "^2.2.0"
}

เมื่อ my_dep_2 หรือ my_dep_3 มีการ publish เวอร์ชัน "2.3.0-beta.1" มา npm ก็จะยังคงติดตั้เวอร์ชัน "2.2.0" มาให้เรา โดยไม่มองว่า "2.3.0-beta.1" เป็นเวอร์ชันใหม่ แต่ ๆ ๆ ถ้าเกิดเราเขียนแบบนี้

"dependencies": {
    "my_dep_4": "~3.0.0-alpha.1.1.0"
}

ถ้าเกิดว่า my_dep_4 มีการ publish เวอร์ชัน "3.0.0-alpha.1.1.1" หรือ "3.0.0-alpha.1.2.0" หรือ "3.0.1" มา เมื่อเราสั่ง npm install ตัว NPM ก็จะติดตั้งเวอร์ชันนั้น ๆให้เรา

ด้วยความที่เราสามารถกำหนดเวอร์ชันของแพ็กเกจแบบ dynamic ได้แบบนี้ มันก็มีทั้งข้อดีและข้อเสียตามมา

  • ข้อดี: เมื่อแพ็กเกจมีการแก้ไขข้อบกพร่องและทำการ publish เวอร์ชันแก้ไขมาแล้ว เราก็จะได้เวอร์ชันใหม่โดยอัตโนมัติเมื่อทำการสั่ง npm install ไม่ต้องคอยติดตามและทำการอัพเดทเวอร์ชันใหม่เอง
  • ข้อเสีย: อาจจะตามมาด้วยปัญหาที่เราคาดไม่ถึง เช่น อยู่ดี ๆซอฟต์แวร์ของเราก็มีปัญหา รันไม่ขึ้นทั้ง ๆที่เรายังไม่ได้อะไรกับมันเลย แต่พอลองตรวจสอบดูก็เจอว่าเพราะแพ็กเกจบางตัวมันถูกอัพเดทเวอร์ชันแล้วเวอร์ชันนั้นดันมีปัญหา อาจจะมีปัญหาด้วยตัวมันเองหรือมีปัญหากับอะไรบางอย่างกับซอฟต์แวร์ของเรา

ด้วยความที่มันมีทั้งข้อดีและข้อเสีย ดังนั้นจึงอยากให้ทุกคนใช้งานมันด้วยความเข้าใจครับ

*FYI: สำหรับ NPM นั้น เราสามารถดูว่าแพ็กเกจต่าง ๆที่เราใช้งานนั้น จริง ๆแล้วมันถูกติดตั้งเวอร์ชันอะไรมาให้เราได้ที่ไฟล์ package-lock.json ซึ่งมันจะบอกเลขเวอร์ชันแบบเป๊ะ ๆของแต่ละแพ็กเกจ รวมถึงว่าแต่ละอันมันมีการใช้งานแพ็กเกจอื่น ๆอะไรบ้างและเป็นเวอร์ชันอะไรด้วย ดังนั้น เราสามารถใช้ไฟล์นี้เป็นส่วนหนึ่งในการตรวจสอบเมื่อมีความผิดพลาดอะไรบางอย่างเกิดขึ้นได้ และสามารถใช้ทำเรื่อง Dependency Caching ได้เหมือนกัน ซึ่งไว้เดี๋ยวผมอาจจะหยิบเรื่องนี้มาเล่าในบล็อกถัด ๆไป

สรุป

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