إعادة هيكلة C++ في OpenBMC: كيف صارع الذكاء الاصطناعي دالة من 280 سطرًا

تجمع دالة createSensors من مشروع OpenBMC (البرنامج الثابت المفتوح المصدر لوحدات إدارة لوحة الخوادم BMC) 280 سطرًا من الكود وتنفّذ 12 مهمة مختلفة: تستقبل التكوين من entity-manager بشكل غير متزامن في خيط تنفيذ واحد، تبحث عن تعريفات I2C عبر sysfs، تتحقق من وجود الأجهزة على الناقل، تطابقها مع التكوين، تنشئ وتسجّل كائنات HwmonTempSensor على D-Bus، تتحكم في عتبات التنبيه، وتردد الاستقصاء، وحالة الطاقة، ومنطق تعدد المستشعرات على جهاز واحد، وتدعم المسح الكامل عند الإقلاع والتحديث الجزئي للتكوين. اقترح وكيل الذكاء الاصطناعي تفكيك الدالة إلى 7 خطوات محددة: استخلاص صنف (class) باسم SensorScanner يضم طرق buildConfigMap و findSensorPaths و ensureDevice و processPath و matchConfig؛ إزالة التكرار اللزج (copy-paste) عند إنشاء المستشعرات (60 سطرًا من التكرار)؛ نقل الأسماء إلى بنى مطابقة (matching structs)؛ فصل منطق المسح عن منطق التحديث؛ عزل التأثيرات الجانبية (side effects)؛ واستبدال التداخل العميق بعبارات continue مبكرة. • **نتيجة إعادة الهيكلة**: تلاشى التداخل (6-7 مستويات من if/for)، وانخفض حجم الدوال إلى 20-30 سطرًا، وظهرت بنية واضحة. لكن git-diff أظهر أمرًا غير متوقع: أصبح الكود الأخضر (الجديد) أكثر من الأحمر (المحذوف). أُضيفت توقيعات (signatures) لـ 10 طرق، وبنى وأصناف جديدة، عوّضت عن 60 سطرًا من التكرار الموفّرة. • **المصيبة البنيوية**: لا تقرأ خدمات OpenBMC المصغّرة ملفات التكوين مباشرة — بل تقوم بذلك خدمة مركزية هي entity-manager، التي تنشر البيانات على D-Bus على دفعات. لا تعرف خدمتنا متى يكون التكوين جاهزًا (لا توجد معاملات)، لذلك يُستخدم زمن انتظار مدته ثانية واحدة (filterTimer.expires_after(std::chrono::seconds(1))). وتوجد أعلام firstScan و sensorsChanged و activateOnly في createSensors لهذا السبب تمامًا: لتتبّع التحديثات الجزئية.

معالج بواسطة الذكاء الاصطناعي من Habr AI؛ بتحرير Hamidun News
إعادة هيكلة C++ في OpenBMC: كيف صارع الذكاء الاصطناعي دالة من 280 سطرًا
المصدر: Habr AI. كولاج: Hamidun News.
استمع للمقال

ما الذي يستطيع وكيل الذكاء الاصطناعي فعله عند إعادة الهيكلة

حلّل مطوّر خدمة HwmonTempSensor المصغّرة من OpenBMC — مشروع البرنامج الثابت المفتوح المصدر لوحدات إدارة الخوادم المستندة إلى Linux. تتمركز الخدمة بأكملها حول دالة واحدة اسمها createSensors، تجمع قراءات من مستشعرات الأجهزة (الحرارة، الضغط، الرطوبة، التيار، الجهد) وتنشرها عبر ناقل D-Bus لخدمات أخرى، مثل واجهة الويب.

تنفّذ هذه الدالة المكوّنة من 280 سطرًا 12 مهمة منعزلة: تستقبل التكوين من entity-manager بشكل غير متزامن، لكن في خيط تنفيذ واحد؛ تبحث عن تعريفات I2C عبر sysfs؛ تتحقق من وجود الأجهزة على الناقل؛ تطابق التكوين مع الأجهزة المكتشفة؛ تنشئ أو تنشّط أو تعيد إنشاء كائنات HwmonTempSensor؛ تسجّلها على D-Bus؛ تتحكم في عتبات التنبيه؛ تتحكم في تردد الاستقصاء؛ تتحكم في حالة الطاقة؛ تطبّق منطق عمل عدة مستشعرات على جهاز I2C واحد؛ تدعم المسح الكامل الأولي عند الإقلاع؛ تعالج التحديث الجزئي عند تغيّر التكوين في وقت التشغيل.

يبلغ تداخل الحلقات والعبارات الشرطية 6-7 مستويات (for ← for/if ← if ← if ← if). وعندما طلب المطوّر من الذكاء الاصطناعي تحليل المشكلات، قدّم الوكيل تشخيصًا سريعًا: إفراط في تحميل الدالة بالمهام، ومنطق متشابك (spaghetti) من عمليات غير متجانسة (بحث، مطابقة، إنشاء، تصفية)، واستحالة كاملة لاختبارها.

