กรณีตัวอย่างการประยุกต์ใช้ Hyrum's Law ใน Golang
(abenezer.org)- ใน
net/httpของโค้ดเบส Go มีคอมเมนต์ระบุว่า สตริงข้อผิดพลาด"http: request body too large"ที่MaxBytesError.Error()ส่งคืน ไม่สามารถเปลี่ยนได้เพราะ Hyrum's Law - Hyrum's Law คือหลักการที่ว่า เมื่อมีผู้ใช้ API มากพอ จะมีใครสักคนพึ่งพาพฤติกรรมที่สังเกตได้ แม้ไม่ได้อยู่ในสัญญาอย่างเป็นทางการก็ตาม
- แม้แต่สตริงที่ดูเล็กน้อยอย่างข้อความแสดงข้อผิดพลาด หากโค้ดภายนอกทำงานโดยยึดกับถ้อยคำที่ตรงเป๊ะ การเปลี่ยนแปลงก็อาจทำให้โค้ดเดิมพังได้ทันที
- ภายใน Go เองยังมีคอมเมนต์ลักษณะคล้ายกันใน
crypto/rsaและinternal/weakโดยกล่าวถึงความเสี่ยงที่พฤติกรรมของสตรีมสุ่มหรือความหมายเชิง semantics ที่ยังไม่กำหนดชัดจะถูกตรึงไว้ - เนื่องจากปัญหานี้ไม่ได้จำกัดอยู่แค่ Go, API หรือไลบรารีสาธารณะจึงควรออกแบบไม่ให้ พฤติกรรมที่ไม่ได้ตั้งใจ กลายเป็นมาตรฐานโดยพฤตินัย
Hyrum's Law ที่พบในโค้ด Go
MaxBytesError.Error()ในnet/http/request.goส่งคืนสตริงต่อไปนี้"http: request body too large"- ในคอมเมนต์ดังกล่าวเขียนไว้ว่า “Due to Hyrum's law, this text cannot be changed.”
- Hyrum's Law เป็นหลักการที่ตั้งชื่อตาม Hyrum Wright และคำจำกัดความจาก hyrumslaw.com คือดังนี้
- เมื่อมีผู้ใช้ API มากพอ ไม่ว่าสัญญาจะให้คำมั่นอะไรไว้ก็ตาม จะมีใครสักคนพึ่งพาพฤติกรรมที่สังเกตได้ทั้งหมดของระบบ
- แก่นของกรณี
MaxBytesErrorคือถ้อยคำที่ตรงเป๊ะของข้อความข้อผิดพลาดอาจถูกใช้ในโค้ดภายนอก- การเปลี่ยนถ้อยคำเพียงเล็กน้อยก็อาจทำให้โค้ดเดิมพังได้
- จากผลค้นหา
http: request body too largeพบโค้ดโอเพนซอร์ส Go ที่ใช้สตริงดังกล่าว
กรณีในแพ็กเกจ Go อื่นและโค้ดเบสภายนอก
-
การพึ่งพาสตรีมสุ่มของ
crypto/rsaEncryptOAEPในcrypto/rsa/rsa.goมีคอมเมนต์เกี่ยวกับ Hyrum's Law- ฟังก์ชันนี้ไม่ได้ให้คำมั่นว่าจะรันแบบ deterministic สำหรับสตรีมสุ่ม แต่เนื่องจากไม่ได้ใช้
MaybeReadByteจึงมีความเป็นไปได้ที่ใครบางคนจะพึ่งพาพฤติกรรมปัจจุบัน SignPSSในcrypto/rsa/pss.goก็มีคอมเมนต์ในบริบทเดียวกัน- ทั้งสองกรณีถูกมองว่าเป็นคำมั่นที่ยอมรับได้ เพราะมี ไบต์สุ่ม จำนวนที่นิยามไว้อย่างชัดเจนถูกรวมเข้าไปใน ciphertext หรือ signature ด้วยวิธีที่นิยามไว้อย่างชัดเจน
-
ความเสี่ยงที่ semantics ของ
internal/weakจะถูกตรึงไว้internal/weakระบุว่า toolchain ห้ามอย่างชัดเจนไม่ให้เข้าถึงแพ็กเกจนี้และฟังก์ชันอ้างอิงผ่านgo:linkname- semantics ของแพ็กเกจนี้ยังไม่ได้ผ่านกระบวนการเสนอ และหากเปิดเผยฟังก์ชันออกไป semantics เดิมอาจถูกตรึงไว้เพราะ Hyrum's Law
-
รูปแบบที่เกิดซ้ำแม้นอก Go
- การกล่าวถึง Hyrum's Law ไม่ได้จำกัดอยู่เฉพาะ Go
- สามารถดูกรณีจากหลายภาษาได้ใน ผลค้นหาหลายภาษา ของ grep.app
urllib.parseของ Python และarray.hของ Pixar OpenUSD ก็เป็นตัวอย่างโค้ดเบสที่เกี่ยวข้อง- วิวัฒนาการของ JavaScript ก็เชื่อมโยงกับกรณีที่การพึ่งพาพฤติกรรมแปลก ๆ และไม่ได้ตั้งใจจำนวนมาก กลายเป็นมาตรฐานโดยพฤตินัย
สิ่งที่ควรตรวจสอบก่อนเปลี่ยนแปลง
- เมื่อเปลี่ยนโค้ด ควรพิจารณาไม่เฉพาะ API ที่มีเอกสารกำกับ แต่รวมถึง พฤติกรรมที่สังเกตได้ ซึ่งโค้ดภายนอกอาจพึ่งพาด้วย
- จำเป็นต้องออกแบบระบบตั้งแต่แรกเพื่อลดความเป็นไปได้ที่ผู้ใช้จะพึ่งพาพฤติกรรมที่ไม่ได้ตั้งใจ
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
กฎของ Hyrum เป็นข้อสังเกตที่มีประโยชน์ แต่ไม่ควรยึดติดจนสรุปอะไรผิด ๆ
เวลารวมในการทำงานของฟังก์ชันก็เป็นคุณสมบัติที่สังเกตได้ ดังนั้นแม้แต่การปรับแต่งให้ฟังก์ชันเร็วขึ้นก็อาจมองได้ว่าเป็นการเปลี่ยนแปลงที่ทำให้พังได้ เพราะจู่ ๆ คิวอาจถูกล้างเร็วเกินไปจนเกิด deadlock ถึงอย่างนั้น ผู้ใช้ 99.99999999% ก็น่าจะชอบที่โค้ดเร็วขึ้นโดยไม่ต้องทำอะไรเลย
ท้ายที่สุดแล้ว อะไรคือ การเปลี่ยนแปลงที่ทำให้พัง ย่อมเป็นสัญญาทางสังคมมากกว่าสัญญาทางเทคนิค ไม่อย่างนั้นก็จะกลายเป็นว่าเปลี่ยนอะไรไม่ได้เลยจริง ๆ ผู้เขียนไลบรารีควรระบุให้ชัดใน API ว่าอะไรบ้างที่จะไม่เปลี่ยน ประพฤติตัวอย่างมีเหตุผล และเข้าอกเข้าใจผู้ใช้ ส่วนผู้ใช้ไลบรารีก็ควรเข้าใจว่าการเอาอินเทอร์เฟซที่ไม่ได้มีการระบุไว้เป็นการพึ่งพาหลักนั้นเป็นความรับผิดชอบของตัวเอง และควรเข้าอกเข้าใจผู้เขียนด้วย
แต่ถ้ามองอีกมุมหนึ่ง กฎของ Hyrum ไม่ใช่ทั้งสัญญาทางเทคนิคหรือสัญญาทางสังคม มันคือ คุณสมบัติทางเทคนิคเชิงเกิดใหม่ ที่ปรากฏในระบบซึ่งมีการใช้งานมากพอ
จะรับมือกับคุณสมบัตินี้อย่างไรขึ้นอยู่กับบริบททางสังคม ถ้าเป็นผู้ดูแล FOSS ก็จะปล่อยการปรับแต่งที่ทำให้ 99.99% เร็วขึ้น และมีเพียง 0.01% ที่ต้องแก้โค้ดหรือย้ายไปใช้ API ใหม่ ถ้าเป็นบริษัทเทคขนาดใหญ่ ก็ต้องทั้งปรับแต่งและต้องไม่ให้มีอะไรพังเลยแม้แต่ 0% ภายในบริษัท จึงต้องทำงานร่วมกับหลายทีมเพื่อหาจุดประนีประนอม ถ้าเป็นบริษัทซอฟต์แวร์องค์กร ต่อให้พังแค่ 0.1% แต่ถ้าผู้ใช้นั้นเป็นหนึ่งใน 5 สัญญาลูกค้าใหญ่สุด ก็จะไม่ปล่อย
เพราะคนเขียนเดิมเรียกฟังก์ชันอะซิงก์หลายตัวไว้ แล้วตั้งสมมติฐานว่าเมื่อรูทีนที่ช้าในอดีตตัวนั้นจบลง ฟังก์ชันเหล่านั้นก็คงเสร็จหมดแล้ว ใช้เวลานานมากกว่าจะหาว่าเกิดอะไรขึ้นกันแน่
เพราะงั้น PC จึงมี ปุ่มเทอร์โบ สำหรับลดความเร็ว และคอมพิวเตอร์ 8 บิตก็ไม่เพิ่มความเร็วอยู่นานถึง 10 ปี ทั้งที่มี CPU ที่เร็วกว่ามากแล้ว ทุกวันนี้แทบทุกอย่างทำงานบน CPU มากกว่าหนึ่งตัว ดังนั้นนอกจากกรณีที่แค่ต้องเร็วพอแล้ว แทบไม่มีใครพึ่งพาเวลาทำงานของฟังก์ชันอีก แม้แต่ในงาน embedded ก็พยายามหลีกเลี่ยงการพึ่งพาแบบนั้น หลังจากเคยเจอกรณี CPU เดี่ยวเลิกผลิต
ว่าทำไมถึงทำให้ HTTP Status 418 กลายเป็นการพึ่งพาหลักใน API ภายใน และภายใต้ข้อจำกัดที่มีอยู่ ทำไมมันถึงเป็นตัวเลือกที่แย่น้อยที่สุด
สภาพแวดล้อมที่รัน ภาระงานของระบบในขณะนั้น หรือการทำงานของ GC ล้วนมีผลได้ทั้งนั้น
สรุปคือ ผมไม่มองว่า พฤติกรรมเชิงเกิดใหม่ ที่เกิดจากเครื่องเป็นอินเทอร์เฟซที่ตั้งใจไว้หรือเป็นสัญญาในรูปแบบใด ๆ ดังนั้นถึงจะมีใครพึ่งพาพฤติกรรมที่ไม่ได้ตั้งใจไว้ ผมก็ไม่มองว่านี่เป็นการเปลี่ยนแปลงที่ทำให้พัง เหมือนกับที่การแก้บั๊กจุกจิกไม่ถือเป็นการเปลี่ยนแปลงที่ทำให้พัง
ในกรณีนี้ ดูจะเป็นหลักฐานมากกว่าว่า Go ทุ่มเทอย่างมากกับการรักษาความเข้ากันได้ย้อนหลัง
ฮ่าๆ คอมเมนต์ใน
crypto/rsaนั่นฉันเป็นคนเขียนเอง ใน Go เขาให้ความสำคัญกับ กฎของ Hyrum และการคงความเข้ากันได้ย้อนหลัง https://go.dev/doc/go1compat อย่างจริงจังมากตัวอย่างเช่น ในฟังก์ชัน
GenerateKeyหลายตัว จะมีการอ่านเพิ่มอีก 1 ไบต์จากสตรีมสุ่มด้วยMaybeReadBytehttps://pkg.go.dev/crypto/internal/randutil#MaybeReadByte เพื่อไม่ให้อัลกอริทึมถูกตรึงตายตัว เพิ่งเมื่อวานนี้ก็มีรายงานเข้ามาว่า private ECDSA key ที่มี public key เป็น nil เคยใช้ได้ในอดีตแต่ตอนนี้ใช้ไม่ได้แล้ว ซึ่งคงต้องทำให้กลับมาใช้ได้อีกครั้ง https://go.dev/issue/70468การวนลูปแมปใช้ลำดับแบบสุ่มเพื่อไม่ให้รายละเอียดการทำงานภายในรั่วออกมา เอาต์พุตของ
rand.Randถือเป็นส่วนหนึ่งของสัญญาความเข้ากันได้ จึงต้องทุ่มความพยายามพอสมควรกว่าจะปรับปรุงได้ https://go.dev/blog/randv2 https://go.dev/blog/chacha8randเราถกกันเสมอว่าควรเขียนคำรับประกันอะไรไว้ในเอกสาร และควรระบุพฤติกรรมไหนว่า “อาจเปลี่ยนได้” เพราะรู้ดีว่าสิ่งที่เอกสารระบุไว้แล้วจะเปลี่ยนไม่ได้เด็ดขาด และแม้แต่สิ่งที่ไม่ได้ระบุว่า “อาจเปลี่ยนได้” ก็มักจะเปลี่ยนได้ยากอยู่ดี https://go-review.googlesource.com/c/go/+/598336/comment/5d6...
ถึงอย่างนั้นก็ยังมองว่าเป็นการแลกเปลี่ยนที่คุ้มค่า ฉันใช้ Go เยอะและชอบการคงความเข้ากันได้ย้อนหลังแบบเข้มแข็ง แต่ถ้าการเพิ่มอิสระให้ทีม Go ปรับปรุงประสิทธิภาพและเพิ่มฟีเจอร์ได้มากขึ้น ต้องแลกกับสัดส่วน breaking change ที่สูงขึ้นเล็กน้อย ฉันก็ยินดีรับได้
พอมองไปที่นรกที่ผู้ใช้ใน ecosystem อื่นต้องเผชิญ อย่างเช่น Python ก็คิดว่านี่ไม่ใช่มุมมองของฉันคนเดียว
MaybeReadByteในฟังก์ชันGenerateKeyหลายตัวก็จริง แต่ดูเหมือน ed25519 จะไม่ได้ทำแบบนั้นก่อนจะมี
ed25519.NewKeyFromSeed()วิธีเดียวที่จะได้ public Ed25519 key จาก private key ก็คือวิธีนี้ และแทบจะแน่นอนว่าฉันเคยเขียนโค้ดที่พึ่งพาพฤติกรรมนี้ แม้จะไม่ค่อยชอบนัก แต่ตอนนั้นมันเป็นทางเลือกเดียว เลยจำได้ง่ายอย่างไรก็ดี เป็นเรื่องดีที่เอกสารของ
ed25519.GenerateKeyระบุชัดว่าเอาต์พุตเป็นแบบกำหนดได้แน่นอน ดูเหมือน Go จะทำได้ดีมากในการตรวจสอบและรักษาพฤติกรรมที่ตกผลึกไปแล้วใน API ด้านคริปโต พร้อมกับป้องกันไม่ให้มีพฤติกรรมใหม่ๆ ตกผลึกเพิ่มมันจะกลายเป็นเหมือน A20 line อันเลื่องชื่อ (https://en.wikipedia.org/wiki/A20_line) ที่ต้องแบกพฤติกรรมพังๆ นี้ไปตลอดกาล
วิธีแก้ปัญหาที่ถูกยกมาแบบเฉพาะเจาะจงคืออย่าใช้อะไรแบบ string-based error แต่ให้ใช้ sentinel error https://thomas-guettler.de/go/wrapping-and-sentinel-errors
ถ้ามองกว้างกว่านั้น ก็คือไม่ควรสร้างโค้ดที่ทำให้ผู้ใช้ API อยากพึ่งพาสตริงที่ไม่ใช่องค์ประกอบเชิงเทคนิคของภาษาแม้แต่น้อย หากใช้สิ่งที่เป็น first-class ของภาษา เช่น predefined error value, type หรือค่าคงที่ที่เก็บสตริงที่ไม่ใช่เชิงเทคนิค ผู้ใช้ API ก็จะเทียบค่าที่คืนมากับค่าคงที่ได้ แทนที่จะต้อง hardcode สตริงเอง
กฎของ Hyrum มีอยู่จริงแน่นอน แต่เราลดผลกระทบของมันได้
Grafana ซึ่งดูจากการค้นหาที่ลิงก์มาแล้วน่าจะเป็นต้นเหตุระดับบนสุด ควรใช้
errors.As(&http.MaxBytesError{})แทนการเปรียบเทียบสตริงใจความสำคัญของกฎของ Hyrum คือ ต่อให้ออกแบบ API มาดีแค่ไหนก็ไม่ได้ช่วยอะไร ผู้คนจะพึ่งพาพฤติกรรม ไม่ใช่สัญญา
คุณยังสามารถเขียนโค้ดแบบ
err.String() == "no more tea available."ได้อยู่ ฉันเห็นด้วยว่าไม่ควรทำแบบนั้น แต่ก็ไม่มีอะไรป้องกันไม่ให้ทำแถม
errors.Isก็เพิ่งถูกเพิ่มเข้ามาใน Go ได้ไม่นานนัก ดังนั้นในช่วงที่คนยังตรวจ error กันแบบนี้อยู่ การเช็กสตริงล้วนๆ จึงเป็นวิธีที่ง่ายกว่า ใน Go ผู้ให้บริการ API ไม่สามารถห้ามผู้ใช้ตรวจค่า.String()ที่คืนมาได้โดยเฉพาะใน standard library แทบไม่มีข้อแก้ตัวเลย
นี่แหละคือสิ่งที่เกิดขึ้นเมื่อคุณจงใจไม่สนใจประวัติศาสตร์ของภาษาโปรแกรม แล้วเลือกแนวทาง “ค่อยออกแบบไปพร้อมกับการสร้าง”
วิธีรับมือกับกฎของ Hyrum ก็เป็นหัวข้อที่น่าสนใจเช่นกัน
ความเป็นไปได้อย่างหนึ่งคือใส่ ความสุ่ม ลงไปในส่วนที่เราไม่อยากให้คนพึ่งพา
ถ้าจำไม่ผิด โปรโตคอล QUIC ก็ทำแบบนี้ มีฟิลด์ที่ไม่ได้ใช้ในเวอร์ชันปัจจุบันอยู่ แต่เพื่อไม่ให้เราเตอร์เริ่มใช้ฟิลด์นั้นในการระบุแพ็กเก็ต สเปกจึงกำหนดให้ตั้งค่าเป็นค่าสุ่มแทนที่จะเป็น null byte
แหล่งอ้างอิงน่าจะอยู่ที่นี่: https://www.rfc-editor.org/rfc/rfc9000#section-17.2.1
“ค่าของฟิลด์ Unused จะถูกตั้งโดยเซิร์ฟเวอร์เป็นค่าใดก็ได้แบบสุ่ม ไคลเอนต์ต้องเพิกเฉยต่อค่าของฟิลด์นี้เสมอ [...] โปรดทราบว่า QUIC เวอร์ชันอื่นอาจไม่มีคำแนะนำแบบเดียวกันนี้”
เข้าใจว่าสิ่งนี้เรียกว่า greasing และมีไว้เพื่อป้องกัน ossification
https://www.rfc-editor.org/rfc/rfc8701.html
ร่างแรกสุดของ RFC นี้ย้อนกลับไปได้ถึงกลางปี 2016 และน่าจะเป็นช่วงที่คำนี้ปรากฏต่อสาธารณะครั้งแรก: https://datatracker.ietf.org/doc/html/draft-davidben-tls-gre...
ไม่มีอะไรแย่ไปกว่าการตื่นขึ้นมาอีก 10 ปีให้หลังแล้วพบว่าบิตพวกนั้นจำเป็นจริง ๆ แต่เราเตอร์ 20 รุ่นจาก 10 แบรนด์ดันตัดสินไปแล้วว่าบิตเหล่านั้นต้องมีรูปแบบเฉพาะเท่านั้น
ถ้าฝั่งตรงข้ามมี checksum หรือการเข้ารหัสอยู่ด้วย จนบิตเสียหายแล้วทุกอย่างพัง ก็ถือว่าได้คะแนนเพิ่ม พวก “แฮ็กอัจฉริยะ” ของ middlebox นี่ปวดหัวจริง ๆ
นี่เป็นตัวอย่างที่ดีของซอฟต์แวร์แบบ stringly typed
ผู้ออกแบบ Go ไม่ต้องการ exception แต่ก็ยังมีสิ่งคล้ายกันผ่าน
panic/recoverและ error ที่ไม่มี type ก็เป็นโทษ ในทางกลับกันจะจัดการกับ error ที่มี type โดยไม่มี pattern matching ได้อย่างไร?catchของภาษาส่วนใหญ่ก็เป็น pattern matching แบบพื้นฐานอยู่แล้วhttps://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
เมื่อก่อนที่ที่ทำงานเก่า ฉันเคยเจอ คำสะกดผิด ในข้อความ error แล้วแก้ไขมัน แต่ต่อมาก็พบว่าเครือข่ายการพึ่งพาข้อความสะกดผิดนั้นลึกมากจนแทบแก้จริงไม่ได้ สุดท้ายเลยต้องย้อนกลับไปใช้ข้อความที่สะกดผิดเหมือนเดิม
จนถึงตอนนี้ก็ยังคาใจอยู่
https://en.wikipedia.org/wiki/HTTP_referer
นี่ก็เป็น Hyrum's Law แบบหนึ่ง แต่ในทางปฏิบัติก็คือ ความเป็น Go แบบ Go
ถ้า error เป็น enum type ฝั่งผู้ใช้ก็อาจเปลี่ยนได้ด้วยการแทนที่สตริงอย่างเดียว แต่เมื่อใช้สตริงราวกับเป็น type ก็จะไม่มีทางรู้ว่าผู้ใช้พึ่งพามันอย่างไรอยู่บ้าง แค่ไปตรวจ 6 ตัวอักษรกลางข้อความ error แล้วเปลี่ยน ก็อาจพังได้
ทั้งที่ภาษอื่น ๆ ใช้ทางเลือกที่ดีกว่ามานานหลายสิบปีแล้ว แต่นี่ก็เป็นอีกหนึ่งการตัดสินใจออกแบบที่น่ากลัวและล้าสมัย ความผิดพลาดช่วงต้นที่แก้ไม่ได้ในภายหลังจะผูกมัดไปตลอดกาล
น่าสนใจที่กฎนี้ตรงข้ามกับ หลักความทนทาน หรือกฎของ Postel อย่างพอดี
“เข้มงวดเมื่อส่ง ใจกว้างเมื่อรับ”
ถ้าคุณรับอินพุตอย่างใจกว้าง คุณต้องเข้าใจว่าตัวเองใจกว้างในรูปแบบไหน และอย่างน้อยก็บันทึกไว้ภายในองค์กร เพราะด้วย Hyrum's Law หลังจากเปลี่ยนโค้ดเบสขนาดใหญ่แล้ว คุณจะต้องรองรับวิธีเหล่านั้นทั้งหมดตลอดไป
นั่นจึงเป็นเหตุผลที่ฉันพยายามไม่สร้าง API ที่ “ใจกว้างกับสิ่งที่รับเข้า”
ถ้าคุณตั้งเกณฑ์ข้อมูลที่ API รับให้หลวม สุดท้ายคุณก็ต้องตัดสินใจว่าจะนวดข้อมูลนั้นให้กลายเป็น รูปแบบมาตรฐาน แบบไหน และการตัดสินใจนั้นก็มักลงเอยด้วยพฤติกรรมที่ผู้ใช้รู้สึกว่าแปลกใจในทางใดทางหนึ่งแทบทุกครั้ง
ดูเหมือนผู้เขียนแพ็กเกจแต่ละคนจะยอมรับปัญหานี้ไม่เท่ากัน ไม่กี่วันก่อนฉันเห็นคอมเมนต์นี้ในแพ็กเกจ
jsonisValidNumberรายงานว่าsเป็น JSON number literal ที่ถูกต้องหรือไม่isValidNumberควรเป็นรายละเอียด implementation ภายใน แต่แพ็กเกจที่ใช้งานแพร่หลายกลับเข้าถึงมันผ่านlinknameสมาชิกตัวเด่นใน hall of shame รวมถึง
github.com/bytedance/sonicนี่คือสิ่งที่เรียนรู้จากการปล่อย API
ไคลเอนต์จะทำทุกอย่างที่จำเป็นเพื่อให้งานตัวเองเสร็จ แม้ว่าจะไม่ใช่วิธีที่ผู้เผยแพร่ตั้งใจไว้ก็ตาม ไคลเอนต์ไม่อ่านเอกสารประกอบ ถ้ามีไคลเอนต์มากพอพึ่งพาพฤติกรรมใดพฤติกรรมหนึ่ง บั๊กก็กลายเป็นส่วนหนึ่งของ API จำนวนครั้งที่เรียก API ไม่ได้สอดคล้องกับความสำคัญเสมอไป
เพราะอย่างนั้นเวลาพัฒนา API ฉันจึงพยายามออกเบต้า API ให้เร็วที่สุดเพื่อดูว่าคนเอาไปใช้อย่างไรและลดเรื่องไม่คาดคิดลง โดยส่วนใหญ่ก็จะรองรับเวอร์ชันก่อนหน้าควบคู่กับการเพิ่ม major version ซึ่งจะทำแบบนั้นได้ก็ต้องนิยาม SLA ของ API ให้ชัดเจน