بياناتنا كانت شبحًا متغيرًا: كيف أنقذتنا ‘الكائنات غير القابلة للتغيير’ (Immutability) من جحيم الآثار الجانبية الخفية؟

يا جماعة الخير، السلام عليكم ورحمة الله. معكم أخوكم أبو عمر.

خلوني أحكي لكم قصة صارت معي ومع فريقي قبل كم سنة، قصة من قصص “ليش الكود ما بشتغل” اللي بتخليك تشد شعرك. كنا شغالين على نظام إدارة محتوى ضخم، فيه صلاحيات ومستخدمين ومحررين، وشغل “مرتب” زي ما بنحكي. المشكلة الكبرى كانت في صفحة تعديل المقالات، كان فيها ميزة الحفظ التلقائي (Auto-save) كل كم ثانية.

اللي كان يصير إنه المحرر بيكتب، والنظام بحفظ، وفجأة، وبدون سابق إنذار، بتلاقي جزء من المقال اختفى، أو صورة رجعت لمكانها القديم، أو تنسيق انضرب. كنا نقضي ساعات وأيام واحنا بنحاول نصيد “البَغْ” (Bug). كل ما نمسك طرف خيط، نلاقيه بيودينا لمكان ثاني. المشكلة كانت زي الشبح، بتظهر وبتختفي، وما إلها نمط واضح. والله يا جماعة، كانت شغلة بتجلط.

في ليلة من الليالي، وأنا قاعد بقلّب في الكود للساعة 3 الفجر ومعي فنجان القهوة الثالث، خطرتلي فكرة. لاحظت إنه كائن المقال (article object) بنمرّره بين 5 أو 6 دوال (functions) مختلفة: دالة للحفظ التلقائي، دالة لرفع الصور، دالة لتدقيق الإملاء، دالة لتحديث عدد الكلمات… وكل دالة بتعدّل على نفس الكائن مباشرة. هون ضربت الإشارة عندي. شو اللي بيضمن إنه دالة منهم ما بتعمل تعديل “بالغلط” أو بتستخدم نسخة قديمة من البيانات؟

كانت هاي هي اللحظة اللي قررنا فيها نتبنى مفهوم الـ Immutability أو “الكائنات غير القابلة للتغيير”. وبصراحة، هاي الخطوة أنقذتنا من جحيم كنا عايشين فيه.

ما هي الكائنات غير القابلة للتغيير (Immutability)؟

ببساطة شديدة، الكائن غير القابل للتغيير هو كائن لا يمكن تعديل حالته (state) بعد إنشائه. إذا أردت تغيير قيمة فيه، فأنت لا تعدّل على الكائن الأصلي، بل تقوم بإنشاء كائن جديد بالكامل مع التعديلات المطلوبة.

تخيلها مثل صك ملكية أرض موقّع ومختوم. لو بدك تغيّر أي معلومة فيه، ما بتقدر تشطب وتكتب فوقه. لازم تعمل صك جديد بالمعلومات الجديدة وتلغي القديم. أما البيانات القابلة للتغيير (Mutable Data)، فهي مثل مسودة على ورقة، كل واحد بيجي بشطب وبزيد عليها، وبعد فترة ما بتعرف مين كتب إيش وليش.

لماذا البيانات القابلة للتغيير (Mutable Data) كابوس؟

قبل ما نحكي عن الحل، لازم نفهم عمق المشكلة. الاعتماد على البيانات القابلة للتغيير، وهو الوضع الافتراضي في كثير من لغات البرمجة مثل JavaScript، يفتح علينا أبواب من المشاكل:

1. الآثار الجانبية الخفية (Hidden Side Effects)

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

شوف هالمثال البسيط في JavaScript:


function addDiscount(cart, discount) {
  // ❌ الطريقة السيئة: تعديل الكائن الأصلي مباشرة
  cart.total = cart.total * (1 - discount);
  return cart;
}

const myCart = { items: ['item1', 'item2'], total: 100 };
const discountedCart = addDiscount(myCart, 0.10);

