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

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

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

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

هون كان “الأها مومنت”. المشكلة ما كانت “عفريت”، المشكلة كانت “الآثار الجانبية” (Side Effects) الناتجة عن تعديل البيانات القابلة للتغيير (Mutable Data). ومن يومها، صار مبدأ “اللامتغيرية” (Immutability) واحد من أهم المبادئ اللي بحرص أطبقها وأعلّمها لكل فريق بشتغل معه. خلوني أحكيلكم كيف هالمفهوم البسيط ممكن ينقذكم من جحيم مشابه.

ما هي ‘اللامتغيرية’ (Immutability) وليش لازم نهتم؟

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

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

المشكلة الكلاسيكية: الكائنات القابلة للتغيير (Mutable Objects)

لنأخذ مثالًا بسيطًا بلغة JavaScript لتوضيح الكارثة التي يمكن أن تحدث. تخيل أن لديك كائنًا يمثل بيانات مستخدم:

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

// دالة "سيئة" تعدل البيانات مباشرة
function addProject(userData, newProject) {
  // ❌ هذا هو التعديل المباشر (Mutation) الخطير
  userData.projects.push(newProject); 
  console.log("تم تحديث المشاريع داخل الدالة.");
  return userData;
}

// جزء آخر من الكود يعرض البيانات الأصلية
function displayOriginalProjects(userData) {
  console.log("المشاريع الأصلية:", userData.projects.join(', '));
}

// ------ السيناريو ------
console.log("--- قبل أي تعديل ---");
displayOriginalProjects(user); // سيعرض: مشروع أ, مشروع ب

// الآن، لنستدعي الدالة لإضافة مشروع جديد
let updatedUser = addProject(user, "مشروع ج");

console.log("--- بعد التعديل ---");
// المشكلة الكبرى: الكائن الأصلي 'user' تغير أيضاً!
displayOriginalProjects(user); // سيعرض: مشروع أ, مشروع ب, مشروع ج

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

الحل السحري: تبني مفهوم اللامتغيرية

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

الطريقة اليدوية: النسخ قبل التعديل

يمكننا إعادة كتابة الدالة السابقة بطريقة “لامتغيرة” باستخدام تقنيات حديثة في JavaScript مثل عامل النشر (Spread Operator ...).

let user = { 
  name: "أبو عمر", 
  country: "فلسطين",
  projects: ["مشروع أ", "مشروع ب"] 
};

// دالة "نقية" و"لامتغيرة"
function addProjectImmutably(userData, newProject) {
  // ✅ ننشئ نسخة جديدة من الكائن، ونسخة جديدة من مصفوفة المشاريع
  return {
    ...userData, // نسخ كل خصائص الكائن الأصلي
    projects: [...userData.projects, newProject] // إنشاء مصفوفة جديدة تحتوي على العناصر القديمة + العنصر الجديد
  };
}

// ------ السيناريو الجديد ------
console.log("--- قبل أي تعديل ---");
console.log("المشاريع الأصلية:", user.projects); // ["مشروع أ", "مشروع ب"]

// نستدعي الدالة الجديدة
let newUserState = addProjectImmutably(user, "مشروع ج");

console.log("--- بعد التعديل ---");
// الكائن الأصلي لم يتغير إطلاقاً!
console.log("المشاريع الأصلية لا تزال:", user.projects); // ["مشروع أ", "مشروع ب"]

// الحالة الجديدة تحتوي على التغيير
console.log("حالة المشاريع الجديدة:", newUserState.projects); // ["مشروع أ", "مشروع ب", "مشروع ج"]

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

مكتبات متخصصة: لراحة البال

عندما تتعقد هياكل البيانات وتصبح متداخلة (nested objects)، قد تصبح عملية النسخ اليدوي مملة وعرضة للخطأ. هنا يأتي دور المكتبات المتخصصة التي تسهل هذه العملية.

مكتبة Immer.js هي واحدة من أروع المكتبات في هذا المجال لأنها تتيح لك كتابة كود يبدو وكأنه “يعدل مباشرة” (mutable)، لكنها في الخلفية تقوم بكل العمل الشاق لضمان اللامتغيرية.

import { produce } from "immer";

let user = { 
  name: "أبو عمر", 
  country: "فلسطين",
  projects: ["مشروع أ", "مشروع ب"] 
};

