استخراج التوابع (Extract Methods)
المشكلة
وجود أجزاء من الشيفرة يُمكن عزلها وتجميعها سويةً.
الحل
نقل الشيفرة إلى تابعٍ (method) أو دالةٍ (function) جديدة والاستعاضة عن الجزء (بمكانه السابق) باستدعاءٍ لهذا التابع الجديد.
مثال
قبل إعادة التصميم
نلاحظ وجود جزء من الشيفرة لطباعة بعض البيانات (التفاصيل)، والتي يمكن عزلها بتابعٍ جديد، الشيفرة قبل إعادة التصميم بالشكل:
في لغة Java:
void printOwing() {
printBanner();
// طباعة التفاصيل
System.out.println("name: " + name);
System.out.println("amount: " + getOutstanding());
}
في لغة #C:
void PrintOwing()
{
PrintBanner();
// طباعة التفاصيل
Console.WriteLine("name: " + name);
Console.WriteLine("amount: " + GetOutstanding());
}
في لغة PHP:
function printOwing() {
$this->printBanner();
// طباعة التفاصيل
print("name: " . $this->name);
print("amount " . $this->getOutstanding());
}
في لغة Python:
def printOwing(self):
self.printBanner()
# طباعة التفاصيل
print("name:", self.name)
print("amount:", self.getOutstanding())
في لغة TypeScript:
printOwing(): void {
printBanner();
// طباعة التفاصيل
console.log("name: " + name);
console.log("amount: " + getOutstanding());
}
بعد إعادة التصميم
تصبح الشيفرة بعد إعادة التصميم بالشكل:
في لغة Java:
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + name);
System.out.println("amount: " + outstanding);
}
في لغة #C:
void PrintOwing()
{
PrintBanner();
PrintDetails(GetOutstanding());
}
void PrintDetails(double outstanding)
{
Console.WriteLine("name: " + name);
Console.WriteLine("amount: " + outstanding);
}
في لغة PHP:
function printOwing() {
$this->printBanner();
$this->printDetails($this->getOutstanding());
}
function printDetails($outstanding) {
print("name: " . $this->name);
print("amount " . $outstanding);
}
في لغة Python:
def printOwing(self):
self.printBanner()
self.printDetails(self.getOutstanding())
def printDetails(self, outstanding):
print("name:", self.name)
print("amount:", outstanding)
في لغة TypeScript:
printOwing(): void {
printBanner();
printDetails(getOutstanding());
}
printDetails(outstanding: number): void {
console.log("name: " + name);
console.log("amount: " + outstanding);
}
إذ نُقلت تعليمتَي الطباعة إلى تابعٍ جديدٍ باسم printDetails
(وهو اسم معبِّر ذو دلالة) مع تمرير معاملٍ (ناتجٍ عن الاستدعاء getOutstanding()
) لهذا التابع الذي يحتاج القيمة لطباعتها.
لم إعادة التصميم؟
تصعُب معرفة ما ينفِّذه التابع (method) باذدياد عدد أسطره وهذا هو السبب الرئيسيُّ لإعادة تصميمه، كما وتُعدُّ تقنية استخراج التابع (extract method) خطوةً جيّدةُ لتغيير الشيفرة لما هو أفضل.
فوائد تطبيق الحل
- الحصول على شيفرةٍ مقروءةٍ أكثر، وخاصّة بوجود اسمٍ معبِّرٍ ذي دلالةٍ بهدف التابع، مثل:
createOrder()
وrenderCustomerInfo()
و ...إلخ. - الحدّ من تكرار الشيفرة (duplication) في البرنامج، إذ من الممكن الاستفادة من الشيفرة الموجودة في التابع المُستحدَث في عدّة أجزاء أخرى من البرنامج عبر استدعاءاتٍ بسيطة تحلِّ محلها.
- عزل الأجزاء المستقلَّة من الشيفرة، وهذا يعني نسبةً أخفض من الأخطاء (وخاصّةً عند تعديل المتغيِّر الخطأ [wrong variable]).
آلية الحل
- إنشاء تابعٍ (method) جديدٍ وتسميته تسميةً معبِّرةً تدلُّ على محتواه والهدف منه.
- نسخ الشيفرة المطلوبة إلى ذلك التابع وحذفها من موضعها السابق وتبديلها هناك إلى استدعاءٍ للتابع الجديد.
- الانتباه للمتغيِّرات (variables) المُستخدَمة في تلك الشيفرة، فإن كانت مُعرَّفة بها وغير مُستخدَمةٍ خارجها فستبقى كما هي دون أيّ تعديلٍ لتصبح متغيِّرات محليّة (local variables) للتابع الجديد. أمّا إن كانت المتغيِّرات مُعرَّفة قبل الشيفرة المُستخرَجة فيجب عندئذٍ تمريرها كمعاملاتٍ (parameters) للتابع الجديد بهدف الحصول على قيمها المُخزَّنة مسبقًا، ومن الأفضل -ببعض الحالات- التخلُّص منها عبر تبديل المتغيِّر المؤقَّت (temp) إلى استدعاء (query).
- عندما تطرأ بعض التعديلات على المتعيِّر المحليّ (local variable) في الشيفرة المُستخرَجة فهذا يعني أن القيمة الجديدة المعدَّلة ضروريّة في مكانٍ ما آخر في التابع الرئيسيّ (main method)، فكن يقظًا بمثل تلك الحالة؛ إذ يجب أن تعيد القيمة الجديدة للمتغيِّر إلى التابع الرئيسي للحفاظ على سلامة أداء البرنامج.