เมื่อใดที่คุณไม่ควรใช้นิพจน์ปกติ [ปิด]


50

นิพจน์ทั่วไปเป็นเครื่องมือที่มีประสิทธิภาพในคลังแสงของโปรแกรมเมอร์ แต่มีบางกรณีที่ไม่ใช่ตัวเลือกที่ดีที่สุดหรือแม้แต่เป็นอันตรายทันที

ตัวอย่างง่ายๆ # 1 คือการแยกวิเคราะห์ HTML ด้วย regexpซึ่งเป็นถนนที่รู้จักกันดีในข้อบกพร่องมากมาย อาจเป็นเช่นนี้ยังมีคุณสมบัติในการแยกโดยทั่วไป

แต่มีพื้นที่อื่นที่ไม่ต้องไปสำหรับการแสดงผลปกติหรือไม่?


ป.ล. : " คำถามที่คุณถามปรากฏเป็นเรื่องส่วนตัวและมีแนวโน้มที่จะถูกปิด " - ดังนั้นฉันต้องการเน้นย้ำว่าฉันสนใจตัวอย่างที่ทราบว่าการใช้ regexps ก่อให้เกิดปัญหา


9
การแยก HTML กับ regexp ไม่ได้เป็นเพียง "เส้นทางที่เป็นที่รู้จักไปสู่ข้อบกพร่องมากมาย" มันเป็นไปไม่ได้จริงๆ
Kramii Reinstate Monica

19
ไม่เพียงเป็นไปไม่ได้เท่านั้น แต่ยังนำไปสู่ความบ้าคลั่งและการสาปแช่งชั่วนิรันดร์
Martin Wickman

3
@ Jörg: Regexp เป็นเพียงตัวย่อสำหรับนิพจน์ทั่วไป
Joren

3
@ Jörg: มันเป็นความจริงอย่างมากที่มีความแตกต่างอย่างมากระหว่างนิพจน์ทั่วไปในคณิตศาสตร์และการประยุกต์ใช้ในห้องสมุดซอฟต์แวร์ นอกจากนี้ยังเป็นความจริงที่ว่าไลบรารีนิพจน์ทั่วไปส่วนใหญ่มีส่วนขยายที่ทำให้พวกเขาอยู่ไกลเกินกว่าจะยอมรับเฉพาะภาษาปกติและการเรียกพวกเขาว่านิพจน์ทั่วไปนั้นไม่เหมาะสมเสมอไป ฉันเห็นด้วยกับคุณว่ามีสองแนวคิดที่แตกต่างกัน แต่พวกเขามีชื่อเดียวกัน regexp ยังคงเป็นเพียงตัวย่อไม่ใช่คำศัพท์ในตัว ตัวอย่างมากมายในเว็บไซต์นี้ที่ใช้คำเต็มสำหรับไลบรารีซอฟต์แวร์
Joren

2
@ Jörg - นี่คือความหมาย แม้ว่าจะเป็นความคิดที่ดีที่จะเรียกรูปแบบเหล่านี้ในชื่อต่าง ๆ (หากเพียงเพื่อหลีกเลี่ยง "การเข้าใจผิดปกติสำหรับภาษาธรรมดา" การเข้าใจผิด) "regexp" / "นิพจน์ทั่วไป" ไม่ใช่ความพยายามที่ดีมากและนำไปสู่ ความสับสนเพิ่มเติม
Kobi

คำตอบ:


60

อย่าใช้นิพจน์ทั่วไป:

  • เมื่อมีการแยกวิเคราะห์

นี้ไม่ได้ จำกัด การHTML XML ที่ถูกต้องง่ายไม่สามารถแยกวิเคราะห์ด้วยนิพจน์ปกติได้อย่างสมเหตุสมผลแม้ว่าคุณจะรู้สคีมาและคุณก็รู้ว่ามันจะไม่มีวันเปลี่ยนแปลง

อย่าพยายามยกตัวอย่างเช่นแยกรหัส C # แหล่งที่มา แยกมันแทนเพื่อให้ได้โครงสร้างต้นไม้ที่มีความหมายหรือโทเค็น

  • โดยทั่วไปเมื่อคุณมีเครื่องมือที่ดีกว่าในการทำงานของคุณ

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

