قد تكون البرمجة مهمّة صعبة جداً، خصوصاً عند العمل على مشروع يضمّ مطوّرين مختلفين. فكلّ عضو في الفريق له طريقته الخاصة في البرمجة، مما يؤدي إلى نصوص برمجية متفاوتة جداً.
ولهذا من المهمّ امتلاك code formatter وcode linter موحَّدَين من أجل جعل git commits أنظف. ويمكن تنفيذ ذلك إما بين مرحلتَي الـ staging والـ committing أو ضمن سلسلة الـ CI/CD.
في هذا المقال، سنرى كيف نفعل ذلك كخطوة pre-commit باستخدام git hooks.
الملخّص كالتالي:
- Black
- Pylint
- Git Hooks
Black
Black هو code formatter للغة Python ذو إعدادات أسلوب محدودة عمداً. وهو يجعل الكود أنظف بحيث يمكنك التركيز أكثر على المحتوى. كما تصبح مراجعة الكود أكثر فعّالية لأنّ الـ diffs تكون أصغر ما يمكن.
يمكن تثبيته باستخدام سطر الأوامر التالي:
pip install blackيمكنك تشغيل black على أيّ ملفّ Python بكتابة:
Black pathtofile.py

يمكن تكوين Black بشكل بسيط باستخدام الملفّ pyproject.toml الذي يجب أن يُوضع في جذر المشروع. وفيما يلي مثال على هذا الملفّ:
[tool.black]
line-length = 88
target-version = [‘py36’, ‘py37’, ‘py38’]
include = ‘\.pyi?$’
exclude = '''
/(
\.toml
|\.sh
|\.git
|\.ini
|Dockerfile
|Jenkinfile
)/
'''يمكننا اختيار طول أسطر الأكواد مثلاً وكذلك تحديد الامتدادات التي يجب ألّا تُنسَّق ملفّاتها.
Pylint
Pylint هو "Python static code analysis tool" يُقيِّم جودة النصوص البرمجية المطوَّرة. وهو يوجّه الفريق إلى تبنّي المعايير ذاتها.
يمكن تثبيت Pylint باستخدام سطر الأوامر التالي:
pip install pylintلتقييم جودة برمجتك على نصّ معيّن يمكنك تشغيل:
pylint pathtofile.pyNB: لتشغيل pylint على المشروع بأكمله، يجب أن يتضمّن المستودع ملفّ __init__.py.
مثل Black، يمكن أيضاً تكوين Pylint باستخدام الملفّ .pylintrc الذي يُوضع كذلك في جذر المشروع
[MASTER]
jobs=4 #number of processes to use
[BASIC]
good-names=nameOfYourProject #names to be considered ok
[pre-commit-hook]
command=custom_pylint
disable=E0401, C0301يمكنك مراجعة هذه الصفحة لمزيد من التفاصيل حول التكوين.
Git Hooks
الـ Git hooks هي نصوص برمجية معرَّفة تُشغَّل عند تنفيذ إجراء git.
هناك نوعان من hooks:
- Client-side hooks: تُشغَّل بعد الـ committing والـ merging
- Server-side hooks: تُشغَّل على عمليات الشبكة، بعد دفع commits مثلاً
في هذا المقال سنركّز على Client-side hooks التي يمكن وصف سير عملها بالرسم البياني التالي:

في مستودع git، تُوضع الـ hooks في .git/hooks/ . ويمكن عرضها في VSCode مثلاً بإضافة الأسطر التالية إلى إعدادات الـ Json الخاصة بك:
"files.exclude": {
"**/.git": false
}

إعداد الـ pre-commits
لإضافة إجراء pre-commit إلى مستودعك:
1.أنشئ الملفّ pre-commit في المجلّد .git/hooks/. ولا ينبغي أن تضع أيّ امتداد في اسمه.
2.أضف أوامر bash في ملفّك. في حالتنا:
#!/bin/sh
black .
python lint.py -p ../projectName/السطر الأول يطبّق black formatter، أما السطر الثاني فيطبّق الـ linting على كل ملفّ Python في مشروعك. يمكنك تنزيل الملفّ lint.py من هذا الرابط:
import argparse
import logging
from pylint.lint import Run
logging.getLogger().setLevel(logging.INFO)
parser = argparse.ArgumentParser(prog="LINT")
parser.add_argument('-p',
'--path',
help='path to directory you want to run pylint | '
'Default: %(default)s | '
'Type: %(type)s ',
default='./src',
type=str)
parser.add_argument('-t',
'--threshold',
help='score threshold to fail pylint runner | '
'Default: %(default)s | '
'Type: %(type)s ',
default=7,
type=float)
args = parser.parse_args()
path = str(args.path)
threshold = float(args.threshold)
logging.info('PyLint Starting | '
'Path: {} | '
'Threshold: {} '.format(path, threshold))
results = Run([path], do_exit=False)
final_score = results.linter.stats['global_note']
if final_score < threshold:
message = ('PyLint Failed | '
'Score: {} | '
'Threshold: {} '.format(final_score, threshold))
logging.error(message)
raise Exception(message)
else:
message = ('PyLint Passed | '
'Score: {} | '
'Threshold: {} '.format(final_score, threshold))
logging.info(message)
exit(0)
NB: يُحدَّد افتراضياً عتبة قدرها ٧. وإذا كانت درجة pylint أقلّ من هذا الرقم، فسيفشل الـ commit وسيتعيّن عليك تنظيف نصّك البرمجي قبل عمل commit مرة أخرى.
3. اجعله قابلاً للتنفيذ بتشغيل أمر bash التالي:
chmod +x .git/hooks/pre-commitبهذا الإعداد، سيسبق كلّ commit في مشروعك تنسيق black و linting لـ pylint.
نأخذ التطبيق التالي كمثال للتوضيح:

عند عمل commit لتغيير جديد، نحصل على النتائج التالية:

مشاركة الـ linter والـ formatter
الآن وقد جعلتَ black منسّقك الخاصّ وpylint linter الخاصّ بك، فأنت بالتأكيد تريد أن يكون لدى فريقك نفس التكوين.
للقيام بذلك، يمكنك استخدام ملفّ bash التالي الذي سيُوضع في جذر مشروعك ويُشغَّل من قِبَل كلّ عضو في فريقك بعد استنساخ المستودع:
#!/bin/bash
echo $"#/bin/sh\nblack .\npython lint.py -p ../projectName/'> .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
الخاتمة
تُعدّ الـ pre-commits أدوات قويّة جداً للحفاظ على كودك منسَّقاً جيّداً ولتوحيد طرق التطوير بين أعضاء فريقك. وعلى نحوٍ مماثل، يمكن أن تشمل عمليات تحقّق أخرى إلى جانب الـ linting والتنسيق.
في هذا المقال، استخدمنا طريقة كلاسيكية تستدعي مكتبات Python المثبَّتة، ويمكنك أيضاً استخدام module الـ Python pre-commit الذي يتضمّن العديد من عمليات التحقّق ويستدعيها من مستودعات GitHub الخاصة بها. إنه فعّال جداً ولكن قد يكون صعب الإعداد، خاصة عند مواجهة فلاتر proxy.
