كانت بياناتنا تتغير من تحت أقدامنا: كيف أنقذتنا ‘اللامُتَغَيِّرية’ (Immutability) من جحيم الأخطاء؟

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

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

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

في واحد من اجتماعاتنا اللي وصلت لطريق مسدود، وقفت وقلت للفريق: “يا جماعة، المشكلة مش في الكود اللي بنكتبه، المشكلة في الطريقة اللي بنتعامل فيها مع البيانات نفسها. احنا بنسمح لأي حدا يغيّر أي إشي في أي وقت، وهذا هو سبب البهدلة كلها”. ومن هداك اليوم، بلّشت رحلتنا مع مفهوم غيّر طريقة تفكيرنا تماماً: اللامُتَغَيِّرية (Immutability).

ما هي “المشكلة” بالضبط؟ القابلية للتغيير (Mutability)

قبل ما نحكي عن الحل، خلينا نفهم أصل المشكلة. في معظم لغات البرمجة، لما نعرّف متغير (خصوصاً الكائنات – Objects والمصفوفات – Arrays)، بنقدر نغيّر محتواه في أي وقت وفي أي مكان. هذا هو ما نسميه “القابلية للتغيير” أو الـ Mutability.

خلونا نشوف مثال بسيط في لغة JavaScript:


// نعرّف سلة مشتريات ككائن (Object)
let cart = {
  items: ["خبز", "جبنة"],
  total: 5.00
};

// نمرر السلة لدالة تطبق خصم
function applyDiscount(shoppingCart) {
  // ⛔️ خطأ شائع: تغيير الكائن الأصلي مباشرة!
  shoppingCart.total = shoppingCart.total * 0.90; 
  return shoppingCart;
}

// نطبق الخصم
applyDiscount(cart);

// الآن، قيمة الـ 'cart' الأصلية تغيرت بشكل دائم
console.log(cart); // { items: ["خبز", "جبنة"], total: 4.50 }

على الورق، هذا الكود يبدو بريئاً، أليس كذلك؟ لكن هنا يكمن الشيطان في التفاصيل. لقد قمنا بتغيير الكائن cart الأصلي من داخل دالة. هذا ما يسمى بـ “الأثر الجانبي” (Side Effect).

جحيم الآثار الجانبية (Side Effects)

تخيل أن هناك عشرة أجزاء أخرى من البرنامج تستخدم نفس المتغير cart. أحدها يعرض السعر الإجمالي، والآخر يحسب تكلفة الشحن، والثالث يحفظ السلة في قاعدة البيانات. عندما تقوم دالة applyDiscount بتغيير الكائن الأصلي “بصمت”، فإنها تسبب سلسلة من المشاكل غير المتوقعة في كل مكان آخر:

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

باختصار، القابلية للتغيير تجعل كودك هشاً، صعب الفهم، ومليئاً بالمفاجآت غير السارة.

دخول “اللامتغيرية” (Immutability) إلى المشهد

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

“لحظة يا أبو عمر!”، قد تقول. “كيف بدنا نشتغل إذا ما بنقدر نغيّر إشي؟ كيف أضيف منتج للسلة أو أطبق خصم؟”

الجواب بسيط: بدلاً من تغيير البيانات القديمة، نقوم بإنشاء نسخة جديدة من البيانات مع التعديلات التي نريدها. الأصل يبقى كما هو، نقيّاً لم يمسه أحد.

اللامتغيرية في الممارسة

دعونا نعيد كتابة مثال سلة المشتريات باستخدام مبدأ اللامتغيرية. سنستخدم تقنيات حديثة في JavaScript مثل عامل الانتشار (Spread Operator `…`) لجعل العملية سهلة وقابلة للقراءة.


const originalCart = {
  items: ["خبز", "جبنة"],
  total: 5.00
};

// ✅ طريقة صحيحة: الدالة لا تغير الكائن الأصلي
function applyDiscountImmutable(shoppingCart) {
  // ننشئ كائناً جديداً باستخدام محتويات القديم
  // ثم نحدد القيمة الجديدة للـ total
  const newCart = {
    ...shoppingCart, // انسخ كل خصائص shoppingCart القديم
    total: shoppingCart.total * 0.90 // قم بتجاوز خاصية الـ total فقط
  };
  
  return newCart;
}

// نطبق الخصم ونستقبل السلة الجديدة في متغير جديد
const discountedCart = applyDiscountImmutable(originalCart);

// لنرَ النتائج
console.log("السلة الأصلية (لم تتغير):", originalCart); 
// الناتج: { items: ["خبز", "جبنة"], total: 5.00 }

console.log("السلة الجديدة بعد الخصم:", discountedCart);
// الناتج: { items: ["خبز", "جبنة"], total: 4.50 }

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

وماذا عن إضافة عنصر لمصفوفة؟

نفس المبدأ ينطبق على المصفوفات. بدلاً من استخدام .push() الذي يغير المصفوفة الأصلية، نستخدم طرقاً أخرى.


const originalItems = ["بندورة", "خيار"];

// لإضافة عنصر جديد
const newItems = [...originalItems, "فلفل"]; // نستخدم عامل الانتشار

console.log(originalItems); // ["بندورة", "خيار"] (الأصل لم يتغير)
console.log(newItems); // ["بندورة", "خيار", "فلفل"] (النسخة الجديدة)

