اختبرنا نماذج الذكاء الاصطناعي في كتابة كود معقد من مشاريع حقيقية، والنتيجة صادمة: الكود يُكتب بسرعة لكنه مليء بالثغرات الخفية التي قد تكلفك أياماً من التصحيح. إليك ما لا يخبرك به أحد عن قدرات الذكاء الاصطناعي البرمجية.
في آخر ثلاثة أشهر، كتبت نماذج الذكاء الاصطناعي أكثر من 40 ألف سطر كود لي في مشاريع حقيقية. بعضها كان مذهلاً: حلول ذكية لعمليات I/O معقدة، تحسينات على الـ Event Loop في Node.js، وحتى إعادة هيكلة كاملة لمكتبة React باستخدام Hooks. لكن في نفس الوقت، وجدت نفسي أقضي ساعات في تصحيح أخطاء سخيفة مثل Memory Leaks في كود بايثون كتبه Copilot، أو دوائر لا نهائية في JavaScript بسبب سوء فهم النموذج للسياق. السؤال الذي يطرح نفسه: هل الذكاء الاصطناعي يكتب كوداً جيداً فعلاً، أم أننا ننخدع بالسرعة على حساب الجودة؟
الحقيقة المؤلمة هي أن معظم المطورين يقيسون جودة الكود الناتج من الذكاء الاصطناعي بمعيار واحد فقط: هل يعمل الكود عند تشغيله لأول مرة؟ هذا المعيار خاطئ تماماً. الكود الجيد لا يُقاس فقط بكونه يعمل، بل بكونه قابلاً للصيانة، فعالاً في استخدام الموارد، ومقاوماً للأخطاء. عندما طلبت من Claude 3 كتابة دالة لمعالجة ملفات CSV ضخمة، أعطاني حلاً يستخدم 4 جيجابايت من الذاكرة عند معالجة ملف بحجم 500 ميجابايت. نعم، الكود يعمل، لكنه كارثة في بيئة إنتاجية. هذا هو الفرق بين الكود الذي يكتبه الذكاء الاصطناعي والكود الذي يكتبه مهندس متمرس: الأول يفكر في النتيجة الفورية، والثاني يفكر في كل السيناريوهات الممكنة.
لفهم قدرات الذكاء الاصطناعي في كتابة الكود، يجب أن نفهم أولاً كيف يعمل خلف الكواليس. عندما تطلب من نموذج مثل GPT-4 كتابة دالة، فهو لا يفهم البرمجة بالمعنى الذي نفهمه نحن. بدلاً من ذلك، يستخدم ما يُسمى بنماذج اللغة الكبيرة (LLMs) للتنبؤ بالنص التالي بناءً على السياق الذي قدمته له. هذه النماذج تدربت على مليارات الأسطر من الكود المفتوح المصدر، لكنها لا تملك أي فهم حقيقي للمفاهيم الأساسية مثل الـ Memory Management أو الـ Time Complexity.
على سبيل المثال، عندما طلبت من GitHub Copilot كتابة دالة لحساب الأعداد الأولية باستخدام خوارزمية غربال إراتوستينس، أعطاني حلاً يبدو صحيحاً للوهلة الأولى. لكن عند فحص الكود عن كثب، وجدت أنه يستخدم مصفوفة بحجم n بدلاً من n/2، مما يضيع نصف الذاكرة دون داعٍ. المشكلة ليست في أن النموذج لا يعرف الخوارزمية، بل في أنه لا يفهم لماذا نستخدم n/2 بدلاً من n. هذا النوع من الأخطاء الدقيقة هو ما يجعل الكود الناتج من الذكاء الاصطناعي غير موثوق به في المشاريع الكبيرة.
# مثال على كود غير محسن كتبه Copilot
def sieve_of_eratosthenes(n):
sieve = [True] * n # هنا المشكلة: نستخدم n بدلاً من n+1 أو n//2
sieve[0] = sieve[1] = False
for i in range(2, int(n ** 0.5) + 1):
if sieve[i]:
sieve[i*i : n : i] = [False] * len(sieve[i*i : n : i])
return [i for i, is_prime in enumerate(sieve) if is_prime]
# الحل المحسن الذي يكتبه مهندس متمرس
def optimized_sieve(n):
if n < 2:
return []
sieve = [True] * ((n + 1) // 2) # نستخدم نصف الحجم فقط
sieve[0] = False
for i in range(3, int(n ** 0.5) + 1, 2):
if sieve[i // 2]:
sieve[i*i // 2 :: i] = [False] * len(sieve[i*i // 2 :: i])
return [2] + [2*i + 1 for i, is_prime in enumerate(sieve[1:]) if is_prime]الفرق بين الكودين ليس مجرد مسألة جماليات. في الكود الأول، إذا حاولنا حساب الأعداد الأولية حتى 10 ملايين، سنحتاج إلى 10 ملايين بايت من الذاكرة، بينما الكود الثاني يحتاج إلى 5 ملايين بايت فقط. في بيئة إنتاجية حيث كل ميجابايت مهمة، هذا الفرق يمكن أن يكون حاسماً. المشكلة الأكبر هي أن هذه الأخطاء لا تظهر إلا عند اختبار الكود تحت ضغط حقيقي، وهو ما لا يفعله معظم المطورين عند استخدام الذكاء الاصطناعي.
لاختبار قدرات الذكاء الاصطناعي بشكل عملي، قمت بتصميم سلسلة من المهام البرمجية تتراوح بين البسيطة والمعقدة، وطلبت من عدة نماذج (GPT-4, Claude 3, GitHub Copilot) حلها. النتائج كانت مفاجئة ومخيفة في نفس الوقت. في المهام البسيطة مثل كتابة دالة لحساب المتوسط أو عكس سلسلة نصية، كانت النتائج مثالية تقريباً. لكن عندما انتقلت إلى المهام المعقدة التي تتطلب فهم السياق أو التعامل مع حالات حافة، بدأت المشاكل تظهر.
إحدى المهام كانت كتابة نظام لإدارة المهام (Task Management System) باستخدام Node.js وExpress، مع متطلبات محددة: يجب أن يدعم النظام الـ Rate Limiting، التخزين المؤقت باستخدام Redis، والتعامل مع الأخطاء بطريقة احترافية. طلبت من النماذج الثلاثة كتابة الكود، ثم قمت بمراجعته واختباره تحت ضغط. النتائج كانت كالتالي:
// مثال على خطأ شائع في الكود الذي يكتبه الذكاء الاصطناعي
// هذا الكود يبدو صحيحاً، لكنه يسبب Memory Leak بسبب عدم إغلاق الـ Event Emitter
const EventEmitter = require('events');
const express = require('express');
const app = express();
const emitter = new EventEmitter();
app.get('/leak', (req, res) => {
emitter.on('data', (data) => {
res.write(data);
});
// لا يوجد emitter.off أو إغلاق للـ Event بعد الانتهاء
res.end();
});
app.listen(3000);
// الحل الصحيح الذي يكتبه مهندس متمرس
app.get('/no-leak', (req, res) => {
const listener = (data) => {
res.write(data);
};
emitter.on('data', listener);
res.on('finish', () => {
emitter.off('data', listener); // إغلاق الـ Event بعد الانتهاء
});
res.end();
});المشكلة الأكبر التي واجهتها في هذه التجربة هي أن الكود الناتج من الذكاء الاصطناعي غالباً ما يكون خالياً من التعليقات المفيدة أو التوثيق. في مشروع حقيقي، هذا يعني أن أي مطور آخر سيضطر لقضاء ساعات في فهم الكود قبل أن يتمكن من تعديله أو إصلاحه. في إحدى المرات، اضطررت لإعادة كتابة 300 سطر من كود بايثون كتبه Copilot لأنني ببساطة لم أتمكن من فهم ما يفعله الكود دون تعليقات أو توثيق مناسب.
أحد أكبر الأخطاء التي يقع فيها المطورون عند استخدام الذكاء الاصطناعي هو الثقة الزائدة في الكود الناتج دون اختباره تحت ظروف حقيقية. في إحدى المشاريع التي عملت عليها، استخدمنا GitHub Copilot لكتابة جزء من نظام معالجة المدفوعات. الكود بدا صحيحاً، واجتاز جميع الاختبارات المحلية، وتم نشره في بيئة التطوير. لكن عندما انتقلنا إلى بيئة الإنتاج وبدأنا في استقبال آلاف الطلبات في الثانية، بدأ السيرفر في التعطل بشكل عشوائي.
بعد يوم كامل من تصحيح الأخطاء، اكتشفنا أن المشكلة كانت في طريقة تعامل الكود مع الـ Database Connections. الكود الذي كتبه Copilot كان يفتح اتصالاً جديداً مع قاعدة البيانات في كل طلب، لكنه لم يغلق هذه الاتصالات بشكل صحيح. في بيئة التطوير حيث عدد الطلبات قليل، لم تكن هذه مشكلة. لكن في الإنتاج، أدى هذا إلى استنفاد جميع الـ Database Connections المتاحة، مما تسبب في فشل النظام. هذه المشكلة تحديداً تكلفتنا أكثر من 10 آلاف دولار في خسائر بسبب توقف الخدمة.
// مثال على كود يسبب Database Connection Leak
// هذا الكود يبدو صحيحاً لكنه كارثة في الإنتاج
const { Pool } = require('pg');
const pool = new Pool();
app.get('/leak', async (req, res) => {
const client = await pool.connect(); // فتح اتصال جديد
try {
const result = await client.query('SELECT * FROM users');
res.json(result.rows);
} finally {
// لا يوجد client.release() هنا!
}
});
// الحل الصحيح الذي يكتبه مهندس متمرس
app.get('/no-leak', async (req, res) => {
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM users');
res.json(result.rows);
} finally {
client.release(); // إغلاق الاتصال بشكل صحيح
}
});هذه ليست مشكلة بسيطة يمكن تجاهلها. في بيئة الإنتاج، كل اتصال قاعدة بيانات غير مغلق هو اتصال مهدور يمكن أن يؤدي إلى فشل النظام بأكمله. المشكلة أن معظم المطورين لا يفكرون في هذه السيناريوهات عند استخدام الذكاء الاصطناعي، لأنهم يفترضون أن الكود الناتج سيكون صحيحاً طالما أنه يعمل على أجهزتهم المحلية.
بعد كل هذه التجارب السلبية، قد تعتقد أنني ضد استخدام الذكاء الاصطناعي في البرمجة. الحقيقة هي أنني أستخدمه يومياً، لكن بطرق محددة جداً حيث يكون مفيداً حقاً. الذكاء الاصطناعي ممتاز في المهام الروتينية التي لا تتطلب تفكيراً عميقاً، مثل كتابة الـ Boilerplate Code أو توليد البيانات الوهمية أو كتابة الاختبارات البسيطة. كما أنه مفيد جداً في تعلم لغات أو مكتبات جديدة، حيث يمكنه توليد أمثلة سريعة تساعد في فهم المفاهيم.
على سبيل المثال، عندما كنت أتعلم Rust لأول مرة، استخدمت GitHub Copilot لتوليد أمثلة على كيفية استخدام الـ Ownership System و الـ Borrowing. هذه الأمثلة كانت مفيدة جداً في فهم المفاهيم المعقدة التي يصعب استيعابها من القراءة فقط. لكن عندما حاولت استخدام Copilot لكتابة مشروع كامل في Rust، انتهى بي الأمر بقضاء وقت أطول في تصحيح الأخطاء مما لو كنت قد كتبت الكود بنفسي من البداية.
إذا قررت استخدام الذكاء الاصطناعي في مشاريعك، فهناك بعض القواعد التي يجب اتباعها لتجنب المشاكل. أولاً، تعامل مع الكود الناتج من الذكاء الاصطناعي كما تتعامل مع الكود الذي كتبه مطور مبتدئ: افترض أنه يحتوي على أخطاء حتى تثبت العكس. ثانياً، لا تستخدم الذكاء الاصطناعي لكتابة الكود المعقد أو الحرج، بل استخدمه للمهام البسيطة والروتينية. ثالثاً، دائماً اختبر الكود الناتج تحت ظروف واقعية قبل نشره في الإنتاج.
إحدى الاستراتيجيات الفعالة هي استخدام الذكاء الاصطناعي كـ Pair Programmer. بدلاً من طلب كتابة كود كامل، اطلب منه اقتراحات أو مراجعة الكود الذي كتبته أنت. على سبيل المثال، يمكنك كتابة دالة بنفسك ثم طلب من الذكاء الاصطناعي تحسينها أو إضافة اختبارات لها. بهذه الطريقة، تستفيد من قدرات الذكاء الاصطناعي دون الاعتماد عليه بشكل كامل.
# مثال على كيفية استخدام الذكاء الاصطناعي لتحسين الكود
# بدلاً من طلب كتابة الكود من الصفر، اطلب تحسين كود موجود
# الكود الأصلي الذي كتبته بنفسي
def calculate_discount(price, discount_percent):
if discount_percent > 100:
discount_percent = 100
elif discount_percent < 0:
discount_percent = 0
return price * (1 - discount_percent / 100)
# طلبت من Claude تحسين هذا الكود، وأعطاني هذا الاقتراح
def calculate_discount_optimized(price: float, discount_percent: float) -> float:
"""
Calculate the final price after applying a discount percentage.
Args:
price: The original price (must be positive).
discount_percent: The discount percentage (clamped between 0 and 100).
Returns:
The final price after discount.
Raises:
ValueError: If price is negative.
"""
if price < 0:
raise ValueError("Price cannot be negative")
clamped_discount = max(0, min(100, discount_percent))
return price * (1 - clamped_discount / 100)الفرق بين الكودين واضح. الكود الأول بسيط ويعمل، لكنه يفتقر إلى التوثيق والتعامل مع الأخطاء بشكل صحيح. الكود الثاني أضاف Type Hints، توثيق مفصل، والتعامل مع حالة السعر السلبي التي نسيتها في الكود الأول. هذه هي الطريقة الصحيحة لاستخدام الذكاء الاصطناعي: كمساعد لتحسين الكود بدلاً من بديل لكتابة الكود من الصفر.
بعد أشهر من التجارب والاختبارات، توصلت إلى استنتاج صادم: الذكاء الاصطناعي لا يكتب كوداً جيداً، لكنه يكتب كوداً سريعاً. الفرق بين الاثنين هو الفرق بين بناء منزل على أسس متينة وبين بناء كوخ من الورق المقوى. نعم، الكوخ قد يقف لبعض الوقت، لكنه سينهار عند أول عاصفة. نفس الشيء ينطبق على الكود الذي يكتبه الذكاء الاصطناعي: قد يعمل لبعض الوقت، لكنه سيفشل عندما تواجهه ظروف حقيقية في الإنتاج.
الذكاء الاصطناعي أداة قوية، لكنه ليس بديلاً للمهندس المتمرس. استخدمه للمهام البسيطة والروتينية، لكن عندما يتعلق الأمر بالكود الحرج الذي يؤثر على أعمال حقيقية، لا تثق به أبداً دون مراجعة واختبار دقيقين. في النهاية، البرمجة ليست مجرد كتابة كود يعمل، بل هي فن كتابة كود يمكن الاعتماد عليه في أسوأ الظروف. وهذا شيء لا يستطيع الذكاء الاصطناعي فعله بعد، وربما لن يستطيعه أبداً.
الذكاء الاصطناعي مثل السكين الحاد: يمكن أن يكون أداة مفيدة جداً إذا استخدمته بحذر، لكنه يمكن أن يجرحك بشدة إذا تعاملت معه بلا مبالاة.
— خالد السعداني، مهندس برمجيات سنيور