7 خطوات محددة لإعادة الهيكلة

اقترح الوكيل استخلاص صنف SensorScanner، بحيث تصبح كل خطوة طريقة خاصة (private method) من 20-30 سطرًا:

الخطوة 1: إنشاء بنية (struct) باسم SensorScanner بمُنشئ (constructor) يستقبل المعاملات io و objectServer و sensors و dbusConnection و sensorsChanged و activateOnly؛ وطريقة عامة واحدة اسمها scan كنقطة دخول وحيدة لمعالجة التكوين.

الخطوة 2: تقسيم المنطق إلى طرق خاصة: buildConfigMap (تحليل التكوين)، و findSensorPaths (البحث عن مسارات sysfs)، و ensureDevice (إنشاء مثيلات أجهزة I2C)، و processPath (معالجة مسار sysfs واحد ضمن الحلقة)، و matchConfig (مطابقة المسار مع التكوين).

الخطوة 3: إزالة التكرار عند إنشاء المستشعرات. في الكود الأصلي، يتكرر منطق إنشاء HwmonTempSensor مرتين (للمستشعر الأساسي والمستشعرات الإضافية). يُنقل هذا إلى طريقة createOrActivateSensor بمعاملات مثل hwmonFile و i2cDev وغيرها. وفّر هذا 60 سطرًا من التكرار اللزج.

الخطوات 4-7: نقل الأسماء Name1..N إلى بنى مطابقة منفصلة؛ فصل منطق المسح الكامل عن منطق تحديث التكوين؛ عزل جميع التأثيرات الجانبية في طرق منفصلة؛ استبدال التداخل العميق لعبارات if بعبارات continue مبكرة.

مفارقة النتيجة: إعادة الهيكلة جعلت الكود أجمل، لكن ليس أقصر

بعد تطبيق المقترحات، صارت دالة createSensors سطرًا واحدًا:

```cpp void createSensors(...) { auto scanner = std::make_shared<SensorScanner>(...); scanner->run(); } ```

تلاشى التداخل، وصغُرت الطرق، وصارت البنى واضحة. وفي مقابلات العمل، يُطلب تحديدًا هذا النوع من الكود: C++20، القوالب (templates)، RAII، والربط البنيوي (structured binding).

لكن git-diff أظهر أمرًا غير متوقع: صار الكود الأخضر (السطور الجديدة) أكثر بوضوح من الأحمر (المحذوف). ازداد حجم الكود الإجمالي، مع أن التكرار اللزج تم رصده واستئصاله. السبب؟ تتطلب 10 طرق جديدة 10 توقيعات؛ وبنى وأصناف جديدة (لتجنّب تمرير نحو عشرة معاملات بالمرجع)؛ وتعريفات لعزل المنطق. لم يكن الكود المرعب سوى كود صار منظمًا بأسلوب عصري.

الاكتشاف الأساسي: لا يمكن إعادة هيكلة دالة واحدة بمفردها

وهنا تبدأ المعاناة الحقيقية. فظاعة الدالة المحددة ليست خطأ مبرمج متعب، ولا عطلًا عشوائيًا. إنها نتيجة مباشرة وحتمية لتهافت الأفكار البنيوية للمشروع بأكمله.

تفتخر OpenBMC بميزاتها الأساسية: قابلية إعادة التكوين وقت التشغيل من D-Bus؛ العزل: يُعزل كل نوع من المستشعرات في عملية خلفية (daemon) خاصة به؛ غير متزامن وخيط تنفيذ واحد: يستخدم روابط sdbusplus/asio. يبدو ذلك حديثًا وقابلًا للتوسّع.

لكن الواقع مختلف. فبدلًا من خوارزمية بسيطة (اقرأ ملف تكوين JSON ← اعثر على الأجهزة ← اعمل)، قسّم المؤلفون عملية غير قابلة للتجزئة إلى خدمات مصغّرة. لا تقرأ خدمات المستشعرات المنفصلة ملفات التكوين JSON مباشرة — بل تقوم بذلك خدمة مركزية هي entity-manager. تحلّل هذه الخدمة جميع ملفات التكوين وتنشر البيانات على D-Bus... تدريجيًا! وتُضطر جميع الخدمات المصغّرة إلى الانتظار حتى يُنشر الجزء الذي تحتاجه من التكوين.

المشكلة الرئيسية: لا ضمان للذرية (atomicity). لا تعرف خدمتنا متى سينهي entity-manager بثّ البيانات لجميع الأجهزة في النظام. لم يسمع مطوّرو D-Bus ومفاهيم الخدمات المصغّرة في الأنظمة المدمجة (embedded) قط بالمعاملات (transactions).

