بياناتنا كانت شبحًا متغيرًا: كيف أنقذتنا ‘الكائنات غير القابلة للتغيير’ (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).

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

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

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

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

أبو عمر

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

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

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

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

آخر المدونات

برمجة وقواعد بيانات

كانت استعلاماتنا تزحف: كيف أنقذتنا فهارس قواعد البيانات من جحيم البحث البطيء؟

قصة من الميدان عن كيفية تحويل استعلامات SQL البطيئة التي تشبه السلحفاة إلى عمليات فائقة السرعة باستخدام أداة بسيطة وقوية: فهارس قواعد البيانات. مقالة عملية...

25 مايو، 2026 قراءة المزيد
الشبكات والـ APIs

من جحيم الـ Polling إلى نعيم الـ Webhooks: كيف أنقذت “خطافات الويب” تطبيقاتنا من السؤال المستمر “هل من جديد؟”

أروي لكم قصة من واقع تجربتي كمبرمج، كيف انتقلنا من طريقة الاستطلاع المستمر (Polling) المرهقة للخوادم، إلى الاعتماد على "خطافات الويب" (Webhooks) الذكية. مقالة عملية...

25 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

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

هل ملفك الشخصي مجرد قائمة بمشاريع غير مكتملة أو تطبيقات تعليمية؟ اكتشف كيف حوّلتُ 'مقبرة المشاريع' الخاصة بي إلى قصة نجاح متماسكة باستخدام تقنية 'سردية...

24 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

كان خادمنا ينهار تحت الضغط: كيف أنقذنا ‘موازن الأحمال’ من جحيم نقطة الفشل الواحدة؟

في هذه المقالة، أشارككم قصة حقيقية عن كيفية انهيار خادمنا تحت ضغط المستخدمين، وكيف كان "موازن الأحمال" (Load Balancer) هو البطل الذي أنقذ الموقف. سنتعمق...

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

كان كل سيرفر جزيرة منعزلة: كيف وحّد Ansible أسطولنا وأنقذنا من جحيم التكوينات المتضاربة؟

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

24 مايو، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

من جحيم ‘شو الجديد؟’ إلى حوار حقيقي: كيف حوّلت اجتماعاتي الفردية (1-on-1s) من استجواب إلى استثمار في فريقي؟

هل تشعر أن اجتماعاتك الفردية مع فريقك مجرد تحديث للمهام لا قيمة له؟ بصفتي أبو عمر، أشارككم رحلتي في تحويل هذه الاجتماعات من استجواب ممل...

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

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

أتذكر الليالي الطوال التي قضيناها في المكتب، ليس لكتابة ميزات جديدة، بل لإصلاح ما كسرناه عن غير قصد. هذه ليست مجرد مقالة تقنية، بل هي...

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