Ruby's /o Regex Modifier: ฟีเจอร์ที่ซ่อนอันตรายและสามารถทำลายโค้ดของคุณได้

ทีมชุมชน BigGo
Ruby's /o Regex Modifier: ฟีเจอร์ที่ซ่อนอันตรายและสามารถทำลายโค้ดของคุณได้

โปรแกรมเมอร์ 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
เรือเหาะที่ถูกเปลวไฟห่อหุ้มเป็นสัญลักษณ์ของความล้มเหลวร้ายแรงที่อาจเกิดขึ้นจากการใช้ตัวปรับแต่ง regex /o ที่อันตรายในการเขียนโปรแกรม Ruby

มีทางเลือกที่ดีกว่า

แทนที่จะใช้ /o นักพัฒนาควรแยก regex pattern ที่ใช้บ่อยออกมาเป็น constant หรือคอมไพล์อย่างชัดเจนครั้งเดียว แนวทางนี้ทำให้การปรับปรุงมองเห็นได้ในโค้ดและป้องกันความล้มเหลวแบบเงียบๆ ที่ /o สามารถทำให้เกิดขึ้นได้ การปรับปรุง regex อัตโนมัติของ Ruby สมัยใหม่มักจะขจัดความจำเป็นในการปรับปรุงด้วยตนเองโดยสิ้นเชิง

ความเห็นพ้องต้องกันในหมู่นักพัฒนาที่มีประสบการณ์ชัดเจน: หลีกเลี่ยง /o โดยสิ้นเชิง ประโยชน์ด้านประสิทธิภาพที่น้อยนิดไม่ค่อยจะสมควรกับปัญหาใหญ่ในการแก้ไขบั๊กที่มันสามารถสร้างขึ้นได้ เมื่อการปรับปรุงจำเป็นจริงๆ แนวทางที่ชัดเจนให้การควบคุมและความสามารถในการบำรุงรักษาที่ดีกว่าฟีเจอร์ดั้งเดิมนี้

อ้างอิง: The /o in Ruby regex stands for oh the humanity!