[regex] 왜 정규 표현이 그렇게 논란이 되는가? [닫은]
정규식 (RegEx-es라고도 함)을 탐색 할 때 정규식을 성배라고 생각하는 사람들이 많이 있습니다. 너무 복잡해 보이는 것은 질문에 대한 답이어야합니다. 그들은 정규 표현식을 사용하여 모든 문제를 해결할 수 있다고 생각하는 경향이 있습니다.
반면에 정규 표현식을 피하려고하는 사람들도 많습니다. 정규 표현식이 더 컴팩트 한 솔루션 일지라도 정규 표현식을 둘러싼 방법을 찾고 추가 코딩을 받아들입니다.
왜 정규 표현이 논란의 여지가 있는가? 작동 방식에 대한 오해가 널리 퍼져 있습니까? 아니면 정규 표현이 일반적으로 느리다는 것이 넓은 믿음 일 수 있습니까?
답변
나는 사람들이 느리기 때문에 정규 표현식에 반대한다고 생각하지 않고 오히려 읽고 쓰기가 어렵고 올바르게 이해하기가 까다로워서 생각합니다. 정규식이 문제에 대해 효과적이고 간결한 솔루션을 제공하는 상황이 있지만 때로는 읽기 쉽고 유지 관리가 쉬운 코드 섹션을 사용하는 것이 더 나은 상황에 처하게됩니다.
답변
정규식을 유지 보수 가능하게 만들기
이전에 “정규 표현식”으로 언급 된 패턴을 이해하기위한 주요한 발전 은 공백 (줄 바꿈, 들여 쓰기) 및 주석을 허용하는 Perl의 /x
정규 표현식 플래그 (때때로 (?x)
포함 된 경우)입니다. 이렇게하면 가독성이 향상되어 유지 관리 성이 향상됩니다. 공백은인지 청크를 허용하므로 어떤 그룹으로 무엇을 볼 수 있습니다.
현대식 패턴은 이제 상대적으로 번호가 매겨진 이름과 역 참조를 모두 지원합니다. 그 말 더 이상 필요는 당신이 필요로하는 것을 파악하는 캡처 그룹을 계산하는 $4
나 \7
. 추가 패턴에 포함될 수있는 패턴을 만들 때 도움이됩니다.
다음은 상대적으로 번호가 매겨진 캡처 그룹의 예입니다.
$ dupword = qr {\ b (? : (\ w +) (? : \ s + \ g {-1}) +) \ b} xi; $ quoted = qr {([ " ']) $ dupword \ 1} x;
다음은 명명 된 캡처의 뛰어난 접근 방식의 예입니다.
$dupword = qr{ \b (?: (?<word> \w+ ) (?: \s+ \k<word> )+ ) \b }xi;
$quoted = qr{ (?<quote> ["'] ) $dupword \g{quote} }x;
문법 정규식
무엇보다도 이러한 명명 된 캡처를 (?(DEFINE)...)
블록 내에 배치 하여 패턴의 개별 명명 된 요소 실행과 선언을 분리 할 수 있습니다. 이는 패턴 내에서 서브 루틴처럼 작동하도록합니다.
“문법 정규식”이런 종류의 좋은 예는에서 찾을 수있다 이 대답 하고 이것 . 이것들은 문법 선언과 훨씬 비슷합니다.
후자가 상기 시키 듯이 :
… 라인 노이즈 패턴을 쓰지 마십시오. 당신은 할 필요가 없습니다. 공백, 주석, 서브 루틴 또는 영숫자 식별자를 금지하는 프로그래밍 언어를 유지할 수 없습니다. 따라서 패턴에있는 모든 것을 사용하십시오.
지나치게 강조 할 수 없습니다. 물론 당신이 그러한 것들을 당신의 패턴으로 사용하지 않는다면, 종종 악몽을 일으킬 것입니다. 그러나 당신 이 그들을 사용 한다면 , 당신은 필요하지 않습니다.
다음은 현대적인 문법 패턴의 또 다른 예입니다.이 구문은 RFC 5322 구문 분석을위한 것입니다 : 5.10.0;
$rfc5322 = qr{
(?(DEFINE)
(?<address> (?&mailbox) | (?&group))
(?<mailbox> (?&name_addr) | (?&addr_spec))
(?<name_addr> (?&display_name)? (?&angle_addr))
(?<angle_addr> (?&CFWS)? < (?&addr_spec) > (?&CFWS)?)
(?<group> (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?)
(?<display_name> (?&phrase))
(?<mailbox_list> (?&mailbox) (?: , (?&mailbox))*)
(?<addr_spec> (?&local_part) \@ (?&domain))
(?<local_part> (?&dot_atom) | (?"ed_string))
(?<domain> (?&dot_atom) | (?&domain_literal))
(?<domain_literal> (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?
\] (?&CFWS)?)
(?<dcontent> (?&dtext) | (?"ed_pair))
(?<dtext> (?&NO_WS_CTL) | [\x21-\x5a\x5e-\x7e])
(?<atext> (?&ALPHA) | (?&DIGIT) | [!#\$%&'*+-/=?^_`{|}~])
(?<atom> (?&CFWS)? (?&atext)+ (?&CFWS)?)
(?<dot_atom> (?&CFWS)? (?&dot_atom_text) (?&CFWS)?)
(?<dot_atom_text> (?&atext)+ (?: \. (?&atext)+)*)
(?<text> [\x01-\x09\x0b\x0c\x0e-\x7f])
(?<quoted_pair> \\ (?&text))
(?<qtext> (?&NO_WS_CTL) | [\x21\x23-\x5b\x5d-\x7e])
(?<qcontent> (?&qtext) | (?"ed_pair))
(?<quoted_string> (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*
(?&FWS)? (?&DQUOTE) (?&CFWS)?)
(?<word> (?&atom) | (?"ed_string))
(?<phrase> (?&word)+)
# Folding white space
(?<FWS> (?: (?&WSP)* (?&CRLF))? (?&WSP)+)
(?<ctext> (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e])
(?<ccontent> (?&ctext) | (?"ed_pair) | (?&comment))
(?<comment> \( (?: (?&FWS)? (?&ccontent))* (?&FWS)? \) )
(?<CFWS> (?: (?&FWS)? (?&comment))*
(?: (?:(?&FWS)? (?&comment)) | (?&FWS)))
# No whitespace control
(?<NO_WS_CTL> [\x01-\x08\x0b\x0c\x0e-\x1f\x7f])
(?<ALPHA> [A-Za-z])
(?<DIGIT> [0-9])
(?<CRLF> \x0d \x0a)
(?<DQUOTE> ")
(?<WSP> [\x20\x09])
)
(?&address)
}x;
놀랍지 않고 훌륭하지 않습니까? 기본 구조를 잃지 않고 BNF 스타일 문법을 코드로 직접 변환 할 수 있습니다!
현대식 문법 패턴으로는 여전히 충분하지 않다면 Damian Conway의 화려한 Regexp::Grammars
모듈 은 뛰어난 디버깅과 함께 더욱 깔끔한 구문을 제공합니다. 다음은 RFC 5322 리 캐스트를 해당 모듈의 패턴으로 구문 분석하는 동일한 코드입니다.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Data::Dumper "Dumper";
my $rfc5322 = do {
use Regexp::Grammars; # ...the magic is lexically scoped
qr{
# Keep the big stick handy, just in case...
# <debug:on>
# Match this...
<address>
# As defined by these...
<token: address> <mailbox> | <group>
<token: mailbox> <name_addr> | <addr_spec>
<token: name_addr> <display_name>? <angle_addr>
<token: angle_addr> <CFWS>? \< <addr_spec> \> <CFWS>?
<token: group> <display_name> : (?:<mailbox_list> | <CFWS>)? ; <CFWS>?
<token: display_name> <phrase>
<token: mailbox_list> <[mailbox]> ** (,)
<token: addr_spec> <local_part> \@ <domain>
<token: local_part> <dot_atom> | <quoted_string>
<token: domain> <dot_atom> | <domain_literal>
<token: domain_literal> <CFWS>? \[ (?: <FWS>? <[dcontent]>)* <FWS>?
<token: dcontent> <dtext> | <quoted_pair>
<token: dtext> <.NO_WS_CTL> | [\x21-\x5a\x5e-\x7e]
<token: atext> <.ALPHA> | <.DIGIT> | [!#\$%&'*+-/=?^_`{|}~]
<token: atom> <.CFWS>? <.atext>+ <.CFWS>?
<token: dot_atom> <.CFWS>? <.dot_atom_text> <.CFWS>?
<token: dot_atom> <.CFWS>? <.dot_atom_text> <.CFWS>?
<token: dot_atom_text> <.atext>+ (?: \. <.atext>+)*
<token: text> [\x01-\x09\x0b\x0c\x0e-\x7f]
<token: quoted_pair> \\ <.text>
<token: qtext> <.NO_WS_CTL> | [\x21\x23-\x5b\x5d-\x7e]
<token: qcontent> <.qtext> | <.quoted_pair>
<token: quoted_string> <.CFWS>? <.DQUOTE> (?:<.FWS>? <.qcontent>)*
<.FWS>? <.DQUOTE> <.CFWS>?
<token: word> <.atom> | <.quoted_string>
<token: phrase> <.word>+
# Folding white space
<token: FWS> (?: <.WSP>* <.CRLF>)? <.WSP>+
<token: ctext> <.NO_WS_CTL> | [\x21-\x27\x2a-\x5b\x5d-\x7e]
<token: ccontent> <.ctext> | <.quoted_pair> | <.comment>
<token: comment> \( (?: <.FWS>? <.ccontent>)* <.FWS>? \)
<token: CFWS> (?: <.FWS>? <.comment>)*
(?: (?:<.FWS>? <.comment>) | <.FWS>)
# No whitespace control
<token: NO_WS_CTL> [\x01-\x08\x0b\x0c\x0e-\x1f\x7f]
<token: ALPHA> [A-Za-z]
<token: DIGIT> [0-9]
<token: CRLF> \x0d \x0a
<token: DQUOTE> "
<token: WSP> [\x20\x09]
}x;
};
while (my $input = <>) {
if ($input =~ $rfc5322) {
say Dumper \%/; # ...the parse tree of any successful match
# appears in this punctuation variable
}
}
거기에 좋은 물건을 많이입니다 perlre 맨 페이지는 하지만, 기본적인 정규 표현식 디자인 기능에서이 획기적으로 개선 혼자 펄에 한정되는 것은 있습니다. 실제로 pcrepattern 맨은 쉽게 읽기, 그리고 같은 지역을 커버 할 수있다.
현대의 패턴은 유한 한 오토마타 수업에서 배운 원시적 인 것들과 거의 공통점이 없습니다.
답변
정규 표현식은 훌륭한 도구이지만 사람들은 “이봐 요, 정말 훌륭한 도구입니다. X를하기 위해 사용할 것입니다!” 여기서 X는 다른 도구 (보통 파서)에 더 좋은 것입니다. 드라이버 문제가 필요한 해머를 사용하는 것이 표준입니다.
답변
정규 표현식을 정기적으로 사용하는 사람은 누구나 유닉스 배경을 가지고 있으며 RE를 grep, sed, awk 및 Perl과 같은 일류 프로그래밍 구성으로 취급하는 도구를 사용합니다. 정규 표현식을 사용하는 데 필요한 구문 오버 헤드가 거의 없기 때문에 생산성이 향상됩니다.
반대로 RE가 외부 라이브러리 인 언어를 사용하는 프로그래머는 정규식이 테이블에 가져올 수있는 것을 고려하지 않는 경향이 있습니다. 프로그래머의 “시간 비용”은 너무 높기 때문에 a) RE는 훈련의 일부로 나타나지 않았거나 b) RE와 관련하여 “생각”하지 않고보다 친숙한 패턴으로 돌아 가기를 선호합니다.
답변
정규식을 사용하면 사용자 지정 유한 상태 머신 (FSM)을 간단한 방식으로 작성하여 일련의 입력을 처리 할 수 있습니다. 정규식 사용이 어려운 이유는 두 가지 이상 있습니다.
-
구식 소프트웨어 개발에는 많은 계획, 종이 모델 및 신중한 생각이 필요합니다. 정규 표현식은이 모델에 매우 적합합니다. 효과적인 표현식을 제대로 작성하려면 FSM의 경로를 시각화하여 많은 것을 쳐다 봐야합니다.
최신 소프트웨어 개발자는 코드를 망치고 디버거를 사용하여 실행 단계를 수행하여 코드가 올바른지 확인합니다. 정규 표현식은이 작업 스타일을 잘 지원하지 않습니다. 정규 표현식의 하나의 “실행”은 사실상 원자 연산입니다. 디버거에서 단계별 실행을 관찰하기는 어렵습니다.
-
실수로 의도 한 것보다 더 많은 입력을받는 정규식을 작성하는 것은 너무 쉽습니다. 정규 표현식의 값은 실제로 유효한 입력과 일치하지 않으며 유효 하지 않은 입력과 일치하지 않습니다 . 정규 표현식에 대해 “음성 테스트”를 수행하는 기술은 그다지 진보되지 않았거나 최소한 널리 사용되지는 않습니다.
이것은 정규 표현식을 읽기 어렵다는 점으로갑니다. 정규식을 보는 것만으로, 거부되어야하지만 실수로 허용되는 모든 가능한 입력을 시각화하는 데 많은 집중이 필요합니다. 다른 사람의 정규 표현식 코드 를 디버깅하려고 시도한 적이 있습니까?
오늘날 소프트웨어 개발자들 사이에서 정규 표현식을 사용하는 것에 대한 저항이 있다면, 나는 주로이 두 가지 요인 때문이라고 생각합니다.
답변
사람들은 정규 표현이 어렵다고 생각하는 경향이 있습니다. 그들이 잘못 사용하고 있기 때문입니다. 의견, 들여 쓰기 또는 명명 된 캡처없이 복잡한 원 라이너를 작성합니다. 주석, 들여 쓰기 또는 별명없이 복잡한 SQL 표현식을 한 줄에 넣지 않습니까? 예, 많은 사람들에게 이해가되지 않습니다.
당신의 작업이있는 경우 그러나, 무엇이든 (… 거기 거의 모든 웹 응용 프로그램) 텍스트를 구문 분석과 함께 할을하고 정규 표현식을 모르는, 당신은 당신의 일에 빨아 당신은 당신의 자신의 시간을 낭비하고의 당신의 고용주. 거기에 당신이 알아야 할 것들에 관한 모든 것을 가르쳐 줄 훌륭한 자료 가 있습니다.
답변
그들은 일반적으로 받아 들여지는 IDE에서 가장 인기있는 학습 도구가 없기 때문에 : 정규식 마법사가 없습니다. 자동 완성조차도 아닙니다. 모든 것을 혼자서 코딩해야합니다.