مقدمة: لماذا إعادة هيكلة الكود القديم (Legacy) في Swift؟
العمل مع الكود القديم (legacy) هو جزء لا مفر منه في حياة مطور iOS/macOS. المشاريع المكتوبة بإصدارات Swift المبكرة (2.x, 3.x) أو حتى Objective-C غالبًا ما تحتوي على كلاسات ضخمة (monolithic)، وتكرار في المنطق، وغياب النمطية (modularity). بدون إعادة هيكلة (refactoring)، يصبح هذا الكود مصدرًا للأخطاء (bugs) ويبطئ إضافة الميزات الجديدة.
في هذه المقالة، سنستعرض استراتيجيات عملية لإعادة هيكلة الكود القديم في Swift. ستتعلم كيفية تحسين البنية (architecture) بأمان دون تعطيل الوظائف الحالية. جميع الأمثلة قابلة للتطبيق على تطبيقات iOS و macOS.
1. التحليل والتحضير: من أين نبدأ؟
قبل تغيير الكود، يجب فهم حالته الحالية. بدون تحليل، فإنك تخاطر بتعطيل وظائف تعمل بشكل صحيح.
1.1. التغطية بالاختبارات (Characterization Tests)
الخطوة الأولى هي كتابة اختبارات توصيفية (characterization tests). هذه الاختبارات تثبت السلوك الحالي للكود، حتى لو بدا خاطئًا. فقط بعد ذلك يمكنك إجراء التغييرات بأمان.
// مثال لدالة قديمة: UserManager.swiftclass UserManager { func fetchUser(by id: Int) -> String { // تنفيذ قديم مع force unwrap let dict = ["id": id, "name": "John"] as [String : Any] return dict["name"] as! String }}
// اختبار توصيفيimport XCTestclass UserManagerTests: XCTestCase { func testFetchUserReturnsName() { let manager = UserManager() let result = manager.fetchUser(by: 1) XCTAssertEqual(result, "John") }}1.2. جرد "الممارسات السيئة"
قم بإعداد قائمة بالأنماط (patterns) الإشكالية في الكود:
- Force unwrapping (
!) — المصدر الرئيسي للانهيارات (crashes). - الكلاسات المفردة العالمية (Global Singletons) — تعيق الاختبار.
- الأرقام والنصوص السحرية (Magic Numbers/Strings) — تقلل قابلية القراءة.
- الدوال الطويلة (أكثر من 50 سطرًا) — تنتهك مبدأ المسؤولية الواحدة (SRP).
- غياب الأنواع (استخدام
Any).
2. إعادة هيكلة تدريجية: من البسيط إلى المعقد
من الأفضل إعادة هيكلة الكود القديم بخطوات صغيرة. يجب أن يكون كل تغيير قابلاً للتراجع ومصحوبًا باختبارات.
2.1. التخلص من Force Unwrapping
استبدل ! ببنى آمنة: guard let أو if let.
// قبل إعادة الهيكلةclass NetworkService { func loadData() -> Data { let url = URL(string: "https://api.example.com")! return try! Data(contentsOf: url) }}
// بعد إعادة الهيكلةclass NetworkService { enum NetworkError: Error { case invalidURL case dataLoadingFailed } func loadData() throws -> Data { guard let url = URL(string: "https://api.example.com") else { throw NetworkError.invalidURL } do { return try Data(contentsOf: url) } catch { throw NetworkError.dataLoadingFailed } }}2.2. تفكيك الكلاسات "العملاقة" (God Classes)
غالبًا ما تنتهك الكلاسات القديمة مبدأ المسؤولية الواحدة (SRP). قم بتقسيمها إلى كيانات صغيرة.
// المشكلة: الكلاس يفعل كل شيءclass ProfileViewController: UIViewController { func fetchProfile() { /* طلب شبكة */ } func parseJSON() { /* تحليل */ } func updateUI() { /* تحديث واجهة المستخدم */ } func saveToCoreData() { /* حفظ */ }}
// الحل: التقسيم إلى وحدات (modules)protocol ProfileRepository { func fetchProfile(completion: @escaping (Result<Profile, Error>) -> Void)}
class ProfileNetworkRepository: ProfileRepository { func fetchProfile(completion: @escaping (Result<Profile, Error>) -> Void) { // منطق الشبكة