الهياكل (Structs): تصميم أنواع بيانات مخصصة (مثل: كائن يمثل موظف أو منتج)

دقائق القراءة: 6

الهياكل (Structs): تصميم أنواع بيانات مخصصة (مثل: كائن يمثل موظف أو منتج)

عند بناء عقود Smart Contracts واقعية، لا تكفي أحياناً الأنواع البدائية مثل
uint
أو
address
أو
bool
وحدها. ففي التطبيقات اللامركزية الحقيقية نحتاج إلى تمثيل كيان مركّب مثل موظف، منتج، طلب شراء، أو سجل تصويت. هنا تأتي أهمية
Structs
في Solidity كوسيلة دقيقة لتجميع عدة خصائص داخل نوع بيانات واحد منظم.

إذا كنت قد اطّلعت سابقاً على
أساسيات لغة Solidity: أنواع البيانات والمتغيرات (State Variables)
فستلاحظ أن
Structs
تمثل خطوة منطقية تالية في نمذجة البيانات. وهي مهمة جداً أيضاً عند دمجها مع
المصفوفات (Arrays) في Solidity: تخزين وإدارة قوائم البيانات داخل العقد الذكي
أو
القواميس (Mappings): أسرع طريقة لربط عناوين المحافظ بأرصدتها (Key-Value)
لبناء أنظمة تخزين عملية وقابلة للتوسع داخل بيئة
EVM.

ما هي الهياكل Structs في Solidity؟

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

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

مثال ذهني سريع

  • الموظف قد يحتوي على: المعرف، الاسم، الراتب، عنوان المحفظة، هل هو نشط.
  • المنتج قد يحتوي على: المعرف، الاسم، السعر، المخزون، هل هو متاح للبيع.
  • الطلب قد يحتوي على: المشتري، معرف المنتج، الكمية، وقت الإنشاء، حالة التنفيذ.

لماذا تعتبر Structs مهمة في العقود الذكية؟

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

  1. تنظيم البيانات المركبة ضمن نموذج واحد مفهوم.
  2. تقليل الفوضى الناتجة عن كثرة المتغيرات المستقلة.
  3. تسهيل تمرير البيانات بين الدوال أو تخزينها داخل arrays و mappings.
  4. جعل منطق العقد أوضح أثناء التدقيق والمراجعة الأمنية.

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

الصياغة الأساسية لبنية Struct

يتم تعريف الهيكل داخل العقد باستخدام الكلمة المفتاحية
struct
ثم اسم الهيكل، وبعدها قائمة الحقول. كل حقل يملك نوع بيانات واسم متغير، تماماً كما في بقية تعريفات
Solidity.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract EmployeeRegistry {
    struct Employee {
        uint256 id;
        string name;
        uint256 salary;
        address wallet;
        bool isActive;
    }

    Employee public manager;
}

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

إنشاء وتحديث البيانات داخل Struct

يمكن تعبئة الهيكل عند الإنشاء أو بتحديث الحقول واحداً تلو الآخر. كما يمكن استخدامه داخل دوال القراءة والكتابة التي شرحنا منطقها في
الدوال (Functions) في Solidity: من يمكنه قراءة وتعديل بيانات العقد؟.
فيما يلي مثال عملي لسجل منتجات بسيط.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ProductCatalog {
    struct Product {
        uint256 id;
        string name;
        uint256 price;
        uint256 stock;
        bool available;
    }

    Product public featuredProduct;

    function setFeaturedProduct(
        uint256 _id,
        string memory _name,
        uint256 _price,
        uint256 _stock,
        bool _available
    ) public {
        featuredProduct = Product({
            id: _id,
            name: _name,
            price: _price,
            stock: _stock,
            available: _available
        });
    }

    function updateStock(uint256 _newStock) public {
        featuredProduct.stock = _newStock;
        featuredProduct.available = _newStock > 0;
    }

    function getProduct()
        public
        view
        returns (
            uint256,
            string memory,
            uint256,
            uint256,
            bool
        )
    {
        Product memory p = featuredProduct;
        return (p.id, p.name, p.price, p.stock, p.available);
    }
}

