Ders 05 / 19
Modülerleştirme
Uygulamanın paket sınırlarıyla modüllere bölünmesi, özellik modüllerinin yalnız çekirdek modüle bağlanması kuralı ve derlemenin geçmesine bakan denetimin sınır ihlaline karşı körlüğü.
Yayın Güncelleme
Bu derste
- Uygulamayı paket sınırlarıyla modüllere bölmenin neyi değiştirdiğini söyler
- Modül bağımlılıklarını bir grafik olarak kurup çözülüp çözülmediğini ve döngü olup olmadığını denetler
- Derlemenin geçmesine bakan denetimin modül sınırı ihlaline karşı kör kaldığını ölçer
- Değişen modülün yeniden derlenecek bağımlılarını iş birimi olarak sayıp bütçeye bağlar
İçindekiler
Dördüncü derste bağımlılığı dışarıdan vermeyi öğrendik: nesneler birbirini kurmak yerine bir sözleşmeyle tanır. Aynı fikir bir basamak yukarıda da geçerlidir. Uygulamanın tamamı tek bir hedefte duruyorsa her dosya her dosyayı görebilir; hiçbir kural bir görünümün ağ katmanını doğrudan çağırmasını ya da bir özelliğin başka bir özelliğin iç tiplerine uzanmasını engellemez.
Modülerleştirme kodu paketlere böler ve her paketin yalnız dışarıya açtığı yüzeyi göstermesini sağlar. Hangi modülün hangisine bağlanabileceği ise kuraldır. Bu dersin kuralı şudur: özellik modülleri yalnız çekirdek modüle bağlanır, birbirine bağlanmaz. Kusur bu kuralın çiğnenmesidir: bir özellik modülü başka bir özellik modülüne doğrudan bağlanır. Çekirdek önceki derslerdeki gibi birebir gelir.
// Separation.swift — bir denetimin dogru surumu kusurlu surumden ayirip ayirmadigini siniflandirir
struct Contrast<Output: CustomStringConvertible> {
let label: String
let correct: Output
let faulty: Output
let passesCorrect: Bool
let passesFaulty: Bool
var separation: String {
switch (passesCorrect, passesFaulty) {
case (true, false): return "ayiran"
case (true, true): return "kor"
case (false, false): return "gurultulu"
case (false, true): return "ters"
}
}
func describe() -> String {
let onCorrect = passesCorrect ? "gecti" : "kaldi"
let onFaulty = passesFaulty ? "gecti" : "kaldi"
print("\(label) | dogru=\(correct)/\(onCorrect) kusurlu=\(faulty)/\(onFaulty) -> \(separation)")
return separation
}
}
func contrast<Output>(_ label: String, correct: Output, faulty: Output, check: (Output) -> Bool) -> Contrast<Output> {
Contrast(label: label, correct: correct, faulty: faulty, passesCorrect: check(correct), passesFaulty: check(faulty))
}
func countSeparations(_ results: [String]) {
for name in ["ayiran", "kor", "gurultulu", "ters"] {
print("\(name): \(results.filter { $0 == name }.count)")
}
}
Çekirdek bu derste de yeniden tanımlanmaz. Ayrım adları aynıdır.
Paket Sınırı
Bir modül, paket yöneticisinin bir hedefidir. Hedefler arasındaki bağımlılıklar paket tanım dosyasında yazılır ve paket yöneticisi o dosyayı okuyarak hangi hedefin hangisini içe alabileceğine karar verir. Bir modülün internal bırakılan ögeleri başka modüllerden görünmez; public ya da open ile açılanlar sınırı geçer; aynı paketin modülleri arasında package da yeter.
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "Shop",
targets: [
.target(name: "Core"),
.target(name: "Feed", dependencies: ["Core"]),
.target(name: "Profile", dependencies: ["Core"]),
.target(name: "Settings", dependencies: ["Core"]),
.target(name: "App", dependencies: ["Feed", "Profile", "Settings", "Core"]),
]
)
Bu paket tanımı çalıştırılmaz; paket yöneticisi ve derleme platformun aracıdır ve ders için çıktı iddia etmez. Yapısı kuralı gösterir: Feed, Profile ve Settings yalnız Core’a bağlanır, App hepsini birleştirir. Kusurlu sürümde tek bir satır değişir.
.target(name: "Feed", dependencies: ["Core", "Profile"]),
Feed artık Profile’ı da içe alabilir. Derleme bu satırı reddetmez: Profile bir hedef olarak vardır, aralarında döngü yoktur. Bu dersin sorusu tam olarak buradadır: derleyicinin kabul ettiği bir bağımlılık, kuralın kabul ettiği bir bağımlılık olmayabilir.
Bağımlılık Grafiği
Model paket tanımını bir grafiğe indirger: her modülün adı ve bağlandığı modüllerin listesi. Grafik üç soruya yanıt verir: her bağımlılık gerçekten bir modüle işaret ediyor mu, grafikte döngü var mı ve bir modül değişince hangi modüller yeniden derlenmek zorunda. Üçüncü soru sınırlayıcı ölçümde kullanılır. Modules.swift bir yardımcı dosyadır ve çıktı vermez.
// Modules.swift — modul bagimlilik grafigi
struct ModuleGraph {
let edges: [String: [String]]
var resolves: Bool {
edges.values.allSatisfy { deps in deps.allSatisfy { edges[$0] != nil } }
}
var isAcyclic: Bool {
func visit(_ name: String, _ seen: Set<String>) -> Bool {
if seen.contains(name) { return false }
return (edges[name] ?? []).allSatisfy { visit($0, seen.union([name])) }
}
return edges.keys.allSatisfy { visit($0, []) }
}
func dependents(of module: String) -> [String] {
var found: Set<String> = []
var queue = [module]
while let current = queue.popLast() {
for (name, deps) in edges where deps.contains(current) && !found.contains(name) {
found.insert(name)
queue.append(name)
}
}
return found.sorted()
}
}
func shopGraph(faulty: Bool) -> ModuleGraph {
ModuleGraph(edges: [
"core": [],
"feed": faulty ? ["core", "profile"] : ["core"],
"profile": ["core"],
"settings": ["core"],
"app": ["feed", "profile", "settings", "core"],
])
}
func featureViolations(_ graph: ModuleGraph) -> String {
let broken = ["feed", "profile", "settings"].flatMap { feature in
(graph.edges[feature] ?? []).filter { $0 != "core" }.map { "\(feature)->\($0)" }
}
return broken.isEmpty ? "yok" : broken.joined(separator: ",")
}
resolves her bağımlılığın grafikte bir modül olarak bulunduğunu söyler, isAcyclic döngüsüzlüğü, featureViolations ise kuralı uygular: özellik modüllerinin çekirdek dışında bağlandığı her modülü özellik->hedef biçiminde listeler, hiç yoksa yok döner. Üç denetim aynı iki grafiğe uygulanır; ilk ikisi derlemenin bakacağı şeye bakar, üçüncüsü kurala bakar.
// main.swift — derleme mi, kural mi
let good = shopGraph(faulty: false)
let bad = shopGraph(faulty: true)
let compiles = contrast("derleme-geciyor", correct: good.resolves, faulty: bad.resolves) { $0 }.describe()
let acyclic = contrast("dongu-yok", correct: good.isAcyclic, faulty: bad.isAcyclic) { $0 }.describe()
let rule = contrast("ozellik-yalniz-cekirdege-bagli", correct: featureViolations(good), faulty: featureViolations(bad)) { $0 == "yok" }.describe()
countSeparations([compiles, acyclic, rule])
derleme-geciyor | dogru=true/gecti kusurlu=true/gecti -> kor dongu-yok | dogru=true/gecti kusurlu=true/gecti -> kor ozellik-yalniz-cekirdege-bagli | dogru=yok/gecti kusurlu=feed->profile/kaldi -> ayiran ayiran: 1 kor: 2 gurultulu: 0 ters: 0
Çıktı Swift 6.4’te sandbox’ta üç kez koşturuldu ve üçünde de aynıdır. Üç denetimin biri ayıran, ikisi kör çıktı.
derleme-geciyor bağımlılıkların çözülüp çözülmediğine bakar. feed -> profile bağı gerçek bir modüle işaret ettiği için çözülür ve iki sürümde de true çıkar: denetim kördür. dongu-yok döngü arar. feed -> profile ve profile -> core bir döngü kurmaz, bu yüzden bu da iki sürümde geçer ve kördür. İkisi de yeşildir ve ikisi de yanıltıcıdır: kusur grafiğin geçerliliğinde değil, grafiğin kurala uygunluğundadır.
ozellik-yalniz-cekirdege-bagli kuralı uygular. Doğru sürümde ihlal yoktur ve yok çıkar. Kusurlu sürümde feed->profile listelenir ve denetim kalır. Bu ayıran olur, ve ayırmasının nedeni derleme kadar geniş bir soru sormak yerine kuralın kendi sorusunu sormasıdır. Derleme “bu çalışır mı” der; kural “bu izinli mi” der. Modülerleştirmenin değeri ikinci sorudadır.
Varsayımlar
MM1. Model paket tanım dosyasını bir sözlüğe indirger. Gerçek bir paket yöneticisinin hedef çözümlemesini, sürüm kısıtlarını ve platform koşullarını bu ders ölçmez; paket tanımı KAYNAK kalır.
MM2. “Derleme geçiyor” denetimi, bağımlılıkların çözülmesi ve döngü olmaması olarak modellenir. Gerçek derleme bundan fazlasını da kontrol eder (tip hataları, erişim denetimi); onlar bu modelin dışındadır.
MM3. Derleme süresi ölçülmez. Süre makineye, önbelleğe ve yüke bağlıdır; bu derste yerine bir iş birimi sayılır: değişen modülün yeniden derlenmesi gereken bağımlılarının sayısı.
Sınırlayıcı Ölçüm
Aynı kuralın bir bedeli de yeniden derlemedir. Bir modül değişince onu doğrudan ya da dolaylı bağlanan her modül yeniden derlenir. Her değişen modül için bir bütçe koyarız: dersin kuralı olarak profile değişince en çok bir modül yeniden derlenmeli, core değişince en çok dört. Sayı süre değil, iş birimidir.
// main.swift — degisen modulun yeniden derlenecek bagimlilari
let good = shopGraph(faulty: false)
let bad = shopGraph(faulty: true)
let profileBudget = contrast("profile-butcesi", correct: good.dependents(of: "profile").count, faulty: bad.dependents(of: "profile").count) { $0 <= 1 }.describe()
let coreBudget = contrast("core-butcesi", correct: good.dependents(of: "core").count, faulty: bad.dependents(of: "core").count) { $0 <= 4 }.describe()
countSeparations([profileBudget, coreBudget])
profile-butcesi | dogru=1/gecti kusurlu=2/kaldi -> ayiran core-butcesi | dogru=4/gecti kusurlu=4/gecti -> kor ayiran: 1 kor: 1 gurultulu: 0 ters: 0
Üç koşumda çıktı aynıdır. Doğru sürümde profile değişince yalnız app yeniden derlenir. Kusurlu sürümde feed de profile’a bağlandığı için feed ve app yeniden derlenir ve bütçe aşılır. Denetim ayıran olur. core ise iki sürümde de dört bağımlıya sahiptir: feed, profile, settings ve app. Kusur çekirdeğin bağımlı sayısını değiştirmez, bu yüzden aynı türden bir bütçe denetimi core için kördür. Aynı denetim bir modülde ayıran, ötekinde kör olabilir. Sınır ihlalinin bedeli yalnız onu içeren modülün çevresinde ödenir.
Özet
- Modülerleştirme kodu paket sınırlarıyla böler; hangi modülün hangisine bağlanabileceği paket tanımında yazılır.
- Dersin kuralı: özellik modülleri yalnız çekirdek modüle bağlanır, birbirine bağlanmaz.
- Derlemenin geçmesine ve döngü olmamasına bakan denetimler sınır ihlaline karşı kördür; üç denetimin ikisi kör, biri ayıran çıktı.
- Kuralı uygulayan denetim, ihlali
feed->profileolarak adıyla bulur ve ayıran olur. - Değişen modülün yeniden derlenecek bağımlı sayısı iş birimidir ve bütçeye bağlanabilir;
profilebütçesinde ayıran,corebütçesinde kör kalır. - Bu dersin ayrımı: kör.
Sonraki Adım
Mimari bölümü bitti: hesap ekrandan, gezinme görünümlerden, durum dağınıklıktan, bağımlılık içeriden ve özellikler birbirinden ayrıldı. Sıradaki konu testlerdir. İlk derste Birim Testleri, bu parçaların birini bir test hedefinde sınayacak. Orada kusur bir eşikte olacak: kargo ücreti, sepet tutarı eşiğin tam üstündeyken yanlış hesaplanacak, ve eşiğin altından bir değerle yazılmış test bu kusura karşı kör kalacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.