الأسئلة الشائعة حول تعاون الفريق: العقود والتهيئة وملفات الاختبار
الخلاصة: نمط التسمية، وتنسيقات الوقت، ودلالات null، وأشكال enum، وتنسيقات التهيئة، وتنظيم fixtures، وتعارضات الدمج — اثنتا عشرة مقايضة يتجادل حولها الفرق فعلًا.

الخلاصة: احسم هذه الأمور مرة واحدة في اصطلاح للفريق — مغلّف {"data":…,"meta":…} مع أكواد حالة HTTP، ونمط تسمية واحد في الموقع كله، وطوابع زمنية RFC 3339 مع منطقة زمنية، وenums نصية — ثم طوّر كل API جديد وفق العقد نفسه بإضافة الحقول فقط، دون تغييرها أو حذفها أبدًا.
لا توجد إجابة صحيحة واحدة لهذه الأسئلة، لكن يجب أن يكون للفريق إجابة واحدة — وإلا فكل API جديد يعيد الجدل نفسه من البداية.
1. عقود API
أي بنية يجب أن يعيدها API؟
ثلاثة أشكال شائعة، لكل منها كلفة:
| الشكل | المثال | المزايا | العيوب |
|---|---|---|---|
| بيانات خام | [{...}, {...}] |
بسيط | لا يحمل بيانات وصفية |
| مغلّف ثابت | {"code":0,"message":"ok","data":{...}} |
معالجة أخطاء موحدة | كود الأعمال يكرر كود حالة HTTP |
| مغلّف موارد | {"data":{...},"meta":{...},"links":{...}} |
قابل للتوسيع، ودلالات واضحة | مستوى تداخل إضافي |
لـAPIs الأعمال الداخلية، يُفضَّل "مغلّف موارد + أكواد حالة HTTP تعبّر عن النتيجة":
{
"data": [{ "id": "1", "name": "示例" }],
"meta": { "total": 128, "page": 2, "pageSize": 20 }
}
للأخطاء، استخدم كود حالة HTTP مع جسم خطأ مهيكل بدلًا من تداخل code آخر:
{
"error": {
"type": "validation_failed",
"message": "请求参数校验失败",
"details": [
{ "path": "/email", "reason": "格式不正确" }
]
}
}
النقطة المهمة: أكواد حالة HTTP تعبّر أصلًا عن النجاح أو الفشل، فكود الأعمال زائد. عندما يتعايش النظامان، فالتعارضات مثل "201 لكن code هو 500" شبه حتمية.
تجنّب المصفوفات العارية. بمصفوفة في المستوى الأعلى لا يمكنك إرفاق التجزئة أو الإجماليات أو المؤشرات، وإضافتها لاحقًا تعني تغيير كل مستدعٍ.
هل تستخدم أسماء الحقول snake_case أم camelCase؟
اختر واحدًا، وطبّقه في الموقع كله، ولا تخلطهما أبدًا داخل وثيقة JSON واحدة.
| النمط | المنظومة الشائعة | المثال |
|---|---|---|
camelCase |
JavaScript / TypeScript / Java | userName، createdAt |
snake_case |
Python / Ruby / Go (جزئيًا) / أعمدة قواعد البيانات | user_name، created_at |
kebab-case |
نادر؛ المفاتيح الموصولة بشرطة يصعب الوصول إليها | user-name |
المعايير:
- من يستهلك هذا JSON: اختر camelCase عندما تهيمن الواجهة، وsnake_case عندما تهيمن خطوط البيانات وخدمات Python؛
- هل يُقابل أعمدة قاعدة البيانات مباشرة: snake_case يوفر طبقة تحويل عند تمرير بنية الجدول كما هي؛
- استمرارية الأنظمة القائمة: أبقِ ما يستخدمه API القائم؛ الاتساق يتفوق على الصواب.
إن اضطررت فعلًا إلى التحويل، فافعله عند الحدود (البوابة أو طبقة طلبات الواجهة) بدلًا من نثر استدعاءات toCamelCase في كود الأعمال.
أي تنسيق يجب أن تستخدم حقول الوقت؟
استخدم سلاسل RFC 3339 / ISO 8601 مع إزاحة منطقة زمنية:
{
"createdAt": "2026-10-30T11:37:57+08:00",
"updatedAt": "2026-10-30T03:37:57Z"
}
النقاط المهمة:
- ضمّن المنطقة الزمنية:
2026-10-30 11:37:57دون منطقة سينكسر حتمًا في السيناريوهات عبر المناطق الزمنية؛ - استخدم
Zأو إزاحة بثبات: كليهما صحيح، لكن لا تخلطهما داخل API واحد؛ - تجنّب الطوابع الزمنية الرقمية: سواء كانت ثواني أم مللي ثانية، وسواء شملت الثواني الكبيسة أم لا، كل فريق مضطر إلى إعادة تفسيرها. عند الحاجة إلى الاكتناز، استخدم عددًا صحيحًا بالمللي ثانية، لكن ثبّت الوحدة في الوثائق.
يُفضَّل استخدام أسماء حقول بلاحقة At مثل createdAt / updatedAt / deletedAt،
ليتضح من النظرة الأولى أن القيمة نقطة زمنية لا مدة. وللمدد استخدم timeoutMs وttlSeconds — ضع الوحدة في الاسم.
هل يجب أن تكون القيم الفارغة null أم حقلًا محذوفًا؟
يعتمد على هل "هذا الحقل ذو معنى":
| الدلالة | الشكل | ملاحظات |
|---|---|---|
| الحقل موجود لكن ليس له قيمة بعد | "name": null |
يقول صراحةً "فُحص وهو فارغ" |
| الحقل غير مرتبط بهذا المورد | الحذف | مثلًا، المستخدم العادي لا يجب أن يكون لديه vipExpireAt |
| تحديث جزئي (PATCH) | الحذف = لا تغيير | null فقط يعني "امسح" هنا |
الحالة الثالثة هي الأكثر إغفالًا. في طلب PATCH:
{ "nickname": null }
هذا يعني "امسح الكنية"، بينما ترك nickname خارج جسم الطلب يعني "لا تلمس الكنية".
إن عاملت كليهما كـ"امسح"، فمستخدم يغيّر بريده الإلكتروني سيمسح كنيته.
مرة أخرى: لا تستخدم undefined. إنه ليس قيمة JSON صالحة، وJSON.stringify يسقط المفتاح كاملًا،
فيتطابق السلوك مع "الحذف" بينما يبدو الكود كأن "قيمة فارغة مُررت صراحةً".
كيف يجب تسمية حقول boolean؟
استخدم البادئات is / has / can / should وتجنّب النفي.
{
"isActive": true,
"hasPassword": false,
"canEdit": true
}
ثلاثة دروس:
- لا نفي:
isDisabled: falseيفرض على القارئ قلبها مرة؛ وisEnabled: trueأكثر مباشرة. النفي المزدوج (isNotDisabled) كارثة؛ - لا تستخدم أعدادًا لـbooleans:
"enabled": 1يجعل كل مستهلك يكتب=== 1مرة؛ - لا تستخدم سلاسل نصية:
"enable": "true"صادقة في JavaScript، وif (data.enable)صحيحة أيضًا مع"false"؛ وهذه الأخطاء يصعب تتبعها جدًا.
هل تستخدم حقول enum أعدادًا أم سلاسل نصية؟
استخدم السلاسل النصية إلا إذا كان هناك قيد واضح في التخزين أو عرض النطاق.
{ "status": "active" }
مقارنةً بـ{ "status": 1 }، تفوز السلاسل النصية لأن:
- السجلات ولقطات الحزم وقواعد البيانات قابلة للقراءة مباشرة دون البحث في جدول تعيين؛
- إضافة قيمة enum لا تغيّر معنى القيم القائمة (إدراج قيمة في منتصف enum رقمي يزحزح كل شيء)؛
- الواجهة لا تحتاج إلى صيانة تعيين
1 → 'active'بشكل متزامن.
الكلفة بايتات إضافية قليلة، وهي شبه مهملة بعد الضغط.
الممارسة المرافقة: استخدم أحرفًا صغيرة + شرطات سفلية لقيم enum النصية (pending_review)، وقيّدها بـenum في Schema
حتى تُرفض القيم غير الصالحة في مرحلة التحقق.
2. التهيئة وfixtures
هل تكون التهيئة JSON أم YAML أم TOML؟
اختر حسب الغرض، لا حسب التفضيل:
| التنسيق | جيد لـ | سيئ لـ |
|---|---|---|
| JSON | القراءة والكتابة البرمجية، والبيانات الصارمة البنية، وتحقق Schema | التحرير اليدوي المتكرر (التعليقات غير مسموحة) |
| YAML | تهيئة النشر المتداخلة بعمق (K8s) | الإزاحة المكتوبة يدويًا عرضة للخطأ؛ وyes/no تتحول إلى booleans |
| TOML | تهيئة التبعيات والأدوات المسطحة (Cargo.toml، pyproject.toml) |
قابلية قراءة ضعيفة للتداخل العميق |
| JSONC | تهيئة المحرر التي تحتاج تعليقات (tsconfig.json) |
عبور حدود الأنظمة |
خطأ شائع في الفرق هو تخزين تهيئة الأعمال في YAML — فالتحويل النوعي الضمني في YAML (1.0 يصبح عددًا،
وNO يصبح boolean، و2026-10-30 يصبح تاريخًا) ينتج نتائج مختلفة بين المحللات.
إن كان يجب التحقق من التهيئة برمجيًا بصرامة، فاختر JSON + Schema، ووثّق التعليقات عبر حقل _comment أو وثائق منفصلة.
كيف يجب تنظيم fixtures الاختبار؟
نظّمها وفق "خط أساس واحد + فروق دنيا"، لا بنسخ JSON ضخم لكل حالة اختبار.
fixtures/
├─ users/
│ ├─ base.json # Baseline user object
│ ├─ no-email.json # Only email is missing
│ └─ invalid-age.json # Only age is out of range
└─ fixtures.ts # Utility functions for loading and shallow merging
import baseUser from './users/base.json';
export function makeUser(overrides: Partial<User> = {}): User {
return { ...baseUser, ...overrides };
}
بهذه الطريقة تحتاج حالة اختبار جديدة إلى كتابة الحقول المختلفة فقط، وتغيير حقل في خط الأساس يحدّث كل حالة تلقائيًا.
ثلاث قواعد:
- يجب أن تجتاز fixtures تحقق Schema: fixture خط الأساس نفسه يجب أن يكون بيانات صالحة؛
- لا تصدّر بيانات حقيقية من الإنتاج: أرقام الهواتف وأرقام الهوية والعناوين كلها معلومات شخصية؛
- fixtures مكانها التحكم في الإصدارات لكن يجب أن تبقى صغيرة: ملف واحد يتجاوز بضعة عشرات KB يستحق التقليم أو التوليد.
كيف أقلل تعارضات الدمج في ملفات JSON؟
JSON يتعارض بسهولة: المنسّق يعيد تدفق الملف كله دفعة واحدة، فيتعارض شخصان كل واحد يغيّر سطرًا على الملف كله.
أربع تكتيكات:
- وحّد التنسيق وأضفه إلى
.editorconfig: إزاحة ونهايات أسطر وسطر جديد ختامي متسقة تتجنب فروقًا غير مرتبطة؛ - رتّب المفاتيح: اتفق على الترتيب الأبجدي حتى لا يصطدم حقل جديد بنقطة إدراج شخص آخر؛
- قسّم الملفات: قسّم
config.jsonعملاقًا واحدًا إلى عدة ملفات حسب النطاق، فيتقلص سطح التعارض طبيعيًا؛ - تجنّب التزامات "أعد تنسيق كل شيء": افصل التنسيق عن تغييرات الأعمال في التزامين ليبقى المراجعة قابلة للقراءة.
إن كان الفريق يستخدم ملفات القفل (pnpm-lock.yaml، package-lock.json)، فلا تدمجها يدويًا؛ أعد توليدها بدلًا من ذلك:
git checkout --theirs pnpm-lock.yaml
pnpm install
كيف أحافظ على توافق الإصدارات؟
المبدأ هو "أضف، لا تغيّر، لا تحذف".
| التغيير | التوافق | المقاربة |
|---|---|---|
| إضافة حقل اختياري | متوافق | أضفه؛ المستهلكون يتجاهلون الحقول المجهولة |
| إضافة حقل مطلوب | غير متوافق | ارفع الإصدار، أو وفّر قيمة افتراضية |
| حذف حقل | غير متوافق | علّمه مهجورًا أولًا، وراقب المستدعين، ثم احذفه |
| تغيير نوع حقل | غير متوافق | أضف اسم حقل جديدًا واكتب في الاثنين خلال الانتقال |
| تغيير مجموعة قيم enum | يعتمد | إضافة القيم متوافقة؛ وإزالتها ليست كذلك |
الآليات الداعمة:
- يستخدم المستهلكون "تحليلًا متسامحًا": تجاهل الحقول المجهولة وتوفير قيم افتراضية للمفقودة؛
- يحتفظ الخادم بالحقول القديمة لفترة ويعيد ترويسة
Deprecationفي الرد كتلميح؛ - في Schema، لا تُعلّم الحقول الجديدة كـ
required، ووسم الحقول المهجورة بـdeprecated: true.
كيف أتحقق من العقود في CI؟
عامل Schema كجزء من الكود وتحقق منه في كل التزام.
# Validate sample data against the Schema (using ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false
شغّل ثلاثة أمور في CI:
- صلاحية Schema نفسه: هل إعلان
$schemaصالح وهل يُحلّ$ref؛ - هل تطابق fixtures الـSchema: يضمن ألّا تنحرف بيانات الاختبار؛
- هل العقد متوافق مع الإصدارات السابقة: قارن بالإصدار السابق وتحقق من الحقول المحذوفة أو الأنواع المتغيرة.
البند الثالث يحتاج إلى دعم أدوات (مثل openapi-diff أو json-schema-diff)؛
وبدونه، قارن على الأقل التغييرات في مصفوفات required ومجموعات مفاتيح properties بسكربت.
3. مرجع سريع
| السؤال | التوصية |
|---|---|
| بنية الرد | {"data":..., "meta":...}، مع أكواد حالة HTTP للأخطاء |
| نمط التسمية | camelCase (الواجهة أولًا) أو snake_case (البيانات أولًا)، متسق في الموقع كله |
| تنسيق الوقت | سلاسل RFC 3339 مع منطقة زمنية، وأسماء حقول تنتهي بـAt |
| القيم الفارغة | استخدم null لعدم القيمة، واحذف الحقول غير المرتبطة، والحذف في PATCH يعني "لا تغيير" |
| تسمية boolean | بادئات is/has/can، بلا نفي، بلا 0/1 |
| Enums | سلاسل نصية، أحرف صغيرة مع شرطات سفلية، وأضف enum في Schema |
| تنسيق التهيئة | JSON للتحقق البرمجي، وTOML أو JSONC للتحرير اليدوي |
| Fixtures | خط أساس واحد + تجاوزات فروق دنيا، صالحة وفق Schema |
| تعارضات الدمج | وحّد التنسيق، ورتّب المفاتيح، وقسّم الملفات |
| توافق الإصدارات | أضف، لا تغيّر، لا تحذف؛ وعلّم المهجور قبل الحذف |