บ่ายวันเสาร์ คุณนั่งลงพร้อมกาแฟ ตั้งใจเต็มที่ว่าจะจัดการฟีเจอร์ใหม่ให้เสร็จ หรือไม่ก็ขัดเกลาโปรเจกต์เสริมสักหน่อย ผ่านไปสิบนาที ทุกอย่างก็หยุดชะงัก ไม่ใช่เพราะตรรกะซับซ้อนเกินไป ไม่ใช่เพราะคุณไม่เข้าใจเฟรมเวิร์ก แต่ความคืบหน้าหยุดลงเพียงเพราะมีแท็ก (tag) เพียงแท็กเดียวที่เปิดค้างไว้
นั่นคือสิ่งที่เกิดขึ้นกับความท้าทายในสุดสัปดาห์นี้พอดี ข้อผิดพลาดทางไวยากรณ์ (syntax error) ของ Liquid แท็กไม่ได้ถูกปิดอย่างถูกต้อง ตัว parser อ่านไฟล์ไปจนถึงจุดที่มันคาดหวังลำดับการปิด แต่กลับไม่พบอะไรเลย และแล้ว build ก็ล้มเหลว มันเป็นบั๊กประเภทที่ทำให้แม้แต่เหล่านักพัฒนาที่มีประสบการณ์ต้องถอดใจ และอาจทำให้มือใหม่ตกอยู่ในวังวนของความไม่มั่นใจในตัวเอง ทั้งที่ความจริงแล้วเมื่อคุณเห็นมัน การแก้ไขนั้นใช้เวลาเพียงไม่กี่วินาทีเท่านั้น
เบื้องหลังสิ่งที่เกิดขึ้น
Liquid คือภาษาเทมเพลต (templating language) ที่สร้างโดย Shopify และเป็นขุมพลังให้กับทุกอย่าง ตั้งแต่หน้าร้าน e-commerce ไปจนถึงบล็อกที่ใช้ Jekyll บน GitHub Pages มันอาศัยรูปแบบไวยากรณ์หลักสองแบบ ปีกกาคู่ {{ }} ใช้สำหรับการแสดงผล (output) เช่น {{ page.title }} ส่วนปีกกาที่มีเครื่องหมายเปอร์เซ็นต์ {% %} ใช้สำหรับตรรกะและการควบคุมลำดับการทำงาน (flow control) เช่น {% if user %} หรือ {% for item in list %}
ทุกแท็กที่เปิดต้องมีคู่เสมอ {% if %} ต้องการ {% endif %} ลูป {% for %} ต้องการ {% endfor %} บล็อก capture ต้องการ {% endcapture %} สิ่งเหล่านี้ไม่ใช่แค่คำแนะนำ เพราะ Liquid engine จะอ่านเทมเพลตของคุณตามลำดับ เมื่อมันพบโครงสร้างที่เปิดขึ้น มันจะดันเฟรม (frame) ลงใน stack ภายในและรอ หากไฟล์จบลง หรือหากมีบล็อกหลักอื่นปิดลงก่อนที่แท็กที่คาดหวังจะปรากฏขึ้น engine จะแจ้งข้อผิดพลาด ข้อความมักจะตรงไปตรงมาว่า: tag was not closed correctly. ระบบคาดหวังลำดับการปิด บางครั้งคุณจะได้เลขบรรทัด บางครั้งเลขบรรทัดนั้นก็ชี้ไปยังจุดที่ผิด เพราะ parser จะรู้ตัวว่าขาดคู่ก็ต่อเมื่อมันได้อ่านทุกอย่างที่อยู่ด้านล่างนั้นไปหมดแล้ว
ลองดูตัวอย่างที่เป็นรูปธรรม คุณอาจเขียนอะไรบางอย่างแบบนี้:
{% for product in collections.all.products %}
<div class="card">
<h2>{{ product.title }}</h2>
{% if product.available %}
<span>In stock</span>
{% endif %}
</div>
{% endfor %}
แท็กทั้งสามถูกปิดเรียบร้อยแล้ว ทีนี้ลองจินตนาการว่าคุณกำลังเขียนอย่างรวดเร็ว คัดลอกและวางโค้ดจากเอกสารประกอบ แล้วคุณเผลอทำตัว r ตัวสุดท้ายหล่นหายไป:
{% for product in collections.all.products %}
<div class="card">
<h2>{{ product.title }}</h2>
{% if product.available %}
<span>In stock</span>
</div>
{% endfo %}
หรือบางทีคุณอาจจะลืม {% endfor %} ไปเลย เพราะมันอยู่ใต้โค้ด HTML ยาวเหยียด Engine จะเห็น {% for % ลงทะเบียนลูปไว้ แต่ไม่เคยพบคู่ของมัน ในบริบทของ Shopify นี่หมายความว่าธีมทั้งหมดจะไม่สามารถคอมไพล์ได้ ใน Jekyll, GitHub Pages จะส่งอีเมลแจ้งเตือน build failure มาให้คุณ ส่วนการพัฒนาในเครื่อง (local development) อาจจะพ่น stack trace ที่อ่านไม่รู้เรื่องออกมา แท็กที่ถูกลืมเพียงแท็กเดียวสามารถหยุดทั้ง pipeline ได้เลย
ความร้ายกาจของความผิดพลาดเล็กน้อย
ข้อผิดพลาดเหล่านี้ชวนให้หงุดหงิดใจ เพราะขนาดของผลกระทบไม่ได้แปรผันตามขนาดของความผิดพลาด คุณไม่ได้ออกแบบฐานข้อมูลผิด คุณไม่ได้เลือกอัลกอริทึมที่ผิด คุณแค่ลืมตัวอักษรเพียงตัวเดียว ความผิดพลาดเล็กน้อยก่อให้เกิดบั๊กขนาดใหญ่ได้ {% endif %} ที่หายไปนั้นไม่ได้ทำให้โค้ดพังแค่บรรทัดเดียว แต่มันจะส่งผลกระทบต่อเนื่องเป็นทอดๆ ตัว parser ที่ตอนนี้สับสนว่าเงื่อนไขสิ้นสุดตรงไหน อาจตีความทุกบรรทัดที่อยู่ด้านล่างว่าผิดรูปแบบ สิ่งที่ดูเหมือนเทมเพลตยี่สิบบรรทัด อาจกลายเป็นการแสดงผล error ออกมาถึงหกสิบบรรทัด และส่วนใหญ่ก็เป็นข้อมูลที่ทำให้เข้าใจผิดด้วย
คุณต้องเผชิญกับข้อผิดพลาดเหล่านี้เมื่อลืมตัวอักษรเพียงตัวเดียว และสมองของคุณแทบจะไม่เคยเตรียมพร้อมสำหรับความจริงข้อนั้นเลย มนุษย์อ่านโค้ดผ่านการจดจำรูปแบบ (pattern recognition) เรามองเห็นเจตนา เราเห็น if และตรรกะที่สอดคล้องกัน แล้วเราก็อนุมานขอบเขตเอาเอง แต่คอมพิวเตอร์ไม่ได้อนุมาน มันอ่านทีละตัวอักษร จากบนลงล่าง โดยไม่มีความยืดหยุ่นต่อความคลุมเครือเลย เมื่อมันอ่านจนถึงท้ายไฟล์แล้วยังคงรอแท็กคู่ของมันอยู่ มันก็จะยอมแพ้ งานของคุณคือการฝึกเป็นนักพัฒนาที่คิดแบบเดียวกับ parser ให้ได้นานพอที่จะมองเห็นช่องว่างนั้น
เรื่องนี้ไม่ได้เกิดขึ้นแค่กับ Liquid เท่านั้น ไม่ว่าจะเป็นวงเล็บที่ปิดไม่สนิทใน Python, เครื่องหมาย backtick ที่หายไปใน Markdown, ปีกกาที่ลืมใส่ใน JavaScript หรือเครื่องหมาย angle bracket ที่ค้างอยู่ใน HTML ความท้าทายในสุดสัปดาห์นี้ใช้ Liquid เป็นสื่อการสอน แต่บทเรียนพื้นฐานนี้สามารถนำไปใช้ได้กับทุกภาษาที่คุณจะได้สัมผัส ไวยากรณ์ (syntax) ก็คือหลักภาษา (grammar) และหลักภาษาก็ไม่มีความปรานี
วิธีการตามล่าหาพวกมัน
เมื่อคุณเจอทางตันแบบนี้ สัญชาตญาณแรกคือการอ่านไฟล์ทั้งหมดด้วยความลนลาน จงห้ามใจไว้ การอ่านด้วยความลนลานจะทำให้คุณมองข้ามตัวอักษรที่คุณพลาดไป เพราะสมองของคุณจะทำการแก้ไขคำผิดให้โดยอัตโนมัติ แทนที่จะทำแบบนั้น ให้ทำงานอย่างเป็นระบบ
จับคู่แท็กของคุณให้ชัดเจน ไล่ดูไฟล์และระบุชื่อแท็กเปิดทุกตัวออกมาดังๆ หรือจดลงกระดาษ for ต้องมี endfor, if ต้องมี endif, unless ต้องมี endunless, และ capture ต้องมี endcapture หากคุณมีการซ้อนบล็อก (nesting blocks) ให้เพิ่มตัวนับในใจ เมื่อฉันเปิด if ไว้ข้างใน for นั่นหมายความว่าฉันมีสิ่งที่ต้องจัดการให้ครบถ้วนสองอย่างก่อนจะจบไฟล์
ใช้เครื่องมือแก้ไขของคุณ หากคุณทำงานกับ Liquid เป็นประจำ ให้ติดตั้ง syntax highlighter ที่รองรับไวยากรณ์นี้ Visual Studio Code มีส่วนขยาย (extensions) ที่จะช่วยทำให้แท็ก Liquid ดูจางลงหรือเปลี่ยนสีตามโค้ด เมื่อแท็กปิดผิดรูปแบบ รูปแบบสีจะเปลี่ยนไป Linter บางตัวสามารถตรวจจับบล็อกที่ปิดไม่สนิทได้ก่อนที่คุณจะกดคอมไพล์เสียอีก ใน Vim หรือ Neovim ให้ลองใช้ปลั๊กอินอย่าง vim-liquid หรือตั้งค่า Tree-sitter เพื่อไฮไลต์แท็กที่จับคู่กัน เครื่องมือเหล่านี้ไม่ได้มาแทนที่การใช้ความคิด แต่ช่วยให้เห็นจุดที่ไม่ตรงกันได้ชัดเจนขึ้น
ใช้การค้นหาแบบ Binary Search กับเทมเพลตของคุณ หากข้อความแจ้งข้อผิดพลาดชี้ไปที่บรรทัดที่ 200 แต่ดูเหมือนไม่มีอะไรผิดปกติที่นั่น ตัวการที่แท้จริงน่าจะอยู่ด้านบน ลองคอมเมนต์ครึ่งล่างของเทมเพลตทิ้งไป แล้วดูว่ามัน build ผ่านไหม? ถ้าผ่าน แสดงว่าข้อผิดพลาดอยู่ในส่วนที่คอมเมนต์ไว้ จากนั้นให้เอาคอมเมนต์ออกครึ่งหนึ่งของส่วนนั้น แล้วทำซ้ำไปเรื่อยๆ จนกว่าจะแยกส่วนที่เสียออกมาได้ วิธีนี้อาจดูช้า แต่มันเร็วกว่าการต้องมานั่งอ่านสองร้อยบรรทัดเดิมซ้ำๆ ถึงหกครั้งในขณะที่ความหงุดหงิดของคุณเพิ่มขึ้นเรื่อยๆ
ตรวจสอบส่วน include ของคุณ Liquid รองรับการแยกส่วนประกอบ (modular fragments) ผ่าน {% include %} หรือ {% render %} แท็กที่ปิดไม่สนิทอาจไม่ได้อยู่ในไฟล์หลักเลยก็ได้ แต่มันอาจจะอยู่ใน snippet ที่เทมเพลตหลักดึงมาใช้งาน นี่คือจุดที่ version control จะช่วยรักษาความสงบทางจิตใจของคุณไว้ ให้ลองรัน diff ดูว่ามีอะไรเปลี่ยนแปลงไปบ้างนับจากการ build ครั้งล่าสุดที่สำเร็จ บ่อยครั้งที่คำตอบจะปรากฏให้เห็นชัดเจนผ่านสีแดงและสีเขียว
การย่อหน้าคือการทำเอกสาร หาก {% if %} ของคุณเริ่มที่คอลัมน์ศูนย์ แต่ {% endif %} ที่คู่กันกลับถูกย่อหน้าเข้าไปอยู่ในโครงสร้างที่ซ้อนกัน การจัดวางตำแหน่งทางสายตาจะช่วยให้คุณสังเกตเห็นความไม่สอดคล้องกันได้ หากแท็ก HTML และ Liquid ใช้รูปแบบการย่อหน้าแบบเดียวกัน สายตาของคุณจะตรวจพบแท็กคู่ที่วางอยู่ในระดับความลึกที่ผิดพลาด
หลักสูตรที่แท้จริง
ความท้าทายในช่วงสุดสัปดาห์นั้นสำคัญ เพราะมันจำลองสภาวะเดียวกับที่คุณต้องทำงานจริงๆ ไม่มีผู้จัดการคอยจับตาดู ไม่มีเดดไลน์มาบีบคั้น คุณกำลังเขียนโค้ดเพื่อฝึกทักษะหรือเพื่อความสนุก แล้วจู่ๆ ข้อผิดพลาดเพียงเล็กน้อยก็ทำให้คุณต้องหยุดชะงัก วินาทีนั้นแหละคือบทเรียน คุณไม่ได้เรียนรู้วิธีการ debug จากการอ่านเรื่องการ debug แต่คุณเรียนรู้จากการจ้องมอง build ที่พังในขณะที่คุณอยากจะออกไปข้างนอกมากกว่า โดยการบังคับตัวเองให้มองว่าข้อความแจ้งข้อผิดพลาดคือข้อมูล ไม่ใช่คำตำหนิ
จงเรียนรู้วิธีแก้ไขข้อผิดพลาดเหล่านี้ เพราะมันไม่มีวันหายไปอย่างสิ้นเชิง แม้จะทำงานมาสิบปี คุณก็ยังอาจลืมแท็กปิดในระหว่างการ deploy คืนวันศุกร์ได้ ความแตกต่างระหว่างนักพัฒนา junior และ senior ไม่ใช่การไม่ทำผิดพลาด แต่คือความเร็วในการกู้คืนสถานการณ์ Senior จะเห็น syntax error จดจำรูปแบบ ตรวจสอบจุดที่น่าสงสัยอย่างชัดเจน แล้วไปต่อ ส่วน Junior จะสงสัยว่าเครื่องมือทั้งหมดพังหรือเปล่า การทำซ้ำจะช่วยสร้างสัญชาตญาณนั้นขึ้นมา
แง่มุมของชุมชนจะช่วยเร่งกระบวนการนี้ เมื่อคนหลายคนช่วยกันแก้เทมเพลตที่พังตัวเดียวกันในช่วงสุดสัปดาห์ รูปแบบต่างๆ จะปรากฏขึ้นซึ่งนักพัฒนาคนเดียวอาจมองไม่เห็น บางคนสังเกตเห็นว่าข้อผิดพลาดจะเกิดขึ้นเฉพาะใน for loop ที่ซ้อนกันเท่านั้น บางคนแชร์ shell script ที่ใช้ grep เพื่อหาความไม่สอดคล้องของแท็ก Liquid ที่พบบ่อย ความรู้จะเพิ่มพูนขึ้นเมื่อมีการแลกเปลี่ยน ไม่ใช่การเก็บไว้คนเดียว คุณสามารถอ่านรายละเอียดทั้งหมดของความท้าทายนี้และดูวิธีที่คนอื่นจัดการกับมันได้ ที่โพสต์บน Dev.to หากคุณต้องการแลกเปลี่ยนความรู้กับคนที่กำลังเจอปัญหาเดียวกัน ยังมี ชุมชนการเรียนรู้เสริมบน Telegram ซึ่งบทสนทนาเหล่านี้มักจะดำเนินต่อไปยาวนานกว่าช่วงสุดสัปดาห์
บทสรุป
อย่ามองว่า syntax error คือสิ่งที่มาขัดจังหวะการทำงานจริงของคุณ เพราะมันคือส่วนหนึ่งของงานพื้นฐาน แท็ก Liquid ที่ทำให้ build ของสุดสัปดาห์นี้พัง ไม่ใช่เรื่องของ template engine จริงๆ แต่มันคือการฝึกฝนตัวเองให้ "อ่านอย่างแม่นยำ" ในเวลาที่สมองของคุณอยากจะ "เดา" ลองเปิดไฟล์ที่คุณเขียนเมื่อสัปดาห์ที่แล้ว ไล่ดูแท็กที่คุณเปิดไว้ ตรวจสอบให้แน่ใจว่าทุกแท็กได้รับการปิดอย่างถูกต้อง ปิด loop ของคุณ จัดการเงื่อนไข (conditionals) ให้เรียบร้อย แล้วกลับไปสร้างสรรค์งานต่อ ทีละตัวอักษรที่ถูกต้อง
