YAML เยอะเกินไป
(noyaml.com)- แม้ YAML จะถูกใช้ราวกับเป็นมาตรฐานในงานตั้งค่า DevOps และ CI แต่เพราะมี การแปลงชนิดข้อมูลโดยปริยาย และความต่างของ parser ทำให้แม้เป็นค่าตั้งค่าเดียวกันก็อาจถูกตีความไม่ตรงอย่างที่คาด
- ใน YAML 1.1 ค่าอย่าง
NO,07,08,04:30,0666อาจถูกเปลี่ยนเป็น บูลีน·ตัวเลข·เวลา·เลขฐานแปด ได้ จึงต้องระบุเจตนาให้ชัดหากต้องการให้เป็นสตริง - ตัวอย่างจาก GitHub Actions, Kubernetes, CloudFormation และบริการ CI หลายเจ้า แสดงให้เห็นว่าไวยากรณ์ YAML และโครงสร้างเฉพาะของแต่ละบริการอาจนำไปสู่ การ commit ซ้ำๆ, การซ้อน escape หลายชั้น, และรูปแบบการเขียน job ที่กระจัดกระจายไม่เหมือนกัน
- ลิงก์อ้างอิงที่แนบมารวมทั้งเรื่อง YAML ที่รันได้, ความต่างของพฤติกรรมระหว่าง parser, รูปแบบการเขียนสตริงหลายบรรทัด, ปัญหาที่เวอร์ชัน
1.70ถูก parse เป็น1.7, และเหตุผลเชิงออกแบบของ StrictYAML - แม้จะมีการเสนอทางเลือกอย่าง Nickel, Dhall, CUE, Jsonnet แต่ตัวหน้าเว็บเองก็ยังถูกทำให้ดูเหมือน ช่องข้อความขนาดยักษ์ที่แก้ไขได้ เพื่อสานต่อการเสียดสีเรื่อง usability ของ YAML แบบตรงๆ
บทเสียดสี YAML ในฐานะภาษาสำหรับตั้งค่า DevOps
- แม้ YAML จะถูกใช้บ่อยในการตั้งค่า DevOps แต่หน้านี้สื่ออารมณ์ ความเหนื่อยล้ากับ YAML ผ่านประโยคทำนองว่า “ไม่มีใครอยากใช้ YAML หรอก”
- มีการไล่เหตุผลที่ใช้ YAML ในงาน DevOps แบบประชดประชัน
- เสียดสีว่ามันถูกคอมไพล์และพร้อม deploy อยู่ตลอดเวลา
- ประชดเรื่องที่ระหว่างพัฒนาไม่มีการบังคับจัดการข้อผิดพลาด แต่ปัญหากลับไประเบิดตอนรันจริงใน production
- พูดทำนองว่าข้อความ “มีบางอย่างพัง” ดีกว่า stack trace ที่มีเลขบรรทัด
- สะท้อนความไม่พอใจว่าต้องเผาเวลาไปกับการสร้าง CI pipeline ใหม่
- เสียดสีสถานการณ์ที่เพียงเพราะ Kubernetes ใช้ มันเลยถูกมองเป็นตัวเลือกที่ปลอดภัย
- พร้อมเอ่ยถึงข้อดีว่าอย่างน้อยก็รองรับคอมเมนต์ ไม่เหมือน JSON
กับดักจากการแปลงชนิดข้อมูลโดยปริยาย
- ใน YAML 1.1 ค่า
NOอาจถูก parse เป็น ชนิดบูลีนNO: Norwayอาจก่อปัญหาจากการตีความเป็นบูลีน แทนที่จะเป็นรหัสประเทศ- ถ้าต้องการให้เป็นสตริง ควรใส่เครื่องหมายอัญประกาศเช่น
"NO" - มีการชี้ว่าในสเปก YAML 1.1 มีวิธีเขียน
trueหรือfalseได้ถึง 22 แบบ
- ค่าที่ดูเหมือนตัวเลขก็อาจถูก parser แต่ละตัวรับไปต่างกัน
- ตัวอย่าง
07และ08แสดงให้เห็นว่าผลลัพธ์อาจออกมาเป็น[ 7, "08" ] - มีการเสียดสีว่า Kubernetes cluster จะ deploy ผ่านถึงตัวที่เจ็ด แล้วไปพังที่ตัวที่แปด
- ตัวอย่าง
- สตริงที่ดูเหมือนเวลาก็อาจถูกแปลงอัตโนมัติด้วย
04:30อาจไม่ถูกมองเป็นเวลาที่ผู้ใช้พิมพ์ แต่กลายเป็นค่า16200ซึ่งคือจำนวนวินาทีหลังเที่ยงคืน- ถ้าต้องการให้เป็นสตริง ต้องระบุชัดเช่น
!!str 04:30
- ความต่างของการเขียนเลขฐานแปดใน YAML ก็ทำให้งงได้เช่นกัน
- YAML 1.1 ใช้รูปแบบ
0666 - YAML 1.2 ใช้รูปแบบ
0o666 - และมีการพูดถึงการที่ Kubernetes ใช้ YAML 1.1 ราวกับเป็น “พิธีกรรมผ่านด่านของสาย DevOps”
- YAML 1.1 ใช้รูปแบบ
ปัญหาเรื่องเวอร์ชัน, SHA, และการจัดการสตริง
- เวอร์ชันของแพ็กเกจอาจถูก parse ราวกับเป็นเลขทศนิยม
foo: 1.7และbar: 1.70อาจถูกตีความว่าเป็นเวอร์ชันเดียวกัน- ขณะที่
fizz: 1.7.0และbuzz: 1.70.0อาจยังถูกมองเป็นสตริงเวอร์ชันคนละค่า
- Git SHA แบบสั้นที่ใช้ใน CI ก็อาจไม่ปลอดภัยเสมอไป
- SHA แบบ 8 ตัวอักษรอาจเป็นตัวเลขล้วนทั้งหมดได้
my.flaky_versionที่ใส่${GIT_SHORT_SHA}โดยไม่ใส่อัญประกาศ อาจไม่ถูกมองว่าเป็นสตริง- บทความยกว่าค่านี้จะเป็นสตริงประมาณ 98% และถ้าใส่ครอบแบบ
"${GIT_SHORT_SHA}"ก็จะเป็นสตริง 100%
- มีกรณี Rust toolchain อยู่ในลิงก์อ้างอิงด้วย
ต้นทุนที่โผล่ออกมาในงานตั้งค่า CI และโครงสร้างพื้นฐาน
- มีตัวอย่างว่าระหว่างเรียนรู้ GitHub Actions ผู้เขียน commit/push ไป 8 ครั้งในหนึ่งชั่วโมง และข้อความ commit สุดท้ายคือ “I don't really like yml”
- ยังมีตัวอย่างให้เห็นด้วยว่าถ้าเขียน SQL เป็น YAML จะหน้าตาเป็นอย่างไร
- โครงสร้าง SQL อย่าง
SELECT,FROM,WHERE EXISTS,AND,EQUALS,LTถูกเปลี่ยนให้กลายเป็นโครงสร้างซ้อนกันแบบ YAML - ใช้เพื่อเสียดสีว่าคำสั่ง SQL ที่สั้นกระชับ กลับกลายเป็น YAML ที่ยาวและเยิ่นเย้อ
- โครงสร้าง SQL อย่าง
- วิธีเขียน job และ step ของแต่ละบริการ CI ก็ไม่เหมือนกันอีก
- Azure DevOps ใช้รูปแบบ
jobsใต้job,steps,script - CircleCI ใช้รูปแบบ
jobs,job1,steps,checkout,run - ตัวอย่าง “ระบบ CI แห่งอนาคต” ก็ชี้ให้เห็นว่างานเดียวกันอาจถูกเขียนเป็นโครงสร้างซ้อนอีกแบบหนึ่งได้
- Azure DevOps ใช้รูปแบบ
- ใน CloudFormation หากใส่ฟังก์ชัน
SEARCHไว้ในDashboardBodyของ CloudWatch ก็มีตัวอย่างที่ต้อง escape ข้อความที่ escape มาแล้วอีกครั้ง และต้องครอบ JSON ทั้งก้อนด้วยเครื่องหมายอัญประกาศคู่
YAML ที่รันได้และความต่างของ parser
- คำว่า “executable yaml” ถูกโยงเข้ากับ ปัญหาความปลอดภัยในการ parse YAML
- ปัญหาความเข้ากันได้ของ YAML parser ก็มีแยกอ้างอิงไว้อีกชุด
- Every YAML parser is a custom YAML parser
- จัดวางไว้เพื่อชี้ให้เห็นว่าพฤติกรรมของ parser แต่ละตัวอาจไม่เหมือนกัน
แหล่งข้อมูลที่เกี่ยวข้องและทางเลือกอื่น
- มีการรวบรวมแหล่งอ้างอิงเกี่ยวกับปัญหาของ YAML ไว้ด้วย
- Today we’re going to look at some general problems with the YAML format
- We replaced 1,000 lines of YAML with 10 structs and people started contributing again
- What if you used the same language and tools you use to define your app to define your infrastructure?
- A YAML file is almost always still 'valid' even if it is trunca
- the bug was that the YAML parser ignored the negative signs ... so negative GPS coordinates became positive ones
- There are 63 different ways to write multi-line strings in YAML
- StrictYAML Design Justifications
- ในฐานะทางเลือกต่อ DevOps ที่ยึด YAML เป็นศูนย์กลาง มีการไล่ชื่อเครื่องมือและแนวทางหลายแบบ
ปฏิกิริยาต่อหน้าเว็บเอง
- รวมความเห็นจาก Reddit ที่วิจารณ์ไปถึงดีไซน์ของหน้าเว็บ
- มีความเห็นว่าเว็บไซต์ดูเหมือน ช่องข้อความขนาดยักษ์ที่แก้ไขได้
- มีความเห็นว่าลิงก์ไฮเปอร์ลิงก์กดไม่ได้
- มีมุกว่าปัญหาได้รับการแก้แล้ว เพราะสามารถเลือกข้อความทั้งหน้าแล้วลบทิ้งได้
- มีความเห็นว่าถึงจะเห็นด้วยกับแนวคิดที่ไม่ชอบ YAML แต่ก็ตั้งคำถามกับการตัดสินใจด้านดีไซน์ของเว็บไซต์
- ข้อความปิดท้ายระบุชัดว่าหน้านี้ตั้งใจทำให้ “ใช้งานได้พอๆ กับ YAML”
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เรื่องปวดหัวที่ผมชอบที่สุดคืออันนี้:
0708ผลลัพธ์กลายเป็น
[ 7, "08" ]เพราะสมมติฐานเกี่ยวกับ เลขฐานแปด และสตริง
สมมติฐานนี้ถูกพบใน YAML ที่ถูกสร้างจากเทมเพลตลงไปลึกสามชั้น และทำให้ คลัสเตอร์ k8s ทั้งหมดของเราใช้งานไม่ได้ แต่มีแค่คลัสเตอร์
08เท่านั้นที่พัง ส่วน 7 คลัสเตอร์ก่อนหน้านั้นยังทำงานได้ดี0กันแล้วอ่านคอมเมนต์นี้แล้วก็ขำอยู่ แต่พอคิดว่าในปี 2023 แทบไม่มีใครใช้เลขฐานแปดในไฟล์คอนฟิกแล้ว พฤติกรรมกับสมมติฐานแบบนี้ก็ไม่สมเหตุสมผลเลย ถ้าเป็นฐานสิบหกก็ยังพอว่า ฐานสิบยิ่งแน่นอนอยู่แล้ว แต่ฐานแปดนี่เกินไปหน่อย
ฝั่งที่สร้างมันขึ้นมาคงไม่ได้ serialize ข้อมูลด้วยไลบรารีแน่ ๆ เพราะถ้าใช้ไลบรารี มันก็คงแปลง type ให้เป็นรูปแบบที่ถูกต้องแล้ว
YAML มีปัญหาเยอะก็จริง แต่ผมคิดว่าปัญหาแกนหลักจริง ๆ คือการพยายามใส่ ลอจิกไว้ในคอนฟิก
ถ้าใช้ YAML กับข้อมูลเท่านั้น ไม่ใช้กับลอจิก มันก็เป็นหนึ่งในรูปแบบข้อมูลที่คนอ่านและเขียนได้ดี
CI/CD มักจะมีลอจิกอยู่บ้างเสมอ และแทบไม่มีกรณีไหนที่จบด้วย YAML ล้วน ๆ แถมยังมีเทมเพลตแปลก ๆ ปนเข้ามาอีก เลยคิดว่าน่าจะให้ API จริง ๆ สำหรับภาษาโปรแกรมจริงไปเลยดีกว่าไหม
ตอนใช้ Ant ที่อิง XML เมื่อ 15 ปีก่อนก็มีปัญหาแบบเดียวกัน และนั่นไม่ใช่ความผิดของ XML
ผมมองว่าปัญหาหลักของ YAML คือ ขาดความปลอดภัยด้าน type เช่น การเยื้องผิด คีย์พิมพ์ผิด สตริงที่ถูก parse เป็น boolean
นอกเหนือจากนั้น ผมคิดว่ามันเป็นรูปแบบที่ดี เพราะกระชับและมีสัญญาณรบกวนทางไวยากรณ์น้อยกว่ารูปแบบอื่นมาก ดังนั้นผมจึงสร้าง https://github.com/crdoconnor/strictyaml เพื่อให้ผู้คนใช้ YAML ที่ปลอดภัยด้าน type และได้รับข้อความ error ที่ชัดเจนทันทีสำหรับปัญหาแบบนี้
เพราะพวกเขาดึง abstraction เข้ามาโดยคิดว่าจำเป็น ดังนั้นผมจึงหลีกเลี่ยงการใช้ ภาษาโปรแกรมที่ Turing complete เป็นรูปแบบคอนฟิกเสมอ
ผมเคยใช้เวลาหลายชั่วโมงไล่ AWS CDK ทีละบรรทัดด้วย JavaScript debugger ถ้ามันเป็นไฟล์ yaml/JSON/อะไรก็ได้ที่เรียบง่ายและโง่ ๆ ก็คงไม่มีปัญหาแบบนั้น มันเป็นโปรเจกต์เล็ก ๆ และไม่จำเป็นต้องมีความซับซ้อน
ดังนั้นในคอนฟิกของเครื่องมือ JS ผมจึงชอบ JSON มากกว่า JS นี่ก็เป็นเหตุผลที่คอนฟิก webpack เละเทะ พอใช้ภาษาจริงได้ “เซนเซอร์ DRY” ของผู้คนก็ทำงาน แล้วทำให้มันซับซ้อนขึ้น
ถ้าเป็นแบบ declarative ก็ทำตามแนวปฏิบัติมาตรฐานได้ง่าย และการรองรับจากเครื่องมือก็ดีกว่า ถ้า package.json กลายเป็นเหมือน build.gradle จริง ๆ ก็คงแย่กว่านี้มาก
แค่กำหนดว่า “รันสคริปต์ใน Python interpreter ภายใต้ cgroup ที่จำกัดเข้มงวด แล้วผลลัพธ์ต้องเป็น dictionary ชื่อ
CONFIG” จากนั้นลอจิก wrapper ก็ serialize มันในแบบที่สะดวกต่อโปรแกรมเป้าหมายที่จะถูกตั้งค่าคล้ายกับการบ่นเรื่องความซับซ้อนหรือความแข็งตัวของ
helmHelm chart ไม่ได้เขียนตัวเองขึ้นมาเอง ผมว่าคงเพราะการบ่นแล้วเมินมันง่ายกว่าการทำความเข้าใจปัญหาและลงมือ implementในฐานะรูปแบบไฟล์ YAML มีทั้งข้อดีและข้อเสีย แต่ปัญหาจริง ๆ คือการพยายามแสดงสิ่งอย่างเงื่อนไข ลูป ฟังก์ชัน และคลาส/ซับคลาสผ่านเทมเพลต ด้วยรูปแบบไฟล์ที่เทียบเท่า JSON
YAML ใช้ได้กับคอนฟิกเล็ก ๆ แต่ทันทีที่ต้องมี control flow ไม่ว่าชนิดใด มันจะโตเป็นสปาเกตตีเฉพาะ vendor อย่างรวดเร็ว
Jinja ใน YAML นั้นผมมองว่าเป็น anti-pattern อย่างชัดเจน
คงเกิดจากตอนแรกไม่ได้ออกแบบให้มีความสามารถในการโปรแกรมมากพอ และภายหลังผู้คนก็ทำตามโปรเจกต์ที่เลือกทางนั้นแล้วประสบความสำเร็จ
ในบทความมีทางเลือกอย่าง Dhall และ Jsonnet อยู่แล้ว แต่ยังมีอีกสองอย่างที่น่าคิด
อย่างแรกคือทำไลบรารีตั้งค่าสำหรับภาษาโปรแกรมจริง ๆ แล้วให้ไลบรารีนี้สร้างไฟล์ตั้งค่า JSON ขึ้นมา JSON นั้นไม่ใช่สิ่งที่เอาไว้แก้ด้วยมือ แต่ถือเป็นผลลัพธ์ที่ตรวจสอบได้เท่านั้น ผู้ใช้จะนำการตั้งค่าในรูปแบบโค้ดที่มีเครื่องมือช่วยเหลือไปไว้ในระบบควบคุมเวอร์ชัน ข้อเสียและในเวลาเดียวกันก็เป็นข้อดีคือการแก้ด่วนบนเซิร์ฟเวอร์โดยตรงจะทำได้ยากขึ้น
อย่างที่สองคือ Starlark เป็นภาษาที่แตกแขนงมาจาก Python แบบไม่ Turing-complete ซึ่งเดิมพัฒนาขึ้นสำหรับระบบบิลด์ Bazel มี implementation หลายตัว และผมไม่รู้ว่าความเข้ากันได้ลึกแค่ไหน แต่ก็มี Python binding ด้วย: https://github.com/caketop/python-starlark-go, https://github.com/inducer/starlark-pyo3
{%และ{{ต่างก็เป็นอักขระของ YAML ทำให้ต้องครอบทุกจุดที่ใช้ด้วยเครื่องหมายคำพูดผมว่าแนวทางอย่าง
${{ของ GitHub Actions หรือ<%,<<ดีกว่ามาก แน่นอนว่า<<:เป็นไวยากรณ์ของ YAML จึงมีความเสี่ยงอยู่ แต่ไม่ใช่ Jinja ที่ถูกต้องตามไวยากรณ์ถ้าหมายความว่าไม่ควรใส่อะไรที่รันได้ลงใน YAML เลย ผมว่าเรือลำนั้นออกจากท่าไปแล้ว เพราะผู้คนค้นพบแล้วว่าวิธีปล่อยให้ส่วน literal เป็นค่าเริ่มต้น แล้วใส่ส่วนที่รันได้เป็นครั้งคราวนั้นเหมาะกับการสร้างคอนเทนต์ แบบเดียวกับ ASP/JSP/PHP
ถ้าอยากเริ่มสงครามความเห็นย่อยในเธรดนี้ ก็พูดถึง HCL กับ
for_eachได้ แต่คงดีกว่าถ้าอย่างน้อยก็ไม่ทำตรงนี้ผมไม่รู้ว่าส่วนไหนเป็นความผิดของ CDK และส่วนไหนเป็นเพราะ CloudFormation เดิมทีก็ไม่ค่อยดีอยู่แล้ว
ผมชอบ Jsonnet และ Starlark แต่ในทางปฏิบัติ use case ส่วนใหญ่ไม่ได้ต้องการภาษาโปรแกรมใหม่ ปกติเราแค่อยากสร้างเอกสารพื้นฐาน แล้วแก้ด้วยการ apply patch เท่านั้น แบบนั้นทุกอย่างจะง่ายลงมาก
ประสบการณ์การใช้ YAML ล้วน ๆ เองก็ไม่ได้แย่นัก ถึงรูปแบบจะมีจุดที่น่าสงสัยอยู่หลายอย่าง แต่ก็ใช้ได้ ปัญหาอยู่ที่ความซับซ้อนของวิธีแก้ปัญหาที่ต้องเพิ่มเข้าไปเพื่อปรับเอกสารให้เข้ากับหลาย environment มากกว่า
เคยได้ยินว่าเมื่อก่อนมี Python DSL จริง ๆ ที่ใช้แทน YAML ได้ แต่ดูเหมือนจะเลิกไปแล้ว ดังนั้นตอนนี้ loop กับ if-then จึงกลายเป็นก้อน YAML ยืดยาวน่าสยดสยอง และตีความ Jinja แบบคาดเดาไม่ได้โดยสิ้นเชิง
jsonnetหรือเปล่าผมมองว่า YAML เองนั้นยอดเยี่ยม สิ่งที่ไม่ยอดเยี่ยมคือเราไปทำให้ ส่วนการ deploy ของ CD ยากเกินไป
ผมยอมรับว่าการตั้งค่าของเราใน Azure DevOps ไม่ได้ดีเลิศอะไรนัก แต่ก็น่าประหลาดใจที่มีองค์กรซึ่งหลายทีม หรือผู้ดูแลระบบ 5–6 คน ต้องมาจัดการเครื่องมือแบบนี้ ถ้าเป็นที่อย่าง Google ก็ไม่รู้เหมือนกัน แต่สำหรับ enterprise ทั่วไปที่มีผู้ใช้พร้อมกันสูงสุด 50,000 คน หรือโดยมากน้อยกว่านั้นมาก ผมว่ามันเกินจำเป็น
ช่วงต้นทศวรรษ 2000 การ deploy เว็บแอป enterprise ลงบน IIS แบบ on-premises ที่มีคนทำงานเต็มเวลาน้อยกว่า 0.25 คนจัดการสารพัดอย่าง ทั้ง load balancing, networking ฯลฯ ยังง่ายกว่าการ deploy สิ่งเดียวกันลงบนการตั้งค่า “สมัยใหม่” ในตอนนี้
แน่นอนว่า pipeline สมัยใหม่ก็มีข้อดี มันช่วยข้ามปัญหา “บนเครื่องผมใช้ได้นะ” ไปได้มาก และยกระดับการควบคุมคุณภาพอย่างมากด้วย approval gate ที่ดีกว่าเดิม แต่การ deploy จริง ๆ แม้ในปี 2023 ก็ยังเป็นฝันร้าย
เรื่องนี้อาจไม่ใช่ปัญหาสำหรับโปรแกรมเมอร์ใน HN ที่ทำงานในบริษัทเทคโนโลยีจริง ๆ หรือบริษัทที่มีทีม DevOps เฉพาะทางเก่ง ๆ แต่ในโลก enterprise ที่ไม่ใช่สายเทค CI/CD ไม่เคยแย่เท่านี้มาก่อนในอาชีพของผม
จะโทษ YAML ก็ได้ หรือจะโทษว่าการทำอะไรสักอย่างต้องใช้ YAML มากเกินไปและ template ก็ยากก็ได้ แต่ในความเห็นผม ปัญหาเชิงองค์กร ใหญ่กว่าปัญหาทางเทคนิคมาก เครื่องมือ CD ควรอัตโนมัติกว่านี้มาก เพื่อไม่ให้กลายเป็นงานที่นักพัฒนาต้องอธิบาย infrastructure เป็น code
การที่มันทำได้เป็นเรื่องดี แต่ความจริงคือเรากำลังขอให้นักพัฒนาหลายล้านคน deploy infrastructure ที่พวกเขาอาจแทบไม่เข้าใจ ผมไม่เคยเห็นนักพัฒนาคนไหนที่ไม่อยากแค่ส่ง container ไป แล้วหวังให้ networking กับ “งานฝั่ง server” ถูกจัดการให้เอง
ถ้าไม่ทำแบบนั้น สุดท้ายก็จะมี VNET กับ subnet เต็มไปหมดที่ไม่มีใครเข้าใจดีว่ามันทำงานอย่างไร และองค์กรก็เสียเงินก้อนใหญ่เพราะนักพัฒนาไม่รู้ว่าสามารถทำด้วย
/xได้เราเขียน “job definition” บางชนิดแบบ offline ส่งไปยังระบบ shared แบบ proprietary รออยู่ใน queue แล้วรับ log file ที่ระบบซึ่งเราไม่ได้ควบคุมสร้างขึ้นมา เราไม่สามารถรันโค้ดของระบบ proprietary บน workstation แบบ local ได้ ทำให้รอบการ iterate ภายในสั้นสุดก็หลายสิบนาที ยาวหน่อยก็หลายชั่วโมงหรือหลายวัน
ไม่มีโหมด preview หรือ “what if” หรือ “dry run” แม้จะเรียกว่า “ทดสอบ” แต่เพราะมีระบบอยู่เพียงชุดเดียว จึงเท่ากับทำงานบน production ในทางปฏิบัติ
ปัญหาจริง ๆ ไม่ใช่ YAML ต่อให้ pipeline ถูกสคริปต์ด้วยภาษาโปรแกรมมิ่งของพระเจ้าเองก็คงไม่ต่างกัน
เหตุผลที่การพัฒนาซอฟต์แวร์บน workstation แทน mainframe แบบ time-sharing ศูนย์กลางได้รับความนิยมอย่างมาก ก็เพราะมันทำให้การ iterate ภายในเร็วขึ้นอย่างมหาศาล แยกขาดจาก production environment และคืนการควบคุมให้มือของนักพัฒนา
CI/CD pipeline รุ่นปัจจุบันโดยมากย้อนสิ่งเหล่านั้นกลับไปหมด
Kubernetes บนเครื่องเดียวช่วยนำข้อดีส่วนใหญ่ของการพัฒนาบน workstation กลับมาได้ แต่ยังเป็นระบบที่ใหม่มาก จึงมี growing pain เยอะ
ปัญหาที่เกี่ยวข้องคือ สำหรับนักพัฒนาที่ดูแลแอปเดียวด้วยการคลิกเอง มีทางออกที่ยอดเยี่ยม และสำหรับบริษัทขนาดมหึมาที่ทำ automation ระดับใหญ่สำหรับนักพัฒนาหลายพันคน ก็มีทางออกที่ยอดเยี่ยมเช่นกัน แต่พื้นที่ตรงกลาง ที่นักพัฒนา enterprise ไม่กี่คนต้องจัดการแอปหลายสิบตัว นั่นแหละคือความโกลาหลล้วน ๆ
มันเคยทำงานได้ดีหลายครั้งใน Docker image ของผม แต่พอเป็น image ที่ deploy แล้วกลับพังค่อนข้างบ่อย
นี่เป็นปัญหาที่จะผ่านไปได้ก็ต่อเมื่อ build และ deployment pipeline ทั้งหมดโปร่งใสสมบูรณ์ มีสิทธิ์เข้าถึง image repository อย่างเต็มที่ และควบคุม build instructions ได้จริง ๆ เท่านั้น เรื่องนี้มีข้อจำกัดพอ ๆ กับการควบคุม operating system ในเครื่องตัวเอง และผมคิดว่ามันจะล้มเหลวเท่ากับจำนวนองค์กรที่โค้ดเคยพังเมื่อย้ายไปอีกเครื่องหนึ่ง
การตั้งค่าให้เป็นแบบนั้นในระบบ CI/CD ไหน ๆ ก็ดูค่อนข้างตรงไปตรงมา
ผมคิดว่ามีทางออกที่จะรักษาสันติภาพได้ ถ้าทุกคนเคารพกฎเดียวกันอย่างสากล: อย่าใช้ YAML นอกระบบนิเวศของ Python
แบบนั้นคนที่ชอบรูปแบบสคริปต์ลึกลับซึ่งให้ความสำคัญกับความอ่านง่ายเหนือความถูกต้อง ความทนทาน และความดูแลรักษาได้ ก็ยังใช้แท็บ ไทป์แบบหลวม ๆ และไวยากรณ์ชวนงงต่อไปได้ ส่วนคนอื่น ๆ ก็ไม่จำเป็นต้องทำอย่างนั้น คนที่ชอบไวยากรณ์สไตล์ C จะยังรักษาสติไว้ได้
รู้สึกเหมือนเพิ่งชี้เข้าประเด็นหลักของปัญหาได้แล้ว สำหรับผมที่เป็นนักพัฒนาไวยากรณ์ C แล้ว ช่องว่างที่มีความหมายทางไวยากรณ์ คือความบ้าคลั่งล้วน ๆ ช่องว่างคือการจัดรูปแบบ ไม่ใช่ข้อมูลหรือคำสั่ง การจัดรูปแบบที่ดีช่วยได้และมีประโยชน์ และนักพัฒนาไวยากรณ์ C ที่ดีก็ใส่ใจกับการจัดรูปแบบให้อ่านง่ายเช่นกัน
ใน Python และ YAML การจัดรูปแบบคือข้อมูลคำสั่ง ข้อดีคือโค้ดทุกชิ้นที่ทำงานได้จะอ่านง่ายขึ้น แต่ทำไมโค้ดถึงต้องอ่านง่ายเสมอจึงจะทำงานได้?
ลองจินตนาการว่าทำงานกับเพื่อนร่วมงานแบบ YAML ดู คุณส่งข้อความยาว ๆ ไป แล้วเขาตอบว่า “อะไรนะ? อันนี้ไม่สมเหตุสมผลเลย” ปรากฏว่าสาเหตุคือคุณไม่ได้ใส่บรรทัดว่างระหว่างย่อหน้า ความหมายเลยพัง พอใส่บรรทัดว่างกลับเข้าไปแล้วส่งอีกครั้ง เขาถึงอ่านได้ นั่นหมายความว่าข้อมูลที่ส่งไปไม่มีความหมายถ้าไม่มีการจัดรูปแบบที่ถูกต้องตามไวยากรณ์
วิธีจัดรูปแบบมีมากมายเหลือเกิน และผมชอบให้คนใช้ linter เวลาเขียนโค้ด ถ้าเป็นไปได้ก็อยากให้เป็น linter เดียวกับที่ผมใช้
ภาษาเหล่านั้นบังคับโครงสร้างไวยากรณ์มาตรฐาน ซึ่งเป็นเรื่องดีเพราะลดวิธีเขียนโค้ดที่อ่านยากลง
แต่ตอนนี้ผมเริ่มคิดว่าความแตกต่างนั้นไม่ใช่เรื่องปรัชญา แต่เป็นเรื่องเครื่องมือ เครื่องมือบางอย่าง เช่น text editor หรือโปรแกรมอีเมล รองรับช่องว่างที่มีความหมายได้ดี แต่บางอย่างก็ไม่
text editor ที่ผมใช้ทั้งหมดตั้งค่าให้แสดงช่องว่างและแท็บ และแสดงทั้งสองอย่างต่างกัน ปกติเป็นจุดจาง ๆ กับขีดจาง ๆ ทำนองนั้น ผมชินแล้วจึงไม่รู้สึกรำคาญเลย
จากมุมมองของผม โค้ดไม่ใช่ข้อความตามอำเภอใจ เราใช้ฟอนต์ monospace ที่คงไม่ใช้ในหนังสือ และใช้สีแยกไวยากรณ์ จึงไม่มีเหตุผลที่จะไม่ทำให้ช่องว่างมองเห็นได้
ถึงอย่างนั้นผมก็ยังชอบภาษาที่ไม่มีช่องว่างที่มีความหมายมากกว่า แต่ก็ไม่ได้เกลียดภาษาพวกนั้น สำหรับผมมันไม่ใช่ปัญหาเลย
แต่ถ้าเครื่องมือที่คุณชอบไม่รองรับช่องว่างที่มีความหมายได้ดี ไม่ทำให้ช่องว่างมองเห็นได้ หรือถึงขั้นเขียนโค้ดด้วยฟอนต์ proportional คุณก็ย่อมเกลียดช่องว่างที่มีความหมายอย่างแรง และมองว่ามันเป็นความบ้าคลั่งล้วน ๆ
จุดที่ทำให้เปลี่ยนความคิดกลับเป็นตอนใช้ CoffeeScript อย่างไม่คาดคิด ผมไม่ค่อยชอบ JavaScript แต่ความรู้สึกเวลาใช้ CoffeeScript เหมือนเป็นการกลั่น The Good Parts ของ Crockford ออกมา ไม่สามารถเผลอสร้างส่วนแย่ ๆ ขึ้นมาได้
แถมวิธีที่ indentation กลายเป็นโค้ดก็ค่อนข้างสบาย ข้อไม่สะดวกเพียงอย่างเดียวคือใน Vi ไม่สามารถกด
%ที่วงเล็บปีกกาเปิดหรือปิดเพื่อหาอีกปลายของบล็อกได้ ในทางกลับกัน เพราะ indentation ถ้าโค้ดดูแปลก มันก็มักจะแปลกจริง ๆอย่างไรก็ดี ผมยังไม่ได้เรียน Python มากนัก ทุกวันนี้ใช้ TypeScript แต่ถ้าวันหนึ่งมี CoffeeTypeScript ออกมาละก็…
ผมเลยเริ่มสร้างรูปแบบชื่อ BCL เอง: https://github.com/wkhere/bcl
มันคงไม่ได้ช่วยทุก use case ของ YAML ได้ทันที แต่อย่างน้อยก็อาจเป็นวิธีที่เท่กว่าสำหรับกำหนด resource ในสไตล์คล้าย Terraform จริง ๆ แล้วมันช่วยเป็นตัวแทน HCL ในโปรเจกต์ภายในโปรเจกต์หนึ่งอยู่แล้ว และนั่นก็เป็นแรงผลักสุดท้ายที่ทำให้สร้างมันขึ้นมา
ถ้ามองภาพใหญ่ขึ้น ผมไม่รู้ว่าจะช่วยปัญหาที่ YAML มีอยู่ทุกหนทุกแห่งใน Kubernetes ได้อย่างไร ปัญหา
$daily_jobของผมมากกว่าครึ่งอยู่ที่การรวม Helm chart สุดท้ายจากหลายแหล่งที่มานั้นหยาบเกินไปไม่ได้หมายความว่า Helm เป็นเครื่องมือที่แย่โดยเนื้อแท้ หรือบริษัทเราเลือกใช้ Helm ในวิธีที่ค่อนข้างแย่ ผมคิดว่าทุกคนกำลังทำดีที่สุดตามสถานการณ์
แต่การจัดการ text template ที่มี ช่องว่างที่มีความหมาย นั้นผิดพลาดง่ายเกินไป และกว่าจะพบข้อผิดพลาดก็สายเกินไป ผมคิดว่า Kubernetes น่าจะดีกว่านี้มากถ้าใช้รูปแบบเฉพาะที่อิงไวยากรณ์สไตล์ C แทนที่จะพยายามพิสูจน์ว่า YAML ยอดเยี่ยมแค่ไหน โดยเฉพาะอย่างยิ่ง YAML ก็ไม่ได้ยอดเยี่ยมด้วยซ้ำ
อะไรก็ตามที่สร้าง JSON ได้ก็ใช้สร้าง YAML ได้
Nickel สามารถประเมินค่าออกมาเป็น JSON ได้: https://nickel-lang.org/
https://youtu.be/SEA1Qm8K4gY?feature=shared
ดูค่อนข้างคล้ายกัน
นี่คือ ผลของแพลตฟอร์มภายใน เมื่อแอปพลิเคชันใหญ่ขึ้น การตั้งค่าก็ขยายตาม จนสุดท้ายกลายเป็นภาษาโปรแกรมมิง แต่เป็นภาษาที่มีบั๊กเยอะ สเปกไม่พอ และใช้งานแย่มาก
ก็ประกาศล้มละลายด้านการตั้งค่า แล้วเลือกรูปแบบการตั้งค่าใหม่ จากนั้นก็วนซ้ำ
แน่นอนว่ารูปแบบเองก็ใช่ว่าจะไม่ต้องรับผิดชอบ ยิ่งยืดหยุ่นเท่าไรก็ยิ่งถูกนำไปใช้ซ้ำเป็นภาษาโปรแกรมมิงแย่ ๆ ได้ง่ายเท่านั้น
หลังจากทำพลาดแบบนี้ซ้ำแล้วซ้ำเล่า ทุกวันนี้สำหรับการตั้งค่าพื้นฐาน ผมจะเลือกรูปแบบการตั้งค่าที่เรียบง่ายที่สุดเท่าที่เป็นไปได้ แม้แต่
.iniก็อาจทรงพลังเกินไป ส่วน “การตั้งค่า” ที่ซับซ้อนกว่านั้นจะมอบให้ภาษาโปรแกรมมิงจริง ๆ จัดการ ถ้าเป็นไปได้ก็เป็นภาษาที่ใช้เขียนแอปพลิเคชันนั้นตัวอย่างส่วนใหญ่แบบท่วมท้นของ “YAML แย่” แก้ได้ด้วยการ ใส่เครื่องหมายคำพูดครอบ literal แปลก ๆ ทั้งหมด
จริงอยู่ที่ YAML บางทีก็น่ารำคาญ เช่น ลิสต์ของแมปจะเริ่มดูแปลกได้อย่างรวดเร็ว และช่องว่างที่มีความหมายแทบจะต้องมาทำให้สะดุดเข้าสักวันแน่ ๆ แต่บทความพวกนี้ ต่อให้มองในแง่ดี ก็รู้สึกว่าไม่ค่อยซื่อตรงนัก
ทั้งระบบนิเวศของ YAML ชักจูงให้เขียนค่าโดยไม่ใส่เครื่องหมายคำพูด ส่วนใหญ่ก็ทำงานได้ดี จนมีบางครั้งเท่านั้นที่พังพอให้สะดุดในโปรดักชัน
EDN เป็นซับเซตของ Clojure: https://github.com/edn-format/edn
ชัดเจน สตรีมได้ ขยายได้ และไม่อ่อนไหวต่อช่องว่าง แต่ก็มีแบบแผนการจัดรูปแบบเพื่อให้อ่านง่ายอยู่
แต่ไม่ค่อยเข้าใจว่าความแตกต่างเชิงความหมายระหว่างลิสต์กับเวกเตอร์คืออะไร ในหัวผม อาร์เรย์กับ linked list เป็นรายละเอียดการติดตั้งของโครงสร้างข้อมูลในโค้ด ไม่ใช่ความแตกต่างของรูปแบบข้อมูล
แต่ก็ไม่มี ลูกเล่นการเยื้องบรรทัดเชิงความหมาย เลย การที่คอมมาถูกถือเป็นช่องว่างและไม่จำเป็นต้องมีก็นับว่างดงาม
เมื่อนักศึกษาส่งงานผ่านแพลตฟอร์ม e-learning เราจะได้รับงานส่งทั้งหมดเป็นไฟล์ XML ขนาดค่อนข้างใหญ่
เราอ่านงานส่ง ส่งต่อไปให้ static analysis และการรันตัวอย่าง แล้วเขียนไฟล์ YAML สำหรับแต่ละงาน ซึ่งมีงานส่งทั้งหมด คำใบ้สำหรับการให้คะแนน คอมเมนต์และช่องกรอกคะแนน ฯลฯ
จากนั้นสร้างรายงาน สถิติ และ PDF ฟีดแบ็กจากไฟล์ YAML ผ่าน markdown+การเรนเดอร์ (Pandoc)
สำหรับเรา YAML เหมาะมาก เพราะเพิ่มฟีดแบ็กเพิ่มเติมด้วยไวยากรณ์ Markdown ได้ง่าย เช่น
- you missed a \NOT` here` ที่เยื้องถูกต้องด้วยวิธี escape ข้อความแบบบล็อกที่มีหลายแบบ แม้นักศึกษาจะใช้ delimiter ของ SQL หลากหลาย เราก็สามารถแสดงงานส่ง SQL ออกมาให้ดูดีได้โดยไม่ต้องใช้อักขระ escape
ทั้งหมดเป็น plain text จึงใช้แค่ text editor และเก็บไว้ใน git เพื่อให้มีความรับผิดชอบในการให้คะแนน เพราะเก็บทุกอย่างไว้ในรูปที่เครื่องอ่านได้ จึงยังทดสอบเครื่องมือ static analysis ใหม่ ๆ กับงานส่งในอดีตได้ด้วย
แต่พอต้องเขียน CI pipeline กับการตั้งค่า home automation ด้วย YAML เช่นกัน ก็เข้าใจความเจ็บปวดนั้น
ใน TOML ต้องยอมแพ้เรื่องการเยื้องสตริงหลายบรรทัดจนทำให้อ่านยากลง หรือไม่ก็ต้องใส่แบ็กสแลชท้ายทุกบรรทัด ซึ่งไม่มีทางไหนเหมาะเลย
ดังนั้นสำหรับ DSL หรือการตั้งค่าที่ต้องใส่ Markdown หรือรูปแบบข้อความอื่น ๆ เข้าไป YAML จึงค่อนข้างดี และได้เปรียบกว่าอย่าง TOML
แต่ผมคงไม่โยนความผิดทั้งหมดของ “ความเหนื่อยล้าจาก YAML” ให้เฉพาะเครื่องมือ CI และ DevOps ที่เลือก YAML เป็นรูปแบบพาหะสำหรับ DSL เท่านั้น อย่างที่บทความต้นฉบับสรุปไว้ดีแล้ว ตัว YAML เองก็มีปัญหาใหญ่เช่นกัน
“ปัญหา Norway” อันโด่งดังถูกแก้ใน YAML 1.2 แล้ว และปัญหาการ parse เลขที่มี 0 นำหน้าเป็นฐานแปดก็ถูกแก้ใน YAML 1.2 แล้วเช่นกัน การบังคับแปลงชนิดข้อมูลมากเกินไปสำหรับตัวเลข วันที่ เวลา ฯลฯ อาจทำให้สับสนได้ โหมดการจัดการสตริงหลายบรรทัดก็อาจค่อนข้างชวนงงเช่นกัน การ serialize ที่ไม่ปลอดภัยไม่ใช่ปัญหาใน parser สมัยใหม่ แต่เมื่อต้องใช้ YAML ในภาษาเก่าที่มีฟีเจอร์ไดนามิกอย่าง Ruby, Python, Java ก็ควรระวัง
ทั้งหมดนี้เป็นปัญหาของสเปก YAML เอง