أنظمة البناء المستندة إلى المهام

تتناول هذه الصفحة أنظمة الإصدار المستندة إلى المهام وآلية عملها وبعض الإضافات التي يمكن أن تحدث مع الأنظمة المستندة إلى المهام. بعد استخدام النصوص البرمجية في هيكل النظام، تعتبر أنظمة الإنشاء المستندة إلى المهام هي التطوّر المنطقي التالي للبناء.

فهم أنظمة الإنشاء المستندة إلى المهام

في نظام التصميم المستند إلى المهام، تتمثل الوحدة الأساسية في العمل. كل مهمة هي نص برمجي يمكنه تنفيذ أي نوع من المنطق، وتُحدِّد المهام مهام أخرى كاعتماديات يجب تنفيذها قبلها. معظم أنظمة الإنشاء المستخدمة حاليًا، مثل Ant وMaven وGrdle وGrunt وRake، تستند إلى المهام. بدلاً من استخدام نصوص البرمجة، تتطلب معظم أنظمة التصميم الحديثة من المهندسين إنشاء ملفات إصدار تصف كيفية تنفيذ التصميم.

انظر المثال التالي من دليل النملة:

<project name="MyProject" default="dist" basedir=".">
   <description>
     simple example build file
   </description>
   <!-- set global properties for this build -->
   <property name="src" location="src"/>
   <property name="build" location="build"/>
   <property name="dist" location="dist"/>

   <target name="init">
     <!-- Create the time stamp -->
     <tstamp/>
     <!-- Create the build directory structure used by compile -->
     <mkdir dir="${build}"/>
   </target>
   <target name="compile" depends="init"
       description="compile the source">
     <!-- Compile the Java code from ${src} into ${build} -->
     <javac srcdir="${src}" destdir="${build}"/>
   </target>
   <target name="dist" depends="compile"
       description="generate the distribution">
     <!-- Create the distribution directory -->
     <mkdir dir="${dist}/lib"/>
     <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
     <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
   </target>
   <target name="clean"
       description="clean up">
     <!-- Delete the ${build} and ${dist} directory trees -->
     <delete dir="${build}"/>
     <delete dir="${dist}"/>
   </target>
</project>

تتم كتابة ملف build في XML ويحدّد بعض البيانات الوصفية البسيطة حول الإصدار بالإضافة إلى قائمة بالمهام (علامات <target> في XML). (تستخدم النملة الكلمة target لتمثيل مهمة، وتستخدم الكلمة TASKS للإشارة إلى الأوامر). تنفّذ كل مهمة قائمة بالأوامر المحتملة التي تحددها Ant، التي تشمل هنا إنشاء الأدلة وحذفها، وتشغيل javac، وإنشاء ملف JAR. ويمكن توسيع مجموعة الأوامر هذه من خلال المكوّنات الإضافية المقدّمة من المستخدم لتغطية أي نوع من المنطق. ويمكن لكل مهمة أيضًا تحديد المهام التي تعتمد عليها من خلال سمة تعتمد. وتعمل هذه الارتباطات على تكوين رسم بياني دائري، كما هو موضّح في الشكل 1.

رسم بياني إكريليك يعرض الارتباطات

الشكل 1. رسم بياني أبريل يعرض اعتماديات

يؤدي المستخدمون الإصدارات عن طريق تقديم مهام إلى أداة سطر الأوامر في Ant. على سبيل المثال، عندما يكتب المستخدم ant dist، يتخذ Ant الخطوات التالية:

  1. تحمّل ملفًا اسمه build.xml في الدليل الحالي وستحلّله لإنشاء بنية الرسم البياني المعروضة في الشكل 1.
  2. تبحث عن المهمة باسم dist التي تم تقديمها في سطر الأوامر وتكتشف أنّها تعتمد على المهمة المُسمّاة compile.
  3. تبحث عن المهمة المُسمّاة compile وتكتشف أنّها تعتمد على المهمة المُسمّاة init.
  4. تبحث عن المهمة المُسمّاة init وتكتشف أنّها لا تتضمن تبعيات.
  5. ينفِّذ الأوامر المحدّدة في المهمة init.
  6. ينفّذ الأوامر المحدّدة في المهمة compile لأنّه تم تشغيل جميع تبعيات هذه المهمة.
  7. ينفّذ الأوامر المحدّدة في المهمة dist لأنّه تم تشغيل جميع تبعيات هذه المهمة.

