تعلّم مبادئ باربرا ليسكوف في تجريد البيانات لتصميم واجهات ثابتة تقلّل الكسر، وتبني أنظمة قابلة للصيانة مع وعود واضحة وموثوقة.

باربرا ليسكوف عالِمة حاسوب شكلت صمتًا الطريقة التي تبني بها فرق البرامج الحديثة أشياء لا تنهار. بحثها عن تجريد البيانات وإخفاء المعلومات، ولاحقًا مبدأ استبدال ليسكوف (LSP)، أثّر على كل شيء من لغات البرمجة إلى الطريقة اليومية التي نفكّر بها في واجهات البرمجة: حدد سلوكًا واضحًا، احمِ الداخليّات، واجعل الاعتماد على واجهتك آمنًا للآخرين.
واجهة برمجة موثوقة ليست فقط "صحيحة" من ناحية نظرية. إنها واجهة تساعد المنتج على التحرك أسرع:
تلك الموثوقية هي تجربة: لمطوّر يستدعي واجهتك، للفريق الذي يصونها، وللمستخدمين الذين يعتمدون عليها بشكل غير مباشر.
تجريد البيانات فكرة بسيطة: يجب أن يتعامل المستدعون مع مفهوم (حساب، طابور، اشتراك) عبر مجموعة صغيرة من العمليات — لا عبر تفاصيل فوضوية عن كيفية تخزينه أو حسابه.
عندما تخفي تفاصيل التمثيل، تزيل فئات كاملة من الأخطاء: لا يمكن لأحد "بالخطأ" الاعتماد على حقل قاعدة بيانات لم يُقصَد أن يكون عامًا، أو تغيير حالة مشتركة بطريقة لا يستطيع النظام التعامل معها. والأهم من ذلك، أن التجريد يقلّل من عبء التنسيق: لا تحتاج الفرق إذنًا لإعادة تنظيم الداخل طالما بقي السلوك العام ثابتًا.
بنهاية المقال، ستحصل على طرق عملية لكي:
إن أردت ملخّصًا سريعًا لاحقًا، انتقل إلى /blog/a-practical-checklist-for-designing-reliable-apis.
تجريد البيانات فكرة بسيطة: تتفاعل مع شيء بناءً على ما يفعله، لا بكيف بُنِي.
تخيل آلة بيع: لا تحتاج أن تعرف كيف تدور المحركات أو كيف تُحصَى القطع المعدنية. كل ما تحتاجه هو الضوابط ("اختر السلعة"، "ادفع"، "استلم السلعة") والقواعد ("إذا دفعت ما يكفي تحصل على السلعة؛ إذا تم بيعها تُسترد أموالك"). هذا هو التجريد.
في البرمجيات، الواجهة هي "ما يفعله": أسماء العمليات، ما المُدخلات التي تقبلها، ما المخرجات، والأخطاء المتوقعة. التنفيذ هو "كيف يعمل": جداول قاعدة البيانات، استراتيجية التخزين المؤقت، الأصناف الداخلية، وحيل الأداء.
فصل هذين يساعدك على الحصول على واجهات تبقى ثابتة حتى مع تطور النظام. يمكنك إعادة كتابة الداخل، تبديل المكتبات، أو تحسين التخزين — بينما تبقى الواجهة كما هي للمستخدمين.
نوع البيانات التجريدي هو "حاوية + عمليات مسموح بها + قواعد"، موصوفة دون الالتزام ببنية داخلية محددة.
مثال: Stack (آخر داخل، أول خارج).
push(item): إضافة عنصرpop(): إزالة وإرجاع آخر عنصر أُضيفpeek(): إلقاء نظرة على العنصر الأعلى دون إزالتهالمهم هو الوعد: pop() تُعيد آخر push(). سواء استخدم الستاك مصفوفة أو قائمة مرتبطة أو شيء آخر فهو أمر داخلي خاص.
ينطبق نفس الفصل في كل مكان:
POST /payments هي الواجهة؛ فحوص الاحتيال، إعادة المحاولات، وكتابات قاعدة البيانات هي التنفيذ.client.upload(file) هي الواجهة؛ التجزئة، الضغط، والطلبات المتوازية هي التنفيذ.عندما تصمم بالتجريد، تركز على العقد الذي يعتمد عليه المستخدم—وتمنح نفسك الحرية لتغيير كل شيء خلف الستار دون كسرهم.
الثابت هو قاعدة يجب أن تكون صحيحة دائمًا داخل التجريد. عند تصميم واجهة، الثوابت هي الحواجز التي تمنع بياناتك من الانجراف إلى حالات مستحيلة — مثل حساب بنكي بعملتين في نفس الوقت، أو طلب "مكتمل" بلا عناصر.
اعتبر الثابت "شكل الواقع" لنوعك:
Cart لا يمكن أن يحتوي على كميات سالبة.UserEmail دومًا عنوان بريد إلكتروني صالح (لا "يُحقَّق لاحقًا").Reservation لها start < end، وكلا الوقتين في نفس المنطقة الزمنية.إذا توقفت تلك التصريحات عن أن تكون صحيحة، يصبح نظامك غير متوقَّع، لأن كل ميزة الآن يجب أن تخمن ماذا يعني وجود بيانات "معطوبة".
واجهات جيدة تفرض الثوابت عند الحدود:
هذا يحسّن معالجة الأخطاء تلقائيًا: بدلًا من فشل غامض لاحقًا ("حدث خطأ ما"), يمكن للواجهة أن تشرح أية قاعدة انتُهكت ("النهاية يجب أن تكون بعد البداية").
لا يجب على المستدعين حفظ قواعد داخلية مثل "تعمل هذه الطريقة فقط بعد استدعاء normalize()". إذا كان الثابت يعتمد على طقس خاص، فهو ليس ثابتًا — إنه فتيل خطأ.
صمم الواجهة بحيث:
عند توثيق نوع في الواجهة، اكتب:
الواجهة الجيدة ليست مجموعة دوال فقط — إنها وعد. العقود تجعل ذلك الوعد صريحًا، حتى يعتمد المستدعون السلوك ويغيّر المطوّرين الداخل دون مفاجآت.
على الأقل، وثّق:
هذه الوضوحية تجعل السلوك قابلًا للتوقّع: يعرف المستدعون ما المدخلات الآمنة وما النتائج التي يجب التعامل معها، والاختبارات يمكنها فحص الوعد بدل التخمين.
بدون عقود، تعتمد الفرق على الذاكرة والأعراف غير الرسمية: "لا تمرر null هنا"، "هذه الاستدعاء يعيد المحاولات أحيانًا"، "ترجع قيمة فارغة عند الخطأ". تلك القواعد تضيع أثناء الانضمام أو إعادة التنظيم أو الحوادث.
العقد المكتوب يحوّل تلك القواعد المخفية إلى معرفة مشتركة. كما أنه يخلق هدفًا مستقرًا لمراجعات الكود: تصبح المناقشات "هل تغيّر هذا لا يزال يلبّي العقد؟" بدلًا من "اشتغل معي".
غامض: "ينشئ مستخدمًا."
أفضل: "ينشئ مستخدمًا ببريد إلكتروني فريد.
email يجب أن يكون عنوانًا صالحًا؛ المستدعي يجب أن يمتلك إذن users:create.userId الجديد؛ المستخدم محفوظ ويمكن استرجاعه فورًا.409 إذا كان البريد موجودًا؛ يعيد 400 للحقول غير الصالحة؛ لا يتم إنشاء مستخدم جزئيًا."غامض: "يحصل على العناصر بسرعة."
أفضل: "يعيد حتى limit من العناصر مرتبة حسب createdAt تنازليًا.
nextCursor للصفحة التالية؛ تنتهي صلاحية المؤشرات بعد 15 دقيقة."إخفاء المعلومات هو الجانب العملي لتجريد البيانات: يجب أن يعتمد المستدعون على ما تفعله الواجهة لا على كيف تفعل ذلك. إذا لم يرَ المستخدمون الداخليّات، يمكنك تغييرها دون تحويل كل إصدار إلى تغيير كاسح.
واجهة جيدة تنشر مجموعة صغيرة من العمليات (create, fetch, update, list, validate) وتحافظ على التمثيل—جداول، مخابئ، قوائم انتظار، تخطيطات ملفات—خاصة.
مثال: "أضف عنصرًا إلى السلة" عملية؛ "CartRowId" من قاعدتك هو تفصيل تنفيذ. عند كشف التفصيل، تدعو المستخدمين لبناء منطق خاص بهم حوله، مما يجمد قدرتك على التغيير.
عندما تعتمد العملاء فقط على السلوك الثابت، يمكنك:
... وتبقى الواجهة متوافقة لأن العقد لم يتحرك. هذه هي الفائدة الحقيقية: ثبات للمستخدمين، وحرية للمطوّرين.
بعض الطرق التي تهرب بها الداخليّات:
status=3 بدلًا من اسم واضح أو عملية مخصصة.فضّل الاستجابات التي تصف المعنى، لا الميكانيكا:
"userId": "usr_…") بدلًا من أرقام صفوف.إذا كان التفصيل قد يتغير، لا تنشره. إذا احتاج المستخدمون إليه، قم بترقيته إلى جزء معلن ومتوثّق من عقد الواجهة.
مبدأ استبدال ليسكوف (LSP) بجملة واحدة: إذا كان الشيفرة تعمل مع واجهة، فيجب أن تستمر العمل عند استبدال أي تنفيذ صالح لتلك الواجهة—دون حالات خاصة.
LSP أقل عن الوراثة وأكثر عن الثقة. عندما تنشر واجهة، فأنت تقدم وعدًا حول السلوك. يقول LSP إن كل تنفيذ يجب أن يحافظ على ذلك الوعد، حتى لو استخدم نهجًا داخليًا مختلفًا تمامًا.
يعتمد المستدعون على ما تقوله واجهتك — لا على ما تفعله بالصدفة اليوم. إذا قالت الواجهة "يمكنك استدعاء save() بأي سجل صالح"، فيجب على كل تنفيذ قبول تلك السجلات. إذا قالت الواجهة "get() يرجع قيمة أو نتيجة 'غير موجود' واضحة"، فلا يَجُب على أي تنفيذ أن يرمي أخطاء جديدة أو يرجع بيانات جزئية بلا توضيح.
التمديد الآمن يعني أنك تستطيع إضافة تنفيذات جديدة أو تبديل مزوّدين دون إجبار المستخدمين على إعادة كتابة الشيفرة. هذه هي الفائدة العملية لـ LSP: تبقي الواجهات قابلة للاستبدال.
طريقتان شائعتان تكسران الوعد:
مدخلات أضيق (شروط مسبقة أشد): تنفيذ جديد يرفض مدخلات كانت الواجهة تسمح بها.
مخرجات أضعف (شروط لاحقة أضعف): تنفيذ جديد يرجع أقل مما وُعِد به؛ مثلاً الواجهة تقول النتائج مرتبة وفريدة، لكن تنفيذًا ما يرجع بيانات غير مرتبة أو مكررة أو يسقط عناصر بصمت.
انتهاك ثالث دقيق هو تغيير سلوك الفشل: إذا رجع تنفيذ "غير موجود" بينما تنفيذ آخر يرمي استثناءً لنفس الحالة، لا يستطيع المستدعي استبدال واحد بالآخر بأمان.
لدعم "الإضافات" (plug-ins)، اكتب الواجهة كعقد:
إذا احتاج تنفيذ لقيود أشد، لا تخفها خلف نفس الواجهة. إما (1) عرّف واجهة منفصلة، أو (2) اجعل القيد صريحًا كقدرة (مثلاً supportsNumericIds() أو شرط تكوين موثق). هكذا يختار العملاء الاشتراك عن معرفة، بدلًا من مفاجأتهم بتنفيذ "غير قابل للاستبدال".
لقد شاعَت مبادئها في تجريد البيانات وإخفاء المعلومات، وهي تنطبق مباشرة على تصميم واجهات البرمجة الحديثة: انشر عقدًا صغيرًا ومستقرًا واحتفظ بالمرونة في التنفيذ. الفائدة عملية: تغييرات أقل تكسرًا، إعادة تنظيم داخلي أسهل، وتكامُلات أكثر قابلية للتوقع.
واجهة برمجة موثوقة هي واجهة يمكن للمتصلين الاعتماد عليها عبر الزمن:
الموثوقية ليست أن النظام لا يفشل أبدًا، بل أن يفشل بطريقة متوقعة وتراعي العقد.
اكتب السلوك كـ عقد:
ضمّن حالات الحافة (نتائج فارغة، تكرارات، الترتيب) كي يتمكن المستدعون من اختبار وتنفيذ السلوك المتوقَّع.
الثابت (invariant) قاعدة يجب أن تبقى صحيحة داخل التجريد (مثال: الكمية لا تصبح سالبة). نفّذ الثوابت عند الحدود:
normalize() أولًا".هذا يقلل الأخطاء اللاحقة لأن بقية النظام لا تحتاج للتعامل مع حالات مستحيلة.
إخفاء المعلومات يعني إظهار العمليات والمعنى، لا تمثيل التنفيذ الداخلي. تجنّب ربط المستهلكين بأشياء قد تغيرها لاحقًا (جداول، ذاكرات مؤقتة، مفاتيح شارد).
تكتيكات عملية:
usr_...) بدل أرقام صفوف قاعدة البيانات.status=3).لأن الاعتماد على مفاهيم قاعدة البيانات يُجمِّد التنفيذ. إذا اعتمد العملاء على عوامل تصفية على شكل جدول أو على مفاتيح ربط، سيتحول تعديل المخطط إلى تغيير مكسور للواجهة.
فضّل الأسئلة ذات النطاق الدوميني: "الطلبات لعميل في نطاق تواريخ" بدلًا من أسئلة تخزينية.
يعني LSP: إذا عمل الكود مع واجهة ما، فعليه أن يظل يعمل مع أي تنفيذ صالح لتلك الواجهة بدون حالات خاصة. بمصطلحات API، هو قاعدة "لا تفاجئ المستدعي".
لدعم تنفيذية قابلة للاستبدال، قُم بتوحيد:
انتبه إلى:
إذا احتاج تنفيذ لقيود إضافية، أعلن واجهة منفصلة أو قدرة صريحة ليختار العميل الانضمام عن علم.
حافظ على الواجهات صغيرة ومتناسقة:
options: any وكمّات البوليانات التي تخلق توليفات غامضة.صمّم الأخطاء كجزء من العقد:
الثبات أهم من الآلية بالذات (استثناءات مقابل أنواع نتيجة) طالما أن المستدعين يمكنهم التنبؤ والتعامل مع النتائج.
reservereleaselistvalidateإذا كانت هناك أدوار مختلفة أو معدلات تغيير متباينة، قسّم الموارد/الوحدات.