هنا استخدمنا أسلوب التهيئة المسماة
named arguments
عند إنشاء
Product.
وهذا أفضل من الترتيب الصامت للحقول في كثير من الحالات، لأنه يقلل احتمالات الخطأ عند تمرير القيم.

دمج Structs مع Mappings و Arrays

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

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract EmployeeSystem {
    struct Employee {
        uint256 id;
        string name;
        uint256 salary;
        address wallet;
        bool isActive;
    }

    mapping(uint256 => Employee) public employees;
    uint256[] public employeeIds;

    function addEmployee(
        uint256 _id,
        string memory _name,
        uint256 _salary,
        address _wallet
    ) public {
        require(employees[_id].wallet == address(0), "Employee already exists");

        employees[_id] = Employee({
            id: _id,
            name: _name,
            salary: _salary,
            wallet: _wallet,
            isActive: true
        });

        employeeIds.push(_id);
    }

    function deactivateEmployee(uint256 _id) public {
        require(employees[_id].wallet != address(0), "Employee not found");
        employees[_id].isActive = false;
    }
}

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

فهم storage و memory مع الهياكل

من أكثر النقاط أهمية عند التعامل مع الهياكل هو فهم موقع تخزين البيانات. الكلمة
storage
تشير إلى البيانات الدائمة على السلسلة، بينما
memory
تمثل نسخة مؤقتة أثناء تنفيذ الدالة.

إذا أنشأت متغيراً من نوع هيكل باستخدام
storage
فأنت تشير مباشرة إلى البيانات الأصلية، وأي تعديل سينعكس على الحالة المخزنة. أما إذا استخدمت
memory
فأنت تعمل على نسخة منفصلة لا تحفظ تلقائياً على السلسلة.

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

نصائح أمان وتحسين غاز عند تصميم Structs

بما أن كل بايت مخزن على السلسلة يكلّف رسوماً، فإن تصميم الهيكل يجب أن يكون مدروساً. هذا مرتبط مباشرة بما شرحناه في
التكاليف (Gas Fees): كيف يحسب البلوكتشين تكلفة تنفيذ الأكواد؟
وكذلك
أنواع الدوال : فهم view و pure لتوفير رسوم الـ Gas.

  • لا تضف حقولاً لا تحتاجها فعلاً داخل الهيكل.
  • استخدم أنواعاً أصغر فقط إذا كان ذلك مناسباً منطقياً ويساعد في packing.
  • تجنب تخزين سلاسل نصية طويلة إذا كان يمكن تمثيلها بمعرفات أو hashes.
  • تحقق من وجود السجل قبل تحديثه، خاصة عند استخدام mapping.

لتحسين استهلاك Gas، فكّر في ترتيب الحقول داخل Struct بطريقة تقلل عدد خانات التخزين المستخدمة. كما أن تقليل عمليات الكتابة إلى storage أهم غالباً من مجرد تقليل عدد الأسطر البرمجية.

تجربة عملية واختبار الهيكل

بعد كتابة العقد، يمكنك تجربة السلوك بسرعة عبر
محرر Remix IDE: كتابة ونشر أول عقد ذكي (Smart Contract) على المتصفح مباشرة
أو بيئة
Hardhat.
وعند الاختبار على الشبكات التجريبية تأكد أولاً من
إعداد بيئة التطوير: تثبيت محفظة MetaMask والاتصال بشبكات الاختبار (Testnets)
ثم
الحصول على عملات تجريبية مجانية (Faucet) للبدء في نشر واختبار العقود الذكية.

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

الخلاصة

تمثل
Structs
أحد أهم أدوات نمذجة البيانات في
Solidity،
لأنها تسمح بتصميم كيانات منطقية واضحة مثل الموظفين والمنتجات والطلبات. وعند دمجها مع
Mappings
و
Arrays
يمكنك بناء طبقة بيانات قوية ومرنة داخل العقد الذكي.

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

13 comments

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *