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

يا هلا بالجميع، معكم أبو عمر.

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

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

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

طلع كلامه ذهب. كانت دالة بريئة وظيفتها “عرض المعاينة” بتستلم نسخة من بيانات ترتيب الصفحة، وبدل ما تشتغل على نسخة خاصة فيها، كانت بتعدل على الأصل مباشرة! وبما إنه الكود كان غير متزامن (Asynchronous) في أماكن أخرى، كانت عمليات الحفظ والمعاينة بتتضارب مع بعضها وبتخلق “آثار جانبية” (Side Effects) كارثية. البيانات كانت بتتغير من تحت أقدامنا وإحنا مش داريين. وقتها أدركت تماماً قوة وأهمية مبدأ بسيط لكن عميق: اللامتغيرية (Immutability).

شو القصة؟ ما هي البيانات القابلة للتغيير (Mutability)؟

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

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

المثال الكلاسيكي: سلة التسوق الخائنة

تخيل أن لديك كائن يمثل سلة تسوق. وتريد إضافة خصم على السعر الإجمالي.

الطريقة القابلة للتغيير (Mutable) قد تبدو هكذا:


const cart = {
  items: ['كتاب', 'قلم'],
  totalPrice: 100
};

// دالة خبيثة تقوم بتعديل الكائن الأصلي مباشرة
function applyDiscount(cartObject, discount) {
  cartObject.totalPrice -= discount; // 😱 تعديل مباشر للكائن الأصلي
  return cartObject;
}

console.log('السعر الأصلي:', cart.totalPrice); // الناتج: 100

// تطبيق الخصم
const discountedCart = applyDiscount(cart, 10);

console.log('السعر بعد الخصم:', discountedCart.totalPrice); // الناتج: 90
console.log('السعر الأصلي بعد استدعاء الدالة:', cart.totalPrice); // 😱 الناتج: 90

هل رأيتم الكارثة؟ الكائن cart الأصلي تغير! الآن تخيل أن هناك جزء آخر من الكود يعتمد على السعر الأصلي (ربما لحساب الضرائب). لقد قمت بتخريب بياناته دون أن تدري. هذا هو جحيم الأعطال الجانبية بعينه.

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

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

هذا المبدأ هو حجر الزاوية في البرمجة الوظيفية (Functional Programming)، لكن فوائده عظيمة جداً لدرجة أن كل المبرمجين يجب أن يتبنوه.

لننقذ سلة التسوق!

دعنا نعيد كتابة المثال السابق بطريقة لامتغيرة (Immutable):


const cart = {
  items: ['كتاب', 'قلم'],
  totalPrice: 100
};

// دالة محترمة لا تلمس البيانات الأصلية
function applyDiscountImmutable(cartObject, discount) {
  // ننشئ كائناً جديداً بالكامل
  const newCart = {
    ...cartObject, // ... ننسخ كل الخصائص القديمة
    totalPrice: cartObject.totalPrice - discount // نطبق التعديل على الخاصية المطلوبة فقط
  };
  return newCart;
}

console.log('السعر الأصلي:', cart.totalPrice); // الناتج: 100

// تطبيق الخصم
const discountedCart = applyDiscountImmutable(cart, 10);

console.log('السعر بعد الخصم في النسخة الجديدة:', discountedCart.totalPrice); // الناتج: 90
console.log('السعر الأصلي بعد استدعاء الدالة:', cart.totalPrice); // ✅ الناتج: 100 (بقيت البيانات الأصلية سليمة)

لاحظ استخدام الـ Spread Operator (...) لنسخ خصائص الكائن القديم في كائن جديد. الآن لدينا عالمان منفصلان: عالم ما قبل الخصم وعالم ما بعد الخصم. لا يوجد تضارب، لا توجد مفاجآت.

ليش وجع هالراس؟ فوائد اللامتغيرية العملية

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

1. وداعاً للآثار الجانبية (Side Effects)

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

2. إدارة حالة (State) أسهل وأكثر قابلية للتنبؤ

إذا كنت تعمل مع أطر عمل حديثة مثل React أو Vue أو Angular، فأنت تعلم أن إدارة “الحالة” (State) هي قلب التطبيق. هذه الأطر تعتمد بشكل كبير على اللامتغيرية لمعرفة متى يجب إعادة رسم واجهة المستخدم.

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

3. تحسين الأداء (في أماكن لا تتوقعها)

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

4. السفر عبر الزمن: تصحيح الأخطاء أصبح متعة!

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

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

طيب، اقتنعت يا أبو عمر. كيف أبدأ؟

ابدأ بالتدريج، لا تهدم المعبد

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

استخدم الأدوات المناسبة: تعرف على Immer.js

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

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

مثال سريع مع Immer:


import produce from "immer";

const baseState = {
  user: {
    name: "أبو عمر",
    settings: {
      theme: "light"
    }
  }
};

// مع Immer، الكود يصبح بسيطاً جداً
const nextState = produce(baseState, draftState => {
  // اكتب كودك كأنك تعدل مباشرة!
  draftState.user.settings.theme = "dark";
});

console.log(baseState.user.settings.theme); // "light" (الأصلي لم يتغير)
console.log(nextState.user.settings.theme); // "dark" (النسخة الجديدة فقط هي التي تغيرت)

Immer.js هي أفضل صديق للمبرمج الذي يريد فوائد اللامتغيرية دون تعقيدات الكتابة.

غير طريقة تفكيرك: من “التعديل” إلى “الخلق”

النصيحة الأهم هي تغيير في العقلية. بدلاً من التفكير بـ “كيف أُعدّل هذا الكائن؟”، ابدأ بالتفكير بـ “كيف أُنشئ كائناً جديداً بناءً على هذا الكائن القديم؟”. هذا التحول الذهني سيجعلك مبرمجاً أفضل، وستصبح كتابة كود لامتغير طبيعة ثانية لك.

الخلاصة: هل تستحق اللامتغيرية كل هذا العناء؟

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

تذكروا دائماً قصة فنجان القهوة البارد والبيانات الشبحية. استثمروا في كتابة كود نظيف اليوم، لترتاحوا غداً. صدقوني، ستشكرون أنفسكم لاحقاً. 🙏

يلا، بالتوفيق يا جماعة الخير!

أبو عمر

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

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

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

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

آخر المدونات

ذكاء اصطناعي

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

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

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

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

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

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

كانت واجهاتنا تتصرف بعشوائية: كيف أنقذتنا ‘آلات الحالة المحدودة’ (State Machines) من جحيم الفوضى؟

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

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

كانت صفحاتنا تطلب مئات الاستعلامات: كيف أنقذنا ‘التحميل الشغوف’ (Eager Loading) من جحيم مشكلة N+1؟

أشارككم قصة حقيقية من الميدان، يوم كادت إحدى صفحات موقعنا أن تنهار تحت وطأة مئات استعلامات قاعدة البيانات. سأشرح لكم بالتفصيل مشكلة N+1 وكيف كان...

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

كانت أسرارنا مكشوفة في الكود: كيف أنقذنا “مدير الأسرار” من جحيم التسريبات؟

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

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