ตัวอย่างเช่นโค้ดตัวอย่างในคำตอบของ Ingo เป็นตัวอย่างที่ดีเมื่อคุณต้องไม่ใช้นิพจน์ทั่วไป เพียงแค่ค้นหาแล้วสำหรับfoobar

  • เมื่อแยกวิเคราะห์การเขียนของมนุษย์

ตัวอย่างที่ดีคือตัวกรองอนาจาร ไม่เพียง แต่เป็นความคิดที่ไม่ดีโดยทั่วไปที่จะนำไปใช้ แต่คุณอาจถูกล่อลวงให้ทำโดยใช้นิพจน์ทั่วไปและคุณจะทำผิด มีวิธีมากมายที่มนุษย์สามารถเขียนคำตัวเลขประโยคและจะเข้าใจโดยมนุษย์อื่น แต่ไม่ใช่การแสดงออกปกติของคุณ ดังนั้นแทนที่จะจับความหยาบคายที่แท้จริงการแสดงออกปกติของคุณจะใช้เวลาของเธอทำร้ายผู้ใช้คนอื่น

  • เมื่อตรวจสอบข้อมูลบางประเภท

ตัวอย่างเช่นอย่าตรวจสอบที่อยู่อีเมลผ่านนิพจน์ทั่วไป ในกรณีส่วนใหญ่คุณจะทำผิด ในกรณีที่หายากที่คุณจะทำมันขวาและจบด้วย 6 343 อักขระยาวเข้ารหัสสยองขวัญ

หากไม่มีเครื่องมือที่เหมาะสมคุณจะทำผิดพลาด และคุณจะสังเกตเห็นพวกเขาในช่วงเวลาสุดท้ายหรืออาจจะไม่เคย หากคุณไม่สนใจรหัสที่สะอาดคุณจะต้องเขียนสตริงยี่สิบบรรทัดโดยไม่มีความคิดเห็นไม่มีช่องว่างไม่มีการขึ้นบรรทัดใหม่

  • เมื่อรหัสของคุณจะถูกอ่าน และจากนั้นอ่านอีกครั้งและอีกครั้งและอีกครั้งทุกครั้งโดยนักพัฒนาที่แตกต่างกัน

อย่างจริงจังถ้าฉันใช้รหัสของคุณและต้องตรวจสอบหรือแก้ไขมันฉันไม่ต้องการใช้เวลาหนึ่งสัปดาห์ในการพยายามทำความเข้าใจสัญลักษณ์ที่มีความยาวยี่สิบบรรทัด


9
"อย่างจริงจังถ้าฉันใช้รหัสของคุณและต้องตรวจสอบหรือแก้ไขมันฉันไม่ต้องการใช้เวลาหนึ่งสัปดาห์ในการพยายามทำความเข้าใจสัญลักษณ์ที่มีความยาวยี่สิบบรรทัด" +1!
funkybro

1
นี่เป็นคำตอบที่ดีกว่าขั้นตอนน้องสาวบนกองล้น: stackoverflow.com/questions/7553722/…
Kobi

1
หากคุณกำลังใช้ Perl / PCRE (และอาจเป็น regex รสชาติสมัยใหม่อื่น ๆ ด้วย) อ่านเกี่ยวกับรูทีนย่อยชื่อกลุ่มการจับและการ(?(DEFINE))ยืนยัน;) คุณสามารถเขียน regexes ที่สะอาดมากโดยใช้เหล่านั้นและจริง ๆ แล้วเมื่อคุณใช้ คล้ายกันมากกับสิ่งที่คุณจะเขียนเป็น yacc หรือเหมือนกัน;)
NikiC

2
การใช้นิพจน์ทั่วไปเพื่อแยกคำที่อยู่ในรายการที่ไม่อนุญาตนั้นเป็นข้อผิดพลาดของ clbuttic
Dan Ray

ไม่มีเหตุผลในโลกที่จะหลีกเลี่ยงการขว้าง regex ที่สายอักขระเหมือน"<a href='foo'>stuff</a>"กัน regexes ที่ทันสมัยไม่มีปัญหากับเรื่องนี้
tchrist