في النهاية، يكون الرمز الذي نفّذه Ant عند تشغيل مهمة dist مكافئًا للنص البرمجي التالي في واجهة المستخدم:

./createTimestamp.sh
mkdir build/
javac src/* -d build/
mkdir -p dist/lib/
jar cf dist/lib/MyProject-$(date --iso-8601).jar build/*

عند إزالة البنية، لا يختلف ملف build والنص البرمجي للإصدار كثيرًا. لكننا نجحنا في تحقيق الكثير من خلال تحقيق هذا الهدف. ويمكننا إنشاء ملفات جديدة للإصدار في الأدلة الأخرى وربطها معًا. ويمكننا بسهولة إضافة مهام جديدة تعتمد على المهام الحالية بطرق عشوائية ومعقدة. نحتاج فقط إلى تمرير اسم مهمة واحدة إلى أداة سطر الأوامر ant، ويحدّد كل ما يجب تشغيله.

النملة هي برنامج قديم تم إصداره عام 2000. وهناك أدوات أخرى مثل Maven and Gradle تم تحسينها على Ant في السنوات المتداخلة، وبالتالي حلّت محلّها من خلال إضافة ميزات مثل الإدارة التلقائية للاعتماديات الخارجية وبناء بنية أكثر وضوحًا بدون أي تنسيق XML. ومع ذلك، فإنّ طبيعة هذه الأنظمة الجديدة تظل كما هي، حيث تسمح للمهندسين بكتابة نصوص برمجية بطريقة مستندة إلى الوحدات ووحدات نموذجية كمهام وتوفّر أدوات لتنفيذ هذه المهام وإدارة الارتباطات بينها.

الجانب المظلم من أنظمة التصميم المستندة إلى المهام

وبما أنّ هذه الأدوات تتيح للمهندسين تحديد أي نص برمجي على أنه مهمة، فهي فعّالة للغاية وتتيح لك تنفيذ أي شيء يمكنك تخيله باستخدامها. ومع ذلك، تأتي هذه القوة من السلبيات، ويمكن أن تكون أنظمة إنشاء المهام المستنِدة مهمة صعبة عندما تصبح النصوص البرمجية للإصدار أكثر تعقيدًا. وتكمن المشكلة في هذه الأنظمة في أنّها في النهاية تمنح المستخدمين الكثير من المهندسين ولا تتوفّر لهم طاقة كافية في النظام. ولأن النظام لا يدرك ما تفعله النصوص البرمجية، فإن الأداء يعاني من مشاكل لأنّه يجب أن يكون دقيقًا جدًا بشأن كيفية تحديد موعد تنفيذ الخطوات وتنفيذها. وما من طريقة للنظام للتأكّد من أنّ كل نص برمجي يؤدي ما يجب أن يفعله، لذا تميل النصوص البرمجية إلى النمو في درجة التعقيد وتحوّل في النهاية إلى شيء آخر يحتاج إلى تصحيح الأخطاء.

صعوبة موازاة خطوات الإصدار

تتميّز محطات العمل الحديثة الخاصة بالتطوير العقاري بفعاليتها الكبيرة، وهي توفّر نوى متعددة يمكنها تنفيذ عدّة خطوات إنشاء في الوقت نفسه. ولكنّ الأنظمة المستندة إلى المهام لا يمكنها غالبًا موازاة تنفيذ المهام حتى ولو كان من المفترض أن تتمكّن من ذلك. لنفترض أن المهمة "أ" تعتمد على المهمتين "ب" و"ج". بما أنّ المهام (ب) و(ج) لا تعتمد على بعضهما البعض، هل آمن تشغيلهما في الوقت نفسه ليتمكّن النظام من الوصول إلى المهمة (أ) بسرعة أكبر؟ ربما، إذا لم يتم لمس أي من الموارد نفسها. ربما لا تستخدم، يمكن أن يستخدم كلٌ منهما الملف نفسه لتتبُّع حالاته وتشغيله في الوقت نفسه إلى حدوث تعارض. ليست هناك طريقة عامة يمكن للنظام من خلالها معرفة ما إذا كان سيخاطر بهذه التضاربات، (مما يؤدي إلى حدوث مشاكل في الإصدار نادرة وفي أصعب المشاكل) أو ضرورة تقييد تشغيل الإصدار بأكمله على سلسلة محادثات واحدة في عملية واحدة. يمكن أن يشكّل ذلك هدرًا ضخمًا لأداة قوية لمطوّري البرامج، ويستبعد تمامًا احتمالية توزيع الإصدار على أجهزة متعددة.

صعوبة إنشاء الإصدارات المتزايدة

ويتيح نظام التصميم الجيد للمهندسين إجراء إصدارات تدريجية موثوقة بحيث لا يتطلب التغيير البسيط بناء قاعدة الرموز بالكامل من الخدش. ويُعتبر ذلك أمرًا مهمًا على وجه الخصوص إذا كان نظام الإصدار بطيء ويتعذّر موازاة خطوات الإصدار للأسباب المذكورة أعلاه. ولكن للأسف، تواجه أنظمة الإنشاء المستندة إلى المهام هنا صعوبة أيضًا. بما أنّه يمكن تنفيذ المهام من أي نوع، وليست هناك طريقة عامة لمعرفة ما إذا كانت قد تم تنفيذها من قبل. يتم تنفيذ العديد من المهام بكل بساطة باستخدام مجموعة من الملفات المصدر وتشغيل برنامج تجميع لإنشاء برامج ثنائية، وبالتالي لا تحتاج إلى إعادة تشغيلها إذا لم يتم تغيير ملفات المصدر الأساسية. ولكن بدون معلومات إضافية، لا يمكن للنظام قول هذا بالتأكيد، ربما ينزّل المُهمة ملفًا يمكن تغييره، أو ربما يكتب طابعًا زمنيًا يمكن أن يكون مختلفًا في كل عملية تشغيل. لضمان التصحيح، يجب على النظام عادةً إعادة تشغيل كل مهمة أثناء كل إصدار. وتحاول بعض أنظمة الإنشاء تفعيل الإصدارات المتزايدة من خلال السماح للمهندسين بتحديد الشروط التي تحتاج إلى إعادة تنفيذ مهمة بموجبها. وقد يكون هذا الأمر مجديًا في بعض الأحيان، إلا أنه غالبًا ما يكون الأمر أكثر صعوبة مما يبدو عليه. على سبيل المثال، في لغات مثل ++C التي تسمح بتضمين الملفات مباشرةً بواسطة ملفات أخرى، من المستحيل تحديد المجموعة الكاملة من الملفات التي يجب مشاهدتها للتغيير بدون تحليل مصادر الإدخال. غالبًا ما ينتهي المهندسون بأخذ اختصارات، ويمكن أن تؤدي هذه الاختصارات إلى مشاكل نادرة ومحبطة حيث تتم إعادة استخدام نتيجة مهمة حتى إذا لم تكن مناسبة. عندما يحدث ذلك كثيرًا، يتّبع المهندسون عادةً إجراء عمليات التنظيف قبل كل إصدار للحصول على حالة جديدة، ويهدم تمامًا الغرض من إنشاء تصميم متزايد في المقام الأول. من الصعب جدًا معرفة متى يجب إعادة تنفيذ المهمة، وهذه المهمة أفضل من غيرها من الآلات.

صعوبة حفظ النصوص البرمجية وتصحيح الأخطاء

وأخيرًا، غالبًا ما يكون من الصعب العمل مع نصوص الإصدار البرمجية التي تفرضها أنظمة الإصدار المستندة إلى المهام. وعلى الرغم من أنّها غالبًا ما تكون أقلّ تدقيقًا، فإنّ النصوص البرمجية تُعد رمزًا مماثلاً لإنشاء النظام، وهي أيضًا أماكن سهلة لإخفاء الأخطاء. إليك بعض الأمثلة على الأخطاء الشائعة عند استخدام نظام إصدار مستند إلى المهام:

  • تعتمد المهمة "أ" على المهمة "ب" لإنشاء ملف معيّن كإخراج. ولا يدرك مالك المهمة (ب) أنّ هناك مهام أخرى تعتمد عليها، لذا يغيّرها لإنتاج النتائج في موقع جغرافي مختلف. لا يمكن اكتشاف ذلك إلا عندما يحاول أحد الأشخاص تنفيذ المهمة (أ) ويكتشف أنها فشلت.
  • تعتمد المهمة "أ" على المهمة "ب"، التي تعتمد على المهمة "ج"، التي تنتج ملفًا معيّنًا مثل الإخراج الذي تطلبه المهمة "أ". ويقرر مالك المهمة "ب" أنها لا تحتاج إلى الاعتماد على المهمة "ج" بعد الآن، ما يتسبب في إخفاق المهمة "أ" على الرغم من أن المهمة "ب" لا تهتم بالمهمة "ج".
  • يفترض مطوّر البرامج عن مهمة جديدة عن طريق الخطأ آلة تشغّل المهمة، مثل موقع الأداة أو قيمة متغيرات بيئة معيّنة. تعمل الأداة على الجهاز، ولكنها تتعذّر عند محاولة مطوّر برامج آخر تنفيذها.
  • تحتوي المهمة على مكوِّن غير محدَّد، مثل تنزيل ملف من الإنترنت أو إضافة طابع زمني إلى أحد الإصدارات. والآن، يحصل الأشخاص على نتائج يُحتمل أن تكون مختلفة في كل مرة يتم فيها تشغيل الإصدار، ما يعني أن المهندسين لن يتمكّنوا من إعادة إظهار المشاكل وإصلاحها لبعض الإخفاقات أو الأعطال التي يحدثها نظام التشغيل التلقائي.
  • يمكن للمهام التي تتضمن تبعيات متعددة إنشاء حالات سباق. إذا كانت المهمة (أ) تعتمد على كلٍّ من المهمة (ب) والمهمة (ج)، وحدثت المهمة (ب) و(ج) عدّلت الملف نفسه، تحصل المهمة (أ) على نتيجة مختلفة بناءً على المهمة (ب) و(ج) التي انتهت أولاً.

لا تتوفر طريقة لغرض عام لحل مشاكل الأداء أو الصحة أو الاستدامة ضمن إطار العمل المستند إلى المهام الموضح هنا. وطالما أنّ المهندسين يمكنهم كتابة رمز عشوائي يتم تشغيله أثناء الإصدار، لا تتوفّر على النظام معلومات كافية حتى يتمكّن دائمًا من تشغيل الإصدارات بشكلٍ سريع وصحيح. لحل المشكلة، علينا أن نخرج بعض الأمر من سيطرة المهندسين، ونعيد وضعها إلى نظام النظام ونعيد النظر في دور النظام ليس كمهام تشغيلية، بل كإنتاج تحف أثرية.

أدّى هذا النهج إلى إنشاء أنظمة إنشاء مستندة إلى القطع الأثرية، مثل Blaze وBazel.