هناك نصيحة في مجتمع TDD تقول
أننا لا يجب أن نستخدم كائنات وهمية لأنواع لا نمتلكها . أعتقد أن هذه نصيحة جيدة ، وأحاول اتباعها. بالطبع ، هناك أناس يقولون أنه لا ينبغي لنا استخدام الأشياء الوهمية على الإطلاق. بغض النظر عن رأيك ، فإن النصيحة "عدم تقليد ما ليس لك" تحتوي أيضًا على معنى مخفي. غالبًا ما يمر به الناس ، ويرون كلمة "وهمية" ويسقطون في غضب.
هذا المعنى الخفي هو أنه يجب عليك إنشاء واجهات وعملاء وجسور ومحولات بين تطبيقنا ورمز الطرف الثالث الذي نستخدمه. إن ما إذا كنا سنقوم بإنشاء كائنات وهمية لهذه الواجهات في اختباراتنا أم لا. الشيء المهم هو أننا ننشئ ونستخدم واجهات تفصل شفرتنا عن شفرة الطرف الثالث بشكل أفضل. مثال كلاسيكي على ذلك في عالم PHP هو إنشاء واستخدام عميل HTTP في تطبيقنا الذي يستخدم
عميل Guzzle HTTP ، بدلاً من استخدام Guzzle مباشرة.
لماذا؟ حسنًا ، بالنسبة للمبتدئين ، لدى Guzzle واجهة برمجة تطبيقات أقوى بكثير من تلك التي يحتاجها تطبيقك (في معظم الحالات). سيؤدي إنشاء عميل HTTP الخاص بك ، والذي يوفر فقط المجموعة الضرورية من واجهات برمجة تطبيقات Guzzle ، إلى تقييد مطوري التطبيقات بما يمكنهم فعله مع هذا العميل. إذا تغيرت واجهة برمجة تطبيقات Guzzle في المستقبل ، فسنحتاج إلى إجراء تغييرات في مكان واحد ، بدلاً من تصحيح مكالماتها في جميع أنحاء التطبيق على أمل ألا ينقطع شيء. سببين وجيهين ، وأنا حتى لم أذكر أشياء وهمية!
لا أعتقد أن هذا صعب التحقيق. عادةً ما يكمن رمز الطرف الثالث في مجلد منفصل من تطبيقنا ، وغالبًا ما يكون
vendor/
أو
library/
. كما أنها موجودة في مساحة اسم مختلفة ولها اصطلاح تسمية مختلف عما هو مستخدم في تطبيقنا. من السهل تحديد رمز الطرف الثالث إلى حد ما ، ومع القليل من الانضباط ، يمكننا جعل رمز التطبيق الخاص بنا أقل اعتمادًا على أجزاء الطرف الثالث.
ماذا لو طبقنا نفس القواعد على التعليمات البرمجية القديمة؟
ماذا لو نظرنا إلى الشفرة القديمة وكذلك الطرف الثالث؟ قد يكون من الصعب القيام بذلك ، أو حتى نتائج عكسية إذا تم استخدام رمز قديم حصريًا في وضع الدعم ، عندما نقوم فقط بإصلاح الأخطاء وتعديل أجزاء صغيرة منه قليلاً. ولكن إذا كتبنا كودًا جديدًا (يعيد استخدامه) قديمًا ، فأعتقد أنه يجدر النظر إليه بنفس الطريقة التي يستخدمها رمز الطرف الثالث. على الأقل من وجهة نظر القانون الجديد.
إذا أمكن ، يجب وضع الرمز القديم والجديد في مجلدات ومساحات أسماء مختلفة. لقد مر وقت طويل منذ آخر مرة رأيت فيها النظام
بدون بدء التشغيل ، لذلك هذا ممكن تمامًا. ولكن بدلاً من استخدام الرمز القديم في الرمز الجديد بشكل أعمى ، ماذا لو أنشأنا واجهات له واستخدمه؟
غالبًا ما تكون الشفرة القديمة مليئة بالأشياء "الإلهية" التي تقوم بأشياء كثيرة جدًا. إنهم يستخدمون دولة عالمية ، ولديهم خصائص عامة أو طرق سحرية تتيح الوصول إلى الممتلكات الخاصة كما لو كانت عامة ، ولديهم طرق ثابتة
مناسبة للغاية للاتصال بأي شخص وفي أي مكان. لذا فقد قادتنا هذه الراحة إلى الوضع الذي نحن فيه.
مشكلة أخرى ، ربما أكثر خطورة مع التعليمات البرمجية القديمة هي أننا على استعداد لتغييرها ، إصلاحها ، كسرها لأننا لا نعتبرها رمزًا لجهة خارجية. ماذا نفعل عندما نرى خطأ أو نريد إضافة ميزة جديدة إلى رمز طرف ثالث؟ نصف المشكلة و / أو ننشئ طلب سحب. ما
لا نقوم
به هو عدم الذهاب إلى
vendor/
المجلد ولا نقوم بتحرير الكود هناك. لماذا نفعل ذلك باستخدام الرمز القديم؟ ثم نعبر أصابعنا ونأمل ألا ينكسر شيء.
بدلاً من استخدام رمز قديم في الرمز الجديد بشكل عمياء ، دعنا نحاول كتابة واجهات ستتضمن فقط المجموعة الفرعية المطلوبة من واجهة برمجة التطبيقات للكائن "الإلهي" القديم. لنفترض أن لدينا كائن
User
في رمز قديم يعرف كل شيء عن كل شيء. إنه يعرف كيفية تغيير البريد الإلكتروني وكلمة المرور ، وكيفية ترقية مستخدمي المنتدى إلى مشرفين ، وكيفية تحديث ملفات تعريف المستخدمين العامة ، وتعيين تفضيلات الإخطار ، وحفظ نفسه في قاعدة البيانات والمزيد.
src / Legacy / User.php <?php namespace Legacy; class User { public $email; public $password; public $role; public $name; public function promote($newRole) { $this->role = $newRole; } public function save() { db_layer::save($this); } }
هذا مثال أولي ، ولكنه يعكس المشكلة: كل خاصية عامة ويمكن تغييرها بسهولة إلى أي قيمة ، نحتاج إلى تذكر استدعاء طريقة
save
صراحة بعد أي تغيير للحفظ ، إلخ.
دعونا نحد من أنفسنا ونمنع الوصول إلى هذه الممتلكات العامة ونحاول تخمين كيفية عمل نظام قديم مع زيادة حقوق المستخدم:
src / LegacyBridge / Promoter.php <?php namespace LegacyBridge; interface Promoter { public function promoteTo(Role $role); }
src / LegacyBridge / LegacyUserPromoter.php <?php namespace LegacyBridge; class LegacyUserPromoter implements Promoter { private $legacyUser; public function __construct(Legacy\User $user) { $this->legacyUser = $user; } public function promoteTo(Role $newRole) { $newRole = (string) $newRole;
الآن ، عندما نريد زيادة حقوق
User
في الشفرة الجديدة ، نستخدم واجهة
LegacyBridge\Promoter
، التي تتعامل مع جميع
LegacyBridge\Promoter
الدقيقة لزيادة مستخدم في نظام قديم.
تغيير لغة التراث
تتيح لنا واجهة التعليمات البرمجية القديمة الفرصة لتحسين تصميم النظام ويمكن أن تنقذنا من أخطاء التسمية المحتملة التي حدثت منذ فترة طويلة. عملية تغيير دور المستخدم من مشرف إلى مشارك ليست "ترقية" (زيادة) ، بل "تخفيض" (نقصان). لا أحد يمنعنا من إنشاء واجهتين لهذه الأشياء المختلفة ، حتى إذا كانت الشفرة القديمة تبدو متشابهة.
src / LegacyBridge / Promoter.php <?php namespace LegacyBridge; interface Promoter { public function promoteTo(Role $role); }
src / LegacyBridge / LegacyUserPromoter.php <?php namespace LegacyBridge; class LegacyUserPromoter implements Promoter { private $legacyUser; public function __construct(Legacy\User $user) { $this->legacyUser = $user; } public function promoteTo(Role $newRole) { if ($newRole->isMember()) { throw new \Exception("Can't promote to a member."); } $legacyMemberRole = 2; $this->legacyUser->promote($legacyMemberRole); $this->legacyUser->save(); } }
src / LegacyBridge / Demoter.php <?php namespace LegacyBridge; interface Demoter { public function demoteTo(Role $role); }
src / LegacyBridge / LegacyUserDemoter.php <?php namespace LegacyBridge; class LegacyUserDemoter implements Demoter { private $legacyUser; public function __construct(Legacy\User $user) { $this->legacyUser = $user; } public function demoteTo(Role $newRole) { if ($newRole->isModerator()) { throw new \Exception("Can't demote to a moderator."); } $legacyModeratorRole = 1; $this->legacyUser->promote($legacyModeratorRole); $this->legacyUser->save(); } }
لم يكن مثل هذا التغيير الكبير ، لكن الغرض من الكود أصبح أكثر وضوحًا.
الآن ، في المرة القادمة التي تحتاج فيها إلى استدعاء بعض الطرق من رمز قديم ، حاول إنشاء واجهة لهم. يمكن أن يكون مستحيلاً ، وقد يكون باهظ الثمن. أعلم أن الطريقة الثابتة لهذا الكائن "الإلهي" هي حقًا سهلة الاستخدام للغاية ، وبمساعدة مساعدتها يمكنك القيام بالمهمة بشكل أسرع ، ولكن على الأقل فكر في هذا الخيار. يمكنك فقط تحسين تصميم النظام الجديد الذي تقوم بإنشائه قليلاً.