تركز الاقتراحات الواردة في هذه المقالة بشكل أساسي على سهولة قراءة التعبيرات العادية، ومن خلال تطوير هذه العادات أثناء التطوير، ستفكر بشكل أكثر وضوحًا في بنية التصميم والتعبير، مما سيساعد في تقليل الأخطاء وصيانة التعليمات البرمجية إذا كنت ستشعر براحة أكبر أنت المشرف على هذا الرمز بنفسك. يمكنك إلقاء نظرة بنفسك والانتباه إلى هذه التجارب باستخدام التعبيرات العادية في استخدامك الفعلي.
من الصعب كتابة التعبيرات العادية وقراءتها وصيانتها، وغالبًا ما تكون غير متطابقة مع النص غير المتوقع أو يفتقد نصًا صالحًا، ويعود سبب هذه المشكلات إلى أداء وإمكانيات التعبيرات العادية. إن الجمع بين القدرات والفروق الدقيقة لكل حرف أولي يجعل من المستحيل تفسير الكود دون اللجوء إلى الحيل الفكرية.
تشتمل العديد من الأدوات على ميزات تجعل من السهل قراءة وكتابة التعبيرات العادية، ولكنها أيضًا غير اصطلاحية إلى حد كبير. بالنسبة للعديد من المبرمجين، تعد كتابة التعبيرات العادية فنًا سحريًا. إنهم يلتزمون بالخصائص التي يعرفونها ولديهم موقف من التفاؤل المطلق. إذا كنت على استعداد لتبني العادات الخمس التي تمت مناقشتها في هذه المقالة، فسوف تكون قادرًا على تصميم تعبيرات عادية تصمد أمام التجربة والخطأ.
ستستخدم هذه المقالة لغات Perl وPHP وPython كأمثلة للتعليمات البرمجية، لكن النصيحة الواردة في هذه المقالة تنطبق تقريبًا على أي تطبيق للتعبير البديل (regex).
1. استخدم المسافات والتعليقات
بالنسبة لمعظم المبرمجين، فإن استخدام المسافات البادئة في بيئة التعبير العادي لا يمثل مشكلة إذا لم يفعلوا ذلك، فمن المؤكد أنهم سيسخرون من أقرانهم وحتى الأشخاص العاديين. يعلم الجميع تقريبًا أن ضغط التعليمات البرمجية في سطر واحد يجعل من الصعب قراءتها وكتابتها وصيانتها. ما الفرق بين التعبيرات العادية؟
تحتوي معظم أدوات التعبير البديلة على ميزة المسافة البيضاء الممتدة، والتي تسمح للمبرمجين بتوسيع تعبيراتهم العادية إلى أسطر متعددة وإضافة تعليقات في نهاية كل سطر. لماذا يستفيد عدد قليل فقط من المبرمجين من هذه الميزة؟ تستخدم التعبيرات العادية لـ Perl 6 الأنماط الممتدة بمساحة بشكل افتراضي. لا تدع اللغة توسع المسافات بشكل افتراضي بالنسبة لك، استفد منها بنفسك.
إحدى الحيل التي يجب تذكرها بشأن المسافة البيضاء الممتدة هي إخبار محرك التعبير العادي بتجاهل المسافة البيضاء الممتدة. بهذه الطريقة، إذا كنت بحاجة إلى مطابقة المسافات، فيجب عليك تحديدها بشكل صريح.
في لغة Perl، أضف x في نهاية التعبير العادي، بحيث يصبح "m/foo bar/" بالشكل التالي:
m/
foo
حاجِز
/x
في لغة PHP، أضف x في نهاية التعبير العادي، بحيث يصبح ""/foo bar/"" بالشكل التالي:
"/
foo
حاجِز
/x"
في لغة بايثون، قم بتمرير معلمة تعديل النمط "re.VERBOSE" للحصول على الوظيفة المترجمة كما يلي:
Pattern = r'''
foo
حاجِز
'''
regex = re.compile(pattern, re.VERBOSE)
مع تعبيرات عادية أكثر تعقيدًا، ستصبح المسافات والتعليقات أكثر أهمية. لنفترض أن التعبير العادي التالي يُستخدم لمطابقة أرقام الهواتف في الولايات المتحدة:
(?d{3})?d{3}[-.]d{4}
يطابق هذا التعبير العادي أرقام الهواتف مثل "(314)555-4000"، هل تعتقد أن هذا التعبير العادي يتطابق مع "314-555-4000" أو "555-4000"؟ الجواب هو أنه لا يوجد أي تطابق. كتابة مثل هذا السطر من التعليمات البرمجية يخفي أوجه القصور ونتائج التصميم نفسها مطلوب رمز منطقة الهاتف، ولكن التعبير العادي يفتقر إلى رمز فاصل بين رمز المنطقة والبادئة.
سيؤدي تقسيم هذا السطر من التعليمات البرمجية إلى عدة أسطر وإضافة التعليقات إلى كشف أوجه القصور وتسهيل التعديل.
في لغة بيرل يجب أن يكون بالشكل التالي:
/
(؟ # قوسين اختياريين
d{3} # رمز منطقة الهاتف مطلوب
)؟ # قوسين اختياريين
[-s.]? # يمكن أن يكون المحدد شرطة أو مسافة أو نقطة
d{3} # بادئة مكونة من ثلاثة أرقام
[-.] # محدد آخر
d{4} # رقم الهاتف المكون من أربعة أرقام
/x
يحتوي التعبير العادي المعاد كتابته الآن على فاصل اختياري بعد رمز المنطقة، بحيث يجب أن يتطابق مع "314-555-4000"، ومع ذلك لا يزال رمز المنطقة مطلوبًا. يمكن لمبرمج آخر يحتاج إلى جعل رمز منطقة الهاتف اختياريًا أن يرى بسرعة أنه لم يعد اختياريًا الآن، وتغيير بسيط يمكن أن يحل المشكلة.
2.
هناك ثلاثة مستويات من الاختبار في اختبارات الكتابة، ويضيف كل مستوى طبقة من الموثوقية إلى التعليمات البرمجية الخاصة بك. أولاً، عليك أن تفكر مليًا في الرموز التي تحتاج إلى مطابقتها وما إذا كان بإمكانك التعامل مع حالات عدم التطابق. ثانيًا، تحتاج إلى استخدام مثيلات البيانات لاختبار التعبير العادي. وأخيرا، تحتاج إلى اجتياز لجنة الاختبار رسميا.
إن تحديد ما تريد مطابقته يتعلق في الواقع بإيجاد توازن بين مطابقة النتائج الخاطئة وفقدان النتائج الصحيحة. إذا كان التعبير العادي الخاص بك صارمًا جدًا، فسوف يفتقد بعض المطابقات الصحيحة؛ وإذا كان فضفاضًا جدًا، فسوف ينتج عنه مطابقة غير صحيحة. بمجرد تحرير التعبير العادي إلى كود فعلي، قد لا تلاحظ كليهما. خذ بعين الاعتبار مثال رقم الهاتف أعلاه، والذي سيطابق "800-555-4000 = -5355". من الصعب في الواقع اكتشاف التطابقات الخاطئة، لذا من المهم التخطيط مسبقًا واختبارها جيدًا.
بالاستمرار في مثال رقم الهاتف، إذا كنت تؤكد رقم هاتف في نموذج ويب، فقد تكون راضيًا عن رقم مكون من عشرة أرقام بأي تنسيق. ومع ذلك، إذا كنت تريد فصل أرقام الهواتف عن كمية كبيرة من النص، فقد تحتاج إلى استبعاد المطابقات الخاطئة التي لا تلبي المتطلبات بعناية.
عند التفكير في البيانات التي تريد مطابقتها، اكتب بعض سيناريوهات الحالة. اكتب بعض التعليمات البرمجية لاختبار التعبير العادي الخاص بك مقابل سيناريو الحالة. بالنسبة لأي تعبير عادي معقد، فمن الأفضل كتابة برنامج صغير لاختباره، والذي يمكن أن يتخذ النموذج المحدد التالي.
بلغة بيرل:
#!/usr/bin/Perl
my @tests = ("314-555-4000"،
"800-555-4400"،
"(314)555-4000"،
"314.555.4000"،
"555-4000"،
"أسدكلفجكلاس"،
"1234-123-12345"
)
;
إذا ($اختبار =~ م/
(؟ # قوسين اختياريين
d{3} # رمز منطقة الهاتف مطلوب
)؟ # قوسين اختياريين
[-s.]? # يمكن أن يكون المحدد شرطة أو مسافة أو نقطة
d{3} # بادئة مكونة من ثلاثة أرقام
[-s.] # محدد آخر
d{4} # رقم الهاتف المكون من أربعة أرقام
/س) {
طباعة "متطابقة في $testn";
}
آخر {
طباعة "فشلت المطابقة في $testn";
}
}
في لغة PHP:
<?php
اختبارات $ = مصفوفة ("314-555-4000"،
"800-555-4400"،
"(314)555-4000"،
"314.555.4000"،
"555-4000"،
"أسدكلفجكلاس"،
"1234-123-12345" );
$regex = "/
(؟ # قوسين اختياريين
d{3} # رمز منطقة الهاتف مطلوب
)؟ # قوسين اختياريين
[-s.]? # يمكن أن يكون المحدد شرطة أو مسافة أو نقطة
d{3} # بادئة مكونة من ثلاثة أرقام
[-s.] # محدد آخر
d{4} # رقم الهاتف المكون من أربعة أرقام
/x";
foreach ($اختبارات كـ $test) {
إذا (preg_match($regex, $test)) {
صدى "متطابق في $test
؛"؛
}
آخر {
صدى "فشلت المطابقة في $test
؛"؛
}
}
?>;
في لغة بايثون:
اختبارات
إعادة الاستيراد
= ["314-555-4000"،
"800-555-4400"،
"(314)555-4000"،
"314.555.4000"،
"555-4000"،
"أسدكلفجكلاس"،
"1234-123-12345"
]
النمط = ص ''''
(؟ # قوسين اختياريين
d{3} # رمز منطقة الهاتف مطلوب
)؟ # قوسين اختياريين
[-s.]? # يمكن أن يكون المحدد شرطة أو مسافة أو نقطة
d{3} # بادئة مكونة من ثلاثة أرقام
[-s.] # محدد آخر
d{4} # رقم الهاتف المكون من أربعة أرقام
'''
regex = re.compile(pattern, re.VERBOSE) للاختبار في الاختبارات:
إذا كان regex.match(اختبار):
طباعة "متطابق على"، اختبار، "n"
آخر:
طباعة "فشل المطابقة"، اختبار، "n"
سيؤدي تشغيل رمز الاختبار إلى الكشف عن مشكلة أخرى: فهو يطابق "1234-123-12345".
من الناحية النظرية، تحتاج إلى دمج جميع اختبارات التطبيق بأكمله في فريق اختبار. حتى لو لم يكن لديك مجموعة اختبار بعد، فإن اختبارات التعبير العادي الخاصة بك ستكون أساسًا جيدًا لمجموعة واحدة، والآن هو الوقت المناسب لبدء واحدة. حتى لو لم يكن الوقت المناسب لإنشائه بعد، فلا يزال يتعين عليك تشغيل التعبير العادي واختباره بعد كل تعديل. قضاء القليل من الوقت هنا سيوفر عليك الكثير من المتاعب.
3. العمليات البديلة الجماعية
رمز العملية البديلة ( ) له أولوية منخفضة، مما يعني أنه غالبًا ما يتناوب أكثر مما أراد المبرمج. على سبيل المثال، قد يكون التعبير العادي لاستخراج عناوين البريد الإلكتروني من النص كما يلي:
^CC: To:(.*)
المحاولة المذكورة أعلاه غير صحيحة، ولكن غالبًا لا يتم ملاحظة هذا الخطأ. الغرض من الكود أعلاه هو العثور على النص الذي يبدأ بـ "CC:" أو "إلى:" ثم استخراج عنوان البريد الإلكتروني في نهاية هذا السطر.
لسوء الحظ، إذا ظهر "إلى:" في منتصف السطر، فلن يلتقط هذا التعبير العادي أي سطر يبدأ بـ "CC:" وسيقوم بدلاً من ذلك باستخراج عدة أجزاء عشوائية من النص. بصراحة، يطابق التعبير العادي سطرًا يبدأ بـ "CC:" ولكنه لا يلتقط شيئًا؛ أو يطابق أي سطر يحتوي على "إلى:" ولكنه يلتقط بقية السطر. عادةً ما يلتقط هذا التعبير العادي عددًا كبيرًا من عناوين البريد الإلكتروني، لذلك لن يلاحظ أحد الخطأ.
إذا كنت تريد تحقيق النية الفعلية، فعليك إضافة قوسين لتوضيح الأمر، ويكون التعبير النمطي كما يلي:
(^CC:) (To:(.*))
إذا كانت النية الحقيقية هي التقاط نص يبدأ بـ ". CC:" أو "To:" في بقية السطر، ثم التعبير العادي الصحيح هو:
^(CC: To:)(.*)
هذا خطأ شائع في المطابقة غير المكتملة والذي ستتجنبه إذا اعتدت على التجميع لعمليات بالتناوب هذا الخطأ.
4. استخدم محددات كمية فضفاضة.
يتجنب العديد من المبرمجين استخدام محددات كمية فضفاضة مثل "*؟" و"+؟" و"؟؟"، على الرغم من أنها ستجعل التعبير أسهل في الكتابة والفهم.
تتطابق محددات الكمية المريحة مع أقل قدر ممكن من النص، مما يساعد على نجاح المطابقة التامة. إذا كتبت "foo(.*?)bar"، فسيتوقف مُحدِّد الكمية عن المطابقة في المرة الأولى التي يواجه فيها "bar"، وليس المرة الأخيرة. يعد هذا أمرًا مهمًا إذا كنت ترغب في التقاط "###" من "foo###bar+++bar". يمكن لمحدد كمي صارم التقاط "###bar++ +". ;)، وهذا سوف يسبب الكثير من المتاعب. إذا كنت تستخدم محددات كمية مريحة، فيمكنك إنشاء تعبيرات عادية جديدة عن طريق قضاء وقت قليل جدًا في تجميع أنواع الأحرف.
تعتبر محددات الكمية المريحة ذات قيمة كبيرة عندما تعرف بنية السياق الذي تريد التقاط النص فيه.
5. استخدم المحددات المتاحة.
غالبًا ما تستخدم لغات Perl وPHP شرطة مائلة يسرى (/) لتحديد بداية ونهاية التعبير العادي. تستخدم لغة Python مجموعة من علامات الاقتباس لتحديد البداية والنهاية. إذا كنت تصر على استخدام الخطوط المائلة اليسرى في Perl وPHP، فستحتاج إلى تجنب أي خطوط مائلة في التعبيرات؛ إذا كنت تستخدم علامات الاقتباس في Python، فستحتاج إلى تجنب الخطوط المائلة العكسية (). يمكن أن يسمح لك اختيار محددات أو علامات اقتباس مختلفة بتجنب نصف التعبير العادي. سيؤدي ذلك إلى تسهيل قراءة التعبيرات وتقليل الأخطاء المحتملة الناتجة عن نسيان تجنب الرموز.
تسمح لغات Perl وPHP باستخدام أي أحرف غير رقمية أو مسافات كمحددات. إذا قمت بالتبديل إلى محدد جديد، فيمكنك تجنب فقدان الشرطة المائلة اليسرى عند مطابقة عناوين URL أو علامات HTML (مثل "http://" أو "<br/>;").
على سبيل المثال، يمكن كتابة "/http://(S)*/" بالشكل "#http://(S)*#".
المحددات الشائعة هي "#" و"!" و"". إذا كنت تستخدم أقواسًا مربعة، أو أقواسًا زاوية، أو أقواسًا متعرجة، فاحرص على أن تكون متطابقة. فيما يلي بعض الأمثلة على المحددات الشائعة:
#…#!…! {…} s … … (Perl فقط) s[…][…] (Perl فقط) s<…>;/…/ (Perl فقط)
في بايثون، يتم التعامل مع التعبير العادي أولاً كسلسلة. إذا استخدمت علامات الاقتباس كمحددات، فسوف تفوت جميع الخطوط المائلة العكسية. ولكن يمكنك تجنب هذه المشكلة باستخدام السلسلة "r''". إذا استخدمت ثلاث علامات اقتباس مفردة متتالية لخيار "re.VERBOSE"، فسوف يسمح لك بتضمين أسطر جديدة. على سبيل المثال، يمكن كتابة regex = "( file://w+)(//d +)" بالشكل التالي:
regex = r'''
(ث+)
(د+)
'''