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

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

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

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

هذيك الليلة، ما نمت قبل ما أقرأ كل إشي بقدر عليه عن مفهوم اسمه “اللامتغيرية” أو Immutability. ومن يومها، صارت فلسفتي في كتابة الكود مختلفة تماماً. خلوني أشرحلكم كيف هالمبدأ أنقذنا من جحيم الآثار الجانبية الخفية.


ما هي القابلية للتغيير (Mutability)؟ ولماذا هي خطيرة؟

ببساطة شديدة، الكائن القابل للتغيير (Mutable Object) هو أي كائن (مثل object أو array في جافاسكريبت) يمكن تعديل بياناته مباشرة بعد إنشائه.

خلينا نشوف مثال بسيط في جافاسكريبت:


// لدينا كائن يمثل معلومات مستخدم
let user = {
  name: "أبو عمر",
  country: "فلسطين",
  cart: ["زيت زيتون", "زعتر"]
};

// دالة تقوم بإضافة عرض خاص لسلة التسوق
function addSpecialOffer(customer) {
  // ⚠️ خطأ فادح: تعديل الكائن الأصلي مباشرة!
  customer.cart.push("جبنة نابلسية (عرض خاص)");
  return customer;
}

// لنرى ما سيحدث
console.log("السلة قبل العرض:", user.cart);
addSpecialOffer(user);
console.log("السلة بعد العرض:", user.cart);

الناتج سيكون:


السلة قبل العرض: ["زيت زيتون", "زعتر"]
السلة بعد العرض: ["زيت زيتون", "زعتر", "جبنة نابلسية (عرض خاص)"]

للوهلة الأولى، قد يبدو هذا طبيعياً. لكن تخيل أن كائن user هذا يتم استخدامه في 10 أماكن مختلفة في تطبيقك. دالة addSpecialOffer قامت بتغيير حالة التطبيق بشكل خفي وغير متوقع. أي جزء آخر من الكود يعتمد على النسخة الأصلية من user.cart سيتفاجأ بهذا التغيير. هذا ما نسميه الأثر الجانبي (Side Effect). إنه السبب الرئيسي للbugs الصعبة والمعقدة.

مشاكل التغيير المباشر (Mutation)

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

الحل السحري: اللامتغيرية (Immutability)

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

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


// نفس المستخدم
let user = {
  name: "أبو عمر",
  country: "فلسطين",
  cart: ["زيت زيتون", "زعتر"]
};

// دالة "نقية" لا تغير الكائن الأصلي
function addSpecialOfferImmutable(customer) {
  // 1. ننشئ نسخة جديدة من السلة ونضيف عليها الغرض الجديد
  const newCart = [...customer.cart, "جبنة نابلسية (عرض خاص)"];

  // 2. ننشئ نسخة جديدة من كائن المستخدم مع السلة الجديدة
  const newCustomer = {
    ...customer,
    cart: newCart
  };

  return newCustomer;
}

console.log("المستخدم الأصلي قبل:", user);

// نستقبل المستخدم الجديد في متغير جديد
const userWithOffer = addSpecialOfferImmutable(user);

console.log("المستخدم الأصلي بعد:", user); // لم يتغير!
console.log("المستخدم الجديد مع العرض:", userWithOffer);

الناتج الآن سيكون مختلفًا تمامًا:


المستخدم الأصلي قبل: { name: 'أبو عمر', ..., cart: ['زيت زيتون', 'زعتر'] }
المستخدم الأصلي بعد: { name: 'أبو عمر', ..., cart: ['زيت زيتون', 'زعتر'] } // 👍 بقي كما هو!
المستخدم الجديد مع العرض: { name: 'أبو عمر', ..., cart: ['زيت زيتون', 'زعتر', 'جبنة نابلسية (عرض خاص)'] }

لاحظ الفرق الجوهري: الكائن user الأصلي لم يلمسه أحد. لدينا الآن “لقطة” (snapshot) من الحالة قبل التغيير و”لقطة” أخرى بعد التغيير. هذا هو جوهر إدارة الحالة الحديثة.

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

عندما تتبنى هذا النهج، تبدأ في جني ثمار عديدة:

1. كود يمكن التنبؤ به (Predictable Code)

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

2. تحسين الأداء (Performance Optimization)

قد تفكر: “إنشاء كائنات جديدة طوال الوقت أليس مكلفاً؟”. في الواقع، العكس هو الصحيح في كثير من الأحيان. مكتبات الواجهات الأمامية الحديثة مثل React تعتمد على هذا المبدأ. عندما تريد React أن تعرف هل يجب إعادة رسم مكون على الشاشة أم لا، يمكنها ببساطة مقارنة مرجع الكائن القديم بالجديد (oldState === newState). إذا كانا نفس الكائن، فهذا يعني أن شيئًا لم يتغير. هذه العملية (تسمى Shallow Comparison) أسرع بآلاف المرات من مقارنة كل خاصية في كائنين عملاقين.

3. تصحيح أخطاء السفر عبر الزمن (Time-Travel Debugging)

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

4. التعامل الآمن مع التزامن (Safe Concurrency)