// المشكلة: الكائن الأصلي "myCart" تم تعديله أيضاً!
console.log(myCart.total); // الناتج: 90
console.log(discountedCart.total); // الناتج: 90

في هذا المثال، دالة addDiscount لم تقم فقط بحساب الخصم، بل قامت بتغيير الكائن الأصلي myCart. الآن تخيل عشرات الدوال تتعامل مع هذا الكائن… من المستحيل تتبع من غيّر ماذا ومتى.

2. صعوبة تتبع الأخطاء (Debugging Hell)

لما تكون البيانات قابلة للتغيير، وتكتشف خطأ في قيمة معينة، سؤالك الأول راح يكون: “مين اللي غيّر هاي القيمة؟”. رح تضطر تضع نقاط توقف (breakpoints) في كل مكان في الكود يمكن أن يصل إلى هذا الكائن لتكتشف الفاعل. هذا بالضبط ما حدث معنا في قصة نظام إدارة المحتوى، كنا نطارد شبحًا.

3. مشاكل التزامن (Concurrency Issues)

في التطبيقات الحديثة، كثيرًا ما نقوم بتنفيذ عدة عمليات في نفس الوقت (سواء عبر threads في لغات مثل Java أو عبر عمليات غير متزامنة في JavaScript). إذا كانت عمليتان تحاولان تعديل نفس الكائن في نفس اللحظة، قد تحصل على نتائج كارثية وغير متوقعة. هذا ما يسمى بـ “سباق الظروف” (Race Condition).

الكائنات غير القابلة للتغيير (Immutability): طوق النجاة

الحل يكمن في تغيير طريقة تفكيرنا. بدلًا من “تغيير” البيانات، يجب أن نفكر في “إنشاء” بيانات جديدة.

“لا تغيّر الحالة، بل استبدلها.”

دعنا نعيد كتابة مثال عربة التسوق باستخدام هذا المبدأ:


function addDiscountImmutable(cart, discount) {
  // ✅ الطريقة الصحيحة: إنشاء كائن جديد مع التعديلات
  return {
    ...cart, // انسخ كل خصائص الكائن الأصلي
    total: cart.total * (1 - discount) // ثم قم بتجاوز الخاصية التي تريد تغييرها
  };
}

const myCart = { items: ['item1', 'item2'], total: 100 };
const discountedCart = addDiscountImmutable(myCart, 0.10);

// الآن الكائن الأصلي لم يتأثر وبقي على حاله
console.log(myCart.total); // الناتج: 100 (سليم!)
console.log(discountedCart.total); // الناتج: 90 (الكائن الجديد)

لاحظ الفرق الجوهري. الدالة addDiscountImmutable أصبحت “دالة نقية” (Pure Function)، أي أنها لنفس المدخلات، تعطي دائمًا نفس المخرجات، وليس لها أي آثار جانبية. كودك أصبح فجأة أكثر قابلية للتنبؤ والفهم.

مع المصفوفات (Arrays)

نفس المبدأ ينطبق على المصفوفات. بدلًا من استخدام دوال مثل push, splice التي تعدّل المصفوفة الأصلية، استخدم دوال مثل concat, slice أو الـ Spread Operator (...) التي تعيد مصفوفة جديدة.


const numbers = [1, 2, 3];

// ❌ طريقة سيئة (تعديل مباشر)
// numbers.push(4);

// ✅ طريقة صحيحة (إنشاء مصفوفة جديدة)
const newNumbers = [...numbers, 4]; // أو: const newNumbers = numbers.concat(4);

console.log(numbers); // الناتج: [1, 2, 3] (الأصلية لم تتغير)
console.log(newNumbers); // الناتج: [1, 2, 3, 4] (الجديدة)

