Ders 09 / 10
Evrimsel Mimari
Değişime dayanıklı yapının denetlenmesi: aynı üç değişikliğin bir yapıda 18, yalıtma noktalı yapıda 5 modüle dokunması, yalıtımın kalıcı olarak 5 düğüm ve 4 bağ pahalı olması, uygunluk işlevinin 8 ihlalin 7'sini yakalayıp 1 yanlış alarm vermesi ve bir bağımlılık içeri alınınca aynı kural kümesinin 5 doğru değişikliği ihlal olarak işaretlemesi.
İçindekiler
Önceki ders bağımlılık ve katman kurallarını koşturulabilir bir denetime çevirdi: kural artık bir belge cümlesi değil, ithal grafiği üzerinde çalışan bir yüklemdir. Ama bu kuralların hepsi bugünkü yapıyı korur: bugünkü katman ayrımını dondurur ve yapının kendisi değişmek zorunda kaldığında ya doğru değişimi ucuzlatır ya da onu ihlal olarak işaretler.
Evrimsel mimari (evolutionary architecture), değişimi ucuz tutan yapı kararlarını denetlenebilir hâle getirme uğraşıdır. Kursun sorusu bir adım kayar: değişimi ucuzlatan yapı kararı denetlenebilir mi? Dört ölçü alınır: dokunulan modül ve geri alma adımı; uygunluk işlevinin yakalaması, kaçırması ve yanlış alarmı; kuralın kendi eskimesi; çevrilemeyen kısım.
Aynı Değişiklik Kümesi, İki Yapı
Örnek, kursun kurgu bölgesel kütüphane ağıdır. YN12: altı iç modül (şube ön yüzü, şube
terminali, ödünç, ücretlendirme, raporlama, bildirim) ve üç dış bağımlılık (dışarıdan alınmış
katalog, ayrı üyelik sistemi, belediyedeki kimlik servisi); bugünkü yapı A bunları 16 bağla
bağlar. YN13: bağ, iç biçime dayanıyorsa biçim bağı, bildirilmiş sözleşmeye dayanıyorsa
sözleşme bağıdır. YN14 — sızan modül: A’da odunc ile ucret aldıkları kaydı olduğu gibi
yeniden yayar; B’de her dış bağımlılığın önünde biçimi eşleyen bir yalıtma noktası (kapı) durur
ve sızan modül kalmaz. YN15 — değişiklik kümesi: katalog
kaydına alan eklemek, üyelik sistemini değiştirmek, ücretlendirmeyi ikiye ayırmak.
İki dosya grafı, yayılımı ve beş kuralı kuran süreç içi bir modeldir; ağ ya da depo yoktur.
// evrim/yapi.mjs — kurgu kutuphane aginin modul grafi ve kural denetleyicisi (model). Kenar // yazimi "bagimli>bagimlilik:tur"; "b" bicim bagi, "s" sozlesme bagi. export const bag = (s) => s.trim().split(/\s+/).map((e) => e.split(/[>:]/)); export const YAPI_A = bag(` sube-onyuz>katalog:b sube-onyuz>odunc:b sube-onyuz>kimlik:b sube-terminal>katalog:b sube-terminal>odunc:b sube-terminal>kimlik:b odunc>katalog:b odunc>uyelik:b odunc>ucret:b ucret>odunc:b ucret>uyelik:b rapor>odunc:b rapor>ucret:b rapor>katalog:b bildirim>odunc:b bildirim>uyelik:b`); export const YAPI_B = bag(` sube-onyuz>katalog-kapisi:s sube-onyuz>odunc-kapisi:s sube-onyuz>kimlik-kapisi:s sube-terminal>katalog-kapisi:s sube-terminal>odunc-kapisi:s sube-terminal>kimlik-kapisi:s odunc>katalog-kapisi:s odunc>uyelik-kapisi:s odunc>ucret-kapisi:s ucret>uyelik-kapisi:s rapor>odunc-kapisi:s rapor>ucret:b rapor>katalog-kapisi:s bildirim>odunc-kapisi:s bildirim>uyelik-kapisi:s katalog-kapisi>katalog:b uyelik-kapisi>uyelik:b kimlik-kapisi>kimlik:b odunc-kapisi>odunc:b ucret-kapisi>ucret:b`); // Aldigi bicimi oldugu gibi yeniden yayan moduller (kaba isaret) export const SIZAN = { A: new Set(["odunc", "ucret"]), B: new Set() }; export const UC = ["sube-onyuz", "sube-terminal"]; // "dis" ve "kapi" yapinin degil, yapilandirmanin bildirdigi seylerdir. export const ESKI = { dis: ["katalog", "uyelik", "kimlik"], kapi: { "katalog-kapisi": "katalog", "uyelik-kapisi": "uyelik", "kimlik-kapisi": "kimlik", "odunc-kapisi": "odunc", "ucret-kapisi": "ucret" }, ucIzin: ["katalog-kapisi", "odunc-kapisi", "kimlik-kapisi"], istisna: [["rapor", "ucret"]], }; const { "uyelik-kapisi": _, ...KAPI_YENI } = ESKI.kapi; // uyelik ice alindiktan sonra export const YENI = { ...ESKI, dis: ["katalog", "kimlik"], kapi: KAPI_YENI, ucIzin: [...ESKI.ucIzin, "uyelik"] }; export const modul = (k) => [...new Set(k.flatMap(([a, b]) => [a, b]))]; export const uygula = (k, o) => { const s = (o.sil ?? []).map((x) => `${x[0]}>${x[1]}`); return [...k.filter(([a, b]) => !s.includes(`${a}>${b}`)), ...(o.ek ?? [])]; }; // Bicim bagi degisen bicimi tasir; sozlesme bagi ancak sozlesme degisince gecer. export function dokunulan(kenar, sizan, dis, d) { const kume = new Set([d.kok]); for (let ek = true; ek; ) { ek = false; for (const [a, b, tur] of kenar) { if (kume.has(a) || !kume.has(b)) continue; if (tur === "b" ? b === d.kok || sizan.has(b) : d.sozlesmeDegisen.includes(b)) { kume.add(a); ek = true; } } } return [...kume].filter((m) => !dis.includes(m)); } // Tek kapida kalan degisiklik bir anahtarla geri alinir. export const geriAlma = (d, kapi) => (d.length === 1 && d[0] in kapi ? 1 : d.length); export function dongudekiler(kenar) { const m = modul(kenar), u = new Map(m.map((x) => [x, new Set()])); for (const [a, b] of kenar) u.get(a).add(b); for (let d = true; d; ) { // gecisli kapanis d = false; for (const a of m) for (const b of [...u.get(a)]) for (const c of u.get(b) ?? []) if (!u.get(a).has(c)) { u.get(a).add(c); d = true; } } return m.filter((x) => u.get(x).has(x)); } // Uygunluk islevi: degisime aciklik export function uygunluk(kenar, y) { const kapiAdi = Object.keys(y.kapi); const tam = y.dis.filter((x) => { const g = kenar.filter(([, b]) => b === x).map(([a]) => a); return g.length === 1 && kapiAdi.includes(g[0]); }); return { dugum: modul(kenar).length, bag: kenar.length, "dongudeki modul": dongudekiler(kenar).length, "yalitilmamis dis bag": kenar.filter(([a, b]) => y.dis.includes(b) && !kapiAdi.includes(a)).length, "tam yalitilmis dis": `${tam.length}/${y.dis.length}` }; } // Bes mimari kural; her biri ihlal sayisi dondurur export function kurallar(y) { const kapiAdi = Object.keys(y.kapi); const icHedef = Object.values(y.kapi).filter((m) => !y.dis.includes(m)); return { K1: (k) => k.filter(([a, b]) => y.dis.includes(b) && !kapiAdi.includes(a)).length, K2: (k) => dongudekiler(k).length, K3: (k) => k.filter(([a, b, t]) => t === "b" && icHedef.includes(b) && !kapiAdi.includes(a) && !y.istisna.some(([x, z]) => x === a && z === b)).length, K4: (k) => k.filter(([a, b]) => UC.includes(a) && !y.ucIzin.includes(b)).length, K5: (k) => y.dis.filter((x) => k.filter(([, b]) => b === x).length !== 1 || !kapiAdi.includes((k.find(([, b]) => b === x) ?? [])[0])).length, }; }
// evrim/olc.mjs — iki yapida ayni degisiklik kumesi, bilinen ihlal kumesi, kuralin eskimesi. import { YAPI_A, YAPI_B, SIZAN, ESKI, YENI, bag, uygula, dokunulan, geriAlma, uygunluk, kurallar } from "./yapi.mjs"; const yaz = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("")); const uA = uygunluk(YAPI_A, ESKI), uB = uygunluk(YAPI_B, ESKI); yaz([-24, 8, 8], "uygunluk islevi", "A", "B"); for (const k of Object.keys(uA)) yaz([-24, 8, 8], k, uA[k], uB[k]); const DEGISIKLIK = [ { ad: "yeni alan (katalog)", kok: "katalog", sozlesmeDegisen: [] }, { ad: "bagimlilik degistir (uyelik)", kok: "uyelik", sozlesmeDegisen: [] }, { ad: "ucret'i ikiye ayir", kok: "ucret", sozlesmeDegisen: ["ucret"] }, ]; const D = [-30, 14, 14, 14, 14], T = { A: [0, 0], B: [0, 0] }; console.log(); yaz(D, "degisiklik", "A: dokunulan", "A: geri alma", "B: dokunulan", "B: geri alma"); for (const d of DEGISIKLIK) { const s = {}; for (const [ad, kenar, sizan] of [["A", YAPI_A, SIZAN.A], ["B", YAPI_B, SIZAN.B]]) { const dk = dokunulan(kenar, sizan, ESKI.dis, d), g = geriAlma(dk, ESKI.kapi); s[ad] = [dk.length, g]; T[ad][0] += dk.length; T[ad][1] += g; } yaz(D, d.ad, s.A[0], s.A[1], s.B[0], s.B[1]); } yaz(D, "toplam", T.A[0], T.A[1], T.B[0], T.B[1]); // Bilinen ihlal kumesi: her oneri elle etiketlenmistir (model girdisi). const ONERI = [ ["hizli arama", "sube-onyuz>katalog:b", "ihlal"], ["gece aktarimi", "rapor>katalog:b", "ihlal"], ["borc sorgusu", "ucret>odunc:b", "ihlal"], ["bildirimde ucret", "bildirim>ucret:b", "ihlal"], ["kapidan katalog", "odunc-kapisi>katalog:b", "ihlal"], ["yeni kiosk", "sube-kiosk>odunc:b", "ihlal"], ["bildirim kimlik", "bildirim>kimlik-kapisi:s", "temiz"], ["rapor uyelik", "rapor>uyelik-kapisi:s", "temiz"], ["arsiv modulu", "arsiv>odunc-kapisi:s arsiv>katalog-kapisi:s", "temiz"], ["kapi zinciri", "katalog-kapisi>uyelik-kapisi:s", "ihlal"], ["terminal uyelik", "sube-terminal>uyelik-kapisi:s", "temiz"], ["bildirim uyelik", "bildirim>uyelik:b", "ihlal"], ].map(([ad, e, gercek]) => ({ ad, ek: bag(e), gercek })); function kos(taban, oneriler, y, kapali = []) { const K = kurallar(y), ad = Object.keys(K).filter((x) => !kapali.includes(x)); const temel = Object.fromEntries(ad.map((x) => [x, K[x](taban)])); const satir = oneriler.map((o) => { const aten = ad.filter((x) => K[x](uygula(taban, o)) > temel[x]); const sonuc = o.gercek === "ihlal" ? (aten.length ? "yakalandi" : "kacti") : aten.length ? "yanlis alarm" : "dogru gecti"; return { ad: o.ad, gercek: o.gercek, aten, sonuc }; }); const say = (s) => satir.filter((x) => x.sonuc === s).length; return { satir, temel, yakalandi: say("yakalandi"), kacti: say("kacti"), yanlis: say("yanlis alarm"), dogru: say("dogru gecti") }; } const R = kos(YAPI_B, ONERI, ESKI), ihlal = ONERI.filter((o) => o.gercek === "ihlal").length; console.log(`\nB tabani: ${Object.entries(R.temel).map(([a, b]) => `${a}=${b}`).join(" ")}`); const O = [-18, 8, 14, 14]; yaz(O, "oneri", "gercek", "atesleyen", "sonuc"); for (const s of R.satir) yaz(O, s.ad, s.gercek, s.aten.join(",") || "-", s.sonuc); console.log(`yakalanan ${R.yakalandi}/${ihlal}, kacan ${R.kacti}, ` + `yanlis alarm ${R.yanlis}/${ONERI.length - ihlal} temiz`); // Kurallarin eskimesi: yapi ayni, yapilandirma degisti. const SONRA = [ ["odunc dogrudan", "odunc>uyelik:b", "", "temiz"], ["kapiyi kaldir", "odunc>uyelik:s ucret>uyelik:s bildirim>uyelik:s", "uyelik-kapisi>uyelik odunc>uyelik-kapisi ucret>uyelik-kapisi bildirim>uyelik-kapisi", "temiz"], ["bildirim dogrudan", "bildirim>uyelik:b", "", "temiz"], ["onyuz uyelik", "sube-onyuz>uyelik:s", "", "temiz"], ["uyelik kimlige", "uyelik>kimlik:b", "", "ihlal"], ["rapor uyelik", "rapor>uyelik:s", "", "temiz"], ].map(([ad, e, s, gercek]) => ({ ad, ek: bag(e), sil: s ? bag(s) : [], gercek })); const KAPALI = ["K1", "K5"]; // dis listesini okuyan iki kural console.log("\nuyelik ice alindiktan sonra, ayni alti oneri (5 temiz, 1 ihlal)"); const S = [-22, 11, 8, 14, 13]; yaz(S, "yapilandirma", "yakalanan", "kacan", "yanlis alarm", "dogru gecen"); for (const [ad, y, k] of [["eski (degismemis)", ESKI, []], ["guncellenmis", YENI, []], [`eski, ${KAPALI.join("+")} kapali`, ESKI, KAPALI]]) { const r = kos(YAPI_B, SONRA, y, k); yaz(S, ad, r.yakalandi, r.kacti, r.yanlis, r.dogru); } const eK = kurallar(ESKI), yK = kurallar(YENI); const degisen = Object.keys(ESKI).filter((a) => JSON.stringify(ESKI[a]) !== JSON.stringify(YENI[a])); const davranis = Object.keys(eK).filter((x) => SONRA.some((o) => eK[x](uygula(YAPI_B, o)) !== yK[x](uygula(YAPI_B, o)))); console.log(`guncelleme: ${Object.keys(ESKI).length} yapilandirma girdisinin ${degisen.length}'i ` + `(${degisen.join(", ")}); ${Object.keys(eK).length} kuralin ${davranis.length}'i ` + `(${davranis.join(", ")}) ayni oneride baska sonuc veriyor`); const gevsek = kos(YAPI_B, ONERI, ESKI, KAPALI); console.log(`${KAPALI.join("+")} kapatilinca ilk ihlal kumesi: yakalanan ${R.yakalandi} -> ` + `${gevsek.yakalandi}, kacan ${R.kacti} -> ${gevsek.kacti}, yanlis alarm ${R.yanlis} -> ${gevsek.yanlis}`); // Cevrilemeyen kisim: yazilmamis degisikligin ucuzlugu. const [SENARYO, GOZDEN, GELEN, KAPSANAN] = [6, 4, 12, 5]; // model girdisi console.log(`\ndegisim senaryosu ${SENARYO}, yilda ${GOZDEN} gozden gecirme -> ` + `${SENARYO * GOZDEN} elle inceleme kalemi`); console.log(`bir yilda gelen ${GELEN} degisiklikten ${KAPSANAN}'i yazili senaryoya denk dustu ` + `(%${((100 * KAPSANAN) / GELEN).toFixed(2)}); kalan ${GELEN - KAPSANAN} degisiklik gelmeden sayilmadi`);
uygunluk islevi A B dugum 9 14 bag 16 20 dongudeki modul 2 0 yalitilmamis dis bag 9 0 tam yalitilmis dis 0/3 3/3 degisiklik A: dokunulan A: geri alma B: dokunulan B: geri alma yeni alan (katalog) 6 6 1 1 bagimlilik degistir (uyelik) 6 6 1 1 ucret'i ikiye ayir 6 6 3 3 toplam 18 18 5 5 B tabani: K1=0 K2=0 K3=0 K4=0 K5=0 oneri gercek atesleyen sonuc hizli arama ihlal K1,K4,K5 yakalandi gece aktarimi ihlal K1,K5 yakalandi borc sorgusu ihlal K2,K3 yakalandi bildirimde ucret ihlal K3 yakalandi kapidan katalog ihlal K5 yakalandi yeni kiosk ihlal K3 yakalandi bildirim kimlik temiz - dogru gecti rapor uyelik temiz - dogru gecti arsiv modulu temiz - dogru gecti kapi zinciri ihlal - kacti terminal uyelik temiz K4 yanlis alarm bildirim uyelik ihlal K1,K5 yakalandi yakalanan 7/8, kacan 1, yanlis alarm 1/4 temiz uyelik ice alindiktan sonra, ayni alti oneri (5 temiz, 1 ihlal) yapilandirma yakalanan kacan yanlis alarm dogru gecen eski (degismemis) 1 0 5 0 guncellenmis 1 0 0 5 eski, K1+K5 kapali 0 1 1 4 guncelleme: 4 yapilandirma girdisinin 3'i (dis, kapi, ucIzin); 5 kuralin 3'i (K1, K4, K5) ayni oneride baska sonuc veriyor K1+K5 kapatilinca ilk ihlal kumesi: yakalanan 7 -> 4, kacan 1 -> 4, yanlis alarm 1 -> 1 degisim senaryosu 6, yilda 4 gozden gecirme -> 24 elle inceleme kalemi bir yilda gelen 12 degisiklikten 5'i yazili senaryoya denk dustu (%41.67); kalan 7 degisiklik gelmeden sayilmadi
Sayılar ölçüm sınıfındadır; girdileri yukarıdaki varsayımlardır. A’da üç değişikliğin üçü de
altı iç modülün altısına dokunuyor: biçim bağı odunc ile ucret üzerinden sızdığı için katalog
kaydına alan eklemek bildirime kadar gidiyor. B’de aynı üç değişiklik 1, 1 ve 3 modüle dokunuyor —
18’e karşı 5. Geri alma sütunu ikinci ayrımı verir: A’da dokunulan her modül tek tek geri alınır,
B’de ilk iki değişiklik tek kapının içinde kaldığı için birer anahtar çevrilir. Bu yapısal bir geri
alınabilirliktir, Servis Mimarileri kursundaki boğucu incir geçişinin geçiş turu değil.
Yalıtımın bedeli de tabloda: B, A’nın 9 düğüm ve 16 bağına karşılık 14 ve 20 taşır. Üçüncü
değişikliğin B’de üç modüle dokunması yalıtımın kısmi olduğunu gösterir: rapor ücretlendirmeye
biçim bağıyla bağlı ve o bağ istisna listesinde.
Uygunluk İşlevi ve Bilinen İhlal Kümesi
Uygunluk işlevi (fitness function) burada değişime açıklığı ölçer ve üç nicelik sayar:
döngüdeki modül, yalıtılmamış dış bağ, tam yalıtılmış dış bağımlılık. A’da odunc
ile ucret karşılıklı bağlıdır, dokuz bağ dış bağımlılığa doğrudan gider ve hiçbir dış
bağımlılığın tek kapısı yoktur; B’de sırasıyla 0, 0 ve 3/3.
Beş mimari kural bu niceliklerin eşiğidir: K1 iç modülün dış bağımlılığa doğrudan bağlanmasını, K2 döngüyü, K3 kapısı olan iç modüle biçim bağını, K4 uç katmanın izin listesi dışına çıkmasını, K5 bir dış bağımlılığın tam kapısı olmamasını yasaklar; beşi de B tabanında sıfır verir. YN16 — bilinen ihlal kümesi: on iki öneri elle etiketlenmiştir, sekizi değişim maliyetini artırır.
Denetim sekiz ihlalin yedisini yakalar, birini kaçırır, dört temiz önerinin birine yanlış alarm verir. Kaçan öneri kapı zinciridir: kapının başka bir kapıya bağlanması. Kapı, biçim eşleyen katman olmaktan çıkıp alan modülüne döner, ama hiçbir kural bunu yasaklamaz — denetim yalnız yazılmış olanı görür. Yanlış alarm terminal üyeliktir; kusur önerinin değil K4’ün izin listesinindir. Yakalama ile yanlış alarm, sınama kurslarındaki yanlış kalma ile yanlış geçme ikilisinin aynı olgusudur; burada sınanan kod değil graftır.
Kuralın Kendisi Eskiyor
YN17 — evrim olayı: ayrı üyelik sistemi içeri alınır ve ağın kendi modülü olur. Grafta tek bir kenar değişmez; değişen şey kural yapılandırmasının “dış” listesidir. Aynı beş kural, üyelik içeri alındıktan sonra doğru olan altı öneriye uygulanır: beş temiz önerinin beşini de ihlal işaretler. Kapıyı kaldırmak, ödüncün üyeliği doğrudan çağırması, raporun üyelikten okuması — hepsi K1 ile K5’e takılır, çünkü ikisi de üyeliği hâlâ dışarıda sanır. Kural bozulmadı; dünyası değişti.
Düzeltme yapılandırmadadır: dört girdinin üçü (dış, kapı ve uç izin listeleri) güncellenir, beş kuralın üçü aynı öneride başka sonuç verir. Sonrasında yanlış alarm sıfırlanır, tek gerçek ihlal — içeri alınan üyeliğin kimlik servisini kapısız çağırması — yakalanmayı sürdürür.
İkinci yol gevşetmektir: dış listesini okuyan iki kuralı kapatmak; alt satır bedeli veriyor. Evrim sonrası kümede yanlış alarm beşten bire iner ama tek gerçek ihlal kaçar; ilk ihlal kümesinde yakalanan yediden dörde düşer, kaçan birden dörde çıkar, yanlış alarm birde kalır — kalan yanlış alarm K4’ündü ve K4 açık kaldı. Yanlış alarmın ekipte ürettiği davranış kuralı kapatmaktır; kapatma da yanlış alarmı doğuran kuralı değil, en çok ihlal yakalayan kuralı götürür.
Çevrilemeyen Kısım
Bir sayıya bağlanamayan şey henüz istenmemiş değişikliğin ucuzluğudur: uygunluk işlevi yapıyı yazılmış bir değişiklik kümesine karşı ölçer, kümede olmayan için ürettiği sayı yoktur. Yerine konan şey dönem dönem gözden geçirilen değişim senaryosudur. YN18: altı senaryo ve yılda dört gözden geçirme, yılda 24 elle inceleme kalemi eder; bir yılda gelen on iki değişikliğin beşi yazılı bir senaryoya denk düşer (yüzde 41,67), kalan yedisi ancak geldikten sonra fiyatlanır. Bu oran geriye bakılarak sayılır; on üçüncü, yazılmamış değişikliğin bedeli önceden sayılamaz.
Özet
- Aynı üç değişiklik A’da altı iç modülün altısına dokunuyor (18 dokunuş, 18 geri alma adımı); yalıtma noktalı B’de 1, 1 ve 3 modüle (5 ve 5).
- Uygunluk işlevi açıklığı üç nicelikle sayar (döngüdeki modül 2 → 0, yalıtılmamış dış bağ 9 → 0, tam yalıtılmış dış bağımlılık 0/3 → 3/3); yalıtımın kalıcı bedeli, A’nın 9 düğüm ve 16 bağına karşılık B’nin 14 düğümü ve 20 bağıdır.
- Denetim sekiz ihlalin yedisini yakalar, kapıdan kapıya bağı kaçırır, bir temiz öneriye yanlış alarm verir; ikisi de kuralın yazılışındandır.
- Bir bağımlılık içeri alınınca graf değişmeden kural eskir: beş doğru değişikliğin beşi ihlal işaretlenir; dört yapılandırma girdisinden üçü güncellenince yanlış alarm sıfırlanır, güncelleme yerine iki kural kapatılırsa yakalanan 7’den 4’e iner.
- Çevrilemeyen kısım henüz istenmemiş değişikliğin ucuzluğudur; yerine konan altı senaryo ve yılda dört gözden geçirme (24 kalem) gelen on iki değişikliğin beşini kapsar.
Sonraki Adım
Bu dersteki kuralların hepsi içeriden geldi: kapı, izin ve istisna listelerini mimarinin kendisi seçti, eskidiklerinde yine kendisi güncelledi; gevşetmek de onun elindeydi. Bazı kısıtlar ise dışarıdan gelir ve pazarlığa açık değildir — verinin nerede duracağı, ne kadar saklanacağı, silme isteğinin nasıl karşılanacağı gibi. Sonraki ders bunları ele alır: kaldırılması seçenek olmayan bir kısıt denetime çevrildiğinde ne oluyor, çevrilemeyen kısmın yerine ne konuyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.