مقدمة: لماذا إعادة هيكلة الكود القديم بلغة Swift هي فن
العمل مع الكود القديم بلغة Swift هو أحد أصعب وأهم المهام لمطور iOS/macOS. غالبًا ما تحتوي المشاريع القديمة على "كائنات ضخمة" (God objects)، وتبعيات زائدة عن UIKit، وغياب الاختبارات، وهندسة معمارية فوضوية. إعادة الهيكلة المتقدمة ليست مجرد إعادة تسمية المتغيرات، بل تحولًا منهجيًا لقاعدة الكود مع الحفاظ على 100% من الوظائف. في هذه المقالة، سنناقش التقنيات التي ستساعد في تحويل الفوضى إلى بنية قابلة للصيانة.
1. التقسيم إلى وحدات: تفكيك الكتلة الواحدة إلى مكونات
الخطوة الأولى نحو كود منظم هي استخراج وحدات مستقلة. بدلاً من هدف واحد ضخم، نقوم بإنشاء أطر عمل منفصلة أو وحدات Swift Package Manager.
تحديد حدود الوحدة
نستخدم مبدأ "التماسك العالي، الاقتران المنخفض". مثال: استخراج طبقة طلبات الشبكة، طبقة منطق الأعمال، وطبقة واجهة المستخدم.
// قبل: كل شيء في ملف ViewController واحدclass LegacyViewController: UIViewController { func fetchData() { // كود URLSession هنا } func parseData() { // تحليل JSON هنا }}
// بعد: وحدة Networkpublic class NetworkService { public func fetchData(completion: @escaping (Result<Data, Error>) -> Void) { // طبقة شبكة نظيفة }}الترحيل عبر الواجهات (Facades)
لا تكسر الكود الموجود: أنشئ واجهة (Facade) تقوم بتفويض الاستدعاءات إلى الوحدة الجديدة. استبدل الاستدعاءات القديمة تدريجيًا.
class LegacyNetworkFacade { private let newService = NetworkService() @objc func oldFetchMethod() { newService.fetchData { result in // التكيف مع API القديم } }}2. حقن التبعيات (DI) بدلاً من الكائنات المفردة العالمية
غالبًا ما يعاني الكود القديم من الكائنات المفردة: UserManager.shared, APIClient.shared. هذا يجعل الاختبار مستحيلاً. ننتقل إلى حقن التبعيات.
الحقن اليدوي عبر المُهيئ (Initializer)
// المشكلةclass OldViewModel { let api = APIClient.shared let user = UserManager.shared}
// الحلprotocol APIClientProtocol { /* ... */ }protocol UserManagerProtocol { /* ... */ }
class NewViewModel { private let api: APIClientProtocol private let userManager: UserManagerProtocol init(api: APIClientProtocol, userManager: UserManagerProtocol) { self.api = api self.userManager = userManager }}دمج حاوية حقن التبعيات (Swinject/Resolver)
للمشاريع الكبيرة، نستخدم المكتبات. مثال مع Resolver:
import Resolver
extension Resolver: ResolverRegistering { public static func registerAllServices() { register { NetworkService() as NetworkServiceProtocol } register { UserDefaultsStorage() as StorageProtocol } }}
class RefactoredViewModel { @Injected private var network: NetworkServiceProtocol @Injected private var storage: StorageProtocol}3. الاختبار: إعادة الهيكلة تحت حماية الاختبارات
بدون اختبارات، إعادة الهيكلة هي لعبة روليت روسية. نقوم بإنشاء "هيكل اختباري" (characterization tests) للكود القديم.
اختبارات التوصيف (Characterization tests) للكود القديم
نسجل السلوك الحالي كاختبارات، حتى لو لم يكن مثاليًا.
class LegacyCalculatorTests: XCTestCase { func testLegacyMultiply() { // تثبيت السلوك الحالي let result = LegacyCalculator().multiply(2, 3) XCTAssertEqual(result, 6) // حتى لو كان المنطق غريبًا }}إعادة الهيكلة المتوازية مع النموذج الذهبي (Golden Master)
مقارنة مخرجات التنفيذ القديم والجديد. نستخدم XCTAssertEqual للتحقق من التطابق.
func testRefactoredMatchesL