الحل: زمن انتظار تقديري. بينما يرسل entity-manager أجزاء التكوين، يُعيد الفلتر ضبط العدّاد باستمرار. تُحدّد مهلة بمقدار ثانية واحدة (filterTimer.expires_after(std::chrono::seconds(1))). وإذا لم تصل بيانات جديدة طوال ثانية كاملة — نعتبر أن التكوين جاهز، ونستدعي createSensors.

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

الخلاصة: هذا جهد عقيم يشبه عقاب سيزيف. لا يمكن إعادة هيكلة دالة C++ واحدة، بصرف النظر عن مدى فظاعتها، وتوقّع أن ذلك سينقذ المشروع. الكود هنا ليس السبب الجذري، بل هو مجرد تطبيق لمفاهيم تجريدية جلبها أحدهم إلى الأنظمة المدمجة دون التفكير في التفاصيل.

أسئلة وأجوبة حول الخبر

ما أبرز التفاصيل حول ما الذي يستطيع وكيل الذكاء الاصطناعي فعله عند إعادة الهيكلة؟

حلّل مطوّر خدمة HwmonTempSensor المصغّرة من OpenBMC — مشروع البرنامج الثابت المفتوح المصدر لوحدات إدارة الخوادم المستندة إلى Linux. تتمركز الخدمة بأكملها حول دالة واحدة اسمها createSensors، تجمع قراءات من مستشعرات الأجهزة (الحرارة، الضغط، الرطوبة، التيار، الجهد) وتنشرها عبر ناقل D-Bus لخدمات أخرى، مثل واجهة الويب. تنفّذ هذه الدالة المكوّنة من 280 سطرًا 12 مهمة منعزلة: تستقبل التكوين من entity-manager بشكل غير متزامن، لكن في خيط تنفيذ واحد؛ تبحث عن تعريفات I2C عبر sysfs؛ تتحقق من وجود الأجهزة على الناقل؛ تطابق التكوين مع الأجهزة المكتشفة؛ تنشئ أو تنشّط أو تعيد إنشاء كائنات H

ما أبرز التفاصيل حول 7 خطوات محددة لإعادة الهيكلة؟

اقترح الوكيل استخلاص صنف SensorScanner، بحيث تصبح كل خطوة طريقة خاصة (private method) من 20-30 سطرًا: الخطوة 1: إنشاء بنية (struct) باسم SensorScanner بمُنشئ (constructor) يستقبل المعاملات io و objectServer و sensors و dbusConnection و sensorsChanged و activateOnly؛ وطريقة عامة واحدة اسمها scan كنقطة دخول وحيدة لمعالجة التكوين. الخطوة 2: تقسيم المنطق إلى طرق خاصة: buildConfigMap (تحليل التكوين)، و findSensorPaths (البحث عن مسارات sysfs)، و ensureDevice (إنشاء مثيلات أجهزة I2C)، و processPath (معالجة مسار sysfs واحد ضمن الحلقة)، و matchConfig (مطابقة المسار مع التكوين). الخط

ما أبرز التفاصيل حول مفارقة النتيجة: إعادة الهيكلة جعلت الكود أجمل، لكن ليس أقصر؟

بعد تطبيق المقترحات، صارت دالة createSensors سطرًا واحدًا: ```cpp void createSensors(...) { auto scanner = std::make_shared<SensorScanner>(...); scanner->run(); } ``` تلاشى التداخل، وصغُرت الطرق، وصارت البنى واضحة. وفي مقابلات العمل، يُطلب تحديدًا هذا النوع من الكود: C++20، القوالب (templates)، RAII، والربط البنيوي (structured binding). لكن git-diff أظهر أمرًا غير متوقع: صار الكود الأخضر (السطور الجديدة) أكثر بوضوح من الأحمر (المحذوف). ازداد حجم الكود الإجمالي، مع أن التكرار اللزج تم رصده واستئصاله. السبب؟ تتطلب 10 طرق جديدة 10 توقيعات؛ وبنى وأصناف جديدة (لتجنّب تمرير نحو ع

ZK
جمال حميدون — مدير المنتجات في Alpina Digital ومدير منتج AlpinaGPT. يبني منتجات الذكاء الاصطناعي منذ 2019. بدأ Hamidun News كمشروع شخصي ثم أصبح مُجمِّعًا إنتاجيًا بأكثر من 6000 قارئ.
✦ Hands-On AI Engineering · Hamidun Academy

Build & Ship Production AI Agents Without Boilerplate Code

Start your free 72-hour trial at Hamidun Academy (academy.hamidun.com) — interactive lessons on Claude Code & Cursor, live Tech Radar, and production agent templates.

Start 72h Free Trial →Vibe-Coding Intensive
ما رأيك؟
جارٍ تحميل التعليقات…