بما أن البيانات لا يتم تغييرها أبدًا، فلا داعي للقلق بشأن وصول عمليتين مختلفتين إليها في نفس الوقت. لا يوجد شيء “يتسابقون” لتغييره. هذا يبسط بشكل كبير كتابة التعليمات البرمجية المتوازية وغير المتزامنة.


نصائح أبو عمر العملية لتطبيق اللامتغيرية

الكلام النظري جميل، لكن كيف نطبق هذا “عالأرض”؟

1. استخدم أدوات اللغة المتاحة

الجافاسكريبت الحديثة (ES6+) أعطتنا أدوات رائعة تساعد على الكتابة بأسلوب غير قابل للتغيير:

  • Spread Operator (`…`): صديقك المفضل لإنشاء نسخ جديدة من الكائنات والمصفوفات.
    const newObj = { ...oldObj, propToChange: 'newValue' };
    const newArr = [...oldArr, newItem];
  • Object.assign: طريقة قديمة لفعل نفس الشيء مع الكائنات.
    const newObj = Object.assign({}, oldObj, { propToChange: 'newValue' });
  • Object.freeze: يمكنك استخدامها لجعل الكائن غير قابل للتغيير على المستوى السطحي (Shallow Freeze). هذا يمنع التعديلات المباشرة بالخطأ.
    const user = Object.freeze({ name: "أبو عمر" });
    user.name = "علي"; // سيتم تجاهل هذا التغيير في strict mode أو يرمي خطأ
    

2. لا تخف من المكتبات المساعدة

عندما تتعقد الكائنات وتصبح متداخلة (nested)، يصبح التعامل معها يدويًا مملاً وعرضة للخطأ. هنا تأتي المكتبات لإنقاذ الموقف:

  • Immer.js: هذه المكتبة هي المفضلة عندي شخصياً. إنها تسمح لك بكتابة كود يبدو وكأنه يغير الحالة مباشرة (mutable style)، لكنها في الخلفية تقوم بكل العمل الشاق لإنشاء نسخة جديدة غير قابلة للتغيير. إنها تجمع أفضل ما في العالمين: سهولة الكتابة وأمان اللامتغيرية.
    import produce from "immer";
    
    const nextState = produce(currentState, draftState => {
      // الكود هنا يبدو وكأنك تغير مباشرة
      draftState.user.profile.age = 45;
    });
    // لكن currentState الأصلي لم يتغير!
    
  • Immutable.js: مكتبة من فيسبوك توفر هياكل بيانات خاصة بها (مثل Map و List) تكون غير قابلة للتغيير بشكل كامل. هي قوية جدًا ولكنها تتطلب منك تعلم واجهتها البرمجية الخاصة (API).

3. غيّر طريقة تفكيرك

“لا تفكر في البيانات على أنها كتلة طين تشكلها كما تريد، بل فكر فيها كسلسلة من الصور الفوتوغرافية. كل تغيير هو صورة جديدة في الألبوم.”

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


الخلاصة 😉

في ذلك اليوم المشؤوم، تعلمنا بالطريقة الصعبة أن السماح للبيانات بالتغير “بغدر” هو وصفة لكارثة برمجية. مبدأ اللامتغيرية (Immutability) ليس مجرد تقنية أو نمط برمجي، بل هو فلسفة في بناء برمجيات قوية وموثوقة ويمكن صيانتها بسهولة.

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

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

أبو عمر

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

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

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

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

آخر المدونات

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

كنا ضائعين بين المونوليث والخدمات المصغرة: كيف أنقذنا ‘المونوليث النمطي’ (Modulith) من جحيم التعقيد؟

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

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

كانت إجابات نموذجنا من وحي الخيال: كيف أنقذنا البحث المعزز بالتوليد (RAG) من جحيم الهلوسة؟

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

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

كانت شخصياتنا في اللعبة تسير في حوائط: كيف أنقذتنا خوارزمية A* من جحيم المسارات الغبية؟

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

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

كانت واجهاتنا جزرًا معزولة: كيف أنقذنا ‘نظام التصميم’ من جحيم الفوضى البصرية؟

أشارككم قصة حقيقية من قلب المعركة البرمجية، كيف انتقلنا من فوضى الواجهات والتصاميم المتضاربة إلى نظام متناغم وموحّد. هذه رحلتنا في بناء "نظام تصميم" (Design...

17 مايو، 2026 قراءة المزيد
برمجة وقواعد بيانات

كانت تحديثات قاعدة البيانات كابوساً: كيف أنقذتنا أدوات الترحيل (Migrations) من جحيم التعديلات اليدوية؟

هل عانيت يوماً من تحديث مخطط قاعدة البيانات يدوياً بين فريقك؟ أبو عمر يشارككم قصة حقيقية حول كيف غيّرت أدوات الترحيل (Migrations) طريقة عمل فريقه،...

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

كانت خوادمنا تستجدي التحديثات: كيف أنقذتنا ‘خطاطيف الويب’ (Webhooks) من جحيم الاستقصاء المستمر (Polling)؟

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

17 مايو، 2026 قراءة المزيد
الحوسبة السحابية

كانت بنيتنا التحتية قصراً من رمال: كيف أنقذتنا “البنية التحتية ككود” (IaC) من جحيم البيئات المتضاربة؟

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

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