فوائد تبني اللامتغيرية

عندما بدأنا في تطبيق هذا المبدأ في مشروعنا، كانت النتائج أشبه بالسحر:

  1. شيفرة قابلة للتنبؤ (Predictable Code): اختفت الأخطاء الغامضة. أصبحنا نعرف بالضبط أين وكيف تتغير البيانات، لأن كل تغيير ينتج عنه نسخة جديدة وواضحة.
  2. تصحيح أخطاء أسهل بـ 10 أضعاف: أصبح بإمكاننا مقارنة الحالة “قبل” و “بعد” التغيير بسهولة. أدوات المطورين مثل Redux DevTools، التي تعتمد بشكل كبير على هذا المبدأ، تسمح لنا بـ “السفر عبر الزمن” ورؤية كل تغيير حدث في حالة التطبيق خطوة بخطوة.
  3. تحسين الأداء (نعم، حقاً!): قد تظن أن إنشاء نسخ جديدة باستمرار أمر مكلف. لكن في الواقع، العديد من أطر العمل الحديثة مثل React تستغل اللامتغيرية لتحسين الأداء. يمكنها مقارنة مرجع الكائن القديم بالجديد بسرعة البرق (Shallow Comparison). إذا كان المرجع مختلفاً، فهذا يعني أن البيانات تغيرت ويجب تحديث واجهة المستخدم. إذا كان المرجع هو نفسه، فلا داعي لفعل أي شيء.
  4. إدارة حالة أبسط (Simpler State Management): أصبحت إدارة الحالة العامة للتطبيق (Global State) أكثر تنظيماً وأماناً. لم نعد نخاف من أن يقوم جزء من التطبيق بإفساد البيانات على جزء آخر.

نصائح أبو عمر العملية

الانتقال إلى هذا النمط من التفكير يحتاج بعض الممارسة. إليك بعض النصائح من خبرتي الشخصية:

  • ابدأ بالتدريج: لست مضطراً لإعادة كتابة كل مشروعك. ابدأ بتطبيق اللامتغيرية في الأجزاء الأكثر حساسية، مثل إدارة الحالة المركزية (State Management Store).
  • استخدم الأدوات المساعدة: لا تخترع العجلة من جديد. إذا كان التعامل مع الكائنات المتشعبة صعباً، استخدم مكتبات مثل Immer.js. هذه المكتبة تسمح لك بكتابة كود يبدو وكأنه يغير البيانات مباشرة، لكنها في الكواليس تقوم بإنشاء نسخ جديدة بشكل آمن وفعال.
  • اجعلها عادة: كلما كتبت دالة تأخذ كائناً أو مصفوفة، اسأل نفسك: “هل يجب أن أغير هذا المدخل، أم يجب أن أعيد نسخة جديدة؟”. في 99% من الحالات، الخيار الثاني هو الأصح.
  • فكر بطريقة “وظيفية”: اللامتغيرية هي حجر الزاوية في البرمجة الوظيفية (Functional Programming). تعلم المزيد عن الدوال النقية (Pure Functions) وتجنب الآثار الجانبية سيجعلك مبرمجاً أفضل بشكل عام.

الخلاصة: اجعل كودك صخرة، لا رملاً متحركاً! 🧗‍♂️

العودة إلى قصتنا، بعد تبني اللامتغيرية، تحول مشروعنا من كابوس فوضوي إلى نظام منظم يمكن التنبؤ به. استغرق الأمر بعض الوقت لتغيير عاداتنا، لكن المردود كان هائلاً من حيث استقرار النظام، سرعة التطوير، وراحة البال التي لا تقدر بثمن.

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

أتمنى أن تكون هذه المشاركة قد أنارت لكم الطريق. والله ولي التوفيق.

أبو عمر

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

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

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

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

آخر المدونات

​معمارية البرمجيات

كان تغيير بسيط يكسر كل شيء: كيف أنقذتنا ‘المعمارية القائمة على الأحداث’ من جحيم التشابك؟

أشارككم قصة حقيقية من ميدان المعركة البرمجية، يوم كاد تغيير بسيط أن يوقف عملنا بالكامل. سنغوص في أعماق "المعمارية القائمة على الأحداث" (Event-Driven Architecture) لنكتشف...

21 مايو، 2026 قراءة المزيد
ذكاء اصطناعي

كنا نغرق في بحر البيانات: كيف أنقذنا ‘الإشراف الضعيف’ (Weak Supervision) من جحيم التسمية اليدوية؟

مقالة تستعرض تقنية الإشراف الضعيف (Weak Supervision) كحل عملي لمشكلة تسمية البيانات الهائلة في مشاريع الذكاء الاصطناعي. من قصة واقعية إلى دليل تطبيقي، نكتشف كيف...

21 مايو، 2026 قراءة المزيد
خوارزميات

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

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

21 مايو، 2026 قراءة المزيد
تسويق رقمي

كانت تحويلاتنا ضحية لحاصرات الإعلانات: كيف أنقذتنا واجهة برمجة تطبيقات التحويلات (CAPI) من جحيم التتبع المفقود؟

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

21 مايو، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

من الشاشات البيضاء إلى الحوار: فن تصميم حالات الواجهة (Loading, Empty, Error)

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

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