Có một câu nói phổ biến của Jamie Zawinski :
Một số người, khi đối mặt với một vấn đề, nghĩ rằng "Tôi biết, tôi sẽ sử dụng các biểu thức thông thường." Bây giờ họ có hai vấn đề.
Làm thế nào là trích dẫn này được hiểu là?
Có một câu nói phổ biến của Jamie Zawinski :
Một số người, khi đối mặt với một vấn đề, nghĩ rằng "Tôi biết, tôi sẽ sử dụng các biểu thức thông thường." Bây giờ họ có hai vấn đề.
Làm thế nào là trích dẫn này được hiểu là?
Câu trả lời:
Một số công nghệ lập trình thường không được các lập trình viên hiểu rõ ( biểu thức chính quy , dấu phẩy động , Perl , AWK , IoC ... và các công nghệ khác ).
Đây có thể là những công cụ mạnh mẽ đáng kinh ngạc để giải quyết đúng vấn đề. Các biểu thức chính quy đặc biệt rất hữu ích để kết hợp các ngôn ngữ thông thường. Và có một mấu chốt của vấn đề: ít người biết cách mô tả một ngôn ngữ thông thường (đó là một phần của lý thuyết / ngôn ngữ học khoa học máy tính sử dụng các ký hiệu ngộ nghĩnh - bạn có thể đọc về nó theo phân cấp Chomsky ).
Khi xử lý những điều này, nếu bạn sử dụng sai, không chắc bạn đã thực sự giải quyết được vấn đề ban đầu của mình. Sử dụng biểu thức chính quy để khớp với HTML (một sự xuất hiện quá phổ biến) sẽ có nghĩa là bạn sẽ bỏ lỡ các trường hợp cạnh. Và bây giờ, bạn vẫn gặp phải vấn đề ban đầu mà bạn chưa giải quyết và một lỗi tinh vi khác xuất hiện xung quanh đã được đưa ra bằng cách sử dụng giải pháp sai.
Điều này không có nghĩa là không nên sử dụng các biểu thức thông thường, mà là người ta nên làm việc để hiểu tập hợp vấn đề họ có thể giải quyết và không thể giải quyết và sử dụng chúng một cách thận trọng.
Chìa khóa để duy trì phần mềm là viết mã duy trì. Sử dụng các biểu thức thông thường có thể phản lại mục tiêu đó. Khi làm việc với các biểu thức thông thường, bạn đã viết một máy tính mini (cụ thể là máy tự động trạng thái hữu hạn không xác định ) bằng ngôn ngữ cụ thể của miền. Thật dễ dàng để viết tương đương 'Xin chào thế giới' bằng ngôn ngữ này và có được sự tin tưởng thô sơ về ngôn ngữ này, nhưng cần phải tiếp tục tìm hiểu ngôn ngữ thông thường để tránh viết thêm các lỗi có thể rất khó xác định và sửa chữa (bởi vì chúng không phải là một phần của chương trình mà biểu thức chính quy nằm trong).
Vì vậy, bây giờ bạn đã có một vấn đề mới; bạn đã chọn công cụ của biểu thức chính quy để giải quyết nó (khi nó không phù hợp) và bây giờ bạn có hai lỗi, cả hai đều khó tìm hơn, vì chúng bị ẩn trong một lớp trừu tượng khác.
Các biểu thức chính quy - đặc biệt là các biểu thức không tầm thường - có khả năng khó viết mã, hiểu và duy trì. Bạn chỉ cần nhìn vào số lượng câu hỏi trên Stack Overflow được gắn thẻ [regex]
nơi người hỏi đã cho rằng câu trả lời cho vấn đề của họ là một biểu thức chính quy và sau đó đã bị mắc kẹt. Trong rất nhiều trường hợp, vấn đề có thể (và có lẽ nên) được giải quyết theo một cách khác.
Điều này có nghĩa là, nếu bạn quyết định sử dụng regex, bây giờ bạn có hai vấn đề:
Về cơ bản, tôi nghĩ rằng anh ta có nghĩa là bạn chỉ nên sử dụng một biểu thức chính quy nếu không có cách nào khác để giải quyết vấn đề của bạn. Một giải pháp khác có lẽ sẽ dễ dàng hơn để viết mã, bảo trì và hỗ trợ. Nó có thể chậm hơn hoặc kém hiệu quả hơn, nhưng nếu điều đó không dễ bảo trì và hỗ trợ thì đó sẽ là mối quan tâm lớn nhất.
Nó chủ yếu là một trò đùa lưỡi, mặc dù với một sự thật.
Có một số nhiệm vụ mà biểu thức chính quy là một sự phù hợp tuyệt vời. Tôi đã từng thay thế 500 dòng mã trình phân tích cú pháp gốc đệ quy được viết thủ công bằng một biểu thức chính quy mất khoảng 10 phút để gỡ lỗi hoàn toàn. Mọi người nói regex rất khó hiểu và gỡ lỗi, nhưng những cái được áp dụng một cách thích hợp không khó để gỡ lỗi như một trình phân tích cú pháp được thiết kế bằng tay khổng lồ. Trong ví dụ của tôi, phải mất hai tuần để gỡ lỗi tất cả các trường hợp biên của giải pháp phi regex.
Tuy nhiên, để diễn giải chú Ben:
Với biểu cảm tuyệt vời đi kèm với trách nhiệm lớn.
Nói cách khác, regexes thêm biểu cảm cho ngôn ngữ của bạn, nhưng điều đó đặt ra nhiều trách nhiệm hơn cho người lập trình để chọn chế độ biểu đạt dễ đọc nhất cho một tác vụ nhất định.
Một số thứ ban đầu trông giống như một nhiệm vụ tốt cho các biểu thức thông thường, nhưng không. Ví dụ: mọi thứ có mã thông báo lồng nhau, như HTML. Đôi khi mọi người sử dụng một biểu thức chính quy khi một phương pháp đơn giản hơn rõ ràng hơn. Ví dụ, string.endsWith("ing")
dễ hiểu hơn regex tương đương. Đôi khi mọi người cố gắng nhồi nhét một vấn đề lớn vào một regex duy nhất, trong đó phá vỡ nó thành từng mảnh là phù hợp hơn. Đôi khi mọi người thất bại trong việc tạo ra sự trừu tượng thích hợp, lặp đi lặp lại một biểu thức chính quy thay vì tạo ra một hàm có tên tốt để thực hiện cùng một công việc (có thể được thực hiện trong nội bộ với biểu thức chính quy).
Vì một số lý do, regexes có xu hướng kỳ lạ là tạo ra điểm mù đối với các nguyên tắc công nghệ phần mềm thông thường như trách nhiệm đơn lẻ và DRY. Đó là lý do tại sao ngay cả những người yêu thích họ đôi khi thấy họ có vấn đề.
Jeff Atwood đưa ra một cách giải thích khác nhau trong một bài đăng trên blog thảo luận về chính trích dẫn này: Biểu thức thông thường: Bây giờ bạn có hai vấn đề (nhờ Euphoric cho liên kết)
Phân tích toàn bộ bài viết của Jamie trong chủ đề gốc năm 1997, chúng tôi thấy như sau:
Bản chất của Perl khuyến khích việc sử dụng các biểu thức chính quy gần như loại trừ tất cả các kỹ thuật khác; họ ở xa và cách xa "rõ ràng" nhất (ít nhất là đối với những người không biết gì hơn) để đi từ điểm A đến điểm B.
Các trích dẫn đầu tiên là quá glib để được thực hiện nghiêm túc. Nhưng điều này, tôi hoàn toàn đồng ý với. Đây là điểm mà Jamie đang cố gắng thực hiện: không phải những biểu hiện thông thường là xấu xa, mà là lạm dụng những biểu hiện thông thường là xấu xa.
Thậm chí nếu bạn làm hoàn toàn hiểu được biểu thức thông thường, bạn chạy vào The Golden Hammer vấn đề, cố gắng giải quyết một vấn đề với biểu thức thông thường, khi nó sẽ được dễ dàng hơn và rõ ràng hơn để làm điều tương tự với mã thường xuyên (xem thêm CodingHorror: Sử dụng Regex so với lạm dụng Regex ).
Có một bài viết trên blog khác nhìn vào bối cảnh của trích dẫn và đi sâu vào chi tiết hơn Atwood: Blog của Jeffrey Friedl: Nguồn của cuốn sách nổi tiếng Bây giờ bạn có hai vấn đề trích dẫn
Có một vài điều đang xảy ra với trích dẫn này.
Câu trích dẫn là một sự phục hồi của một trò đùa trước đó:
Bất cứ khi nào phải đối mặt với một vấn đề, một số người nói "Hãy sử dụng AWK." Bây giờ họ có hai vấn đề. - D. Tilbrook
Đó là một trò đùa và một đào thực sự, nhưng đó cũng là một cách làm nổi bật regex như một giải pháp tồi bằng cách liên kết nó với các giải pháp xấu khác. Đó là một khoảnh khắc tuyệt vời ha ha chỉ nghiêm trọng .
Đối với tôi, tâm trí của bạn, trích dẫn này được mở ra một cách có chủ đích để giải thích, ý nghĩa của nó là thẳng tiến. Đơn giản chỉ cần thông báo ý tưởng sử dụng một biểu thức thông thường đã không giải quyết được vấn đề. Ngoài ra, bạn đã tăng độ phức tạp về nhận thức của mã bằng cách thêm một ngôn ngữ bổ sung với các quy tắc khác biệt với bất kỳ ngôn ngữ nào bạn đang sử dụng.
Mặc dù buồn cười như một trò đùa, bạn cần so sánh độ phức tạp của một giải pháp phi regex với độ phức tạp của giải pháp regex + độ phức tạp bổ sung bao gồm các biểu thức chính quy. Có thể đáng để giải quyết vấn đề với regex, mặc dù chi phí bổ sung thêm regexes.
Chính quy Expressions
. không biết rằng bạn có thể làm được.)
Đây là một ví dụ tầm thường:
^(?:[^,]*+,){21}[^,]*+$
Dù sao nó không thực sự khó đọc hay duy trì, nhưng thậm chí còn dễ hơn khi nó trông như thế này:
(?x) # enables comments, so this whole block can be used in a regex.
^ # start of string
(?: # start non-capturing group
[^,]*+ # as many non-commas as possible, but none required
, # a comma
) # end non-capturing group
{21} # 21 of previous entity (i.e. the group)
[^,]*+ # as many non-commas as possible, but none required
$ # end of string
Đó là một ví dụ điển hình (bình luận $
gần giống với nhận xét i++
) nhưng rõ ràng không có vấn đề gì trong việc đọc, hiểu và duy trì điều đó.
Miễn là bạn rõ ràng khi nào các biểu thức thông thường phù hợp và khi chúng là một ý tưởng tồi, không có gì sai với chúng và hầu hết các lần trích dẫn JWZ không thực sự được áp dụng.
*+
gì? Làm thế nào là bất kỳ khác nhau (chức năng) từ chỉ *
?
*+
trường hợp này; tất cả mọi thứ đều được neo và có thể được khớp trong một lần chạy bằng một máy tự động có thể đếm tới 22. Công cụ sửa đổi chính xác trên các bộ không dấu phẩy này chỉ đơn giản là cũ *
. (Hơn nữa, cũng không nên có sự khác biệt giữa các thuật toán kết hợp tham lam và không tham lam ở đây. Đây là một trường hợp cực kỳ đơn giản.)
Ngoài câu trả lời của ChrisF - rằng các biểu thức thông thường "khó mã hóa, hiểu và duy trì", còn tệ hơn: chúng chỉ đủ mạnh để lừa mọi người cố gắng sử dụng chúng để phân tích những thứ họ không thể, như HTML. Xem nhiều câu hỏi về SO về "làm cách nào để phân tích HTML?" Chẳng hạn, câu trả lời hoành tráng nhất trong tất cả các SO!
Biểu thức thông thường rất mạnh mẽ, nhưng chúng có một vấn đề nhỏ và lớn; chúng khó viết và gần như không thể đọc được.
Trong trường hợp tốt nhất, việc sử dụng biểu thức chính quy sẽ giải quyết vấn đề, do đó bạn chỉ gặp vấn đề bảo trì mã phức tạp. Nếu bạn không có biểu thức chính quy vừa phải, bạn có cả vấn đề ban đầu và vấn đề với mã không thể đọc được mà không hoạt động.
Đôi khi các biểu thức chính quy được gọi là mã chỉ ghi. Đối mặt với một biểu thức thông thường cần sửa chữa, thường bắt đầu từ đầu nhanh hơn là cố gắng hiểu biểu thức.
Vấn đề là regex là một con thú phức tạp và bạn chỉ giải quyết vấn đề của mình nếu bạn sử dụng regex một cách hoàn hảo. Nếu bạn không, bạn kết thúc với 2 vấn đề: vấn đề ban đầu và regex của bạn.
Bạn tuyên bố rằng nó có thể thực hiện công việc của một trăm dòng mã, nhưng bạn cũng có thể đưa ra lập luận rằng 100 dòng mã rõ ràng, súc tích sẽ tốt hơn một dòng regex.
Nếu bạn cần một số bằng chứng về điều này: Bạn có thể kiểm tra SO Classic này hoặc đơn giản là kết hợp thông qua Thẻ SO Regex
Ý nghĩa có hai phần:
Khi bạn yêu cầu nó vào năm 2014, sẽ rất thú vị khi tập trung vào hệ tư tưởng ngôn ngữ lập trình của bối cảnh năm 1997 so với bối cảnh ngày nay. Tôi sẽ không tham gia cuộc tranh luận này ở đây nhưng ý kiến về Perl và bản thân Perl đã thay đổi rất nhiều.
Tuy nhiên, để ở trong bối cảnh năm 2013 ( de l'eau a coulé sous les ponts depuis), tôi khuyên bạn nên tập trung vào việc tái hiện trong các trích dẫn bằng truyện tranh XKCD nổi tiếng là một trích dẫn trực tiếp của Jamie Zawinski :
Đầu tiên tôi gặp vấn đề để hiểu truyện tranh này vì nó liên quan đến trích dẫn của Zawinski, và trích dẫn lời bài hát của Jay-z, và một tài liệu tham khảo về program --help -z
cờ GNU 2 , vì vậy, nó quá nhiều văn hóa để tôi hiểu nó.
Tôi biết điều đó thật thú vị, tôi đã cảm nhận được điều đó, nhưng tôi không thực sự biết tại sao. Mọi người thường đùa giỡn về Perl và regexes, đặc biệt vì đây không phải là ngôn ngữ lập trình mạnh mẽ nhất, không thực sự biết tại sao nó được cho là vui vẻ ... Có lẽ vì những người mong muốn Perl làm những điều ngớ ngẩn .
Vì vậy, trích dẫn ban đầu dường như là một trò đùa châm biếm dựa trên các vấn đề thực tế (đau?) Gây ra bởi lập trình với các công cụ gây tổn thương. Giống như một cái búa có thể làm tổn thương thợ xây, lập trình với các công cụ không phải là công cụ mà nhà phát triển sẽ chọn nếu anh ta có thể làm tổn thương (não, cảm xúc). Đôi khi, những cuộc tranh luận lớn về công cụ nào là tốt nhất xảy ra, nhưng nó gần như vô giá trị vì nó là một vấn đề của khẩu vị của bạn hoặc hương vị đội ngũ lập trình của bạn , văn hóa hoặc kinh tế lý do. Một truyện tranh XKCD xuất sắc khác về điều này:
Tôi có thể hiểu mọi người cảm thấy đau đớn về regexes và họ tin rằng một công cụ khác phù hợp hơn với những gì regexes được thiết kế cho. Khi @ karl-bielefeldt trả lời câu hỏi của bạn với tính biểu cảm cao sẽ có trách nhiệm lớn và các biểu thức đặc biệt quan tâm đến vấn đề này. Nếu một nhà phát triển không quan tâm đến cách họ xử lý các biểu thức, cuối cùng sẽ là một nỗi đau cho những người sẽ duy trì mã sau này.
Tôi sẽ kết thúc với câu trả lời này về việc trích dẫn lại bằng một trích dẫn cho thấy một ví dụ điển hình từ Thực tiễn tốt nhất của Damian Conw ay Perl (một cuốn sách năm 2005).
Ông giải thích rằng viết một mô hình như thế này:
m{'[^\\']*(?:\\.[^\\']*)*'}
... không thể chấp nhận hơn là viết một chương trình như thế này :
sub'x{local$_=pop;sub'_{$_>=$_[0
]?$_[1]:$"}_(1,'*')._(5,'-')._(4
,'*').$/._(6,'|').($_>9?'X':$_>8
?'/':$")._(8,'|').$/._(2,'*')._(
7,'-')._(3,'*').$/}print$/x($=).
x(10)x(++$x/10).x($x%10)while<>;
Nhưng nó có thể được viết lại , nó vẫn không đẹp, nhưng ít nhất bây giờ nó có thể sống sót.
# Match a single-quoted string efficiently...
m{ ' # an opening single quote
[^\\']* # any non-special chars (i.e., not backslash or single quote)
(?: # then all of...`
\\ . # any explicitly backslashed char
[^\\']* # followed by any non-special chars
)* # ...repeated zero or more times
' # a closing single quote
}x
Loại mã hình chữ nhật này là vấn đề thứ hai không phải là biểu thức có thể được định dạng theo cách rõ ràng, có thể duy trì và có thể đọc được.
/* Multiply the first 10 values in an array by 2. */ for (int i = 0 /* the loop counter */; i < 10 /* continue while it is less than 10 */; ++i /* and increment it by 1 in each iteration */) { array[i] *= 2; /* double the i-th element in the array */ }
Nếu có một điều bạn nên học từ khoa học máy tính, đó là hệ thống phân cấp Chomsky . Tôi muốn nói rằng tất cả các vấn đề với các biểu thức thông thường đến từ các nỗ lực phân tích ngữ pháp không ngữ cảnh với nó. Khi bạn có thể áp đặt một giới hạn (hoặc nghĩ rằng bạn có thể áp đặt một giới hạn) cho các mức lồng nhau trong CFG, bạn sẽ có được các biểu thức chính quy dài và phức tạp đó.
Các biểu thức thông thường phù hợp với mã thông báo hơn là phân tích toàn bộ quy mô.
Nhưng, một tập hợp lớn những điều đáng ngạc nhiên mà các lập trình viên cần phân tích cú pháp có thể được phân tích cú pháp bằng một ngôn ngữ thông thường (hoặc tệ hơn, gần như có thể phân tích cú pháp bằng một ngôn ngữ thông thường và nếu bạn chỉ viết thêm một chút mã ...).
Vì vậy, nếu một người đã quen với "aha, tôi cần phải tách văn bản ra, tôi sẽ sử dụng một biểu thức chính quy", thật dễ dàng để đi theo tuyến đường đó, khi bạn cần một cái gì đó gần hơn với máy tự động đẩy xuống, trình phân tích cú pháp CFG hoặc thậm chí ngữ pháp mạnh mẽ hơn. Điều đó thường kết thúc trong nước mắt.
Vì vậy, tôi nghĩ rằng trích dẫn không phải là quá nhiều các biểu thức chính tả, chúng có công dụng của chúng (và được sử dụng tốt, chúng thực sự rất hữu ích), nhưng sự phụ thuộc quá mức vào các biểu thức chính quy (hay cụ thể là sự lựa chọn không chính xác của chúng) .
jwz chỉ đơn giản là tắt rocker của mình với trích dẫn đó. các biểu thức thông thường không khác gì bất kỳ tính năng ngôn ngữ nào - dễ dàng sử dụng, khó sử dụng một cách thanh lịch, mạnh mẽ, đôi khi không phù hợp, thường được ghi chép tốt, thường hữu ích.
điều tương tự cũng có thể được nói đối với số học dấu phẩy động, bao đóng, hướng đối tượng, I / O không đồng bộ hoặc bất cứ thứ gì khác mà bạn có thể đặt tên. nếu bạn không biết bạn đang làm gì, ngôn ngữ lập trình có thể khiến bạn buồn.
nếu bạn nghĩ regexes khó đọc, hãy thử đọc triển khai trình phân tích cú pháp tương đương để sử dụng mẫu đang đề cập. thường regexes giành chiến thắng vì chúng nhỏ gọn hơn các trình phân tích cú pháp đầy đủ ... và trong hầu hết các ngôn ngữ, chúng cũng nhanh hơn.
không được sử dụng các biểu thức thông thường (hoặc bất kỳ tính năng ngôn ngữ nào khác) vì một blogger tự quảng cáo đưa ra các tuyên bố không đủ tiêu chuẩn. thử mọi thứ cho chính mình và xem những gì làm việc cho bạn.
Câu trả lời chuyên sâu, yêu thích của tôi về câu hỏi này được đưa ra bởi Rob Pike nổi tiếng trong một bài đăng trên blog được sao chép từ một nhận xét mã nội bộ của Google: http://commandcenter.blogspot.ch/2011/08/THER-expressions-in-lexing- và.html
Tóm tắt là không phải là chúng xấu , nhưng chúng thường được sử dụng cho các nhiệm vụ vì chúng không nhất thiết phải phù hợp, đặc biệt là khi nói đến việc lexing và phân tích một số đầu vào.
Các biểu thức thông thường khó viết, khó viết tốt và có thể tốn kém so với các công nghệ khác ... Mặt khác, bộ xử lý khá dễ viết chính xác (nếu không gọn nhẹ) và rất dễ kiểm tra. Xem xét việc tìm định danh chữ và số. Không quá khó để viết regrec (một cái gì đó như "[a-ZA-Z _] [a-ZA-Z_0-9] *"), nhưng thực sự không khó để viết như một vòng lặp đơn giản. Hiệu suất của vòng lặp, tuy nhiên, sẽ cao hơn nhiều và sẽ liên quan đến mã ít hơn nhiều dưới vỏ bọc. Một thư viện biểu thức chính quy là một điều lớn. Sử dụng một để phân tích số nhận dạng cũng giống như sử dụng một chiếc Ferrari để đến cửa hàng để lấy sữa.
Ông nói nhiều hơn thế, lập luận rằng các biểu thức chính quy rất hữu ích, ví dụ như kết hợp các mẫu trong các trình soạn thảo văn bản nhưng hiếm khi được sử dụng trong mã biên dịch, v.v. Nó đáng để đọc.
Điều này có liên quan đến epigram # 34 của Alan Perlis:
Chuỗi là một cấu trúc dữ liệu rõ ràng và ở mọi nơi nó được thông qua có nhiều sự trùng lặp của quá trình. Nó là một phương tiện hoàn hảo để che giấu thông tin.
Vì vậy, nếu bạn chọn chuỗi ký tự làm cấu trúc dữ liệu của mình (và, một cách tự nhiên, mã dựa trên regex làm thuật toán để thao tác nó), bạn có một vấn đề, ngay cả khi nó hoạt động: thiết kế xấu xung quanh việc thể hiện dữ liệu không phù hợp, khó có thể mở rộng, và không hiệu quả.
Tuy nhiên, thường thì nó không hoạt động: vấn đề ban đầu không được giải quyết, và vì vậy trong trường hợp đó bạn có hai vấn đề.
Regexes được sử dụng rộng rãi để phân tích văn bản nhanh và bẩn. Chúng là một công cụ tuyệt vời để thể hiện các mẫu phức tạp hơn một chút so với chỉ một chuỗi khớp đơn giản.
Tuy nhiên, khi regexes nhận được các vấn đề máy chủ phức tạp hơn, hãy ngẩng cao đầu.
Do đó, thật dễ dàng để bắt đầu với một vấn đề xử lý văn bản, áp dụng các biểu thức chính quy cho nó và kết thúc với hai vấn đề, vấn đề ban đầu mà bạn đang cố gắng giải quyết và xử lý các biểu thức thông thường đang cố gắng giải quyết (nhưng không giải quyết chính xác) vấn đề ban đầu.