Audacity
معمارية تطبيقات المصادر المفتوحة (المجلد الأول)Audacity
معمارية تطبيقات المصادر المفتوحة (المجلد الأول) Audacity
James Crook
إذا استمتعت بهذه الكتب، فقد يعجبك أيضًا التصميم البرمجي بالأمثلة في بايثون (Software Design by Example in Python) والتصميم البرمجي بالأمثلة في جافاسكربت (Software Design by Example in JavaScript).
Audacity برنامج شهير لتسجيل الصوت وتحريره. إنه برنامج قوي مع بقائه سهل الاستخدام. معظم المستخدمين على ويندوز، لكن الشيفرة المصدرية نفسها لـ Audacity تُصرَّف لتعمل على لينكس وماك أيضًا.
كتب Dominic Mazzoni النسخة الأصلية من Audacity عام 1999 بينما كان طالبًا في مرحلة البحث العلمي في جامعة Carnegie Mellon. أراد Dominic إنشاء منصّة يمكن عليها تطوير خوارزميات معالجة الصوت وتنقيحها. ونما البرنامج ليصبح مفيدًا بذاته بطرق كثيرة أخرى. وعندما صدر Audacity بوصفه برمجية مفتوحة المصدر، جذب مطوّرين آخرين. وعلى مدى السنين، قام فريق صغير ومتغيّر باستمرار من الشغوفين بتعديل Audacity وصيانته واختباره وتحديثه وكتابة التوثيق له ومساعدة المستخدمين فيه وترجمة واجهته إلى لغات أخرى.
أحد الأهداف أن تكون واجهة المستخدم قابلة للاكتشاف (discoverable): ينبغي أن يجلس المستخدمون دون دليل ويبدؤوا باستخدامه فورًا، وهم يكتشفون ميزاته تدريجيًا. وقد كان هذا المبدأ بالغ الأهمية في منح Audacity اتساقًا أكبر في واجهة المستخدم مما كان ليكون عليه لولاه. وفي مشروع يشارك فيه كثيرون، فإن هذا المبدأ الموحِّد أهم مما قد يبدو للوهلة الأولى.
كان من الأفضل لو كانت معمارية Audacity تفتدي بمبدأ إرشادي مماثل، وبنوع مماثل من القابلية للاكتشاف. وأقرب ما لدينا إلى ذلك هو عبارة «حاول وكن متسقًا». فعند إضافة شيفرة جديدة، يحاول المطوّرون اتباع أسلوب الشيفرة المجاورة واتفاقياتها. غير أن قاعدة شيفرة Audacity عمليًا مزيج من شيفرة ذات بنية جيدة وأخرى ذات بنية أضعف. وليس لدينا معمارية شاملة، بل تشبيه المدينة الصغيرة أبلغ: فهناك بعض المباني المبهرة، لكنك ستجد أيضًا أحياء متداعية أقرب إلى أحياء الأكواخ.
2.1. البنية داخل Audacity
يقوم Audacity على طبقات فوق عدة مكتبات. ورغم أن معظم البرمجة الجديدة في شيفرة Audacity لا تتطلب معرفة تفصيلية بما يجري تحديدًا داخل هذه المكتبات، فإن الإلمام بواجاتها البرمجية (APIs) وبما تقوم به مهم. وأهم مكتبتين هما PortAudio التي توفّر واجهة صوتية منخفضة المستوى بطريقة عابرة للمنصات، وwxWidgets التي توفّر مكوّنات واجهة رسومية بطريقة عابرة للمنصات أيضًا.
عند قراءة شيفرة Audacity، من المفيد أن ندرك أن جزءًا يسيرًا فقط من الشيفرة جوهري. وتساهم المكتبات في كثير من الميزات الاختيارية—مع أن من يستخدمون تلك الميزات قد لا يعدّونها اختيارية. فمثلًا، إلى جانب تأثيرات الصوت المدمجة الخاصة به، يدعم Audacity واجهة LADSPA (Linux Audio Developer's Simple Plugin API) لتأثيرات الصوت الإضافية القابلة للتحميل ديناميكيًا. وتؤدي واجهة VAMP داخل Audacity الوظيفة نفسها للإضافات التي تحلّل الصوت. وبدون هاتين الواجهتين، سيكون Audacity أقل غنى بالميزات، لكنه لا يعتمد عليهما بشكل مطلق.
ومن المكتبات الاختيارية الأخرى التي يستخدمها Audacity: libFLAC وlibogg وlibvorbis. وتوفّر هذه تنسيقات ضغط صوتي متنوعة. أما تنسيق MP3 فيُعتنى به عبر تحميل مكتبة LAME أو FFmpeg ديناميكيًا. وتمنع قيود الترخيص هذه المكتبات الضاغطة شديدة الشهرة من أن تكون مدمجة في البرنامج.
أيضًا، تقف قيود الترخيص وراء بعض القرارات الأخرى المتعلقة بمكتبات Audacity وبنيته. فمثلًا، دعم إضافات VST غير مدمج بسبب قيود الترخيص. ونودّ كذلك أن نستخدم شيفرة FFTW شديدة الكفاءة الخاصة بتحويل فورييه السريع (fast Fourier transform) في بعض شيفرتنا. غير أننا لا نوفّرها إلا كخيار لمن يصرّفون Audacity بأنفسهم، ونرجع بدلًا منها إلى نسخة أبطأ قليلًا في بناءاتنا المعتادة. وما دام Audacity يقبل الإضافات، فقد قيل — وصحيح — إنه لا يستطيع استخدام FFTW. لا يريد مؤلفو FFTW أن تكون شيفرتهم متاحة كخدمة عامة لأي شيفرة أخرى اعتباطية. وعليه، فإن القرار المعماري بدعم الإضافات يقودنا إلى مقايضة فيما نقدّمه: فهو يجعل إضافات LADSPA ممكنة، لكنه يحرمنا من استخدام FFTW في الملفات التنفيذية الجاهزة التي نبنيها مسبقًا.
وتتشكل المعمارية أيضًا بحسب الاعتبارات المتعلقة بأفضل طريقة لاستخدام وقت المطوّرين الشحيح لدينا. فمع فريق صغير من المطوّرين، لا تتوفر لدينا الموارد للقيام، مثلًا، بالتحليل المعمّق للثغرات الأمنية كما تفعل الفرق العاملة على Firefox وThunderbird. غير أننا لا نريد أن يوفّر Audacity طريقًا لتجاوز جدار حماية، ولذلك اتّخذنا قاعدة بعدم وجود أي اتصالات TCP/IP من Audacity أو إليه. وتجنّب TCP/IP يُسقط بكثير من المخاوف الأمنية. ووعينا بمحدودية مواردنا يدفعنا إلى تصميم أفضل. فهو يساعدنا على إسقاط الميزات التي ستكلّفنا وقت مطوّرين أكثر مما ينبغي، وعلى التركيز على ما هو جوهري.
ينطبق اعتبار مماثل بشأن وقت المطوّرين على لغات البرمجة النصية (scripting). نريد البرمجة النصية، لكن شيفرة تنفيذ هذه اللغات لا يلزم أن تكون داخل Audacity. فلا معنى لصرّف نسخ من كل لغة برمجة نصية داخل Audacity لمنح المستخدمين كل الخيارات التي قد يريدونها.[1] وبدلًا من ذلك، نفّذنا البرمجة النصية بوحدة إضافات واحدة وأنبوب (pipe)، وسنغطي ذلك لاحقًا.

الشكل 2.1: طبقات Audacity
يعرض الشكل 2.1 بعض الطبقات والوحدات في Audacity. ويُبرز المخطط ثلاث أصناف (classes) مهمة داخل wxWidgets، لكل منها انعكاس في Audacity. فنحن نبني تجريدات (abstractions) من مستوى أعلى انطلاقًا من تجريدات مرتبطة في مستوى أدنى. فمثلًا، نظام BlockFile انعكاس لـ wxWidgets' wxFiles ومبني عليه. وقد يكون من المفيد في مرحلة ما فصل BlockFiles وShuttleGUI ومعالجة الأوامر إلى مكتبة وسطية قائمة بذاتها. وسيشجعنا ذلك على جعلها أكثر عمومية.
في أسفل المخطط شريط ضيق مخصَّص لـ «طبقات التنفيذ الخاصة بالمنصة» (Platform Specific Implementation Layers). كلتا wxWidgets وPortAudio طبقتا تجريد لنظام التشغيل (OS abstraction layers). وتحتوي كل منهما على شيفرة شرطية تختار بين تنفيذات مختلفة بحسب المنصة المستهدفة.
تتضمن فئة «مكتبات الدعم الأخرى» (Other Supporting Libraries) مجموعة واسعة من المكتبات. ومن اللافت أن عددًا كبيرًا منها يعتمد على وحدات محمَّلة ديناميكيًا. وهذه الوحدات الديناميكية لا تعرف شيئًا عن wxWidgets.
على منصة ويندوز، كنا سابقًا نصرّف Audacity كملف تنفيذي أحادي (monolithic executable) واحد يضم wxWidgets وشيفرة تطبيق Audacity في الملف التنفيذي نفسه. وفي عام 2008 انتقلنا إلى استخدام بنية معيارية (modular) مع wxWidgets كـ DLL منفصلة. والهدف من ذلك السماح بتحميل DLLs إضافية اختيارية في وقت التشغيل، حيث تستخدم تلك الـ DLLs مباشرةً ميزات wxWidgets. أما الإضافات الموصولة فوق الخط المتقطّع في المخطط فيمكنها استخدام wxWidgets.
للقرار باستخدام DLLs من أجل wxWidgets جوانب سلبية. فالتوزيع أصبح أكبر حجمًا، جزئيًا لأن كثيرًا من الدوال غير المستخدمة موفَّرة الآن في الـ DLLs، وكانت ستُحذَّف في عملية التحسين السابقة. كما أن Audacity يستغرق وقتًا أطول في بدء التشغيل لأن كل DLL يُحمَّل على حدة. أما المزايا فكثيرة. ونتوقع أن تحقّق الوحدات بالنسبة لنا مزايا مماثلة لما تحققه في Apache. ومن وجهة نظرنا، تتيح الوحدات أن تبقى نواة Apache مستقرة جدًا مع تيسير التجريب والميزات الخاصة والأفكار الجديدة داخل الوحدات. وتساهم الوحدات مساهمة كبيرة في كبح إغراء تفريع المشروع (fork) لتوجيهه في اتجاه جديد. نرى أن هذا كان تغييرًا معماريًا بالغ الأهمية بالنسبة لنا. ونحن نتوقع هذه المزايا لكننا لم نرها بعد. فكشف دوال wxWidgets ليس سوى خطوة أولى، وما زال علينا الكثير لنحصل على نظام معياري مرن.
من الواضح أن بنية برنامج مثل Audacity لم تُصمَّم مسبقًا، بل هي شيء يتطور مع الوقت. وفي المجمل، فإن المعمارية التي لدينا الآن تعمل جيدًا بالنسبة لنا. غير أننا نجد أنفسنا نتصارع مع المعمارية عندما نحاول إضافة ميزات تؤثر في كثير من ملفات الشيفرة المصدرية. فمثلًا، يتعامل Audacity حاليًا مع المسارات الاستريو (stereo) والمسارات أحادية القناة (mono) بأسلوب خاص بحالات خاصة (special cased). ولو أردت تعديل Audacity للتعامل مع الصوت المحيط (surround sound)، لاضطررت إلى إجراء تغييرات في كثير من أصناف Audacity. وتجاوز الاستريو: قصة GetLink
لم يكن لدى Audacity قط تجريد لعدد القنوات. وبدلًا من ذلك، التجريد الذي يستخدمه هو ربط قنوات الصوت. وهناك دالة GetLink تُعيد القناة الصوتية الأخرى في زوج إذا كان عددهما اثنتين، وتُعيد NULL إذا كان المسار أحادي القناة. والشيفرة التي تستخدم GetLink تبدو عادةً تمامًا كأنها كُتبت أصلًا لأجل الصوت أحادي القناة، ثم استُخدم لاحقًا اختبار (GetLink() != NULL) لتوسيع تلك الشيفرة والتعامل مع الاستريو. لست متأكدًا من أنها كُتبت حقًا على هذا النحو، لكنني أظن ذلك. ولا يوجد أي حلقة تكرار (loop) باستخدام GetLink لاجتياز كل القنوات في قائمة مترابطة. أما الرسم والخلط والقراءة والكتابة، فتتضمن جميعها اختبارًا لحالة الاستريو بدلًا من شيفرة عامة تعمل مع n قناة، حيث n على الأرجح واحد أو اثنان. ولو أردنا الانتقال إلى الشيفرة الأكثر عمومية، لاضطررنا إلى إجراء تغييرات على نحو 100 استدعاء من هذه الاستدعاءات للدالة GetLink، بما يغيّر 26 ملفًا على الأقل.
من السهل البحث في الشيفرة عن استدعاءات GetLink، والتغييرات المطلوبة ليست معقدة إلى حد كبير، لذا فإن إصلاح هذه «المشكلة» ليس بالخطورة التي قد تبدو أولًا. فقصة GetLink ليست عن عيب بنيوي صعب الإصلاح، بل هي توضيح لكيفية انتقال عيب صغير نسبيًا إلى قدر كبير من الشيفرة إذا سمحنا بذلك.
وبالنظر إلى ما تبيّن لاحقًا، كان من الأفضل جعل الدالة GetLink خاصة، وتوفير بدلًا منها مُكرِّرًا (iterator) لاجتياز كل القنوات في المسار. وكان ذلك سيتجنّب قدرًا كبيرًا من شيفرة الحالات الخاصة للاستريو، وفي الوقت نفسه يجعل الشيفرة التي تستخدم قائمة قنوات الصوت غير مبالِصة بتنفيذ القائمة (agnostic with respect to the list implementation).
من المرجح أن يقودنا التصميم الأكثر معيارية إلى إخفاء أفضل للبنية الداخلية. فمع تعريفنا لواجهة برمجية خارجية (external API) وتوسيعها، سنضطر إلى النظر عن قرب أكبر في الدوال التي نوفّرها. وهذا سيوجّه انتباهنا إلى التجريدات التي لا نرغب في تثبيتها ضمن واجهة برمجية خارجية.
2.2. مكتبة واجهة المستخدم wxWidgets
أهم مكتبة منفردة لمبرمجي واجهة المستخدم في Audacity هي مكتبة الواجهة الرسومية wxWidgets، التي توفّر أمثال الأزرار والأشرطة المنزلقة (sliders) ومربعات الاختيار والنوافذ والحوارات. وهي التي توفّر أكثر السلوك وضوحًا عبر المنصات. لدى مكتبة wxWidgets صنف سلاسل خاص بها هو wxString، كما لها تجريدات عابرة للمنصات للخيوط وأنظمة الملفات والخطوط، وآلية للتعريب إلى لغات أخرى، وكلها نستخدمها. وننصح كل من هو جديد على تطوير Audacity بأن ينزّل wxWidgets أولًا ويصرّف بعض الأمثلة التي تأتي مع المكتبة ويجرّبها. وwxWidgets طبقة رقيقة نسبيًا فوق كائنات الواجهة الرسومية التي يوفّرها نظام التشغيل.
لبناء حوارات معقّدة، لا توفّر wxWidgets عناصر واجهة (widgets) مفردة فحسب، بل توفّر أيضًا منظِّمات أحجام (sizers) تتحكم في أحجام العناصر ومواضعها. وهذا أفضل بكثير من منح عناصر الواجهة الرسومية مواضع مطلقة ثابتة. فإذا أُعيدت أحجام العناصر إما مباشرةً من المستخدم أو، مثلًا، باستخدام حجم خط مختلف، فإن مواضع العناصر داخل الحوارات تتحدّث بطريقة طبيعية جدًا. والمنظِّمات مهمة لتطبيق عابر للمنصات؛ فمن دونها قد نضطر إلى تخطيطات مخصَّصة للحوارات لكل منصة.
غالبًا ما يكون تصميم هذه الحوارات في ملف موارد يقرأه البرنامج. غير أن Audacity يصرّف تصاميم الحوارات حصريًا داخل البرنامج على هيئة سلسلة من الاستدعاءات إلى دوال wxWidgets. ويوفّر هذا أقصى قدر من المرونة؛ أي حوارات يتحدّد محتواها وسلوكها بدقة بواسطة شيفرة مستوى التطبيق.
كان بإمكانك في وقت من الأرباع أن تجد في Audacity مواضع كانت فيها الشيفرة الأولية لإنشاء واجهة رسومية قد أُنشئت بوضوح بواسطة مولّد شيفرة باستخدام أداة بصرية لبناء الحوارات. وقد ساعدتنا تلك الأدوات على الحصول على تصميم أساسي. ثم جُرِّحت الشيفرة الأساسية تدريجيًا (hacked around) لإضافة ميزات جديدة، فنتج عن ذلك مواضع كثيرة أُنشئت فيها حوارات جديدة عبر نسخ شيفرة حوارات قائمة كانت قد جُرِّحت وتُعديلت من جديد.
بعد سنوات عديدة من هذا النوع من التطوير، اكتشفنا أن أقسامًا كبيرة من الشيفرة المصدرية لـ Audacity، وخاصة حوارات ضبط تفضيلات المستخدم، تتألف من شيفرة متشابكة ومتكررة. وتلك الشيفرة، وإن كانت بسيطة فيما تفعله، كانت صعبة المتابعة على نحو مدهش. وجزء من المشكلة أن التسلسل الذي بُنيت به الحوارات كان عشوائيًا إلى حد كبير: فالعناصر الأصغر جُمعت في عناصر أكبر ثم في حوارات كاملة في النهاية، لكن ترتيب إنشاء العناصر بواسطة الشيفرة لم يكن مطابقًا (ولم يكن بحاجة إلى أن يكون مطابقًا) للترتيب الذي وُضعت به العناصر على الشاشة. وكانت الشيفرة أيضًا مطوّلة ومتكررة. فكان هناك شيفرة متعلقة بالواجهة الرسومية تنقل البيانات من التفضيلات المخزَّنة على القرص إلى متغيرات وسيطة، وشيفرة تنقل من المتغيرات الوسيطة إلى الواجهة الرسومية المعروضة، وشيفرة تنقل من الواجهة الرسومية المعروضة إلى المتغيرات الوسيطة، وشيفرة تنقل من المتغيرات الوسيطة إلى التفضيلات المخزَّنة. وكانت في الشيفرة تعليقات من نوع //this is a mess، لكنّ وقتًا طويلًا مرّ قبل أن يتم أي شيء بشأن ذلك.
2.3. طبقة ShuttleGui
كان حل فك تشابك كل هذه الشيفرة هو صنف جديد هو ShuttleGui، الذي قلّل كثيرًا عدد أسطر الشيفرة اللازمة لتحديد حوار، مما جعل الشيفرة أكثر قابلية للقراءة. وShuttleGui طبقة إضافية بين مكتبة wxWidgets وبين Audacity، ومهمتها نقل المعلومات بينهما. وفيما يلي مثال ينتج عناصر الواجهة الرسومية الموضَّحة في الشكل 2.2.
ShuttleGui S;
// GUI Structure
S.StartStatic("Some Title",…);
{
S.AddButton("Some Button",…);
S.TieCheckbox("Some Checkbox",…);
}
S.EndStatic();

الشكل 2.2: مثال حوار
تعرّف هذه الشيفرة صندوقًا ثابتًا (static box) في حوار، ويحتوي هذا الصندوق على زر ومربع اختيار. وينبغي أن يكون التطابق بين الشيفرة والحوار واضحًا. أما StartStatic وEndStatic فاستدعاءان متقابلان. وتُستخدم أزواج مشابهة من نوع StartSomething/EndSomething — ويجب أن تتطابق — للتحكم في جوانب أخرى من تخطيط الحوار. الأقواس المعقوفة والمسافات البادئة المصاحبة لها ليست ضرورية ليكون هذا شيفرة صحيحة. وقد تبنّينا قاعدة إضافتها لتصبح البنية، وخاصة تطابق الاستدعاءات المتقابلة، واضحة. فهي تساعد فعلًا في قابلية القراءة في الأمثلة الأكبر.
الشيفرة المصدرية المعروضة لا تنشئ الحوار فحسب. فالشيفرة التي تلي التعليق «//GUI Structure» يمكن أن تُستخدم أيضًا لنقل البيانات من الحوار إلى خارج حيث تُخزَّن تفضيلات المستخدم، ولإعادتها إلى الداخل. وكان كثير من الشيفرة المتكررة سابقًا ناتجًا عن الحاجة إلى ذلك. أما اليوم فتلك الشيفرة تُكتب مرة واحدة فقط وتُدفن داخل الصنف ShuttleGui.
هناك امتدادات أخرى لـ wxWidgets الأساسية في Audacity. فـ Audacity له صنف خاص به لإدارة أشرطة الأدوات. ولماذا لا يستخدم الصنف المدمج لأشرطة الأدوات في wxWidget؟ السبب تاريخي: فقد كُتبت أشرطة أدوات Audacity قبل أن توفّر wxWidgets صنفًا لأشرطة الأدوات.
2.4. لوحة المسارات (TrackPanel)
اللوحة الرئيسية في Audacity التي تعرض الموجات الصوتية هي TrackPanel. وهذا عنصر تحكّم مخصَّص (custom control) يرسمه Audacity. وهو مكوَّن من أجزاء مثل لوحات أصغر تحمل معلومات المسار، ومسطرة للخط الزمني (timebase)، ومساطر للسعة (amplitude)، ومسارات قد تعرض موجات أو أطيافًا أو تسميات نصية. ويمكن تغيير أحجام المسارات وتحريكها بالسحب. أما المسارات التي تحتوي تسميات نصية فتستخدم إعادة تنفيذ خاصة بنا لمربع نص قابل للتحرير بدلًا من مربع النص المدمج. وقد تظن أن هذه اللوحات والمسارات والمساطر ينبغي أن يكون كل منها مكوّنًا من wxWidgets، لكنها ليست كذلك.

الشكل 2.3: واجهة Audacity مع تحديد عناصر لوحة المسارات
تعرض لقطة الشاشة في الشكل 2.3 واجهة مستخدم Audacity. وجميع المكوّنات التي وُسمت مخصَّصة لـ Audacity. أما بالنسبة إلى wxWidgets، فهناك مكوّن wxWidget واحد يمثّل TrackPanel. وتتكفّل شيفرة Audacity — لا wxWidgets — بموضعية العناصر داخله وإعادة رسمها.
الطريقة التي تتجمع بها كل هذه المكوّنات لتشكيل TrackPanel فظيعة حقًا. (الشيفرة هي الفظيعة؛ أما النتيجة النهائية التي يراها المستخدم فتبدو سليمة تمامًا.) فشيفرة الواجهة الرسومية والشيفرة الخاصة بالتطبيق مختلطة كلها معًا، غير منفصلة بنظافة. وفي التصميم الجيد، ينبغي ألا يعرف إلا شيفرتنا الخاصة بالتطبيق أمور قنوات الصوت اليمنى واليسرى والديسيبل (decibels) والكتم (muting) والعزل (soloing). أما عناصر الواجهة الرسومية فيجب أن تكون عناصر محايدة تجاه التطبيق وقابلة لإعادة الاستخدام في تطبيق غير صوتي. وحتى الأجزاء الخاصة بالواجهة الرسومية في TrackPanel هي رقعة من شيفرة الحالات الخاصة بمواضع وأحجام مطلقة، مع قدر غير كافٍ من التجريد. ليكون أجمل بكثير وأنظف وأكثر اتساقًا لو كانت هذه المكوّنات الخاصة عناصر واجهة رسومية قائمة بذاتها، ولو استخدمت منظِّمات أحجام بواجهات من الأنواع ذاتها التي تستخدمها wxWidgets.
وللوصول إلى مثل هذا TrackPanel، سنحتاج إلى منظِّم أحجام جديد في wxWidgets يمكنه تحريك المسارات وتغيير أحجامها، بل وأي عنصر واجهة آخر. ومنظِّمات wxWidgets ليست مرنة إلى هذا الحد حتى الآن. وكميزة جانبية («ناتج ثانوي») يمكننا استخدام ذلك المنظِّم في مواضع أخرى. يمكننا استخدامه في أشرطة الأدوات التي تحمل الأزرار، فيصبح تخصيص ترتيب الأزرار داخل الشريط بالسحب أمرًا سهلًا.
أُنجز بعض العمل الاستكشافي في إنشاء هذه المنظِّمات واستخدامها، لكنه ليس كافيًا. وواجهت بعض التجارب التي جعلت عناصر الواجهة الرسومية مكوّنات wxWidgets مكتملة مشكلة: فذلك يقلّل تحكّمنا في إعادة رسم العناصر، ويؤدي إلى وميض (flicker) عند تغيير أحجام المكوّنات وتحريكها. وسنحتاج إلى تعديل wxWidgets على نطاق واسع لتحقيق إعادة رسم بلا وميض، وفصل خطوات تغيير الحجم عن خطوات إعادة الرسم بصورة أفضل.
وثاني سبب للتحذّر من هذا النهج في TrackPanel هو أننا نعلم بالفعل أن wxWidgets تبدأ بالعمل ببطء شديد عندما تكون أعداد العناصر (widgets) كبيرة. وأغلب هذا خارج عن سيطرة wxWidget. فكل عنصر wxWidget وكل زر وكل مربع إدخال نصي يستهلك موردًا من نظام النوافذ، ولكلٍّ منها مقبض (handle) للوصول إليه. ومعالجة أعداد كبيرة من هذه العناصر تستغرق وقتًا. والمعالجة بطيئة حتى عندما تكون أغلب العناصر مخفية أو خارج الشاشة. ونحن نريد القدرة على استخدام عناصر صغيرة كثيرة على مساراتنا.
أفضل حل هو استخدام نمط الخفيف (flyweight pattern): عناصر واجهة خفيفة الوزن نرسمها بأنفسنا، لا تمتلك كائنات مقابلة تستهلك موارد نظام النوافذ أو مقابضه. وسنستخدم بنية تشبه بنية منظِّمات wxWidgets وعناصرها المكوِّنة، ونمنح المكوّنات واجهة برمجية مشابهة من دون أن ترث فعلًا من أصناف wxWidgets. وهذا يعني إعادة هيكلة شيفرة TrackPanel الحالية بحيث تصبح بنيتها أوضح بكثير. ولو كان هذا حلًا سهلًا لكان قد نُفِّذ بالفعل، لكن الآراء المتباعدة حول ما نريد الوصول إليه تحديدًا أعاقت محاولة سابقة. أما تعميم نهجنا الحالي المرتجل (ad hoc) فسيتطلب قدرًا كبيرًا من عمل التصميم والبرمجة. وهناك إغراء شديد بترك الشيفرة المعقّدة التي تعمل جيدًا بما يكفي دون المساس بها.
2.5. مكتبة PortAudio: التسجيل والتشغيل
PortAudio هي المكتبة الصوتية التي تمنح Audacity القدرة على تشغيل الصوت وتسجيله بطريقة عابرة للمنصات. وبدونها لن يستطيع Audacity استخدام بطاقة الصوت في الجهاز الذي يعمل عليه. وتوفّر PortAudio المخازن الدائرية (ring buffers)، وتحويل معدل العيّنات (sample rate conversion) عند التشغيل والتسجيل، والأهم من ذلك، توفّر واجهة برمجية تخفي الفروق بين الصوت على Mac وLinux وWindows. وداخل PortAudio توجد ملفات تنفيذ بديلة تدعم هذه الواجهة لكل منصة.
لم أحتج يومًا إلى الغوص في PortAudio لمتابعة ما يجري في داخلها. غير أنه من المفيد أن نعرف كيف نتفاعل معها. يقبل Audacity حزم البيانات من PortAudio (التسجيل) ويرسل حزمًا إليها (التشغيل). ومن المفيد النظر في كيفية حدوث الإرسال والاستلام بالضبط، وكيف ينسجم ذلك مع القراءة والكتابة على القرص والتحديثات المعروضة على الشاشة.
تجري في الوقت نفسه عدة عمليات مختلفة. بعضها يتكرر كثيرًا وينقل كميات صغيرة من البيانات ويجب الاستجابة له بسرعة. وبعضها يتكرر أقل وينقل كميات أكبر، وتوقيت حدوثه الدقيق أقل أهمية. وهذا هو عدم تطابق المعاوقة (impedance mismatch) بين العمليات، وتُستخدم المخازن المؤقتة لاستيعابه. والجزء الثاني من الصورة أننا نتعامل مع أجهزة صوت وأقراص صلبة وشاشة. فنحن لا ننزل إلى مستوى الأسلاك، ولذلك علينا العمل بالواجهات البرمجية المعطاة لنا. ومع أننا نودّ أن تبدو كل عملياتنا متشابهة — مثلًا أن تعمل كل واحدة منها من wxThread — إلا أننا لا نتمتع بهذه الرفاهية (الشكل 2.4).

الشكل 2.4: الخيوط والمخازن المؤقتة في التشغيل والتسجيل
يبدأ خيط صوتي واحد بواسطة شيفرة PortAudio ويتفاعل مباشرةً مع جهاز الصوت. وهو ما يقود التسجيل أو التشغيل. ويجب أن يكون هذا الخيط سريع الاستجابة وإلا فُقدت الحزم. ويقوم هذا الخيط، تحت سيطرة شيفرة PortAudio، باستدعاء audacityAudioCallback التي، أثناء التسجيل، تضيف الحزم الصغيرة الواردة حديثًا إلى مخزن التقاط أكبر (خمس ثوانٍ). وأثناء التشغيل فإنها تأخذ مقاطع صغيرة من مخزن تشغيل مدته خمس ثوانٍ. ولا تعرف مكتبة PortAudio شيئًا عن wxWidgets، ولذلك فإن هذا الخيط الذي تنشئه PortAudio هو pthread.
يبدأ خيط ثانٍ بواسطة شيفرة في صنف AudioIO الخاص بـ Audacity. وأثناء التسجيل، يأخذ AudioIO البيانات من مخزن الالتقاط ويلحقها بمسارات Audacity حتى تُعرض في النهاية. وإضافةً إلى ذلك، عندما تُضاف بيانات كافية، يكتب AudioIO البيانات على القرص. ويقوم هذا الخيط نفسه أيضًا بقراءات القرص اللازمة لتشغيل الصوت. والدالة AudioIO::FillBuffers هي الدالة المفتاح هنا، وهي تتولى — بحسب إعدادات بعض المتغيرات المنطقية — التسجيل والتشغيل معًا في الدالة نفسها. ومن المهم أن تتولى الدالة الواحدة كلا الاتجاهين. فجزآ التسجيل والتشغيل يُستخدمان معًا عند إجراء «التشغيل البرمجي» (software play through)، حيث يُعاد تسجيل ما سُجّل سابقًا فوقه. وفي خيط AudioIO نحن رهنًا تامًا لمشيئة إدخال/إخراج القرص في نظام التشغيل. فقد نتوقف عند القراءة من القرص أو الكتابة إليه مدة غير معروفة. ولا يمكننا إجراء تلك القراءات أو الكتابات داخل audacityAudioCallback بسبب الحاجة إلى السرعة في الاستجابة هناك.
يتم الاتصال بين الخيوطين عبر متغيرات مشتركة. ولأننا نتحكم في الخيوط التي تكتب في هذه المتغيرات وفي التوقيت الذي تكتب فيه، فإننا نتجنب الحاجة إلى أقفال متبادلة (mutexes) أكثر كلفة.
في كل من التشغيل والتسجيل، هناك متطلب إضافي: يحتاج Audacity أيضًا إلى تحديث الواجهة الرسومية. وهذه أقل العمليات حساسية للزمن. ويجري التحديث في خيط الواجهة الرسومية الرئيسي نتيجة لمؤقّت دوري ينبض عشرين مرة في الثانية. ويؤدي نبض هذا المؤقّت إلى استدعاء TrackPanel::OnTimer، فإذا ثبت وجود حاجة إلى تحديثات في الواجهة الرسومية، فتم تطبيقها. ويُنشأ خيط الواجهة الرسومية الرئيسي هذا داخل wxWidgets لا بواسطة شيفرتنا. وهو خيط مميّز لأن الخيوط الأخرى لا تستطيع تحديث الواجهة الرسومية مباشرةً. واستخدام مؤقّت يجعل خيط الواجهة الرسومية يفحص ما إذا كان بحاجة إلى تحديث الشاشة يتيح لنا تقليل عدد عمليات إعادة الرسم إلى مستوى مقبول لعرض سريع الاستجابة، دون أن نحمّل زمن المعالج أعباءً ثقيلة لأغراض العرض.
هل من التصميم الجيد أن يكون لدينا خيط جهاز الصوت، وخيط للمخازن/القرص، وخيط واجهة رسومية بمؤقّت دوري، للتعامل مع عمليات نقل بيانات الصوت هذه؟ إن وجود هذه الخيوط الثلاثة المختلفة التي لا تستند إلى صنف أساسي تجريدي واحد أمرٌ مرتجل (ad hoc) إلى حد ما. غير أن هذا التردّي (ad-hockery) تحدده إلى حد بعيد المكتبات التي نستخدمها. فـ PortAudio تتوقع أن تنشئ خيطًا بنفسها، وإطار عمل wxWidgets يحتوي تلقائيًا على خيط واجهة رسومية. أما حاجتنا إلى خيط لملء المخازن فناتجة عن حاجتنا إلى معالجة عدم تطابق المعاوقة بين الحزم الصغيرة المتكررة الخاصة بخيط جهاز الصوت والحزم الأكبر الأقل تكرارًا الخاصة بقرص التخزين. والفائدة من استخدام هذه المكتبات واضحة جدًا. وثمن استخدامها هو أننا ننتهي إلى استخدام التجريدات التي تقدّمها. ونتيجة لذلك، ننسخ البيانات في الذاكرة من مكان إلى آخر أكثر مما هو ضروري تمامًا. وفي مفاتيح تحويل البيانات السريع التي عملت عليها، رأيت شيفرة شديدة الكفاءة في معالجة هذا النوع من حالات عدم تطابق المعاوقات، وهي مدفوعة بالمقاطعات ولا تستخدم الخيوط إطلاقًا؛ إذ تُمرَّر مؤشرات المخازن بدلًا من نسخ البيانات. ولا يمكن فعل ذلك إلا إذا كانت المكتبات التي تستخدمها مصمَّمة بتجريد مخازن أغنى. ومع الواجهات القائمة، فإننا مضطرون إلى استخدام الخيوط ومضطرون إلى نسخ البيانات.
2.6. ملفات الكتل (BlockFiles)
أحد التحديات التي يواجهها Audacity هو دعم الإدراج والحذف في تسجيلات صوتية قد تستمر ساعات. ومن السهل أن تكون التسجيلات طويلة أكثر من أن تتسع في الذاكرة العشوائية (RAM) المتاحة. وإذا كان التسجيل الصوتي في ملف قرص واحد، فإن إدراج الصوت في مكان ما قرب بداية ذلك الملف قد يعني نقل قدر كبير من البيانات لإفساح المجال. ونسخ تلك البيانات على القرص سيكون مستهلكًا للوقت، وسيعني أن Audacity لن يستطيع بعدها الاستجابة بسرعة لعمليات التحرير البسيطة.
وحل Audacity لذلك هو تقسيم الملفات الصوتية إلى عدد كبير من ملفات الكتل (BlockFiles)، كل منها بحجم يقارب 1 ميغابايت. وهذا هو السبب الرئيسي لامتلاك Audacity تنسيق ملفات صوتية خاصًا به، وهو ملف رئيسي امتداده .aup.
توازن ملفات الكتل بين قوتين متعارضتين. فنحن نستطيع إدراج الصوت وحذفه من دون نسخ مفرط، وأثناء التشغيل نضمن الحصول على مقاطع صوتية كبيرة بما يكفي مع كل طلب إلى القرص. وكلما صغُرت الكتل زاد عدد طلبات القرص المحتملة لجلب كمية الصوت نفسها؛ وكلما كبرت الكتل زاد النسخ عند الإدراج والحذف.
لا تحوي ملفات الكتل في Audacity أبدًا على مساحات خالية داخلية، ولا تتجاوز أبدًا الحد الأقصى لحجم الكتلة. ولإبقاء هذا قائمًا عند الإدراج أو الحذف، قد ننتهي إلى نسخ ما يعادل كتلة واحدة من البيانات. وعندما لا نحتاج إلى كتلة ما بعد ذلك نحذفها. أما ملفات الكتل فتُحسب مراجعها، ولذلك فإن حذفنا لجزء من الصوت لا يؤدي إلى اختفاء ملفات الكتل المعنية فورًا، لأنها تبقى موجودة لدعم آلية التراجع (undo) حتى نقوم بالحفظ. ولا نحتاج أبدًا إلى جمع القمامة (garbage collection) للمساحة الخالية داخل ملفات كتل Audacity، وهو ما كان علينا تطبيقه مع نهج تجميع كل شيء في ملف واحد.
يُعدّ دمج المقاطع الكبيرة من البيانات وتقسيمها من أساسيات أنظمة إدارة البيانات، من أشجار B إلى أجهزة اللوح (tablets) في Google BigTable إلى إدارة القوائم المترابطة المفرودة (unrolled linked lists). ويوضّح الشكل 2.5 ما يحدث في Audacity عند إزالة مقطع صوتي قرب البداية.

الشكل 2.5: قبل الحذف، يحتوي ملف .aup وملفات الكتل على التسلسل ABCDEFGHIJKLMNO. وبعد حذف FGHI، يتم دمج ملفتَي كتل.
لا تُستخدم ملفات الكتل للصوت نفسه فحسب، بل توجد أيضًا ملفات كتل تخزّن معلومات الملخص (summary). فإذا طُلب من Audacity عرض تسجيل مدته أربع ساعات على الشاشة، فلا يقبل أن يعالج الصوت كاملًا في كل مرة يعيد فيها رسم الشاشة. وبدلًا من ذلك، يستخدم معلومات الملخص التي توفّر أقصى سعة صوتية ودنياها على نطاقات من الزمن. وعند التكبير، يرسم Audacity باستخدام العيّنات الفعلية. وعند التصغير، يرسم Audacity باستخدام معلومات الملخص.
أحد التحسينات في نظام ملفات الكتل هو أن الكتل لا يلزم أن تكون ملفات أنشأها Audacity. إذ يمكن أن تكون مراجع إلى أجزاء فرعية من ملفات صوتية، مثل امتداد زمني من صوت مخزَّن بتنسيق .wav. ويمكن للمستخدم إنشاء مشروع Audacity واستيراد صوت من ملف .wav وخلط عدد من المسارات مع إنشاء ملفات كتل لمعلومات الملخص فقط. وهذا يوفّر مساحة القرص ووقت نسخ الصوت. وعلى إجمالي الأمر، فهو فكرة سيئة إلى حد كبير. فالعدد الكبير جدًا من مستخدمينا حذف ملف الصوت الأصلي .wav ظنًّا أن هناك نسخة كاملة في مجلد مشروع Audacity. وليس الأمر كذلك، وبدون ملف .wav الأصلي لا يمكن تشغيل المشروع الصوتي بعد ذلك. والإعداد الافتراضي في Audacity اليوم هو نسخ الصوت المستورد دائمًا، مع إنشاء ملفات كتل جديدة في العملية.
واجه حل ملفات الكتل مشكلات على أنظمة ويندوز، حيث كان وجود عدد كبير من ملفات الكتل يعمل أداءً سيئًا جدًا. ويبدو أن السبب هو أن ويندوز كان أبطأ بكثير في معالجة الملفات عندما يكون عدد كبير منها في المجلد نفسه، وهي مشكلة مماثلة للتباطؤ مع أعداد كبيرة من عناصر الواجهة. وقد أُضيف لاحقًا استخدام تسلسل هرمي من المجلدات الفرعية، بحيث لا يوجد في أي مجلد فرعي أكثر من مئة ملف إطلاقًا.
المشكلة الرئيسية في بنية ملفات الكتل أنها معروضة للمستخدم النهائي. فنسمع كثيرًا من المستخدمين الذين ينقلون ملف .aup ولا يدركون أنهم يحتاجون أيضًا إلى نقل المجلد الذي يحتوي جميع ملفات الكتل. وكان من الأفضل لو كانت مشاريع Audacity ملفًا واحدًا تتولى Audacity فيه مسؤولية كيفية استخدام المساحة داخل الملف. بل إن هذا من شأنه أن يزيد الأداء لا أن ينقصه. وستكون الشيفرة الإضافية الرئيسية المطلوبة لجمع القمامة (garbage collection). وكان نهجًا بسيطًا لذلك هو نسخ الكتل إلى ملف جديد عند الحفظ إذا كانت نسبة غير مستخدمة من الملف تتجاوز حدًا مقررًا.
2.7. البرمجة النصية (Scripting)
يمتلك Audacity إضافة تجريبية تدعم عدة لغات برمجة نصية. وهي توفّر واجهة برمجة نصية عبر أنبوب مسمّى (named pipe). والأوامر المتاحة عبر البرمجة النصية بصيغة نصية، وكذلك الردود. ما دام بإمكان لغة البرمجة النصية للمستخدم الكتابة إلى أنبوب مسمّى والقراءة منه، فيمكن للغة البرمجة النصية أن تتحكّم في Audacity. ولا تحتاج بيانات الصوت وغيرها من البيانات كثيرة الحجم إلى المرور عبر الأنبوب (الشكل 2.6).

الشكل 2.6: إضافة البرمجة النصية توفّر البرمجة النصية عبر أنبوب مسمّى
لا تعرف الإضافة نفسها شيئًا عن محتوى حركة المرور النصية التي تحملها، وإنما تقتصر على نقله. وواجهة الإضافات (أو نقطة الامتداد البدائية) التي تستخدمها إضافة البرمجة النصية للتوصيل بـ Audacity تكشف بالفعل أوامر Audacity بصيغة نصية. ولذلك فإن إضافة البرمجة النصية صغيرة، ومحتواها الرئيسي شيفرة الأنبوب.
للأسف، يقدّم الأنبوب مخاطر أمنية مشابهة لما يقدّمه وجود اتصال TCP/IP—We've ruled out اتصالات TCP/IP في Audacity لأسباب أمنية. ولتقليل تلك المخاطر تكون الإضافة اختيارية وتُوزَّع على صورة DLL. عليك أن تتخذ قرارًا متعمَّدًا للحصول عليها واستخدامها، وهي تأتي مصحوبة بتحذير صحي وأمني.
بعد أن بدأت ميزة البرمجة النصية بالفعل، ظهر في صفحة طلبات الميزات في ويكي لدينا اقتراح بأن نُفكّر في استخدام معيار D-Bus الخاص بـ KDE لتوفير آلية استدعاء بين العمليات عبر TCP/IP. وكنا قد بدأنا بالفعل في سلوك مسار مختلف، لكنه قد يكون من المعقول تكييف الواجهة التي انتهينا إليها لدعم D-Bus.
أصول شيفرة البرمجة النصية
نشأت ميزة البرمجة النصية من تكييف قام به أحد الشغوفين في Audacity لغرض معين، وكان ذلك التكييف يميل في اتجاه أن يصبح تفريعًا (fork) للمشروع. وتُسمّى هذه الميزات مجتمعةً CleanSpeech، وهي توفّر تحويل خطب وعظات إلى mp3. وتضيف CleanSpeech تأثيرات جديدة مثل اقتطاع الصمت (truncate silence)—وهي ميزة تجد الصمتات الطويلة في الصوت وتقطعها—بالإضافة إلى القدرة على تطبيق تسلسل ثابت من تأثيرات إزالة الضوضاء والتطبيع (normalization) وتحويل mp3 الحالية على دفعة كاملة من التسجيلات الصوتية. أردنا بعض هذه الوظائف الممتازة، لكن طريقة كتابتها كانت خاصة جدًا بـ Audacity. وإحضارها إلى Audacity الأساسي قادنا إلى كتابة شيفرة لتسلسل مرن بدلًا من تسلسل ثابت. ويمكن للتسلسل المرن استخدام أي من التأثيرات عبر جدول بحث (look-up table) لأسماء الأوامر، وصنف Shuttle لحفظ معاملات الأوامر بصيغة نصية في تفضيلات المستخدم. وتُسمّى هذه المزايا سلاسل الدفعات (batch chains). وتعمّدنا التوقف عند حد عدم إضافة عبارات شرطية أو حسابات، تفاديًا لاختراع لغة برمجة نصية مرتجلة (ad hoc).
وبالنظر إلى ما تبيّن لاحقًا، كان الجهد الذي بذلناه لتفادي التفريع (fork) قد استحق العناء. فهناك حتى اليوم وضع CleanSpeech مدفون داخل Audacity يمكن تفعيله بتعديل تفضيل ما. وهو أيضًا يقلّص واجهة المستخدم بإزالة الميزات المتقدمة. وقد طُلبت نسخة مبسّطة من Audacity لاستخدامات أخرى، وعلى رأسها المدارس. والمشكلة أن رؤية كل شخص للميزات المتقدمة مقابل الجوهرية تختلف. وقد نفّذنا لاحقًا حيلة بسيطة تستثمر آلية التعريب. فإذا بدأت ترجمة عنصر قائمة بعلامة «#»، لم يعد يظهر في القوائم. وبذلك يستطيع من يريد تقليل القوائم أن يختار بنفسه دون إعادة تصريف—وهي طريقة أكثر عمومية وأقل تدخلًا من العَلَم mCleanspeech في Audacity، الذي قد نتمكن مع الوقت من إزالته كليًا.
أعطانا عمل CleanSpeech سلاسل الدفعات والقدرة على اقتطاع الصمت. وقد جذب كلاهما تحسينات إضافية من خارج الفريق الأساسي. وقد أدت سلاسل الدفعات مباشرةً إلى ميزة البرمجة النصية. وبدورها، بدأت هذه الميزة عملية دعم إضافات أكثر عمومية الغرض لتكييف Audacity.
2.8. التأثيرات الفورية (Real-Time Effects)
لا يملك Audacity تأثيرات فورية، أي تأثيرات صوتية تُحسب عند الحاجة أثناء تشغيل الصوت. وبدلًا من ذلك، تطبّق تأثيرًا في Audacity ثم عليك الانتظار حتى يكتمل. وتُعدّ التأثيرات الفورية، وعمليات عرض تأثيرات الصوت في الخلفية بينما تبقى واجهة المستخدم سريعة الاستجابة، من أكثر طلبات الميزات تكرارًا الخاصة بـ Audacity.
إحدى مشكلاتنا أن ما قد يكون تأثيرًا فوريًا على جهاز ما قد لا يعمل بالسرعة الكافية ليكون فوريًا على جهاز أبطأ بكثير. ويعمل Audacity على نطاق واسع من الأجهزة. ونودّ أن يكون هناك تراجع رشيق (graceful fallback). فعلى الجهاز الأبطأ نودّ أن نتمكّن من طلب تطبيق تأثير على مسار كامل، ثم الاستماع إلى الصوت المعالَج قرب منتصف المسار بعد انتظار قصير، مع أن يعرف Audacity أن يعالج ذلك الجزء أولًا. أما على الجهاز البطيء جدًا لعرض التأثير في الوقت الفعلي، فيمكننا الاستماع إلى الصوت حتى يلحق التشغيل بالعرض. ولتحقيق ذلك سنحتاج إلى إزالة القيود التي تجعل تأثيرات الصوت تحجز واجهة المستخدم، وتلك التي تجعل ترتيب معالجة كتل الصوت صارمًا من اليسار إلى اليمين.
وتُسمّى إضافة حديثة نسبيًا في Audacity التحميل عند الطلب (on demand loading) وهي تحتوي على كثير من العناصر التي نحتاجها للتأثيرات الفورية، وإن كانت لا تتضمن أي تأثيرات صوتية. فحين تستورد ملفًا صوتيًا إلى Audacity، بات بإمكانه إنشاء ملفات كتل الملخص في مهمة في الخلفية. وسوف يعرض Audacity عنصرًا نائبًا من أشرطة زرقاء ورمادية مائلة للصوت الذي لم يعالجه بعد، ويستجيب للعديد من أوامر المستخدم بينما لا يزال الصوت قيد التحميل. ولا يلزم أن تُعالَج الكتل بترتيب من اليسار إلى اليمين. وقد كان القصد منذ البداية أن تُستخدم الشيفرة ذاتها في حينه للتأثيرات الفورية.
يوفّر التحميل عند الطلب نهجًا تطوريًا لإضافة التأثيرات الفورية. وهذه خطوة تتجنب بعض تعقيدات جعل التأثيرات نفسها فورية. وستحتاج التأثيرات الفورية إضافةً إلى ذلك إلى تداخل (overlap) بين الكتل، وإلا فإن تأثيرات مثل الصدى لن تتّصل على نحو صحيح. وسنحتاج أيضًا إلى السماح بأن تتغيّر المعاملات أثناء تشغيل الصوت. وبإجراء التحميل عند الطلب أولًا، تُستخدم الشيفرة في مرحلة أبكر مما كانت ستُستخدم فيه لولا ذلك. وستستفيد من الملاحظات والتحسينات الناتجة عن الاستخدام الفعلي.
2.9. الخلاصة
توضّح الأقسام السابقة من هذا الفصل كيف تسهم البنية الجيدة في نمو البرنامج، أو كيف يعيقها غياب البنية الجيدة.
- الواجهات البرمجية لطرف ثالث مثل PortAudio وwxWidgets كانت ذات فائدة هائلة. فقد أعطتنا شيفرة صالحة للبناء عليها، وأخفت فروقًا كثيرة بين المنصات. وأحد الثمن الذي ندفعه مقابل استخدامها أننا لا نحصل على مرونة اختيار التجريدات. فشيفرة التشغيل والتسجيل لدينا أقل مما ينبغي من الجمال لأن علينا معالجة الخيوط بثلاث طرق مختلفة. كما أن الشيفرة تنسخ بيانات أكثر مما كان يمكن أن تنسخه لو كنا نتحكم في التجريدات.
- الواجهة البرمجية التي وفّرتها wxWidgets أغرتنا بكتابة شيفرة تطبيق مطوّلة يصعب تتبّعها. وكان حلولنا لذلك إضافة واجهة أمامية (facade) أمام wxWidgets لتمنحنا التجريدات التي نريدها وشيفرة تطبيق أنظف.
- في TrackPanel الخاص بـ Audacity احتجنا إلى الخروج عن الميزات التي يمكن بسهولة الحصول عليها من عناصر الواجهة القائمة. ونتيجة لذلك بنينا نظامنا المرتجل (ad hoc) بأنفسنا. وهناك نظام أنظف يتكون من عناصر واجهة ومنظِّمات أحجام وكائنات على مستوى التطبيق متباينة منطقيًا يكافح للخروج من TrackPanel.
- القرارات البنيوية أوسع نطاقًا من مجرد اتخاذ قرار بكيفية بناء الميزات الجديدة. فقرار بشأن ما لا ينبغي تضمينه في برنامج قد لا يقل أهمية عن غيره. وقد يقود إلى شيفرة أنظف وأكثر أمانًا. ومن دواعي السرور الاستفادة من مزايا لغات البرمجة النصية مثل Perl دون تحمّل عبء صيانة نسختنا الخاصة. كذلك تقود القرارات البنيوية خطط النمو المستقبلي. ويُتوقع أن يقود نظامنا المعياري النشئ إلى مزيد من التجريب بجعل التجريب أكثر أمانًا. ويُتوقع أن يكون التحميل عند الطلب خطوة تطورية نحو المعالجة عند الطلب للتأثيرات الفورية.
كلما نظرت أكثر، ازداد وضوح أن Audacity مجهود مجتمعي. والمجتمع أكبر من مجرد المساهمين مباشرةً، لأنه يعتمد على مكتبات، لكل منها مجتمعها وخبراؤها المتخصصون. وبعد أن قرأت عن مزيج البنية في Audacity، فلا شك أنه ليس مفاجئًا أن المجتمع الذي يطوّره يرحّب بمطوّرين جديدين ويستطيع التعامل جيدًا مع نطاق واسع من مستويات المهارة.
بالنسبة إليّ لا شك في أن طبيعة المجتمع خلف Audacity تنعكس في نقاط القوة والضعف في الشيفرة. فالمجموعة الأكثر انغلاقًا كانت تستطيع كتابة شيفرة عالية الجودة باتساق أكبر مما فعلنا، لكنه سيكون أصعب أن نطابق نطاق القدرات التي يتمتع بها Audacity بعدد أقل من المساهمين.
الحواشي
- الاستثناء الوحيد لذلك هو لغة Nyquist القائمة على Lisp، وهي مدمجة في Audacity منذ أيامه الأولى. ونودّ جعلها وحدة منفصلة مرفقة مع Audacity، لكننا لم نتّسع الوقت بعد لإجراء هذا التغيير.
الاستثناء الوحيد لذلك هو لغة Nyquist القائمة على Lisp، وهي مدمجة في Audacity منذ أيامه الأولى. ونودّ جعلها وحدة منفصلة مرفقة مع Audacity، لكننا لم نتّسع الوقت بعد لإجراء هذا التغيير. ↩︎