نصائح عملية من خبرتي (من أخوكم أبو عمر)

  • ابدأ بالتدريج: لست مضطرًا لإعادة كتابة مشروعك بالكامل. ابدأ بتطبيق هذا المبدأ في الأجزاء الحساسة من تطبيقك، مثل إدارة الحالة العامة (Global State)، أو عند التعامل مع مكتبات مثل React و Redux التي تعتمد بشكل كبير على هذا المفهوم.
  • استخدم الأدوات المساعدة: في JavaScript، استخدام const يمنع إعادة إسناد المتغير، لكنه لا يمنع تغيير محتوى الكائن أو المصفوفة. يمكنك استخدام Object.freeze() لفرض عدم القابلية للتغيير بشكل سطحي. لمشاريع أكبر، انظر إلى مكتبات مثل Immer (التي تسهل العملية كثيرًا) أو Immutable.js (لحلول أكثر صرامة).
  • فكر بطريقة وظيفية (Functional Programming): عدم القابلية للتغيير هي أحد أعمدة البرمجة الوظيفية. كلما تعلمت أكثر عن هذا النمط، كلما أصبح التفكير بهذه الطريقة طبيعيًا أكثر.
  • الوضوح أهم من الأداء (في البداية): قد يقول قائل إن إنشاء كائنات جديدة باستمرار قد يؤثر على الأداء. هذا صحيح نظريًا، لكن في 99% من الحالات، هذا التأثير لا يذكر، ومحركات JavaScript الحديثة محسّنة جدًا لهذه العمليات. الفائدة التي ستحصل عليها من حيث وضوح الكود وسهولة صيانته تفوق بكثير أي قلق بسيط حول الأداء. لا تحاول التحسين المبكر (Premature Optimization).

الخلاصة: ودّع الأشباح ونام مرتاح 😴

تبني مفهوم “الكائنات غير القابلة للتغيير” هو ليس مجرد تقنية برمجية، بل هو نقلة في طريقة التفكير. هو قرار بأن تكتب كودًا يمكن التنبؤ به، يسهل اختباره، ويحميك وفريقك من قضاء ليالٍ طويلة في مطاردة أشباح الآثار الجانبية.

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

نصيحتي لك: في المرة القادمة التي تكتب فيها دالة تأخذ كائنًا كمدخل، اسأل نفسك: “هل أنا بحاجة لتغيير هذا الكائن، أم يمكنني إنشاء نسخة جديدة منه؟”. إجابتك على هذا السؤال قد تكون هي الفاصل بين الكود الفوضوي والكود النظيف. والله ولي التوفيق.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

التكنلوجيا المالية Fintech

معاملاتنا الاحتيالية كانت تُكتشف بعد فوات الأوان: كيف أنقذتنا ‘نماذج التعلم الآلي في الوقت الفعلي’ من جحيم الخسائر المالية؟

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

16 أبريل، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

سيرفراتنا كانت جزرًا منعزلة: كيف أنقذنا Kubernetes من جحيم الإدارة اليدوية للحاويات؟

أشارككم قصة حقيقية من قلب المعركة التقنية، كيف انتقلنا من فوضى إدارة عشرات الحاويات (Containers) يدويًا على سيرفرات متفرقة، إلى عالم الأتمتة والتناغم بفضل Kubernetes....

16 أبريل، 2026 قراءة المزيد
اختبارات الاداء والجودة

اختباراتنا كانت تجتاز والموقع ينهار: كيف أنقذنا ‘اختبار الحمل بالسيناريوهات الواقعية’ (k6) من جحيم الأداء الوهمي؟

كانت كل اختبارات الأداء لدينا تظهر نتائج ممتازة، لكن الموقع كان ينهار مع أولى موجات المستخدمين الحقيقيين. هذه المقالة هي قصتنا مع "الأداء الوهمي"، وكيف...

16 أبريل، 2026 قراءة المزيد
​معمارية البرمجيات

خدماتنا كانت متشابكة كخيوط العنكبوت: كيف أنقذتنا ‘معمارية الأحداث’ من جحيم الاقتران الخانق؟

أشارككم قصة حقيقية من قلب المعركة البرمجية، كيف انتقلنا من نظام هش متشابك إلى معمارية مرنة وقابلة للتوسع. اكتشفوا معنا سحر 'معمارية الأحداث' (Event-Driven Architecture)...

16 أبريل، 2026 قراءة المزيد
البودكاست