18

สิ่งที่สำคัญที่สุดเมื่อภาษาที่คุณจะแยกไม่ได้เป็นภาษาปกติ

HTML คือไม่เป็นภาษาปกติและแยกกับการแสดงออกปกติคือไม่ได้เป็นไปได้ (ไม่เพียง แต่ยากหรือถนนรหัสรถม้าชนิดเล็ก)


4
ไม่ถูกต้อง! หากคุณกำลังใช้รสชาติที่ทันสมัยของ regex (Perl, PCRE, Java, .NET, ... ) คุณสามารถทำการสอบถามซ้ำและยืนยันและทำให้สามารถแยกวิเคราะห์ยังตรงกับไวยากรณ์ที่ไม่มีบริบทและบริบทที่ไวต่อบริบท
NikiC

9
@NikiC ไม่ผิด "รสชาติ regex สมัยใหม่" ไม่ใช่การแสดงออกปกติ (ซึ่งสามารถใช้ในการแยกภาษาปกติดังนั้นชื่อ) ฉันยอมรับว่าด้วย PRE คุณสามารถทำอะไรได้มากกว่า แต่ฉันจะไม่เรียกพวกเขาว่า "การแสดงออกปกติ" (เหมือนในคำถามเดิม)
Matteo

1
regexes สมัยใหม่นั้นไกลเกินกว่าที่คุณยายสอนไว้ว่า regexes สามารถทำตามคำแนะนำของเธอได้อย่างไร้สาระ และแม้แต่ regexes ดั้งเดิมก็สามารถจัดการตัวอย่าง HTML ได้เล็กน้อย การห้ามผ้าห่มนี้ไร้สาระและไม่สมจริง Regexes ถูกสร้างขึ้นมาเพื่อสิ่งนี้ และใช่ฉันรู้ว่าฉันกำลังพูดถึงอะไร
tchrist

12

ใน stackoverflow มักจะเห็นคนถาม regexes ที่ค้นหาว่าสตริงที่กำหนดไม่ได้มีนี้หรือว่า นี่คือ IMHO เพื่อย้อนกลับวัตถุประสงค์ของการแสดงออกปกติ แม้ว่าจะมีวิธีแก้ปัญหาอยู่ (โดยใช้การตรวจสอบเชิงลบหรือการยืนยันสิ่งต่าง ๆ ) แต่ก็มักจะดีกว่าการใช้ regex สำหรับสิ่งที่มันถูกสร้างขึ้นมาและจัดการกรณีเชิงลบด้วยตรรกะโปรแกรม

ตัวอย่าง:

# bad
if (/complicated regex that assures the string does NOT conatin foo|bar/) {
    # do something
}

# appropriate
if (/foo|bar/) {
    # error handling
} else {
    # do something
}

1
+1: สองสามครั้งฉันหลีกเลี่ยงการเขียนโค้ดตัวเองในมุมหนึ่งกับ regexes โดยหยุดและถามตัวเองว่า "โอเคฉันพยายามจับคู่อะไรเป็นพิเศษ?" มากกว่า "ฉันกำลังพยายามหลีกเลี่ยงอะไร"

5

สองกรณี:

เมื่อมีวิธีที่ง่ายกว่า

  • ภาษาส่วนใหญ่มีฟังก์ชั่นง่าย ๆ เช่น INSTR เพื่อตรวจสอบว่าสตริงหนึ่งเป็นชุดย่อยของอีกสตริงหรือไม่ หากนั่นคือสิ่งที่คุณต้องการทำใช้ฟังก์ชั่นที่ง่ายขึ้น อย่าเขียนนิพจน์ปกติของคุณเอง

  • หากมีไลบรารีสำหรับดำเนินการจัดการสตริงที่ซับซ้อนให้ใช้แทนการเขียนนิพจน์ปกติของคุณเอง

เมื่อการแสดงออกปกติไม่ได้ทรงพลังเพียงพอ

  • หากคุณต้องการใช้เครื่องมือแยกวิเคราะห์ใช้โปรแกรมแยกวิเคราะห์

