Ders 10 / 11
Belgenin Güncel Kalması
Belge sapmasının gerçekten ölçülmesi: bir modül ithal grafiğinden çıkarılan gerçek ile belgedeki iddianın karşılaştırılması, sekiz değişiklik boyunca sapmanın büyümesi ve üç güncel tutma düzeninin yakaladığı sapma, ödettiği bakım maliyeti ve ürettiği yanlış alarm üzerinden karşılaştırılması.
İçindekiler
Önceki ders kalite gereksinimlerini ölçülebilir hale getirdi ve ortaya bir belge çıktı; o belge yazıldığı gün kodla uyumluydu. Bu ders sonrasını ölçer: kod değişmeye devam eder, belge yerinde durur, aradaki fark büyür. Bu farkın adı belge sapması (documentation drift) ve tahmin edilerek değil sayılarak bulunur.
Ölçüm yöntemi ödünç alınmıştır. Servis Mimarileri kursunun sınır çizme dersinde ithal kenarları kaynak dosyalardan çıkarılıp sınır ihlali sayılmıştı. Aynı çıkarım burada koddan okunan grafiği belgenin iddiasıyla karşılaştırmak için kullanılır; ölçülen nicelik farklıdır.
Kod, Belge ve Denetim
Aşağıdaki blok üç şeyi tanımlar: bölgesel kütüphane ağı sisteminin modül haritasını, on dört iddiadan oluşan belgeyi ve iddiaları koda karşı sınayan denetimi. Modül haritası bir modeldir; her modül kaynak dosya olarak diske yazılır ve grafik o dosyalardan çıkarılır — ölçüm gerçek dosyalar üzerinde yapılır.
İddiaların üçe ayrılması dersin eksenidir: kural iddiası mimari niyeti söyler, kod ona uymazsa sapma vardır; anlık iddia kodun o günkü hâlinin sayımıdır, kod değişince bayatlar; belirsiz iddia koddan denetlenemez.
// ortam.mjs — kod tabanini, belgeyi ve denetimi tanimlar; dogrudan calistirildiginda // baslangic durumunu raporlar. Sonraki iki blok bu dosyayi ithal eder. import { mkdirSync, rmSync, writeFileSync, readdirSync, readFileSync } from "node:fs"; // GR19: bolgesel kutuphane agi sisteminin alti alan modulu. Her modul kaynaginda hangi // modulleri ithal ettigini, hangi tablolara yazdigini ve hangi tablonun sahibi oldugunu tasir. export const BASLANGIC = { sube: { ithal: [], yazar: ["sube"], sahip: ["sube"] }, katalog: { ithal: [], yazar: ["kitap", "nusha"], sahip: ["kitap", "nusha"] }, uyelik: { ithal: [], yazar: ["uye"], sahip: ["uye"] }, odunc: { ithal: ["katalog", "uyelik"], yazar: ["odunc"], sahip: ["odunc"] }, bildirim: { ithal: ["uyelik", "odunc"], yazar: ["bildirim"], sahip: ["bildirim"] }, ucret: { ithal: ["odunc"], yazar: ["ucret"], sahip: ["ucret"] }, }; export const yaz = (m) => { // kod/ dizini her yazimda bastan olusturulur rmSync("kod", { recursive: true, force: true }); mkdirSync("kod", { recursive: true }); for (const [ad, x] of Object.entries(m)) { writeFileSync(`kod/${ad}.mjs`, `// sahip: ${x.sahip.join(", ")}\n` + x.ithal.map((i) => `import { ${i}Islem } from "./${i}.mjs";\n`).join("") + `export const yaz = (t, v) => ({ t, v });\n` + `export function ${ad}Islem(v) {\n` + x.yazar.map((t) => ` yaz("${t}", v);\n`).join("") + "}\n"); } }; // Ithal grafigi kaynak dosyalardan cikarilir. Yontem Servis Mimarileri kursunun sinir cizme // dersinden odunc alinmistir; orada sinir ihlali olculmustu, burada belge sapmasi olculuyor. export const grafik = () => Object.fromEntries(readdirSync("kod").map((d) => { const t = readFileSync(`kod/${d}`, "utf8"); return [d.replace(".mjs", ""), { ithal: [...t.matchAll(/from "\.\/(\w+)\.mjs"/g)].map((x) => x[1]), yazar: [...t.matchAll(/yaz\("(\w+)"/g)].map((x) => x[1]), sahip: t.match(/^\/\/ sahip: (.+)$/m)[1].split(", "), }]; })); const ithalEden = (g, a) => Object.entries(g).filter(([, m]) => m.ithal.includes(a)).map(([x]) => x); const yazan = (g, t) => Object.entries(g).filter(([, m]) => m.yazar.includes(t)).map(([x]) => x); // Belge uc tur iddia tasir. KURAL: mimari niyet, kod uymazsa sapma vardir. ANLIK: kodun o // gunku halinin sayimi, kod degisince bayatlar. BELIRSIZ: koddan denetlenemez. export const KURAL = [ ["K1", "uyelik", "Uyelik hicbir alan modulunu ithal etmez.", (g) => g.uyelik.ithal.length === 0], ["K2", "katalog", "Katalogu yalniz odunc ithal edebilir.", (g) => ithalEden(g, "katalog").every((a) => a === "odunc")], ["K3", "bildirim", "Bildirime hicbir modul baglanamaz.", (g) => ithalEden(g, "bildirim").length === 0], ["K4", "ucret", "Ucret yalniz odunc modulunu ithal edebilir.", (g) => g.ucret.ithal.every((a) => a === "odunc")], ["K5", "sube", "Sube hicbir alan modulunu ithal etmez.", (g) => g.sube.ithal.length === 0], ["K6", "odunc", "Odunc tablosuna yalniz odunc yazabilir.", (g) => yazan(g, "odunc").every((a) => a === "odunc")], ]; export const ANLIK = [ ["A1", "sistem", "Sistem alti alan modulunden olusur.", (g) => Object.keys(g).length], ["A2", "odunc", "Odunc, katalog ve uyelik modullerini ithal eder.", (g) => g.odunc.ithal], ["A3", "sistem", "Grafikte bes ithal kenari vardir.", (g) => Object.values(g).reduce((s, m) => s + m.ithal.length, 0)], ["A4", "bildirim", "Bildirim, uyelik ve odunc modullerini ithal eder.", (g) => g.bildirim.ithal], ["A5", "ucret", "Ucret yalniz ucret tablosuna yazar.", (g) => g.ucret.yazar], ["A6", "katalog", "Katalog kitap ve nusha tablolarinin sahibidir.", (g) => g.katalog.sahip], ]; export const BELIRSIZ = [ ["D1", "katalog", "Katalog sistemi disaridan alinmistir."], ["D2", "sistem", "Gece bakim penceresi 02.00-04.00 arasidir."], ]; // GR20: sonraki iki ceyrekte uygulanan sekiz degisiklik. Niyet etiketi, degisikligin mimari // kurala uyup uymadigini soyler; etiket iddia edilmez, denetim sonucu ile karsilastirilir. export const DEGISIM = [ ["rezervasyon modulu eklenir", "kurala uygun", "rezervasyon", (m) => { m.rezervasyon = { ithal: ["uyelik"], yazar: ["rezervasyon"], sahip: ["rezervasyon"] }; }], ["odunc bildirim ithal eder", "kural disi", "odunc", (m) => m.odunc.ithal.push("bildirim")], ["ucret uyelik ithal eder", "kural disi", "ucret", (m) => m.ucret.ithal.push("uyelik")], ["bildirim uyelik ithalini birakir", "kurala uygun", "bildirim", (m) => { m.bildirim.ithal = m.bildirim.ithal.filter((a) => a !== "uyelik"); }], ["rezervasyon odunc tablosuna yazar", "kural disi", "rezervasyon", (m) => m.rezervasyon.yazar.push("odunc")], ["katalog etiket tablosunu devralir", "kurala uygun", "katalog", (m) => { m.katalog.sahip.push("etiket"); m.katalog.yazar.push("etiket"); }], ["ucret ceza tablosuna yazar", "kurala uygun", "ucret", (m) => m.ucret.yazar.push("ceza")], ["sube uyelik ithal eder", "kural disi", "sube", (m) => m.sube.ithal.push("uyelik")], ]; if (process.argv[1]?.endsWith("ortam.mjs")) { yaz(structuredClone(BASLANGIC)); const g = grafik(); const satir = [ ...KURAL.map(([ad, ozne, metin, sina]) => [ad, "kural", ozne, sina(g) ? "dogru" : "yanlis", metin]), ...ANLIK.map(([ad, ozne, metin, oku]) => [ad, "anlik", ozne, String(oku(g)), metin]), ...BELIRSIZ.map(([ad, ozne, metin]) => [ad, "belirsiz", ozne, "-", metin]), ]; console.log(`${"iddia".padEnd(6)}${"tur".padEnd(10)}${"ozne".padEnd(10)}${"t=0".padEnd(16)}metin`); for (const [a, b, c, d, e] of satir) { console.log(`${a.padEnd(6)}${b.padEnd(10)}${c.padEnd(10)}${d.padEnd(16)}${e}`); } console.log(`\n${Object.keys(g).length} modul, ` + `${Object.values(g).reduce((s, x) => s + x.ithal.length, 0)} ithal kenari; belge ${satir.length} ` + `iddia tasiyor (${KURAL.length} kural, ${ANLIK.length} anlik, ${BELIRSIZ.length} belirsiz)`); console.log(`t=0 sapmasi: 0 yanlis kural iddiasi, 0 bayat anlik iddia ` + `(belge kodla ayni gun yazildi)`); }
iddia tur ozne t=0 metin K1 kural uyelik dogru Uyelik hicbir alan modulunu ithal etmez. K2 kural katalog dogru Katalogu yalniz odunc ithal edebilir. K3 kural bildirim dogru Bildirime hicbir modul baglanamaz. K4 kural ucret dogru Ucret yalniz odunc modulunu ithal edebilir. K5 kural sube dogru Sube hicbir alan modulunu ithal etmez. K6 kural odunc dogru Odunc tablosuna yalniz odunc yazabilir. A1 anlik sistem 6 Sistem alti alan modulunden olusur. A2 anlik odunc katalog,uyelik Odunc, katalog ve uyelik modullerini ithal eder. A3 anlik sistem 5 Grafikte bes ithal kenari vardir. A4 anlik bildirim uyelik,odunc Bildirim, uyelik ve odunc modullerini ithal eder. A5 anlik ucret ucret Ucret yalniz ucret tablosuna yazar. A6 anlik katalog kitap,nusha Katalog kitap ve nusha tablolarinin sahibidir. D1 belirsiz katalog - Katalog sistemi disaridan alinmistir. D2 belirsiz sistem - Gece bakim penceresi 02.00-04.00 arasidir. 6 modul, 5 ithal kenari; belge 14 iddia tasiyor (6 kural, 6 anlik, 2 belirsiz) t=0 sapmasi: 0 yanlis kural iddiasi, 0 bayat anlik iddia (belge kodla ayni gun yazildi)
Sapma Nasıl Büyür
Belge sıfır sapmayla başlıyor. Sonraki blok sekiz değişikliği sırayla uygular, her adımdan sonra on dört iddiayı yeniden sınar ve belgeye hiç dokunmaz.
// sapma.mjs — ortam.mjs'i ithal eder; guncellenmeyen belgenin sapmasini adim adim olcer import { BASLANGIC, DEGISIM, KURAL, ANLIK, BELIRSIZ, yaz, grafik } from "./ortam.mjs"; const m = structuredClone(BASLANGIC); yaz(m); const ilk = grafik(); const beklenen = Object.fromEntries(ANLIK.map(([ad, , , oku]) => [ad, JSON.stringify(oku(ilk))])); const sapmaOlc = (g) => ({ kural: KURAL.filter(([, , , sina]) => !sina(g)).map(([ad]) => ad), anlik: ANLIK.filter(([ad, , , oku]) => JSON.stringify(oku(g)) !== beklenen[ad]).map(([ad]) => ad), }); console.log(`${"t".padStart(2)} ${"degisiklik".padEnd(35)}${"niyet".padEnd(14)}` + `${"dusen iddia".padEnd(14)}${"yanlis kural".padStart(13)}${"bayat anlik".padStart(13)}` + `${"sapma".padStart(7)}`); let onceki = []; DEGISIM.forEach(([ad, niyet, , uygula], t) => { uygula(m); yaz(m); const s = sapmaOlc(grafik()); const hepsi = [...s.kural, ...s.anlik]; const yeni = hepsi.filter((a) => !onceki.includes(a)); onceki = hepsi; console.log(`${String(t + 1).padStart(2)} ${ad.padEnd(35)}${niyet.padEnd(14)}` + `${(yeni.join(",") || "-").padEnd(14)}${String(s.kural.length).padStart(13)}` + `${String(s.anlik.length).padStart(13)}${String(hepsi.length).padStart(7)}`); }); const son = sapmaOlc(grafik()); const toplam = KURAL.length + ANLIK.length + BELIRSIZ.length; console.log(`\nsekiz degisiklik sonunda ${toplam} iddianin ` + `${son.kural.length + son.anlik.length} tanesi yanlis, ${BELIRSIZ.length} tanesi denetlenemez, ` + `${toplam - son.kural.length - son.anlik.length - BELIRSIZ.length} tanesi dogru ` + `(dogru kalanlar: ${KURAL.filter(([a]) => !son.kural.includes(a)).map(([a]) => a).join(", ")})`); console.log(`kurala uygun ${DEGISIM.filter(([, n]) => n === "kurala uygun").length} degisiklik ` + `hicbir kural iddiasini dusurmedi; kural disi ` + `${DEGISIM.filter(([, n]) => n === "kural disi").length} degisiklik ` + `${son.kural.length} kural iddiasini dusurdu`);
t degisiklik niyet dusen iddia yanlis kural bayat anlik sapma 1 rezervasyon modulu eklenir kurala uygun A1,A3 0 2 2 2 odunc bildirim ithal eder kural disi K3,A2 1 3 4 3 ucret uyelik ithal eder kural disi K4 2 3 5 4 bildirim uyelik ithalini birakir kurala uygun A4 2 4 6 5 rezervasyon odunc tablosuna yazar kural disi K6 3 4 7 6 katalog etiket tablosunu devralir kurala uygun A6 3 5 8 7 ucret ceza tablosuna yazar kurala uygun A5 3 6 9 8 sube uyelik ithal eder kural disi K5 4 6 10 sekiz degisiklik sonunda 14 iddianin 10 tanesi yanlis, 2 tanesi denetlenemez, 2 tanesi dogru (dogru kalanlar: K1, K2) kurala uygun 4 degisiklik hicbir kural iddiasini dusurmedi; kural disi 4 degisiklik 4 kural iddiasini dusurdu
Sapma ilk değişiklikte başlıyor ve tek yönde büyüyor: 2, 4, 5, 6, 7, 8, 9, 10. Sonunda on dört iddianın onu yanlış, ikisi denetlenemez; doğru kalan iki kural iddiasıdır. Belge konuşmaya devam ediyor ama çoğunun kodda karşılığı yok.
İki tür iddianın davranışı ayrı. Kurala uygun dört değişikliğin hiçbiri bir kural iddiasını düşürmedi; hepsi yalnız anlık iddiaları bayattı. Kural dışı dört değişikliğin dördü de bir kural iddiasını düşürdü. Kural iddiasının yanlışlığı bir kod kusurudur; anlık iddianın bayatlığı bir belge kusurudur. İkisi aynı tabloda görünse de biri kodda, öteki belgede düzeltilir.
Üç Güncel Tutma Düzeni
Aynı sekiz değişiklik şimdi üç düzende koşturulur. Elle güncelleme: değişikliği yapan, yalnız dokunduğu modülün iddialarını gözden geçirir. Üretilen denetim: her değişiklikten sonra bütün iddialar koda karşı sınanır. Yalnız kural belgesi: belge anlık iddia hiç taşımaz.
// duzen.mjs — ortam.mjs'i ithal eder; ayni sekiz degisiklikte uc guncel tutma duzenini karsilastirir import { BASLANGIC, DEGISIM, KURAL, ANLIK, BELIRSIZ, yaz, grafik } from "./ortam.mjs"; // elle: degisikligi yapan, dokundugu modulun iddialarini gozden gecirir. // denetim: her degisiklikten sonra butun iddialar koda karsi otomatik sinanir. // kural: belge yalniz kural iddialarini tasir; anlik iddia hic yazilmaz. const kosu = (duzen) => { const m = structuredClone(BASLANGIC); yaz(m); const belge = duzen === "kural" ? [] : ANLIK.map(([ad, ozne, , oku]) => ({ ad, ozne, oku, deger: JSON.stringify(oku(grafik())) })); const farkedilen = new Set(); let guncelleme = 0, uyari = 0, yanlisAlarm = 0; for (const [, niyet, dokunulan, uygula] of DEGISIM) { uygula(m); yaz(m); const g = grafik(); const ihlal = KURAL.filter(([, , , sina]) => !sina(g)); const bayat = belge.filter((i) => JSON.stringify(i.oku(g)) !== i.deger); if (duzen === "elle") { for (const [ad, ozne] of ihlal) if (ozne === dokunulan) farkedilen.add(ad); for (const i of bayat) if (i.ozne === dokunulan) { i.deger = JSON.stringify(i.oku(g)); guncelleme += 1; } } else { for (const [ad] of ihlal) farkedilen.add(ad); uyari += bayat.length; if (niyet === "kurala uygun") yanlisAlarm += bayat.length; for (const i of bayat) { i.deger = JSON.stringify(i.oku(g)); guncelleme += 1; } } } const g = grafik(); const gercek = KURAL.filter(([, , , sina]) => !sina(g)).map(([ad]) => ad); return { duzen, belge, gercek, farkedilen, guncelleme, uyari, yanlisAlarm, bayat: belge.filter((i) => JSON.stringify(i.oku(g)) !== i.deger).map((i) => i.ad) }; }; const SONUC = ["elle", "denetim", "kural"].map(kosu); console.log(`${"duzen".padEnd(9)}${"yakalanan".padStart(10)}${"kacan".padStart(18)}` + `${"guncelleme".padStart(12)}${"anlik uyari".padStart(13)}${"yanlis alarm".padStart(14)}` + `${"bayat kalan".padStart(13)}`); for (const s of SONUC) { const kacan = s.gercek.filter((a) => !s.farkedilen.has(a)); console.log(`${s.duzen.padEnd(9)}${`${s.farkedilen.size}/${s.gercek.length}`.padStart(10)}` + `${(kacan.join(",") || "-").padStart(18)}${String(s.guncelleme).padStart(12)}` + `${String(s.uyari).padStart(13)}${String(s.yanlisAlarm).padStart(14)}` + `${(s.bayat.join(",") || "-").padStart(13)}`); } // Sonradan gelen birinin belgeye sordugu yedi soru; her soru bir iddiaya duser. const SORU = [ ["Sistem kac alan modulunden olusur?", "A1"], ["Odunc modulu hangi modulleri kullanir?", "A2"], ["Grafikte kac ithal kenari var?", "A3"], ["Bildirim moduluna kim baglanabilir?", "K3"], ["Odunc tablosuna kim yazabilir?", "K6"], ["Katalog hangi tablolarin sahibi?", "A6"], ["Gece bakim penceresi ne zaman?", "D2"], ]; const g = grafik(); const yanit = (s, iddia) => { if (KURAL.some(([a]) => a === iddia)) return "dogru"; // kural, niyeti soyler if (BELIRSIZ.some(([a]) => a === iddia)) return s.duzen === "kural" ? "yanitsiz" : "denetlenemez"; const i = s.belge.find((x) => x.ad === iddia); if (!i) return "yanitsiz"; return JSON.stringify(i.oku(g)) === i.deger ? "dogru" : "yanlis"; }; console.log(`\n${"soru".padEnd(40)}${"iddia".padEnd(7)}${"elle".padEnd(14)}${"denetim".padEnd(14)}kural`); for (const [soru, iddia] of SORU) { console.log(`${soru.padEnd(40)}${iddia.padEnd(7)}` + SONUC.map((s) => yanit(s, iddia).padEnd(14)).join("").trimEnd()); } for (const s of SONUC) { const v = SORU.map(([, i]) => yanit(s, i)); console.log(`${s.duzen.padEnd(9)}${v.filter((x) => x === "dogru").length} dogru, ` + `${v.filter((x) => x === "yanlis").length} yanlis, ` + `${v.filter((x) => x === "yanitsiz").length} yanitsiz, ` + `${v.filter((x) => x === "denetlenemez").length} denetlenemez`); }
duzen yakalanan kacan guncelleme anlik uyari yanlis alarm bayat kalan elle 3/4 K6 4 0 0 A1,A3 denetim 4/4 - 10 10 6 - kural 4/4 - 0 0 0 - soru iddia elle denetim kural Sistem kac alan modulunden olusur? A1 yanlis dogru yanitsiz Odunc modulu hangi modulleri kullanir? A2 dogru dogru yanitsiz Grafikte kac ithal kenari var? A3 yanlis dogru yanitsiz Bildirim moduluna kim baglanabilir? K3 dogru dogru dogru Odunc tablosuna kim yazabilir? K6 dogru dogru dogru Katalog hangi tablolarin sahibi? A6 dogru dogru yanitsiz Gece bakim penceresi ne zaman? D2 denetlenemez denetlenemez yanitsiz elle 4 dogru, 2 yanlis, 0 yanitsiz, 1 denetlenemez denetim 6 dogru, 0 yanlis, 0 yanitsiz, 1 denetlenemez kural 2 dogru, 0 yanlis, 5 yanitsiz, 0 denetlenemez
Elle güncelleme dört gerçek sapmanın üçünü yakalıyor, dördüncüsünü kaçırıyor. Kaçan sapma K6: ödünç tablosuna yazan yeni modül rezervasyondur, ama iddianın öznesi ödünçtür ve rezervasyona dokunan ödüncün iddiasına bakmaz. Elle güncellemenin kör noktası budur — dokunulan modül ile iddianın öznesi farklı olduğunda sapma görünmez. Bakım maliyeti ise en düşük: dört iddia güncellemesi.
Üretilen denetim dört sapmanın dördünü de yakalıyor ve hiç bayat iddia bırakmıyor. Bedeli on iddia güncellemesi ve on uyarıdır. Uyarıların altısı kurala uygun değişikliklerden, yani kod kusursuzken gelmiştir: yanlış alarm. Kaynağı bellidir — anlık iddialar her meşru değişiklikte düşer ve denetim onları gerçek sapmadan ayırmaz.
Yalnız kural belgesi iki kalemi de sıfırlıyor: dört sapma yakalanıyor, sıfır güncelleme, sıfır yanlış alarm. Bedeli soru tablosunda — yedi sorunun beşi yanıtsız. Bu düzen ucuzdur çünkü az şey söyler.
Soru tablosu üç düzeni bu kursun kendi ölçüsüyle ayırır. Elle güncellenen belge yedi sorunun dördünü doğru, ikisini yanlış yanıtlıyor: kaç modül ve kaç ithal kenarı olduğunu güvenle söylüyor, ikisi de yanlış. Yanıtsız soru okuyucuyu koda gönderir, yanlış yanıtlanan soru yanlış yere gönderir. Kural iddiaları üç düzende de doğru kalır: koda uymayan bir kural iddiası belgeyi değil kodu yanlışlar.
Özet
- Belge sapması ölçülebilir bir niceliktir: koddan çıkarılan ithal grafiği belgedeki iddiayla karşılaştırılır; yöntem Servis Mimarileri kursunun sınır ihlali ölçümünden ödünç alınmıştır.
- İddialar üç türdür ve ayrı davranır: kurala uygun dört değişiklik hiçbir kural iddiasını düşürmedi, yalnız anlık iddiaları bayattı; kural dışı dördü dört kural iddiasını düşürdü. Güncellenmeyen belgede sapma 2’den 10’a büyür, 14 iddianın yalnız 2’si doğru kalır.
- Elle güncelleme 4 sapmanın 3’ünü yakalar, 4 güncelleme ödetir; kör noktası dokunulan modül ile iddianın öznesinin farklı olduğu durumdur. Üretilen denetim 4’ünü de yakalar, 10 güncelleme ve 10 uyarı ödetir; uyarıların 6’sı yanlış alarmdır ve kaynağı anlık iddialardır.
- Yedi sorunun yanıtları düzenlere göre ayrışır: elle güncellemede 4 doğru ve 2 yanlış, üretilen denetimde 6 doğru, yalnız kural belgesinde 2 doğru ve 5 yanıtsız.
Sonraki Adım
Buraya kadar ölçülen her şey geçmişe ya da bugüne bakıyordu: verilen karar, çizilen sınır, koşan kod. Mimarın yazılı verdiği bir şey daha var ve o geleceğe bakıyor — bu iş ne kadar sürer. Sonraki ders tahmini ölçer: aynı iş kümesi tek sayı ve belirsizlik bandı olarak tahmin edilip gerçekleşmeyle karşılaştırılır. Sapma nasıl dağılıyor, bant gerçekleşmelerin kaçını kapsıyor ve tahmini parçalara bölmek sapmayı küçültüyor mu.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.