Có một lý do cụ thể cho khả năng đọc kém của thiết kế cú pháp biểu thức chính quy không?


160

Tất cả các lập trình viên dường như đồng ý rằng khả năng đọc mã quan trọng hơn nhiều so với các cú pháp một cú pháp ngắn hoạt động, nhưng đòi hỏi một nhà phát triển cao cấp phải diễn giải với bất kỳ mức độ chính xác nào - nhưng dường như đó chính xác là cách các biểu thức thông thường được thiết kế. Có một lý do cho điều này?

Tất cả chúng ta đồng ý rằng selfDocumentingMethodName()tốt hơn nhiều e(). Tại sao điều đó không nên áp dụng cho các biểu thức thông thường là tốt?

Dường như với tôi, thay vì thiết kế một cú pháp logic một dòng không có tổ chức cấu trúc:

var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})(0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

Và điều này thậm chí không phải là phân tích cú pháp nghiêm ngặt của một URL!

Thay vào đó, chúng ta có thể tạo một cấu trúc đường ống có tổ chức và có thể đọc được, ví dụ cơ bản:

string.regex
   .isRange('A-Z' || 'a-z')
   .followedBy('/r');

Cú pháp cực kỳ ngắn gọn của biểu thức chính quy mang lại lợi ích gì ngoài cú pháp logic và thao tác ngắn nhất có thể? Cuối cùng, có một lý do kỹ thuật cụ thể cho khả năng đọc kém của thiết kế cú pháp biểu thức chính quy không?


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
maple_shaft

1
Tôi đã cố gắng giải quyết chính xác vấn đề dễ đọc này với một thư viện có tên RegexToolbox. Cho đến nay, nó được chuyển sang C #, Java và JavaScript - xem github.com/markwhitaker/RegexToolbox.CSharp .
Mark Whitaker

nhiều nỗ lực đã được thực hiện để giải quyết vấn đề này, nhưng văn hóa khó thay đổi. xem câu trả lời của tôi về cách diễn đạt bằng lời ở đây . Mọi người tiếp cận với công cụ chung thấp nhất có sẵn.
Parivar Saraff

Câu trả lời:


178

Có một lý do lớn tại sao các biểu thức chính quy được thiết kế ngắn gọn như vậy: chúng được thiết kế để được sử dụng làm lệnh cho trình soạn thảo mã, không phải là ngôn ngữ để mã hóa. Chính xác hơn, edlà một trong những chương trình đầu tiên sử dụng biểu thức chính quy và từ đó các biểu thức chính quy bắt đầu cuộc chinh phục thế giới của họ. Chẳng hạn, edlệnh này đã g/<regular expression>/psớm truyền cảm hứng cho một chương trình riêng biệt được gọi là grep, chương trình này vẫn còn được sử dụng cho đến ngày nay. Vì sức mạnh của họ, sau đó họ đã được tiêu chuẩn hóa và sử dụng trong nhiều công cụ như sedvim

Nhưng đủ cho những chuyện vặt vãnh. Vậy tại sao nguồn gốc này lại thiên về một ngữ pháp ngắn gọn? Bởi vì bạn không gõ lệnh biên tập để đọc nó thêm một lần nữa. Nó đủ cho bạn có thể nhớ làm thế nào để đặt nó lại với nhau, và bạn có thể làm những thứ với nó mà bạn muốn làm. Tuy nhiên, mỗi ký tự bạn phải nhập làm chậm tiến trình chỉnh sửa tệp của bạn. Cú pháp biểu thức chính quy được thiết kế để viết các tìm kiếm tương đối phức tạp theo kiểu vứt bỏ và đó chính xác là điều khiến mọi người đau đầu, những người sử dụng chúng làm mã để phân tích một số đầu vào cho chương trình.


5
regex không có nghĩa là phân tích cú pháp. nếu không, stackoverflow.com/questions/1732348/ . và đau đầu.
njzk2

19
@ njzk2 Câu trả lời đó thực sự sai. Một tài liệu HTML không phải là một ngôn ngữ thông thường, mà là một thẻ mở HTML , đó là những gì câu hỏi yêu cầu, thực sự là.
Random832

11
Đây là một câu trả lời tốt giải thích tại sao regex ban đầu lại khó hiểu như vậy, nhưng nó không giải thích tại sao hiện tại không có tiêu chuẩn thay thế nào với khả năng đọc tăng.
Doc Brown

13
Vì vậy, đối với những người nghĩ rằng đó greplà một "lấy" phát âm sai, thực tế nó đến từ g/ re(cho biểu thức chính quy) / p?
Hagen von Eitzen

6
@DannyPflughoeft Không, không. Một thẻ mở chỉ là <aaa bbb="ccc" ddd='eee'>, không có thẻ nào được lồng bên trong nó. Bạn không thể lồng các thẻ, những gì bạn lồng là các phần tử (thẻ mở, nội dung bao gồm các phần tử con, thẻ đóng), mà câu hỏi không được hỏi về phân tích cú pháp. Thẻ HTML là ngôn ngữ thông thường - việc cân bằng / lồng nhau xảy ra ở cấp độ trên các thẻ.
Random832

62

Biểu thức thông thường mà bạn trích dẫn là một mớ hỗn độn khủng khiếp và tôi không nghĩ có ai đồng ý rằng nó có thể đọc được. Đồng thời, phần lớn sự xấu xí đó là do vấn đề đang được giải quyết: Có một số lớp lồng nhau và ngữ pháp URL tương đối phức tạp (chắc chắn quá phức tạp để giao tiếp ngắn gọn trong bất kỳ ngôn ngữ nào). Tuy nhiên, chắc chắn đúng là có nhiều cách tốt hơn để mô tả những gì regex này đang mô tả. Vậy tại sao họ không sử dụng?

Một lý do lớn là quán tính và phổ biến. Điều đó không giải thích làm thế nào chúng trở nên phổ biến ở nơi đầu tiên, nhưng bây giờ, bất cứ ai biết biểu thức chính quy đều có thể sử dụng các kỹ năng này (với rất ít sự khác biệt giữa các phương ngữ) trong hàng trăm ngôn ngữ khác nhau và hàng ngàn công cụ phần mềm ( ví dụ: trình soạn thảo văn bản và các công cụ dòng lệnh). Nhân tiện, cái sau sẽ không và không thể sử dụng bất kỳ giải pháp nào tương đương với việc viết chương trình , bởi vì chúng không được sử dụng nhiều bởi những người không lập trình.

Mặc dù vậy, các biểu thức thông thường thường được sử dụng quá mức, nghĩa là, được áp dụng ngay cả khi một công cụ khác sẽ tốt hơn nhiều. Tôi không nghĩ cú pháp regex là khủng khiếp . Nhưng rõ ràng là tốt hơn nhiều ở các mẫu ngắn và đơn giản: Ví dụ điển hình về số nhận dạng trong các ngôn ngữ giống như C, [a-zA-Z_][a-zA-Z0-9_]*có thể được đọc với kiến ​​thức regex tối thiểu tuyệt đối và một khi thanh đó được đáp ứng thì rõ ràng và cô đọng. Yêu cầu ít nhân vật hơn không phải là xấu, hoàn toàn ngược lại. Là súc tích là một đức tính miễn là bạn vẫn có thể hiểu được.

Có ít nhất hai lý do tại sao cú pháp này vượt trội ở các mẫu đơn giản như sau: Nó không yêu cầu thoát cho hầu hết các ký tự, vì vậy nó đọc tương đối tự nhiên và nó sử dụng tất cả các dấu câu có sẵn để diễn tả nhiều cách kết hợp phân tích cú pháp đơn giản. Có lẽ quan trọng nhất, nó không yêu cầu bất cứ điều gì cả cho trình tự. Bạn viết điều đầu tiên, sau đó là điều đến sau nó. Tương phản điều này với của bạn followedBy, đặc biệt là khi mẫu sau không phải là một nghĩa đen mà là một biểu thức phức tạp hơn.

Vậy tại sao họ lại rơi vào những trường hợp phức tạp hơn? Tôi có thể thấy ba vấn đề chính:

  1. Không có khả năng trừu tượng. Các ngữ pháp chính thức, bắt nguồn từ cùng một lĩnh vực khoa học máy tính lý thuyết như regexes, có một bộ sản phẩm, vì vậy chúng có thể đặt tên cho các phần trung gian của mẫu:

    # This is not equivalent to the regex in the question
    # It's just a mock-up of what a grammar could look like
    url      ::= protocol? '/'? '/'? '/'? (domain_part '.')+ tld
    protocol ::= letter+ ':'
    ...
    
  2. Như chúng ta có thể thấy ở trên, khoảng trắng không có ý nghĩa đặc biệt là hữu ích để cho phép định dạng dễ dàng hơn trong mắt. Điều tương tự với ý kiến. Biểu thức thông thường không thể làm điều đó bởi vì một không gian chỉ là như vậy, theo nghĩa đen ' '. Lưu ý rằng: một số triển khai cho phép chế độ "dài dòng" trong đó khoảng trắng bị bỏ qua và có thể nhận xét.

  3. Không có ngôn ngữ meta để mô tả các mẫu và tổ hợp phổ biến. Ví dụ, người ta có thể viết digitquy tắc một lần và tiếp tục sử dụng nó trong ngữ pháp không ngữ cảnh, nhưng người ta không thể định nghĩa một "hàm" để nói rằng nó được tạo ra một sản phẩm pvà tạo ra một sản phẩm mới có thêm một thứ gì đó, ví dụ như tạo ra một sản phẩm cho một danh sách các dấu phẩy được phân tách bằng dấu phẩy p.

Cách tiếp cận mà bạn đề xuất chắc chắn giải quyết được những vấn đề này. Nó chỉ không giải quyết chúng rất tốt, bởi vì nó giao dịch với sự đồng nhất hơn nhiều so với nó là cần thiết. Hai vấn đề đầu tiên có thể được giải quyết trong khi vẫn nằm trong một ngôn ngữ cụ thể theo miền tương đối đơn giản và ngắn gọn. Tất nhiên, thứ ba ... một giải pháp lập trình đòi hỏi một ngôn ngữ lập trình có mục đích chung, nhưng theo kinh nghiệm của tôi thì thứ ba là ít nhất trong số những vấn đề đó. Rất ít mẫu có đủ sự xuất hiện của cùng một nhiệm vụ phức tạp mà lập trình viên khao khát về khả năng xác định các tổ hợp mới. Và khi điều này là cần thiết, ngôn ngữ thường phức tạp đến mức không thể và không nên phân tích cú pháp bằng các biểu thức thông thường.

Giải pháp cho những trường hợp tồn tại. Có khoảng mười nghìn thư viện trình kết hợp trình phân tích cú pháp thực hiện gần đúng những gì bạn đề xuất, chỉ với một tập hợp hoạt động khác nhau, thường là cú pháp khác nhau và hầu như luôn có sức mạnh phân tích cú pháp nhiều hơn các biểu thức thông thường (nghĩa là chúng xử lý các ngôn ngữ không ngữ cảnh hoặc một số ngôn ngữ khá lớn tập hợp con của những cái đó). Sau đó, có các trình tạo trình phân tích cú pháp, đi theo phương pháp "sử dụng DSL tốt hơn" được mô tả ở trên. Và luôn có tùy chọn viết một số phân tích cú pháp bằng tay, bằng mã thích hợp. Bạn thậm chí có thể trộn và kết hợp, sử dụng các biểu thức thông thường cho các tác vụ phụ đơn giản và thực hiện những điều phức tạp trong mã gọi các biểu thức chính.

Tôi không biết đủ về những năm đầu của máy tính để giải thích cách các biểu thức chính quy trở nên phổ biến. Nhưng họ ở đây để ở lại. Bạn chỉ cần sử dụng chúng một cách khôn ngoan, và không sử dụng chúng khi đó là khôn ngoan hơn.


9
I don't know enough about the early years of computing to explain how regular expressions came to be so popular.Chúng ta có thể mạo hiểm đoán: một công cụ biểu thức chính quy cơ bản rất dễ thực hiện, dễ dàng hơn nhiều so với trình phân tích cú pháp không ngữ cảnh hiệu quả.
biziclop

15
@biziclop Tôi sẽ không đánh giá quá cao biến này. Yacc, dường như có đủ các phiên bản tiền nhiệm được gọi là " trình biên dịch trình biên dịch khác ", được tạo ra vào đầu những năm 70 và được đưa vào Unix một phiên bản trước đó greplà (Phiên bản 3 so với Phiên bản 4). Nó xuất hiện lần đầu tiên sử dụng regex là vào năm 1968.

Tôi chỉ có thể tiếp tục với những gì tôi tìm thấy trên Wikipedia (vì vậy tôi sẽ không tin 100%) nhưng theo đó, yaccđược tạo ra vào năm 1975, toàn bộ ý tưởng về các trình phân tích cú pháp LALR (nằm trong số các trình phân tích cú pháp thực tế có thể sử dụng đầu tiên của họ loại) có nguồn gốc từ năm 1973. Trong khi đó, triển khai công cụ regrec đầu tiên mà JIT đã biên dịch biểu thức (!) đã được xuất bản vào năm 1968. Nhưng bạn nói đúng, thật khó để nói điều gì đã vung nó, thực tế là rất khó để nói khi nào các biểu thức bắt đầu "thực hiện" tắt". Nhưng tôi nghi ngờ một khi chúng được đưa vào trình soạn thảo văn bản mà các nhà phát triển đã sử dụng, họ cũng muốn sử dụng chúng trong phần mềm của riêng họ.
biziclop

1
@ jpmc26 mở cuốn sách của mình, JavaScript Các bộ phận tốt cho Chương Regex.
Viziionary

2
with very few differences between dialectsTôi sẽ không nói đó là "rất ít". Bất kỳ lớp ký tự được xác định trước có một số định nghĩa giữa các phương ngữ khác nhau. Và cũng có những phân tích phân tích cụ thể cho từng phương ngữ.
nhahtdh

39

Quan điểm lịch sử

Bài viết Wikipedia khá chi tiết về nguồn gốc của các biểu thức chính quy (Kleene, 1956). Cú pháp ban đầu là tương đối đơn giản với chỉ *, +, ?, |và nhóm (...). Thật là ngắn gọn ( có thể đọc được, cả hai không nhất thiết phải đối lập nhau), bởi vì các ngôn ngữ chính thức có xu hướng được thể hiện bằng các ký hiệu toán học ngắn gọn.

Sau đó, cú pháp và khả năng đã phát triển với các biên tập viên và phát triển với Perl , vốn đang cố gắng trở nên ngắn gọn bởi thiết kế ( "các công trình chung nên ngắn" ). Điều này làm phức tạp cú pháp rất nhiều, nhưng lưu ý rằng mọi người giờ đã quen với các biểu thức thông thường và rất giỏi viết (nếu không đọc) chúng. Thực tế là đôi khi chúng chỉ viết cho thấy rằng khi chúng quá dài, chúng thường không phải là công cụ phù hợp. Biểu thức thông thường có xu hướng không thể đọc được khi bị lạm dụng.

Ngoài các biểu thức chính quy dựa trên chuỗi

Nói về các cú pháp thay thế, chúng ta hãy xem xét một cú pháp đã tồn tại ( cl-ppcre , trong Common Lisp ). Biểu thức chính quy dài của bạn có thể được phân tích cú pháp ppcre:parse-stringnhư sau:

(let ((*print-case* :downcase)
      (*print-right-margin* 50))
  (pprint
   (ppcre:parse-string "^(?:([A-Za-z]+):)?(\\/{0,3})(0-9.\\-A-Za-z]+)(?::(\\d+))?(?:\\/([^?#]*))?(?:\\?([^#]*))?(?:#(.*))?$")))

... và kết quả ở dạng sau:

(:sequence :start-anchor
 (:greedy-repetition 0 1
  (:group
   (:sequence
    (:register
     (:greedy-repetition 1 nil
      (:char-class (:range #\A #\Z)
       (:range #\a #\z))))
    #\:)))
 (:register (:greedy-repetition 0 3 #\/))
 (:register
  (:sequence "0-9" :everything "-A-Za-z"
   (:greedy-repetition 1 nil #\])))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\:
    (:register
     (:greedy-repetition 1 nil :digit-class)))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\/
    (:register
     (:greedy-repetition 0 nil
      (:inverted-char-class #\? #\#))))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\?
    (:register
     (:greedy-repetition 0 nil
      (:inverted-char-class #\#))))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\#
    (:register
     (:greedy-repetition 0 nil :everything)))))
 :end-anchor)

Cú pháp này dài dòng hơn, và nếu bạn nhìn vào các bình luận bên dưới, không nhất thiết phải dễ đọc hơn. Vì vậy, đừng cho rằng vì bạn có cú pháp nhỏ gọn hơn nên mọi thứ sẽ tự động rõ ràng hơn .

Tuy nhiên, nếu bạn bắt đầu gặp rắc rối với các biểu thức thông thường, biến chúng thành định dạng này có thể giúp bạn giải mã và gỡ lỗi mã của mình. Đây là một lợi thế so với các định dạng dựa trên chuỗi, trong đó một lỗi ký tự đơn có thể khó phát hiện. Ưu điểm chính của cú pháp này là thao tác các biểu thức thông thường bằng cách sử dụng định dạng có cấu trúc thay vì mã hóa dựa trên chuỗi. Điều đó cho phép bạn soạn thảoxây dựng các biểu thức như bất kỳ cấu trúc dữ liệu nào khác trong chương trình của bạn. Khi tôi sử dụng cú pháp trên, điều này thường là vì tôi muốn xây dựng các biểu thức từ các phần nhỏ hơn (xem thêm câu trả lời CodeGolf của tôi ). Ví dụ của bạn, chúng tôi có thể viết 1 :

`(:sequence
   :start-anchor
   ,(protocol)
   ,(slashes)
   ,(domain)
   ,(top-level-domain) ... )

Các biểu thức chính quy dựa trên chuỗi cũng có thể được tạo, sử dụng nối chuỗi và hoặc nội suy được gói trong các hàm trợ giúp. Tuy nhiên, có những hạn chế với chuỗi các thao tác mà có xu hướng lộn xộn các đang (suy nghĩ về vấn đề làm tổ, không giống như backticks vs $(...)trong bash; cũng vậy, thoát khỏi nhân vật có thể cung cấp cho bạn đau đầu).

Cũng lưu ý rằng biểu mẫu trên cho phép (:regex "string")các biểu mẫu để bạn có thể trộn các ký hiệu ngắn gọn với cây. Tất cả điều đó dẫn IMHO đến khả năng đọc và khả năng kết hợp tốt; nó giải quyết ba vấn đề được thể hiện bằng delnan , một cách gián tiếp (tức là không phải bằng ngôn ngữ của chính các biểu thức chính quy).

Để kết luận

  • Đối với hầu hết các mục đích, ký hiệu terse trên thực tế có thể đọc được. Có những khó khăn khi xử lý các ký hiệu mở rộng liên quan đến quay lui, v.v., nhưng việc sử dụng chúng hiếm khi được biện minh. Việc sử dụng không chính đáng các biểu thức thông thường có thể dẫn đến các biểu thức không thể đọc được.

  • Các biểu thức thông thường không cần phải được mã hóa dưới dạng chuỗi. Nếu bạn có thư viện hoặc công cụ có thể giúp bạn xây dựng và soạn các biểu thức thông thường, bạn sẽ tránh được rất nhiều lỗi tiềm ẩn liên quan đến thao tác chuỗi.

  • Ngoài ra, ngữ pháp chính thức dễ đọc hơn và tốt hơn trong việc đặt tên và trừu tượng hóa các biểu thức con. Thiết bị đầu cuối thường được thể hiện dưới dạng biểu thức chính quy đơn giản.


1. Bạn có thể thích xây dựng các biểu thức của mình trong thời gian đọc, bởi vì các biểu thức thông thường có xu hướng là hằng số trong một ứng dụng. Xem create-scannerload-time-value:

'(:sequence :start-anchor #.(protocol) #.(slashes) ... )

5
Có thể tôi chỉ quen với cú pháp RegEx truyền thống, nhưng tôi không chắc rằng 22 dòng có thể đọc được dễ hiểu hơn so với regex một dòng tương đương.

3
@ dan1111 "phần nào có thể đọc được" ;-) Được rồi, nhưng nếu bạn cần phải có một regex thực sự dài, nó làm cho tinh thần để xác định các tập con, giống như digits, ident, và soạn chúng. Theo cách chúng tôi thấy nó được thực hiện nói chung với các thao tác chuỗi (nối hoặc nội suy), điều này mang lại các vấn đề khác như thoát thích hợp. Tìm kiếm sự xuất hiện của \\\\`các gói emacs, ví dụ. Btw, điều này được thực hiện tồi tệ hơn bởi vì các ký tự thoát cùng được sử dụng cả cho các ký tự đặc biệt như \n\"và cú pháp regex \(. Một ví dụ không thể thiếu của cú pháp tốt là printf, nơi %dkhông xung đột với \d.
coredump

1
điểm công bằng về các tập con được xác định. Điều đó làm cho rất nhiều ý nghĩa. Tôi chỉ hoài nghi rằng tính dài dòng là một sự cải thiện. Nó có thể dễ dàng hơn cho người mới bắt đầu (mặc dù các khái niệm như greedy-repetitionkhông trực quan và vẫn phải học). Tuy nhiên, nó hy sinh khả năng sử dụng cho các chuyên gia, vì khó hơn nhiều để xem và nắm bắt toàn bộ mô hình.

@ dan1111 Tôi đồng ý rằng bản thân sự dài dòng không phải là một sự cải tiến. Điều có thể là một cải tiến là thao tác regex bằng cách sử dụng dữ liệu có cấu trúc thay vì chuỗi.
coredump

@ dan1111 Có lẽ tôi nên đề xuất chỉnh sửa bằng Haskell? Parsec thực hiện nó chỉ trong chín dòng; như một lớp lót : do {optional (many1 (letter) >> char ':'); choice (map string ["///","//","/",""]); many1 (oneOf "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."); optional (char ':' >> many1 digit); optional (char '/' >> many (noneOf "?#")); optional (char '?' >> many (noneOf "#")); optional (char '#' >> many (noneOf "\n")); eof}. Với một vài dòng như chỉ định chuỗi dài domainChars = ...section start p = optional (char start >> many p)trông khá đơn giản.
CR Drost

25

Vấn đề lớn nhất với regex không phải là cú pháp quá ngắn gọn, đó là chúng ta cố gắng diễn đạt một định nghĩa phức tạp trong một biểu thức, thay vì kết hợp nó từ các khối xây dựng nhỏ hơn. Điều này tương tự như lập trình nơi bạn không bao giờ sử dụng các biến và hàm và thay vào đó nhúng tất cả mã của bạn vào một dòng.

So sánh regex với BNF . Cú pháp của nó không sạch hơn regex, nhưng nó được sử dụng khác nhau. Bạn bắt đầu bằng cách xác định các ký hiệu được đặt tên đơn giản và soạn chúng cho đến khi bạn đến một biểu tượng mô tả toàn bộ mẫu bạn muốn khớp.

Ví dụ, hãy xem cú pháp URI trong rfc3986 :

URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
hier-part     = "//" authority path-abempty
              / path-absolute
              / path-rootless
              / path-empty
...

Bạn có thể viết gần giống như vậy bằng cách sử dụng một biến thể của cú pháp regex hỗ trợ nhúng các biểu thức con có tên.


Cá nhân tôi nghĩ rằng một cú pháp regex ngắn gọn như vậy là tốt cho các tính năng thường được sử dụng như lớp nhân vật, nối, lựa chọn hoặc lặp lại, nhưng đối với các tính năng phức tạp và hiếm hơn như tên verbose nhìn về phía trước thì thích hợp hơn. Khá giống với cách chúng ta sử dụng các toán tử như +hoặc *trong lập trình bình thường và chuyển sang các hàm được đặt tên cho các hoạt động hiếm hơn.


12

selfDocumentingMethodName () tốt hơn nhiều so với e ()

Là nó? Có một lý do mà hầu hết các ngôn ngữ có {và} là các dấu phân cách khối thay vì BEGIN và END.

Mọi người thích sự căng thẳng, và một khi bạn biết cú pháp, thuật ngữ ngắn sẽ tốt hơn. Hãy tưởng tượng ví dụ regex của bạn nếu d (đối với chữ số) là 'chữ số' thì regex sẽ còn kinh khủng hơn khi đọc. Nếu bạn làm cho nó dễ phân tích cú pháp hơn với các ký tự điều khiển, thì nó sẽ trông giống XML hơn. Không phải là tốt khi bạn biết cú pháp.

Để trả lời đúng câu hỏi của bạn, bạn phải nhận ra rằng regex xuất phát từ những ngày mà sự căng thẳng là bắt buộc. Thật dễ dàng để nghĩ rằng một tài liệu XML 1 MB không phải là vấn đề lớn ngày nay, nhưng chúng ta đang nói về những ngày mà 1 MB là khá nhiều toàn bộ dung lượng lưu trữ của bạn. Ngoài ra còn có ít ngôn ngữ sử dụng trở lại sau đó, và regex không phải là một triệu dặm từ perl hoặc C, vì vậy cú pháp sẽ là quen thuộc đối với các lập trình viên trong ngày người sẽ được hạnh phúc với việc học cú pháp. Vì vậy, không có lý do để làm cho nó dài dòng hơn.


1
selfDocumentingMethodNameđược nói chung đồng ý là tốt hơn so với evì lập trình trực giác không thẳng hàng với thực tế về những gì thực sự tạo nên khả năng đọc hoặc mã chất lượng tốt . Những người đồng ý là sai, nhưng đó là như thế.
Leushenko

1
@Leushenko: Bạn có cho rằng điều đó e()tốt hơn selfDocumentingMethodName()không?
JacquesB

3
@JacquesB có thể không có trong tất cả các bối cảnh (như tên toàn cầu). Nhưng đối với những thứ có phạm vi chặt chẽ? Gần như chắc chắn. Chắc chắn thường xuyên hơn so với sự khôn ngoan thông thường nói.
Leushenko

1
@Leushenko: Tôi có một thời gian khó tưởng tượng ra một bối cảnh là một tên hàm chữ cái tốt hơn một tên mô tả. Nhưng tôi đoán đây là ý kiến ​​thuần túy.
JacquesB

1
@MilesRout: Ví dụ thực sự là e()so với tên phương thức tự viết tài liệu . Bạn có thể giải thích trong bối cảnh nào đó là một cải tiến để sử dụng tên phương thức một chữ cái thay vì tên phương thức mô tả không?
JacquesB

6

Regex giống như những mảnh lego. Thoạt nhìn, bạn thấy một số bộ phận bằng nhựa có hình dạng khác nhau có thể được nối. Bạn có thể nghĩ rằng sẽ không có quá nhiều thứ khác nhau có thể bạn có thể định hình nhưng sau đó bạn thấy những điều tuyệt vời mà người khác làm và bạn chỉ tự hỏi nó là một món đồ chơi tuyệt vời như thế nào.

Regex giống như những mảnh lego. Có một vài đối số có thể được sử dụng nhưng xâu chuỗi chúng ở các dạng khác nhau sẽ tạo thành hàng triệu mẫu biểu thức chính quy khác nhau có thể được sử dụng cho nhiều tác vụ phức tạp.

Mọi người hiếm khi sử dụng tham số regex một mình. Nhiều ngôn ngữ cung cấp cho bạn các hàm để kiểm tra độ dài của chuỗi hoặc tách các phần số ra khỏi chuỗi. Bạn có thể sử dụng các hàm chuỗi để cắt các văn bản và cải tổ chúng. Sức mạnh của regex được chú ý khi bạn sử dụng các hình thức phức tạp để thực hiện các nhiệm vụ phức tạp rất cụ thể.

Bạn có thể tìm thấy hàng chục ngàn câu hỏi regex trên SO và chúng hiếm khi được đánh dấu là trùng lặp. Điều này một mình cho thấy các trường hợp sử dụng duy nhất có thể rất khác nhau.

Và không dễ để cung cấp các phương thức được xác định trước để xử lý các tác vụ độc đáo khác nhau này. Bạn có các hàm chuỗi cho các loại tác vụ đó, nhưng nếu các hàm đó không đủ cho tác vụ cụ thể của bạn, thì đã đến lúc sử dụng regex


2

Tôi nhận ra đây là một vấn đề của thực tiễn hơn là tiềm năng. Vấn đề thường phát sinh khi các biểu thức chính quy được thực hiện trực tiếp , thay vì giả định tính chất tổng hợp. Tương tự, một lập trình viên giỏi sẽ phân tách các chức năng của chương trình của mình thành các phương thức súc tích.

Ví dụ: chuỗi regex cho một URL có thể được giảm từ khoảng:

UriRe = [scheme][hier-part][query][fragment]

đến:

UriRe = UriSchemeRe + UriHierRe + "(/?|/" + UriQueryRe + UriFragRe + ")"
UriSchemeRe = [scheme]
UriHierRe = [hier-part]
UriQueryRe = [query]
UriFragRe = [fragment]

Biểu thức thông thường là những điều tiện lợi, nhưng chúng dễ bị lạm dụng bởi những người trở nên mải mê với sự phức tạp rõ ràng của chúng . Các biểu thức kết quả là hùng biện, vắng mặt của một giá trị dài hạn.


2
Thật không may, hầu hết các ngôn ngữ lập trình không bao gồm chức năng giúp soạn thảo các biểu thức chính và cách thức hoạt động của nhóm chụp cũng không thân thiện với bố cục.
CodeInChaos

1
Các ngôn ngữ khác cần bắt kịp Perl 5 trong hỗ trợ "biểu thức chính quy tương thích perl" của chúng. Subexpression không giống như các chuỗi đơn giản của đặc tả regex. Chụp nên được đặt tên, không dựa vào đánh số ngầm.
JDługosz

0

Như @cmaster nói, regexps ban đầu được thiết kế để chỉ được sử dụng khi đang di chuyển, và điều đơn giản là kỳ lạ (và hơi thất vọng) rằng cú pháp nhiễu đường truyền vẫn là phổ biến nhất. Những lời giải thích duy nhất tôi có thể nghĩ đến liên quan đến quán tính, khổ dâm hoặc máy móc (không thường xuyên là 'quán tính' là lý do hấp dẫn nhất để làm gì đó ...)

Perl thực hiện một nỗ lực khá yếu trong việc làm cho chúng dễ đọc hơn bằng cách cho phép khoảng trắng và nhận xét, nhưng không làm bất cứ điều gì từ xa tưởng tượng.

Có những cú pháp khác. Một cái tốt là cú pháp scsh cho regexps , theo kinh nghiệm của tôi tạo ra các regexps rất dễ gõ, nhưng vẫn có thể đọc được sau khi thực tế.

[ scsh là tuyệt vời cho các lý do khác, chỉ một trong số đó là văn bản xác nhận nổi tiếng của nó ]


2
Perl6 nào! Nhìn vào ngữ pháp.
JDługosz

@ JDługosz Theo như tôi có thể thấy, nó trông giống như một cơ chế cho các trình tạo trình phân tích cú pháp, hơn là một cú pháp thay thế cho các biểu thức thông thường. Nhưng sự khác biệt có lẽ không phải là một sâu sắc.
Norman Gray

Nó có thể là một sự thay thế, nhưng không giới hạn ở cùng một sức mạnh. Bạn có thể dịch một regedp thành một ngữ pháp nội tuyến với sự tương ứng 1 đến 1 của các sửa đổi nhưng theo một cú pháp dễ đọc hơn. Các ví dụ quảng bá nó như vậy là trong Ngày tận thế Perl ban đầu.
JDługosz

0

Tôi tin rằng các biểu thức chính quy được thiết kế sao cho 'chung chung' và đơn giản nhất có thể, vì vậy chúng có thể được sử dụng (đại khái) theo cùng một cách ở bất cứ đâu.

Ví dụ của bạn regex.isRange(..).followedBy(..)được kết hợp với cả cú pháp của ngôn ngữ lập trình cụ thể và có lẽ là phong cách hướng đối tượng (chuỗi phương thức).

Làm thế nào điều này chính xác 'regex' trong C chẳng hạn? Mã sẽ phải được thay đổi.

Cách tiếp cận 'chung' nhất sẽ là xác định một ngôn ngữ ngắn gọn đơn giản mà sau đó có thể dễ dàng nhúng vào bất kỳ ngôn ngữ nào khác mà không thay đổi. Và đó (gần như) regex là gì.


0

Các công cụ biểu thức chính quy tương thích Perl được sử dụng rộng rãi, cung cấp một cú pháp biểu thức chính quy ngắn gọn mà nhiều biên tập viên và ngôn ngữ hiểu được. Như @ JDługosz đã chỉ ra trong các bình luận, Perl 6 (không chỉ là phiên bản mới của Perl 5, mà là một ngôn ngữ hoàn toàn khác) đã cố gắng làm cho các biểu thức chính quy dễ đọc hơn bằng cách xây dựng chúng từ các yếu tố được xác định riêng lẻ. Ví dụ: đây là một ngữ pháp ví dụ để phân tích cú pháp URL từ Wikibooks :

grammar URL {
  rule TOP {
    <protocol>'://'<address>
  }
  token protocol {
    'http'|'https'|'ftp'|'file'
  }
  rule address {
    <subdomain>'.'<domain>'.'<tld>
  }
  ...
}

Cắt các biểu hiện thường xuyên như thế này cho phép mỗi bit được xác định riêng (ví dụ như hạn chế domainđược chữ và số) hoặc mở rộng thông qua subclassing (ví dụ FileURL is URLrằng trở ngại protocolchỉ được "file").

Vì vậy: không, không có lý do kỹ thuật nào cho sự căng thẳng của các biểu thức thông thường, nhưng các cách mới hơn, sạch hơn và dễ đọc hơn để thể hiện chúng đã có ở đây! Vì vậy, hy vọng chúng ta sẽ thấy một số ý tưởng mới trong lĩnh vực này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.