0

นิพจน์ทั่วไปไม่สามารถระบุโครงสร้างแบบเรียกซ้ำได้ นี่เป็นข้อ จำกัด พื้นฐาน

ใช้ JSON - มันเป็นรูปแบบที่ค่อนข้างเรียบง่าย แต่เนื่องจากวัตถุอาจมีวัตถุอื่น ๆ ที่เป็นค่าสมาชิก (ความลึกโดยพลการ) ไวยากรณ์นั้นวนซ้ำและไม่สามารถแยกวิเคราะห์โดย regex ในขณะที่ CSV สามารถแยกวิเคราะห์โดย regex'es เนื่องจากไม่มีโครงสร้างแบบเรียกซ้ำ

ในการแสดงออกปกติสั้น ๆ ไม่อนุญาตให้รูปแบบที่จะอ้างถึงตัวเอง คุณไม่สามารถพูดได้: ณ จุดนี้ในไวยากรณ์ตรงกับรูปแบบทั้งหมดอีกครั้ง หากต้องการใช้อีกวิธีหนึ่งนิพจน์ทั่วไปจะจับคู่กับเชิงเส้นเท่านั้นซึ่งไม่มีสแต็กที่จะอนุญาตให้ติดตามว่ามันเป็นรูปแบบซ้อนกันหรือไม่

โปรดทราบว่าไม่มีอะไรเกี่ยวข้องกับรูปแบบที่ซับซ้อนหรือซับซ้อน S-expressions นั้นง่ายจริงๆ แต่ไม่สามารถแยกวิเคราะห์ด้วย regex ในทางตรงกันข้าม CSS2 เป็นภาษาที่ค่อนข้างซับซ้อน แต่ไม่มีโครงสร้างแบบเรียกซ้ำและสามารถแยกวิเคราะห์ด้วย regex (แม้ว่าสิ่งนี้จะไม่เป็นจริงสำหรับ CSS3 เนื่องจากการแสดงออกของ CSS ซึ่งมีไวยากรณ์ซ้ำ)

ดังนั้นจึงไม่ใช่เพราะมันน่าเกลียดหรือซับซ้อนหรือมีข้อผิดพลาดในการแยกวิเคราะห์ HTML โดยใช้ regex เท่านั้น มันก็คือว่ามันเป็นเพียงไปไม่ได้

หากคุณต้องการแยกวิเคราะห์รูปแบบที่มีโครงสร้างแบบเรียกซ้ำคุณต้องเพิ่มการใช้นิพจน์ปกติด้วยสแต็กอย่างน้อยเพื่อติดตามระดับของโครงสร้างแบบเรียกซ้ำ โดยทั่วไปนี่เป็นวิธีการแยกวิเคราะห์ นิพจน์ทั่วไปใช้เพื่อรับรู้ส่วน "เชิงเส้น" ในขณะที่รหัสที่กำหนดเองนอก regex ใช้เพื่อติดตามโครงสร้างที่ซ้อนกัน

โดยทั่วไปการแยกวิเคราะห์เช่นนี้จะถูกแยกเป็นระยะ ๆ โทเค็นไลเซชันเป็นเฟสแรกที่นิพจน์ทั่วไปถูกใช้เพื่อแบ่งอินพุตเป็นลำดับของ "โทเค็น" เช่นคำเครื่องหมายวรรคตอนวงเล็บเป็นต้นการแยกเป็นเฟสถัดไปที่โทเค็นเหล่านี้จะถูกวิเคราะห์เป็นโครงสร้างลำดับชั้นต้นไม้ไวยากรณ์

ดังนั้นเมื่อคุณได้ยินว่า HTML หรือ C # ไม่สามารถแยกวิเคราะห์โดยนิพจน์ทั่วไปให้ระวังว่านิพจน์ทั่วไปยังคงเป็นส่วนสำคัญของตัวแยกวิเคราะห์ คุณไม่สามารถแยกวิเคราะห์ภาษาดังกล่าวโดยใช้นิพจน์ทั่วไปเท่านั้นและไม่มีรหัสตัวช่วย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.