// استخدام Immer لتحديث الحالة
const newUserState = produce(user, draftState => {
  // هنا، الكود يبدو وكأنك تعدل مباشرة على "مسودة" (draft)
  // لكن Immer تضمن أن الكائن الأصلي لن يتأثر
  draftState.projects.push("مشروع ج");
});

// النتائج مماثلة للطريقة اليدوية، لكن بكود أبسط وأوضح
console.log("المشاريع الأصلية:", user.projects); // ["مشروع أ", "مشروع ب"]
console.log("حالة المشاريع الجديدة:", newUserState.projects); // ["مشروع أ", "مشروع ب", "مشروع ج"]

فوائد اللامتغيرية العملية: مش بس كلام نظري

  • كود يمكن التنبؤ به (Predictable Code): عندما تعلم أن دوالك لا تغير البيانات من وراء ظهرك، يصبح تدفق البيانات في تطبيقك واضحًا وسهل الفهم.
  • تصحيح أخطاء أسهل (Easier Debugging): بما أن كل تغيير ينتج عنه حالة جديدة، يمكنك بسهولة تتبع تاريخ التغييرات. هذا هو المبدأ وراء أدوات مثل Redux DevTools التي تسمح لك بـ “السفر عبر الزمن” بين حالات التطبيق المختلفة.
  • تحسينات في الأداء (Performance Optimizations): في أطر عمل واجهات المستخدم مثل React، تعد مقارنة التغييرات لتحديد ما إذا كان يجب إعادة رسم جزء من الشاشة عملية مكلفة. عندما تستخدم اللامتغيرية، تصبح المقارنة سهلة جداً: هل مرجع الكائن الجديد هو نفسه مرجع الكائن القديم؟ (oldState === newState). إذا كانت المراجع مختلفة، فهذا يعني أن شيئًا ما قد تغير ويجب تحديث الواجهة. هذا أسرع بكثير من المقارنة العميقة لجميع خصائص الكائن.
  • إدارة حالة (State Management) أكثر أمانًا: معظم مكتبات إدارة الحالة الحديثة (Redux, Zustand, etc.) مبنية على مبدأ اللامتغيرية. فهو يجعل إدارة الحالة المركزية للتطبيق أكثر قوة وأقل عرضة للأخطاء.

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

  • ابدأ بالبسيط: لا تحتاج إلى مكتبة معقدة من اليوم الأول. ابدأ بتطبيق مبدأ اللامتغيرية يدويًا باستخدام عامل النشر ... في JavaScript. هذا سيبني لديك العادة الصحيحة.
  • كن حذرًا من الكائنات المتداخلة: تذكر أن { ...original } يقوم بنسخ سطحي (Shallow Copy). إذا كان لديك كائن داخل كائن، فستحتاج إلى نسخه هو أيضًا بشكل صريح. هنا تتألق مكتبات مثل Immer.
  • في React، اجعل تحديث الحالة دائمًا لامُتغيرًا: بدلًا من استخدام myArray.push(newItem) ثم setState(myArray) (وهو خطأ شائع)، استخدم دائمًا setState(prevArray => [...prevArray, newItem]).
  • فكّر بدوال “نقية”: اجعل هدفك كتابة دوال تأخذ مدخلات وتُرجع مخرجات دون تعديل أي شيء خارجها. اللامتغيرية هي الأداة الرئيسية لتحقيق ذلك.

الخلاصة: من الفوضى إلى النظام 🚀

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

في المرة القادمة التي تجد فيها نفسك تلاحق خطأً غامضًا، اسأل نفسك: “هل يمكن أن يكون هناك جزء من الكود يعدل بياناتي بشكل غير متوقع؟”. تبني اللامتغيرية ليس مجرد تقنية، بل هو تغيير في طريقة التفكير يجعلك مبرمجًا أفضل وأكثر احترافية.

يلا، شدّوا حيلكم يا جماعة، وخلي كودكم دايماً نظيف ومستقر!

أبو عمر

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

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

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

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

آخر المدونات

أتمتة العمليات

بيئتنا التجريبية كانت شبحاً: كيف أنقذتنا ‘البنية التحتية كشيفرة’ (IaC) من جحيم ‘لكنها تعمل على جهازي’؟

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

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

تصاميمنا كانت جزرًا معزولة: كيف أنقذتنا ‘رموز التصميم’ (Design Tokens) من جحيم عدم الاتساق

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

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