Semua programmer tampaknya setuju bahwa keterbacaan kode jauh lebih penting daripada one-liner sintaksis pendek yang berfungsi, tetapi membutuhkan pengembang senior untuk menginterpretasikan dengan tingkat keakuratan apa pun - tetapi tampaknya seperti cara ekspresi reguler dirancang. Apakah ada alasan untuk ini?
Kita semua sepakat bahwa selfDocumentingMethodName()
itu jauh lebih baik daripada e()
. Mengapa itu tidak berlaku untuk ekspresi reguler juga?
Tampak bagi saya bahwa alih-alih mendesain sintaksis logika satu baris tanpa organisasi struktural:
var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})(0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
Dan ini bahkan bukan penguraian URL yang ketat!
Sebagai gantinya, kita dapat membuat struktur pipa tersusun dan dapat dibaca, sebagai contoh dasar:
string.regex
.isRange('A-Z' || 'a-z')
.followedBy('/r');
Apa keuntungan yang ditawarkan oleh sintaks yang sangat singkat dari ekspresi reguler selain dari sintaksis operasi dan logika sesingkat mungkin? Pada akhirnya, adakah alasan teknis khusus untuk keterbacaan yang buruk dari desain sintaks ekspresi reguler?
sumber
Jawaban:
Ada satu alasan besar mengapa ekspresi reguler dirancang sesederhana seperti itu: mereka dirancang untuk digunakan sebagai perintah untuk editor kode, bukan sebagai bahasa untuk kode masuk. Lebih tepatnya,
ed
adalah salah satu program pertama yang menggunakan ekspresi reguler , dan dari sana ekspresi reguler memulai penaklukan mereka untuk menguasai dunia. Misalnya,ed
perintahg/<regular expression>/p
segera menginspirasi program terpisah yang disebutgrep
, yang masih digunakan sampai sekarang. Karena kekuatan mereka, mereka kemudian distandarisasi dan digunakan dalam berbagai alat sepertised
danvim
Tapi cukup untuk hal-hal sepele. Jadi mengapa asal ini mendukung tata bahasa singkat? Karena Anda tidak mengetik perintah editor untuk membacanya sekali lagi. Cukuplah bahwa Anda dapat mengingat bagaimana menyatukannya, dan Anda dapat melakukan hal-hal yang ingin Anda lakukan. Namun, setiap karakter yang Anda ketikkan memperlambat progres Anda mengedit file Anda. Sintaks ekspresi reguler dirancang untuk menulis pencarian yang relatif kompleks dengan cara membuang, dan itulah yang membuat orang sakit kepala yang menggunakannya sebagai kode untuk mengurai beberapa input ke suatu program.
sumber
grep
"ambil" salah diartikan, itu sebenarnya berasal darig
/re
(untuk ekspresi reguler) /p
?<aaa bbb="ccc" ddd='eee'>
, tidak ada tag yang bersarang di dalamnya. Anda tidak dapat membuat sarang tag, apa yang Anda sarang adalah elemen (tag terbuka, konten termasuk elemen anak, tag dekat), yang pertanyaannya tidak diajukan tentang penguraian. Tag HTML adalah bahasa reguler - balancing / nesting terjadi pada level di atas tag.Ekspresi reguler yang Anda kutip adalah kekacauan yang mengerikan dan saya tidak berpikir ada yang setuju bahwa itu dapat dibaca. Pada saat yang sama, banyak keburukan yang melekat pada masalah yang dipecahkan: Ada beberapa lapisan sarang dan tata bahasa URL relatif rumit (tentu saja terlalu rumit untuk berkomunikasi secara ringkas dalam bahasa apa pun). Namun, memang benar bahwa ada cara yang lebih baik untuk menggambarkan apa yang dideskripsikan oleh regex ini. Jadi mengapa tidak digunakan?
Alasan utama adalah kelembaman dan di mana-mana. Itu tidak menjelaskan bagaimana mereka menjadi begitu populer di tempat pertama, tetapi sekarang mereka, siapa pun yang tahu ekspresi reguler dapat menggunakan keterampilan ini (dengan sedikit perbedaan antara dialek) dalam seratus bahasa yang berbeda dan seribu alat perangkat lunak tambahan ( misalnya, editor teks dan alat baris perintah). By the way, yang terakhir tidak akan dan tidak bisa menggunakan solusi apa pun yang berarti menulis program , karena mereka banyak digunakan oleh non-programmer.
Meskipun demikian, ekspresi reguler sering digunakan secara berlebihan, yaitu, diterapkan bahkan ketika alat lain akan jauh lebih baik. Saya tidak berpikir sintaks regex mengerikan . Tetapi jelas jauh lebih baik pada pola pendek dan sederhana: Contoh pola dasar pengidentifikasi dalam bahasa seperti C,
[a-zA-Z_][a-zA-Z0-9_]*
dapat dibaca dengan minimum absolut pengetahuan regex dan sekali bar dipenuhi, itu jelas dan ringkas. Membutuhkan lebih sedikit karakter pada dasarnya tidak buruk, justru sebaliknya. Bersikap ringkas adalah kebajikan asalkan Anda tetap dapat dipahami.Paling tidak ada dua alasan mengapa sintaks ini unggul pada pola-pola sederhana seperti ini: Sintaksis tidak perlu melarikan diri untuk sebagian besar karakter, jadi itu terbaca secara alami, dan ia menggunakan semua tanda baca yang tersedia untuk mengekspresikan berbagai kombinator parsing sederhana. Mungkin yang paling penting, itu tidak memerlukan apa pun untuk sequencing. Anda menulis hal pertama, lalu hal yang datang setelahnya. Bandingkan ini dengan Anda
followedBy
, terutama ketika pola berikut ini bukan ekspresi literal tetapi lebih rumit.Jadi mengapa mereka gagal dalam kasus yang lebih rumit? Saya dapat melihat tiga masalah utama:
Tidak ada kemampuan abstraksi. Tata bahasa formal, yang berasal dari bidang ilmu komputer teoretis yang sama dengan regex, memiliki serangkaian produksi, sehingga mereka dapat memberi nama pada bagian-bagian menengah dari pola:
Seperti yang bisa kita lihat di atas, spasi putih yang tidak memiliki arti khusus berguna untuk mengizinkan pemformatan yang lebih mudah di mata. Sama halnya dengan komentar. Ekspresi reguler tidak dapat melakukan itu karena spasi hanya itu, literal
' '
. Catatan: beberapa implementasi memungkinkan mode "verbose" di mana spasi putih diabaikan dan komentar dimungkinkan.Tidak ada meta-bahasa untuk menggambarkan pola dan kombinator umum. Misalnya, seseorang dapat menulis
digit
aturan sekali dan terus menggunakannya dalam konteks tata bahasa gratis, tetapi orang tidak dapat mendefinisikan "fungsi" sehingga untuk berbicara yang diberi produksip
dan menciptakan produksi baru yang melakukan sesuatu yang ekstra dengannya, misalnya membuat produksi untuk daftar kejadian yang dipisahkan koma darip
.Pendekatan yang Anda usulkan tentu memecahkan masalah ini. Itu hanya tidak menyelesaikan mereka dengan sangat baik, karena diperdagangkan jauh lebih singkat daripada yang diperlukan. Dua masalah pertama dapat diselesaikan sambil tetap menggunakan bahasa khusus domain yang relatif sederhana dan singkat. Yang ketiga, tentu saja ... solusi terprogram membutuhkan bahasa pemrograman tujuan umum tentu saja, tetapi dalam pengalaman saya yang ketiga adalah yang paling sedikit dari masalah itu. Beberapa pola memiliki cukup banyak kejadian dari tugas kompleks yang sama yang programmer rindukan untuk mendefinisikan combinator baru. Dan ketika ini diperlukan, bahasa sering kali cukup rumit sehingga tidak bisa dan tidak boleh diurai dengan ekspresi reguler.
Solusi untuk kasus-kasus itu ada. Ada sekitar sepuluh ribu parser Combinator librari yang melakukan kira-kira apa yang Anda usulkan, hanya dengan serangkaian operasi yang berbeda, sintaksis yang sering berbeda, dan hampir selalu dengan kekuatan parsing lebih dari ekspresi reguler (yaitu, mereka berurusan dengan bahasa bebas-konteks atau cukup besar bagian dari mereka). Lalu ada generator parser, yang sesuai dengan pendekatan "gunakan DSL yang lebih baik" seperti dijelaskan di atas. Dan selalu ada opsi untuk menulis beberapa parsing dengan tangan, dalam kode yang tepat. Anda bahkan dapat mencampur dan mencocokkan, menggunakan ekspresi reguler untuk sub-tugas sederhana dan melakukan hal-hal rumit dalam kode dengan memanggil regex.
Saya tidak cukup tahu tentang tahun-tahun awal komputasi untuk menjelaskan bagaimana ekspresi reguler menjadi sangat populer. Tapi mereka di sini untuk tinggal. Anda hanya harus menggunakannya dengan bijak, dan tidak menggunakannya saat itu lebih bijaksana.
sumber
I don't know enough about the early years of computing to explain how regular expressions came to be so popular.
Namun, kita dapat menebak: mesin ekspresi reguler dasar sangat mudah diimplementasikan, jauh lebih mudah daripada parser bebas konteks yang efisien.grep
adalah (Versi 3 vs Versi 4). Tampaknya penggunaan utama pertama regex adalah pada tahun 1968.yacc
dibuat pada tahun 1975, seluruh ide parser LALR (yang termasuk di antara kelas pertama dari parser yang dapat digunakan secara praktis dari jenis) berasal pada tahun 1973. Sedangkan implementasi mesin regexp pertama yang dikompilasi ekspresi JIT (!) diterbitkan pada tahun 1968. Tetapi Anda benar, sulit untuk mengatakan apa yang mengayunkannya, bahkan sulit untuk mengatakan kapan regex mulai "mengambil mati". Tapi saya curiga begitu mereka dimasukkan ke dalam editor teks yang digunakan pengembang, mereka ingin menggunakannya dalam perangkat lunak mereka sendiri juga.with very few differences between dialects
Saya tidak akan mengatakan itu "sangat sedikit". Setiap kelas karakter standar memiliki beberapa definisi antara dialek yang berbeda. Dan ada juga kebiasaan parsing khusus untuk setiap dialek.Perspektif sejarah
Artikel Wikipedia cukup detail tentang asal mula ekspresi reguler (Kleene, 1956). Sintaks asli relatif sederhana dengan hanya
*
,+
,?
,|
dan pengelompokan(...)
. Itu singkat ( dan dapat dibaca, keduanya tidak perlu menentang), karena bahasa formal cenderung diekspresikan dengan notasi matematika singkat.Kemudian, sintaks dan kapabilitas berevolusi dengan editor dan tumbuh dengan Perl , yang berusaha singkat dengan desain ( "konstruksi umum harus pendek" ). Ini banyak memperumit sintaksis, tetapi perhatikan bahwa orang sekarang terbiasa dengan ekspresi reguler dan pandai menulis (jika tidak membaca) mereka. Fakta bahwa mereka kadang-kadang hanya menulis menunjukkan bahwa ketika mereka terlalu panjang, mereka umumnya bukan alat yang tepat. Ekspresi reguler cenderung tidak terbaca saat disalahgunakan.
Di luar ekspresi reguler berbasis string
Berbicara tentang sintaksis alternatif, mari kita lihat salah satu yang sudah ada ( cl-ppcre , di Common Lisp ). Ekspresi reguler panjang Anda dapat diuraikan
ppcre:parse-string
sebagai berikut:... dan hasil dalam bentuk berikut:
Sintaks ini lebih verbose, dan jika Anda melihat komentar di bawah ini, belum tentu lebih mudah dibaca. Jadi jangan berasumsi bahwa karena Anda memiliki sintaks yang kurang kompak, semuanya akan menjadi lebih jelas secara otomatis .
Namun, jika Anda mulai mengalami masalah dengan ekspresi reguler Anda, mengubahnya menjadi format ini dapat membantu Anda menguraikan dan men-debug kode Anda. Ini adalah salah satu keunggulan dibandingkan format berbasis string, di mana kesalahan satu karakter bisa sulit dikenali. Keuntungan utama sintaks ini adalah memanipulasi ekspresi reguler menggunakan format terstruktur alih-alih pengkodean berbasis string. Itu memungkinkan Anda untuk membuat dan membangun ekspresi seperti itu seperti struktur data lainnya di program Anda. Ketika saya menggunakan sintaks di atas, ini umumnya karena saya ingin membangun ekspresi dari bagian yang lebih kecil (lihat juga jawaban CodeGolf saya ). Sebagai contoh Anda, kami dapat menulis 1 :
Ekspresi reguler berbasis string juga dapat dikomposisikan, menggunakan penggabungan string dan atau interpolasi yang dibungkus dengan fungsi helper. Namun, ada keterbatasan dengan manipulasi string yang yang cenderung kekacauan yang kode (berpikir tentang masalah bersarang, tidak seperti backticks vs
$(...)
di bash, juga, melarikan diri karakter dapat memberikan sakit kepala).Perhatikan juga bahwa formulir di atas memungkinkan
(:regex "string")
formulir sehingga Anda dapat mencampur notasi singkat dengan pohon. Semua itu mengarah IMHO ke keterbacaan dan kompabilitas yang baik; ini membahas tiga masalah yang diungkapkan oleh delnan , secara tidak langsung (yaitu tidak dalam bahasa ekspresi reguler itu sendiri).Untuk menyimpulkan
Untuk sebagian besar tujuan, notasi singkat ini sebenarnya dapat dibaca. Ada kesulitan ketika berhadapan dengan notasi tambahan yang melibatkan backtracking, dll., Tetapi penggunaannya jarang dibenarkan. Penggunaan ekspresi reguler yang tidak beralasan dapat menyebabkan ekspresi yang tidak dapat dibaca.
Ekspresi reguler tidak perlu dikodekan sebagai string. Jika Anda memiliki perpustakaan atau alat yang dapat membantu Anda membangun dan menulis ekspresi reguler, Anda akan menghindari banyak bug potensial yang terkait dengan manipulasi string.
Atau, tata bahasa formal lebih mudah dibaca dan lebih baik dalam penamaan dan abstrak sub-ekspresi. Terminal umumnya dinyatakan sebagai ekspresi reguler sederhana.
1. Anda mungkin lebih suka untuk membangun ekspresi Anda pada waktu-baca, karena ekspresi reguler cenderung konstan dalam aplikasi. Lihat
create-scanner
danload-time-value
:sumber
digits
,ident
, dan menulis mereka. Mereka melihat saya dilakukan umumnya dengan manipulasi string (penggabungan atau interpolasi), yang membawa masalah lain seperti melarikan diri dengan benar. Cari kejadian\\\\`
di paket emacs, misalnya. Btw, ini diperburuk karena karakter pelarian yang sama digunakan untuk karakter khusus seperti\n
dan\"
dan untuk sintaks regex\(
. Contoh non-lisp dari sintaks yang baik adalahprintf
, di mana%d
tidak bertentangan dengan\d
.greedy-repetition
tidak intuitif dan masih harus dipelajari). Namun, ini mengorbankan kegunaan bagi para ahli, karena jauh lebih sulit untuk melihat dan memahami keseluruhan pola.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}
. Dengan beberapa garis seperti menunjuk string panjangdomainChars = ...
dansection start p = optional (char start >> many p)
terlihat cukup sederhana.Masalah terbesar dengan regex bukanlah sintaks yang terlalu singkat, melainkan bahwa kami mencoba untuk mengekspresikan definisi yang kompleks dalam satu ekspresi, alih-alih menyusunnya dari blok bangunan yang lebih kecil. Ini mirip dengan pemrograman di mana Anda tidak pernah menggunakan variabel dan fungsi dan bukannya menanamkan kode Anda semua dalam satu baris.
Bandingkan regex dengan BNF . Sintaksnya tidak jauh lebih bersih daripada regex, tetapi digunakan secara berbeda. Anda mulai dengan mendefinisikan simbol sederhana bernama dan menyusunnya sampai Anda tiba pada simbol yang menggambarkan seluruh pola yang ingin Anda cocokkan.
Sebagai contoh, lihat sintaks URI di rfc3986 :
Anda bisa menulis hal yang hampir sama menggunakan varian sintaks regex yang mendukung embedding sub-ekspresi bernama.
Secara pribadi saya berpikir bahwa rege singkat seperti sintaks baik untuk fitur yang umum digunakan seperti kelas karakter, penggabungan, pilihan atau pengulangan, tetapi untuk fitur yang lebih kompleks dan lebih jarang seperti nama depan yang lebih disukai, verbose lebih disukai. Cukup mirip dengan bagaimana kita menggunakan operator seperti
+
atau*
dalam pemrograman normal dan beralih ke fungsi yang dinamai untuk operasi yang lebih jarang.sumber
Apakah itu? Ada alasan mengapa sebagian besar bahasa memiliki {dan} sebagai pembatas blok daripada BEGIN dan END.
Orang-orang menyukai kesempitan, dan begitu Anda mengetahui sintaksisnya, terminologi pendek lebih baik. Bayangkan contoh regex Anda jika d (untuk digit) adalah 'digit' regex akan lebih mengerikan untuk dibaca. Jika Anda membuatnya lebih mudah diuraikan dengan karakter kontrol, maka itu akan lebih mirip XML. Tidak ada yang baik setelah Anda tahu sintaks.
Untuk menjawab pertanyaan Anda dengan benar, Anda harus menyadari bahwa regex berasal dari hari-hari ketika kesederhanaan adalah wajib. Mudah untuk berpikir dokumen XML 1 MB bukan masalah besar hari ini, tapi kita berbicara tentang hari-hari ketika 1 MB cukup banyak seluruh kapasitas penyimpanan Anda. Ada juga lebih sedikit bahasa yang digunakan saat itu, dan regex tidak sejuta mil jauhnya dari perl atau C, jadi sintaksinya akan akrab bagi para programmer saat itu yang akan senang mempelajari sintaksis. Jadi tidak ada alasan untuk membuatnya lebih bertele-tele.
sumber
selfDocumentingMethodName
adalah umumnya sepakat untuk menjadi lebih baik darie
karena programmer intuisi tidak berbaris dengan realitas dalam hal apa yang sebenarnya merupakan pembacaan atau kode kualitas yang baik . Orang-orang yang melakukan persetujuan itu salah, tapi memang begitu.e()
lebih baik daripadaselfDocumentingMethodName()
?e()
versus nama metode yang mendokumentasikan diri . Bisakah Anda menjelaskan dalam konteks mana ini merupakan peningkatan untuk menggunakan nama metode huruf tunggal daripada nama metode deskriptif?Regex seperti potongan lego. Pada pandangan pertama, Anda melihat beberapa bagian plastik dengan bentuk berbeda yang dapat disatukan. Anda mungkin berpikir tidak akan ada banyak hal berbeda yang bisa Anda bentuk tetapi kemudian Anda melihat hal-hal menakjubkan yang dilakukan orang lain dan Anda hanya bertanya-tanya bagaimana mainan yang luar biasa itu.
Regex seperti potongan lego. Ada beberapa argumen yang dapat digunakan tetapi dengan merantai mereka dalam bentuk yang berbeda akan membentuk jutaan pola regex berbeda yang dapat digunakan untuk banyak tugas rumit.
Orang jarang menggunakan parameter regex saja. Banyak bahasa menawarkan Anda fungsi untuk memeriksa panjang string atau memisahkan bagian-bagian numerik darinya. Anda bisa menggunakan fungsi string untuk mengiris teks dan mereformasi mereka. Kekuatan regex diperhatikan saat Anda menggunakan formulir kompleks untuk melakukan tugas kompleks yang sangat spesifik.
Anda dapat menemukan puluhan ribu pertanyaan regex di SO dan jarang ditandai sebagai duplikat. Ini saja menunjukkan kemungkinan penggunaan unik yang sangat berbeda satu sama lain.
Dan tidak mudah untuk menawarkan metode yang telah ditentukan sebelumnya untuk menangani tugas unik yang jauh berbeda ini. Anda memiliki fungsi string untuk tugas-tugas semacam itu, tetapi jika fungsi-fungsi itu tidak cukup untuk tugas spesifik Anda, maka sekarang saatnya untuk menggunakan regex
sumber
Saya menyadari ini adalah masalah praktik daripada potensi. Masalahnya biasanya muncul ketika ekspresi reguler diimplementasikan secara langsung , alih-alih mengasumsikan sifat komposit. Demikian pula, seorang programmer yang baik akan menguraikan fungsi programnya menjadi metode singkat.
Misalnya, string regex untuk URL dapat dikurangi dari sekitar:
untuk:
Ekspresi reguler adalah hal-hal yang bagus, tetapi mereka rentan terhadap penyalahgunaan oleh orang-orang yang berbelok diserap dalam mereka jelas kompleksitas. Ekspresi yang dihasilkan adalah retorika, tidak ada nilai jangka panjang.
sumber
Seperti yang dikatakan @cmaster, regexps pada awalnya dirancang untuk digunakan hanya dengan on-the-fly, dan itu hanya aneh (dan sedikit menyedihkan) bahwa sintaks garis-noise masih yang paling populer. Satu-satunya penjelasan yang dapat saya pikirkan adalah melibatkan inersia, masokisme, atau kejantanan (tidak sering bahwa 'inersia' adalah alasan yang paling menarik untuk melakukan sesuatu ...)
Perl membuat upaya yang agak lemah untuk membuatnya lebih mudah dibaca dengan membiarkan spasi putih dan komentar, tetapi tidak melakukan apa pun yang jauh imajinatif.
Ada sintaksis lainnya. Yang bagus adalah sintaks scsh untuk regexps , yang dalam pengalaman saya menghasilkan regexps yang cukup mudah diketik , tetapi masih dapat dibaca setelah fakta.
[ scsh sangat bagus karena alasan lain, hanya salah satunya adalah teks ucapan terima kasihnya yang terkenal ]
sumber
Saya percaya ekspresi reguler dirancang untuk menjadi 'umum' dan sesederhana mungkin, sehingga mereka dapat digunakan (secara kasar) dengan cara yang sama di mana saja.
Contoh Anda
regex.isRange(..).followedBy(..)
digabungkan dengan sintaksis bahasa pemrograman tertentu dan mungkin gaya berorientasi objek (metode chaining).Bagaimana 'regex' yang tepat ini terlihat dalam C misalnya? Kode harus diubah.
Pendekatan yang paling 'umum' adalah mendefinisikan bahasa ringkas sederhana yang kemudian dapat dengan mudah disematkan dalam bahasa lain tanpa perubahan. Dan itulah (hampir) apa itu regex.
sumber
Perl-Compatible Regular Expression engine banyak digunakan, memberikan sintaks ekspresi reguler singkat yang dipahami oleh banyak editor dan bahasa. Seperti @ JDługosz tunjukkan dalam komentar, Perl 6 (bukan hanya versi baru dari Perl 5, tetapi bahasa yang sama sekali berbeda) telah berusaha membuat ekspresi reguler lebih mudah dibaca dengan membangunnya dari elemen yang ditentukan secara individual. Misalnya, berikut adalah contoh tata bahasa untuk parsing URL dari Wikibooks :
Memisahkan ekspresi reguler seperti ini memungkinkan setiap bit untuk didefinisikan secara individual (misalnya membatasi
domain
menjadi alfanumerik) atau diperluas melalui subklasifikasi (misalnyaFileURL is URL
batasan ituprotocol
hanya untuk menjadi"file"
).Jadi: tidak, tidak ada alasan teknis untuk kesederhanaan ekspresi reguler, tetapi cara yang lebih baru, lebih bersih, dan lebih mudah untuk mewakili mereka sudah ada di sini! Jadi semoga kita akan melihat beberapa ide baru di bidang ini.
sumber