يا مية أهلاً وسهلاً فيكم يا جماعة. قبل كم سنة، كنت شغال على مشروع “محرز” لتطبيق تجارة إلكترونية، وكان فيه كل “الحبشكلات” اللي ممكن تتخيلوها. الفريق كان شاطر، والقهوة ما كانت توقف، والأمور ماشية زي الحلاوة… أو هيك كنا مفكرين.
المشكلة بلشت تظهر في جزئية بسيطة ومهمة: سلة المشتريات. كنا نضيف منتج، أحياناً ينضاف وأحياناً لأ. نغير الكمية، ترجع للقيمة القديمة بعد ثانية. والسيناريو الأسوأ كان لما العميل يوصل لصفحة الدفع ويلاقي سلة مشترياته فاضية! كانت كارثة حقيقية، وبدينا نسمع من خدمة العملاء جمل زي “يا جماعة التطبيق بخرفن لحاله”.
قضينا أيام، وليالي والله، ونحنا بنبَحْلِق في الكود. كل واحد فينا كان يشك في الثاني: “أكيد فلان عدّل على الـ object تبع السلة في مكان ما ونسي يحكيلنا”. “لأ، المشكلة من الـ API برجع بيانات غلط”. الجو صار متكهرب، والثقة في الكود صارت في الحضيض. كل ما نصلح شغلة، تخرب عشرة غيرها. كنا حرفياً في جحيم اسمه “الآثار الجانبية الخفية” (Hidden Side Effects).
بعد ما قربنا نستسلم، وفي ليلة من ليالي “الديباجينج” الطويلة، واحد من الشباب صاح فينا: “يا جماعة، إحنا ليش بنضل نعدّل على نفس الأوبجكت؟ ليش ما كل تعديل يولّد نسخة جديدة من السلة؟”. وقتها، زي اللي ولعت لمبة فوق راسي. المشكلة ما كانت فينا، المشكلة كانت في “فلسفتنا” في التعامل مع البيانات. كنا بنسمح لأي حدا يغير “الحالة” (State) الأصلية، وهاد كان أصل البلاء. لما غيرنا الكود ليتبع مبدأ “اللامتغيرية” (Immutability)، اختفت 90% من المشاكل كأنها سحر.
هالقصة يا جماعة الخير، مش مجرد فضفضة… هاي مدخل لواحد من أهم المفاهيم في البرمجة الحديثة اللي بتريح الراس وبتخلي كودك أنظف وأقوى.
شو يعني “لامتغيرية” (Immutability) بالضبط؟
ببساطة شديدة، “اللامتغيرية” هي مبدأ بقول إنه بمجرد ما تنشئ متغير أو كائن (Object)، ما بتقدر تغير حالته أو محتواه الداخلي أبداً. خلص، انختم على هيك.
طيب، إذا بدي أعدّل عليه؟
هون الفكرة كلها. بدل ما “تعدّل” عليه، أنت بتعمل “نسخة” جديدة منه مع التعديلات اللي بدك إياها. الأصل بضل زي ما هو، سليم معافى، ما حدا بلمسه.
خلونا ناخذ مثال بسيط من حياتنا. فكر في الأمر كالتالي:
- الكائن القابل للتغيير (Mutable): هو زي لوح أبيض (Whiteboard). بتقدر تكتب عليه، تمسح، وتعدل عليه كيف ما بدك. أي حدا بمر من جنبه بيقدر يغير المكتوب.
- الكائن اللامتغير (Immutable): هو زي كتاب مطبوع. بمجرد ما انطبع، ما بتقدر تغير الكلام اللي فيه. إذا لقيت فيه خطأ أو حبيت تضيف فصل جديد، لازم تطبع “نسخة جديدة” من الكتاب مع التعديلات. النسخة القديمة بتضل زي ما هي.
في البرمجة، الاعتماد على “الألواح البيضاء” (Mutable Objects) في كل مكان هو اللي سبب لنا الكارثة في قصة سلة المشتريات.
جحيم التغيير: الآثار الجانبية (Side Effects)
المشكلة الكبرى في الكائنات القابلة للتغيير هي أنها تفتح الباب على مصراعيه لشيء اسمه “الآثار الجانبية” (Side Effects). الأثر الجانبي هو لما دالة (Function) معينة، بالإضافة لعملها الأساسي، بتروح بتغير شيء خارج نطاقها الخاص. يعني “بتحشر أنفها” في شغل غيرها.
مثال عملي على كارثة “التغيير”
تخيل عندك دالة بسيطة وظيفتها ترتب قائمة أسعار وتطلعلك أعلى سعر. شوف هاد الكود (JavaScript):
// قائمة الأسعار الأصلية
const originalPrices = [10, 50, 20, 5];
function sortAndGetHighestPrice(prices) {
// هذه الدالة تغير المصفوفة الأصلية! (Mutable operation)
prices.sort((a, b) => a - b);
return prices[prices.length - 1];
}
const highestPrice = sortAndGetHighestPrice(originalPrices);
console.log(`أعلى سعر هو: ${highestPrice}`); // "أعلى سعر هو: 50"
console.log(`قائمة الأسعار الأصلية بعد الدالة: ${originalPrices}`);
// المفاجأة: "قائمة الأسعار الأصلية بعد الدالة: 5,10,20,50"
شفتوا المصيبة؟ دالة sortAndGetHighestPrice ما اكتفت بعملها، بل راحت وغيرت المصفوفة الأصلية originalPrices. الآن، أي جزء آخر من البرنامج كان يعتمد على الترتيب الأصلي لهي القائمة رح “يتخرفن” ويشتغل غلط، ورح تقضي ساعات تدور على السبب.
كيف نطبق اللامتغيرية؟ (مع أمثلة كود)
الحل هو تبني عقلية “النسخ بدل التعديل”. خلينا نعيد كتابة المثال السابق بطريقة لامتغيرة (Immutable).
التعامل مع المصفوفات (Arrays)
بدل ما نستخدم دوال بتغير المصفوفة الأصلية (زي push, splice, sort)، بنستخدم دوال بترجع مصفوفة جديدة.
const originalPrices = [10, 50, 20, 5];
function immutableSortAndGetHighestPrice(prices) {
// 1. نعمل نسخة جديدة من المصفوفة باستخدام Spread Operator (...)
const newPrices = [...prices];
// 2. نرتب النسخة الجديدة
newPrices.sort((a, b) => a - b);
// 3. نرجع أعلى قيمة من النسخة الجديدة
return newPrices[newPrices.length - 1];
}
const highestPrice = immutableSortAndGetHighestPrice(originalPrices);
console.log(`أعلى سعر هو: ${highestPrice}`); // "أعلى سعر هو: 50"
console.log(`قائمة الأسعار الأصلية بعد الدالة: ${originalPrices}`);
// النتيجة السليمة: "قائمة الأسعار الأصلية بعد الدالة: 10,50,20,5"
لاحظ كيف بقيت المصفوفة الأصلية originalPrices سليمة ولم تتأثر. الكود صار يمكن التنبؤ به.
التعامل مع الكائنات (Objects)
نفس المبدأ ينطبق على الكائنات. بدل ما تغير خاصية في كائن مباشرة، أنشئ كائناً جديداً مع الخاصية المعدلة.
const user = {
id: 1,
name: "عمر",
role: "مبرمج"
};
// الطريقة الخطأ (Mutable)
function makeAdminMutable(user) {
user.role = "مدير"; // تم تغيير الكائن الأصلي مباشرة
return user;
}
// الطريقة الصحيحة (Immutable)
function makeAdminImmutable(user) {
// أنشئ كائناً جديداً، انسخ كل خصائص القديم، ثم غير ما تريد
return {
...user, // Spread operator for objects
role: "مدير"
};
}
const adminUser = makeAdminImmutable(user);
console.log(user); // { id: 1, name: "عمر", role: "مبرمج" } -> الكائن الأصلي لم يتغير
console.log(adminUser); // { id: 1, name: "عمر", role: "مدير" } -> الكائن الجديد مع التعديل
نصيحة من أبو عمر: الـ Spread Operator
(...)هو صديقك الصدوق في عالم اللامتغيرية في JavaScript. تعلم استخدامه جيداً للمصفوفات والكائنات، وستجد حياتك أصبحت أسهل بكثير.
فوائد اللامتغيرية اللي “بتريح الراس”
لما تتبنى هذا المبدأ، رح تلاحظ فوائد عظيمة على المدى الطويل:
- كود يمكن التنبؤ به (Predictable Code): أكبر فائدة على الإطلاق. بتعرف إنه البيانات ما رح تتغير تحت رجليك فجأة. بتصير قادر تتبع تدفق البيانات بسهولة، لأنه كل تغيير ينتج عنه متغير جديد باسم جديد.
- تصحيح أخطاء أسهل (Easier Debugging): لما يصير خطأ، ما في داعي تسأل “مين غيّر هاي القيمة؟”. بتقدر تشوف “تاريخ” الحالة (State) تبعك، لأن كل نسخة قديمة ما زالت موجودة (إذا احتفظت فيها). هذا هو أساس عمل أدوات رائعة مثل Redux DevTools اللي بتسمحلك تعمل “Time Travel Debugging”.
- أمان في البيئات متعددة الخيوط (Concurrency): إذا كان عندك كود بيشتغل على أكثر من معالج (CPU core) بنفس الوقت، مشاركة البيانات القابلة للتغيير هي وصفة للكوارث (Race Conditions). لكن إذا كانت البيانات لامتغيرة، فما في مشكلة لو ألف “خيط” قرأها بنفس اللحظة، لأنه ما حدا فيهم بيقدر يغيرها.
- تحسين الأداء (بشروط): قد يعتقد البعض أن إنشاء كائنات جديدة طوال الوقت مكلف من ناحية الذاكرة والأداء. هذا صحيح جزئياً، لكن المحركات الحديثة للغات البرمجة والمكتبات المتخصصة (مثل Immer.js أو Immutable.js) تستخدم تقنيات ذكية مثل “المشاركة الهيكلية” (Structural Sharing) بحيث لا يتم نسخ الكائن بأكمله، بل فقط الأجزاء التي تغيرت، مما يجعل العملية فعالة جداً.
خلاصة الكلام ونصيحة من القلب 🧑💻
اللامتغيرية هي أكثر من مجرد تقنية برمجية، هي “فلسفة” في التفكير. هي انتقال من عقلية “اذهب وغير هذا الشيء” إلى عقلية “صف لي كيف يبدو الشيء الجديد بعد التغيير”.
قد تبدو متعبة في البداية، وكأنها شغل زيادة. لكن صدقني، الوقت اللي رح توفره في تصحيح الأخطاء الغامضة، والراحة النفسية اللي رح تكسبها من وراء كود ثابت ويمكن التنبؤ به، لا تقدر بثمن.
نصيحتي الأخيرة: ابدأ صغيراً. لا تحاول تحويل كل مشروعك ليكون لامتغيراً بالكامل في يوم وليلة. ابدأ بتطبيقه على الأجزاء الحساسة من تطبيقك، مثل إدارة الحالة الرئيسية (Global State). استخدم المكتبات المساعدة عند الحاجة، وشيئاً فشيئاً، ستجد أن هذا الأسلوب أصبح طبيعتك الثانية.
في عالم البرمجة الفوضوي، اللامتغيرية هي طوق النجاة اللي بخلي كودك ثابت زي شجر الزيتون في أرضنا… ما بتغيره ريح.