โปรแกรมเมอร์ Ruby เพิ่งค้นพบ regex modifier ที่อันตรายซึ่งสามารถทำลายแอปพลิเคชันอย่างเงียบๆ ได้ แฟล็ก /o
ที่สืบทอดมาจาก Perl จะทำให้ regex pattern ที่มีการแทรกตัวแปรคอมไพล์เพียงครั้งเดียวและเพิกเฉยต่อการเปลี่ยนแปลงของตัวแปรในอนาคต พฤติกรรมนี้สร้างบั๊กที่ละเอียดอ่อนซึ่งยากต่อการตรวจจับและแก้ไข
พฤติกรรมของ Ruby /o Modifier:
- คอมไพล์รูปแบบ regex เฉพาะในการรันครั้งแรกเท่านั้น
- ไม่สนใจการเปลี่ยนแปลงของตัวแปรในการรันครั้งต่อๆ ไป
- สืบทอดมาจาก Perl 4 (ช่วงปี 1990)
- สร้างบั๊กแบบเงียบๆ เมื่อตัวแปรที่ถูก interpolate มีการเปลี่ยนแปลง
- Ruby สมัยใหม่จะปรับแต่งการคอมไพล์ regex โดยอัตโนมัติ
ธรรมชาติที่หลอกลวงของ /o
modifier /o
ดูไม่เป็นอันตรายในเอกสารของ Ruby โดยถูกอธิบายว่าเป็น interpolation mode อย่างไรก็ตาม การตั้งชื่อนี้ทำให้เข้าใจผิด เมื่อคุณเพิ่ม /o
ลงใน regex pattern ที่มีตัวแปร Ruby จะคอมไพล์ pattern เฉพาะในการรันครั้งแรกและเก็บไว้ในแคชอย่างถาวร การเปลี่ยนแปลงใดๆ ต่อตัวแปรที่ถูกแทรกในภายหลังจะถูกเพิกเฉยอย่างสมบูรณ์
ตัวอย่างเช่น หากคุณมี pattern เช่น /admin@#{domain}/o
และตัวแปร domain เปลี่ยนแปลงระหว่างการรันโปรแกรม regex จะยังคงใช้ค่า domain เดิมจากความพยายามในการจับคู่ครั้งแรก สิ่งนี้สร้างสถานการณ์ที่อันตรายซึ่งโค้ดของคุณดูเหมือนจะทำงานได้อย่างถูกต้องแต่ล้มเหลวอย่างเงียบๆ เมื่อเงื่อนไขเปลี่ยนแปลง
Interpolation: กระบวนการแทรกค่าตัวแปรลงในสตริงหรือ pattern ในขณะรันไทม์
ฟีเจอร์ดั้งเดิมที่อยู่เกินจุดประสงค์
modifier /o
มีต้นกำเนิดจาก Perl 4 ในช่วงทศวรรษ 1990 เมื่อ compiled regex object ไม่สามารถเก็บไว้โดยตรงได้ ในสมัยนั้น การปรับปรุงประสิทธิภาพนี้มีเหตุผลด้านประสิทธิภาพ Ruby และ Perl สมัยใหม่จะปรับปรุงการคอมไพล์ regex โดยอัตโนมัติ ทำให้ /o
ไม่จำเป็นเป็นส่วนใหญ่
การสนทนาในชุมชนเผยให้เห็นว่าแม้แต่เอกสารปัจจุบันของ Perl ก็เตือนไม่ให้ใช้ modifier นี้ นักพัฒนาคนหนึ่งสังเกตว่าเอกสารของ Perl ตอนนี้อธิบาย /o
ว่าเป็นวิธีการแกล้งทำเป็นปรับปรุงโค้ดของคุณ แต่จริงๆ แล้วสร้างบั๊ก ฟีเจอร์นี้ยังคงอยู่ในทั้งสองภาษาเพื่อความเข้ากันได้แบบย้อนหลังเป็นหลัก แต่การมีอยู่อย่างต่อเนื่องสร้างความเสี่ยงอย่างต่อเนื่องสำหรับนักพัฒนา
ทำไมสิ่งนี้จึงสร้าง Footgun
ผู้ออกแบบภาษาโปรแกรมโดยทั่วไปพยายามหลีกเลี่ยง footgun - ฟีเจอร์ที่ทำให้นักพัฒนาสามารถทำร้ายโค้ดของตัวเองได้โดยไม่ตั้งใจ modifier /o
เป็นตัวแทนของฟีเจอร์ประเภทอันตรายนี้อย่างแท้จริง ไม่เหมือนกับปัญหาประสิทธิภาพที่ผู้เชี่ยวชาญสามารถระบุและแก้ไขได้ modifier นี้สร้างบั๊กลึกลับที่สามารถคงอยู่โดยไม่ถูกตรวจจับในระบบการผลิต
นี่คือ footgun ภาษาควรพยายามไม่เพิ่ม footgun ทุก footgun ที่คุณให้มา จะมีใครสักคนที่จะยิงเท้าตัวเองด้วยมัน ดังนั้นนั่นจึงเป็นราคาที่สูง
ปัญหาจะแย่ลงเพราะแนวทางทางเลือก - การแยก regex pattern ออกมาเป็น constant ด้วยตนเอง - นั้นทั้งปลอดภัยและชัดเจนกว่า เมื่อนักพัฒนาต้องการการปรับปรุง regex พวกเขาสามารถสร้าง compiled regex object อย่างชัดเจนแทนที่จะพึ่งพาพฤติกรรมการแคชที่ซ่อนอยู่
ทางเลือกที่ปลอดภัยกว่าสำหรับ /o:
- แยกรูปแบบ regex ออกมาเป็นค่าคงที่
- ใช้
Regexp.new()
เพื่อคอมไพล์รูปแบบอย่างชัดเจน - พึ่งพาการปรับแต่ง regex อัตโนมัติของ Ruby
- เก็บออบเจ็กต์ regex ที่คอมไพล์แล้วไว้ในตัวแปรอินสแตนซ์
- หลีกเลี่ยงการแทรกค่าเมื่อค่ารูปแบบเป็นแบบคงที่
![]() |
---|
เรือเหาะที่ถูกเปลวไฟห่อหุ้มเป็นสัญลักษณ์ของความล้มเหลวร้ายแรงที่อาจเกิดขึ้นจากการใช้ตัวปรับแต่ง regex /o ที่อันตรายในการเขียนโปรแกรม Ruby |
มีทางเลือกที่ดีกว่า
แทนที่จะใช้ /o
นักพัฒนาควรแยก regex pattern ที่ใช้บ่อยออกมาเป็น constant หรือคอมไพล์อย่างชัดเจนครั้งเดียว แนวทางนี้ทำให้การปรับปรุงมองเห็นได้ในโค้ดและป้องกันความล้มเหลวแบบเงียบๆ ที่ /o
สามารถทำให้เกิดขึ้นได้ การปรับปรุง regex อัตโนมัติของ Ruby สมัยใหม่มักจะขจัดความจำเป็นในการปรับปรุงด้วยตนเองโดยสิ้นเชิง
ความเห็นพ้องต้องกันในหมู่นักพัฒนาที่มีประสบการณ์ชัดเจน: หลีกเลี่ยง /o
โดยสิ้นเชิง ประโยชน์ด้านประสิทธิภาพที่น้อยนิดไม่ค่อยจะสมควรกับปัญหาใหญ่ในการแก้ไขบั๊กที่มันสามารถสร้างขึ้นได้ เมื่อการปรับปรุงจำเป็นจริงๆ แนวทางที่ชัดเจนให้การควบคุมและความสามารถในการบำรุงรักษาที่ดีกว่าฟีเจอร์ดั้งเดิมนี้