Ders 05 / 18
Mikroçekirdek ve Eklenti Mimarisi
Değişmeyen küçük bir çekirdek ile sayısı önceden bilinmeyen eklentilerin ayrılması: çekirdek dosyasının eklentiler hakkında bildiği ad sayısının dörtten sıfıra inmesi, beşinci eklenti eklendiğinde çekirdekte düzenlenen satır sayısının ölçülmesi, eklentisiz çekirdeğin tek dosyalık kapanışla koşması ve sözleşmeyi bozan bir eklentinin iki düzende kaç isteği durdurduğunun sayılması.
İçindekiler
Bileşen sınırı yeniden kullanım birimini verdi, ama o ölçülerin hepsi yayın zamanındaydı. Açıkta kalan soru şudur: bu birimlerden hangisi program koşarken takılıp çıkarılabilir. Sınır bir yayın kararıdır, takılıp çıkarılabilirlik bir çalışma zamanı kararı; ikisi aynı kümeyi vermez.
Bu ders birimleri iki sınıfa bölen düzeni ele alır: değişmeyen, tek başına koşabilen küçük bir parça ile onun üzerine takılan, sayısı önceden bilinmeyen parçalar. Dört şey ölçülür: küçük parçanın ötekiler hakkında bildiği ad sayısı, yeni bir parçanın düzenlettiği satır sayısı, parçasız çekirdeğin koşup koşmadığı ve bozuk bir parçanın bütünü durdurup durdurmadığı.
Çekirdek, Eklenti ve Sözleşme
Mikroçekirdek (microkernel) düzeni üç şey ilan eder. Birincisi çekirdektir: hiçbir eklenti takılmasa bile bir yanıt üretebilen en küçük çalışan bütün. Linux’a Giriş kursunda geçen çekirdek işletim sisteminin donanımı yöneten parçasıydı; buradaki çekirdek bir kitaplığın en küçük çalışan parçasıdır ve iki kullanım aynı şey değildir. İkincisi eklentidir (plugin): çekirdeğin yeteneğini genişleten, çekirdeğin adını bilmediği birim. Üçüncüsü eklenti sözleşmesidir: eklentinin karşılaması gereken adların bildirimi.
Bu sözleşme yeni bir kavram değil, bir bağlantı noktasıdır; Alan Odaklı Tasarım kursundaki Bağlantı Noktaları ve Bağdaştırıcılar dersinde kurulmuştur. Bileşen sınırı ile bileşen bölme ilkeleri de Tasarım İlkeleri kursundaki Bileşen Uyum İlkeleri ve Bileşen Bağlaşım İlkeleri derslerinde kurulmuştu. Burada anlatılan ilke değil düzen: sözleşme bir çalışma zamanı kaydına bağlandığında hangi sayıların değiştiği. Belirleyici kısıt tek cümledir — çekirdek hiçbir eklentinin adını taşımaz, yalnız sözleşmenin adlarını taşır.
Kitaplığın Eklentili Düzeni
Ücret hesabı iki parçaya bölünüyor. Çekirdek kademe tablosundan tabanı bulur, yuvarlar ve asgari
ücreti uygular; tabana eklenen ya da tabandan düşen her kalem bir eklentidir. Dört eklenti aynı
sözleşmeyi karşılıyor: ad, okuduğu gönderi alanlarını bildiren alanlar ve kuruş döndüren
kalem. Son dosya kitaplığın değil ölçümün parçasıdır.
mkdir -p mikrocekirdek/eklentiler cat > mikrocekirdek/tarife.mjs <<'EOF' // mikrocekirdek/tarife.mjs — kademe tablosu: eklenti degil, cekirdegin kurulus girdisi export const KADEMELER = [ { ustAgirlik: 1, ucret: 4990 }, { ustAgirlik: 5, ucret: 8490 }, { ustAgirlik: 15, ucret: 14990 }, { ustAgirlik: 30, ucret: 24990 }, ]; EOF cat > mikrocekirdek/eklentiler/sozlesme-indirimi.mjs <<'EOF' // mikrocekirdek/eklentiler/sozlesme-indirimi.mjs — sozlesme oranina gore eksi kalem const ORAN = { yok: 0, standart: 0.05, hacimli: 0.12 }; export const sozlesmeIndirimi = { ad: "sozlesme-indirimi", alanlar: ["sozlesme"], kalem: (g, taban) => -Math.round(taban * (ORAN[g.sozlesme] ?? 0)) }; EOF cat > mikrocekirdek/eklentiler/hacim-farki.mjs <<'EOF' // mikrocekirdek/eklentiler/hacim-farki.mjs — hacimsel agirligin gercek agirligi astigi kadar const HACIM_BOLENI = 5000; const KG_UCRETI = 900; export const hacimFarki = { ad: "hacim-farki", alanlar: ["agirlik", "hacim"], kalem: (g) => Math.round(Math.max(0, g.hacim / HACIM_BOLENI - g.agirlik) * KG_UCRETI) }; EOF cat > mikrocekirdek/eklentiler/sigorta-ucreti.mjs <<'EOF' // mikrocekirdek/eklentiler/sigorta-ucreti.mjs — beyan degerinin binde dordu const SIGORTA_ORANI = 0.004; export const sigortaUcreti = { ad: "sigorta-ucreti", alanlar: ["beyanDegeri"], kalem: (g) => Math.round(g.beyanDegeri * SIGORTA_ORANI) }; EOF cat > mikrocekirdek/eklentiler/bolge-farki.mjs <<'EOF' // mikrocekirdek/eklentiler/bolge-farki.mjs — bolgeye gore sabit ek kalem const FARK = { yakin: 0, orta: 1500, uzak: 4200 }; export const bolgeFarki = { ad: "bolge-farki", alanlar: ["bolge"], kalem: (g) => FARK[g.bolge] ?? FARK.uzak }; EOF cat > ornek-gonderiler.mjs <<'EOF' // ornek-gonderiler.mjs — butun olcumlerde kullanilan dort gonderi export const GONDERILER = [ { agirlik: 3, hacim: 24000, bolge: "orta", beyanDegeri: 150000, sozlesme: "standart" }, { agirlik: 0.4, hacim: 1200, bolge: "yakin", beyanDegeri: 0, sozlesme: "yok" }, { agirlik: 12, hacim: 90000, bolge: "uzak", beyanDegeri: 400000, sozlesme: "hacimli" }, { agirlik: 28, hacim: 60000, bolge: "orta", beyanDegeri: 25000, sozlesme: "yok" }, ]; EOF
Karşılaştırma iki çekirdek arasında yapılıyor. Birincisi dört bileşeni adıyla ithal eder ve sırasını kendi içinde sabitler.
// mikrocekirdek/gomulu.mjs — dort bileseni adiyla ithal eden ve sirasini kendi icinde sabitleyen cekirdek import { sozlesmeIndirimi } from "./eklentiler/sozlesme-indirimi.mjs"; import { hacimFarki } from "./eklentiler/hacim-farki.mjs"; import { sigortaUcreti } from "./eklentiler/sigorta-ucreti.mjs"; import { bolgeFarki } from "./eklentiler/bolge-farki.mjs"; const ASGARI_UCRET = 3990; const YUVARLAMA_ADIMI = 50; export const gomuluCekirdek = (kademeler) => ({ hesapla: (gonderi) => { const kademe = kademeler.find((k) => gonderi.agirlik <= k.ustAgirlik); if (kademe === undefined) throw new RangeError("agirlik kademesi yok"); const taban = kademe.ucret; const toplam = taban + sozlesmeIndirimi.kalem(gonderi, taban) + hacimFarki.kalem(gonderi, taban) + sigortaUcreti.kalem(gonderi, taban) + bolgeFarki.kalem(gonderi, taban); const net = Math.round(toplam / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI; return { tutar: Math.max(net, ASGARI_UCRET), uygulanan: 4, dusen: [] }; }, });
İkincisi eklenti kaydı tutar. Sözleşmeyi karşılamayan eklenti takma anında reddedilir; koşum anında bozan eklenti o istekte düşürülür ve adı raporlanır.
// mikrocekirdek/eklentili.mjs — cekirdek yalniz sozlesme adlarini bilir; hicbir eklenti adi gecmez const ASGARI_UCRET = 3990; const YUVARLAMA_ADIMI = 50; export const SOZLESME = ["ad", "alanlar", "kalem"]; const karsilar = (e) => typeof e?.ad === "string" && Array.isArray(e?.alanlar) && typeof e?.kalem === "function"; export const cekirdek = (kademeler) => { const kayit = []; return { tak: (e) => (karsilar(e) ? (kayit.push(e), true) : false), takili: () => kayit.map((e) => e.ad), hesapla: (gonderi) => { const kademe = kademeler.find((k) => gonderi.agirlik <= k.ustAgirlik); if (kademe === undefined) throw new RangeError("agirlik kademesi yok"); const taban = kademe.ucret; const dusen = []; let toplam = taban; for (const e of kayit) { let d = null; try { if (e.alanlar.every((a) => gonderi[a] !== undefined)) d = e.kalem(gonderi, taban); } catch { d = null; } if (Number.isFinite(d)) toplam += d; else dusen.push(e.ad); } const net = Math.round(toplam / YUVARLAMA_ADIMI) * YUVARLAMA_ADIMI; return { tutar: Math.max(net, ASGARI_UCRET), uygulanan: kayit.length - dusen.length, dusen }; }, }; };
Eklenti adlarının yazılı olduğu tek yer bileşim köküdür.
// mikrocekirdek/kur.mjs — bilesim koku: cekirdegi kurar, eklentileri bildirilmis sirayla takar import { cekirdek } from "./eklentili.mjs"; import { sozlesmeIndirimi } from "./eklentiler/sozlesme-indirimi.mjs"; import { hacimFarki } from "./eklentiler/hacim-farki.mjs"; import { sigortaUcreti } from "./eklentiler/sigorta-ucreti.mjs"; import { bolgeFarki } from "./eklentiler/bolge-farki.mjs"; export const EKLENTILER = [sozlesmeIndirimi, hacimFarki, sigortaUcreti, bolgeFarki]; export const kur = (kademeler, eklentiler = EKLENTILER) => { const c = cekirdek(kademeler); const red = eklentiler.filter((e) => c.tak(e) === false).length; return { c, red }; };
Çekirdeğin Bildiği Ad Sayısı
Betik üç dosyanın metninde kaç eklenti adının geçtiğini sayar, kapanışlarını çıkarır, eklentisiz çekirdeği koşturur ve iki düzenin aynı tutarı verdiğini sınar.
// cekirdek-olcum.mjs — cekirdek dosyalarinin bildigi eklenti adi, ithal kapanisi ve eklentisiz kosum import { readFileSync } from "node:fs"; import { dirname, join, normalize } from "node:path"; import { KADEMELER } from "./mikrocekirdek/tarife.mjs"; import { gomuluCekirdek } from "./mikrocekirdek/gomulu.mjs"; import { cekirdek, SOZLESME } from "./mikrocekirdek/eklentili.mjs"; import { EKLENTILER, kur } from "./mikrocekirdek/kur.mjs"; import { GONDERILER } from "./ornek-gonderiler.mjs"; const ADLAR = EKLENTILER.map((e) => e.ad); const kapanis = (kok, g = new Set()) => { if (g.has(kok)) return g; g.add(kok); const bul = readFileSync(kok, "utf8").matchAll(/^import\s.*?from\s+"(\.[^"]+)"/gm); for (const [, y] of bul) kapanis(normalize(join(dirname(kok), y)), g); return g; }; console.log(`eklenti = ${ADLAR.length}, sozlesme adi = ${SOZLESME.length} (${SOZLESME.join(", ")})`); for (const d of ["gomulu.mjs", "eklentili.mjs", "kur.mjs"]) { const metin = readFileSync(`mikrocekirdek/${d}`, "utf8"); console.log(` ${d.padEnd(14)} bildigi eklenti adi = ${ADLAR.filter((a) => metin.includes(a)).length}` + ` ithal kapanisi = ${kapanis(`mikrocekirdek/${d}`).size} dosya`); } const bos = cekirdek(KADEMELER).hesapla(GONDERILER[0]); console.log(`\neklentisiz cekirdek: takili = 0, tutar = ${bos.tutar}, uygulanan = ${bos.uygulanan}`); const cift = GONDERILER.map((g) => [gomuluCekirdek(KADEMELER).hesapla(g), kur(KADEMELER).c.hesapla(g)]); console.log(`dort eklentiyle tutarlar = ${cift.map(([a]) => a.tutar).join(", ")}` + ` uygulanan = ${cift[0][1].uygulanan}`); console.log(`ayrilan sonuc = ${cift.filter(([a, b]) => a.tutar !== b.tutar).length} / ${cift.length}` + `, reddedilen eklenti = ${kur(KADEMELER).red}`);
node cekirdek-olcum.mjs
eklenti = 4, sozlesme adi = 3 (ad, alanlar, kalem) gomulu.mjs bildigi eklenti adi = 4 ithal kapanisi = 5 dosya eklentili.mjs bildigi eklenti adi = 0 ithal kapanisi = 1 dosya kur.mjs bildigi eklenti adi = 4 ithal kapanisi = 6 dosya eklentisiz cekirdek: takili = 0, tutar = 8500, uygulanan = 0 dort eklentiyle tutarlar = 11800, 5000, 24400, 26600 uygulanan = 4 ayrilan sonuc = 0 / 4, reddedilen eklenti = 0
İlk üç satır düzeni ölçüyor. Gömülü çekirdek dört eklenti adı taşıyor ve kapanışı beş dosya: kuralı yüklemek için dört eklentiyi de yüklemek gerekiyor. Eklentili çekirdek sıfır ad taşıyor ve kapanışı bir dosya. Adlar yok olmadı, yer değiştirdi: bileşim kökü dördünü de biliyor ve kapanışı altı dosya. Düzen bilme yükümlülüğünü çekirdekten alıp tek bir kuruluş dosyasına yığıyor.
Son üç satır eklentisiz çekirdeğin koştuğunu gösteriyor: 3 kg’lık gönderi için taban 8.490 kuruş yuvarlanarak 8.500 kuruş çıktı, uygulanan kalem 0. Dört eklentiyle aynı gönderi 11.800 kuruşa çıkıyor ve dört gönderinin dördünde iki düzenin tutarı birebir aynı.
Beşinci Eklentinin Maliyeti
Uzantı maliyeti, yeni bir yetenek eklendiğinde var olan dosyalarda düzenlenen satır sayısıdır. Beşinci eklenti tabanın yüzdesi olarak bir yakıt farkı ekliyor. Blok, ağacın bir kopyasında iki düzene de aynı eklentiyi ekler; yama yol yorumlarını da düzelttiği için sayım ilk satırı atlıyor.
cp -r mikrocekirdek besinci cat > besinci/eklentiler/yakit-farki.mjs <<'EOF' // besinci/eklentiler/yakit-farki.mjs — besinci eklenti: tabanin yuzdesi olarak yakit farki const YAKIT_ORANI = 0.085; export const yakitFarki = { ad: "yakit-farki", alanlar: ["bolge"], kalem: (g, taban) => Math.round(taban * YAKIT_ORANI) }; EOF python3 - <<'EOF' from pathlib import Path ITHAL = 'import { bolgeFarki } from "./eklentiler/bolge-farki.mjs";' YAKIT = 'import { yakitFarki } from "./eklentiler/yakit-farki.mjs";' YAMA = { "gomulu.mjs": [(ITHAL, f"{ITHAL}\n{YAKIT}"), (" + bolgeFarki.kalem(gonderi, taban);", " + bolgeFarki.kalem(gonderi, taban)\n + yakitFarki.kalem(gonderi, taban);"), ("uygulanan: 4", "uygulanan: 5"), ("dort bileseni", "bes bileseni")], "kur.mjs": [(ITHAL, f"{ITHAL}\n{YAKIT}"), ("bolgeFarki];", "bolgeFarki, yakitFarki];")], } for yol in sorted(Path("besinci").rglob("*.mjs")): metin = yol.read_text().replace("// mikrocekirdek/", "// besinci/") for eski, yeni in YAMA.get(yol.name, []): metin = metin.replace(eski, yeni) yol.write_text(metin) EOF for d in gomulu.mjs eklentili.mjs kur.mjs; do f=$(diff <(tail -n +2 mikrocekirdek/$d) <(tail -n +2 besinci/$d)) echo "$d cikan satir = $(echo "$f" | grep -c '^<') eklenen satir = $(echo "$f" | grep -c '^>')" done echo "eklenen dosya = $(( $(find besinci -name '*.mjs' | wc -l) - $(find mikrocekirdek -name '*.mjs' | wc -l) ))" cat > besinci-kontrol.mjs <<'EOF' // besinci-kontrol.mjs — yamali agacta iki duzen ayni tutari uretiyor mu import { KADEMELER } from "./besinci/tarife.mjs"; import { gomuluCekirdek } from "./besinci/gomulu.mjs"; import { kur } from "./besinci/kur.mjs"; import { GONDERILER } from "./ornek-gonderiler.mjs"; const iki = [gomuluCekirdek(KADEMELER), kur(KADEMELER).c].map((c) => c.hesapla(GONDERILER[0]).tutar); console.log(`besinci eklentiyle gomulu = ${iki[0]}, eklentili = ${iki[1]}`); EOF node besinci-kontrol.mjs
gomulu.mjs cikan satir = 2 eklenen satir = 4 eklentili.mjs cikan satir = 0 eklenen satir = 0 kur.mjs cikan satir = 1 eklenen satir = 2 eklenen dosya = 1 besinci eklentiyle gomulu = 12500, eklentili = 12500
Aynı yetenek iki düzende de bir dosya ekletti ve iki düzen aynı tutarı verdi, ama düzenlenen satırların yeri farklı. Gömülü çekirdekte üç ayrı yer değişti — ithal satırı, toplama terimi ve uygulanan sayısı — 2 satır çıktı, 4 satır girdi. Eklentili çekirdek dosyasında 0 satır değişti; değişen tek dosya bileşim kökü oldu. Ölçü çekirdeğin kapalı olduğunu söylüyor: yeni bir kalem türü çekirdeği yeniden sınamayı ve yayınlamayı gerektirmiyor.
Sözleşmeyi Bozan Eklenti
Takılıp çıkarılabilirliğin ikinci yüzü, takılan şeyin bozuk olabilmesidir. Blok, sigorta eklentisini imzası doğru, davranışı bozuk bir eklentiyle değiştiriyor.
cp -r mikrocekirdek bozuk cat > bozuk/eklentiler/sigorta-ucreti.mjs <<'EOF' // bozuk/eklentiler/sigorta-ucreti.mjs — sozlesmeyi kosum aninda bozan eklenti: sayi yerine hata atiyor export const sigortaUcreti = { ad: "sigorta-ucreti", alanlar: ["beyanDegeri"], kalem: () => { throw new TypeError("beyan degeri okunamadi"); } }; EOF
// dayaniklilik.mjs — sozlesmeyi bozan eklentinin iki duzende kac istegi durdurdugunu sayar import { KADEMELER } from "./mikrocekirdek/tarife.mjs"; import { gomuluCekirdek as saglamGomulu } from "./mikrocekirdek/gomulu.mjs"; import { EKLENTILER, kur as saglamKur } from "./mikrocekirdek/kur.mjs"; import { gomuluCekirdek as bozukGomulu } from "./bozuk/gomulu.mjs"; import { kur as bozukKur } from "./bozuk/kur.mjs"; import { GONDERILER } from "./ornek-gonderiler.mjs"; const DUZENLER = [ ["gomulu / saglam", () => saglamGomulu(KADEMELER)], ["gomulu / bozuk ", () => bozukGomulu(KADEMELER)], ["eklentili / saglam", () => saglamKur(KADEMELER).c], ["eklentili / bozuk ", () => bozukKur(KADEMELER).c], ]; for (const [ad, yap] of DUZENLER) { const c = yap(); let yanit = 0, ilk = "-", dusen = "-", hata = ""; for (const g of GONDERILER) { try { const s = c.hesapla(g); if (++yanit === 1) { ilk = String(s.tutar); dusen = s.dusen.join(",") || "yok"; } } catch (h) { hata = hata || `${h.name}: ${h.message}`; } } console.log(`${ad} yanitlanan = ${yanit}/${GONDERILER.length} ilk tutar = ${ilk.padStart(6)}` + ` ${hata === "" ? `dusen = ${dusen}` : hata}`); } const kalemsiz = { ad: "yakit-farki", alanlar: ["bolge"] }; const { c, red } = saglamKur(KADEMELER, [...EKLENTILER, kalemsiz]); console.log(`\nsozlesmeyi takma aninda bozan eklenti: reddedilen = ${red}, takili = ${c.takili().length}`); console.log(` takili adlar = ${c.takili().join(", ")}`); console.log(` ayni gonderinin tutari = ${c.hesapla(GONDERILER[0]).tutar}`);
node dayaniklilik.mjs
gomulu / saglam yanitlanan = 4/4 ilk tutar = 11800 dusen = yok gomulu / bozuk yanitlanan = 0/4 ilk tutar = - TypeError: beyan degeri okunamadi eklentili / saglam yanitlanan = 4/4 ilk tutar = 11800 dusen = yok eklentili / bozuk yanitlanan = 4/4 ilk tutar = 11200 dusen = sigorta-ucreti sozlesmeyi takma aninda bozan eklenti: reddedilen = 1, takili = 4 takili adlar = sozlesme-indirimi, hacim-farki, sigorta-ucreti, bolge-farki ayni gonderinin tutari = 11800
Aynı bozuk dosya iki düzende iki ayrı sonuç veriyor. Gömülü çekirdekte dört isteğin dördü yanıtsız kaldı: bileşen çağrısı hesabın gövdesinde olduğu için hata bütün hesabı durdurdu. Eklentili çekirdekte dördü de yanıtlandı, bozuk eklenti düşürüldü ve adı raporlandı; tutar 11.200’e indi, yani sigorta kalemi eksik. Bu doğru bir yanıt değil, eksik bir yanıttır ve raporlanması eksikliği görülebilir kılar.
Son üç satır sözleşmenin öteki yüzü. kalem alanı olmayan eklenti takma anında reddedildi:
reddedilen 1, takılı 4 ve tutar 11.800’de kaldı. İki savunma noktası var — biri imzayı, öteki
dönen değeri sınıyor — ve ikisi de eklenti adını bilmiyor.
Ödünleşimin Sayıları
Kazanç iki kalite niteliğinde ölçüldü. Değiştirilebilirlikte: çekirdeğin bildiği eklenti adı 4’ten 0’a, kapanışı 5 dosyadan 1 dosyaya indi ve beşinci eklenti çekirdekte 0 satır düzenletti. Kullanılabilirlikte: yanıtlanan istek 0/4’ten 4/4’e çıktı.
Bedel üç kalemde yazılıyor. Birincisi yer değiştirmedir: dört ad bileşim kökünde toplandı ve o dosyanın kapanışı 6 dosya oldu, sıra artık yalnız o dosyadan okunur. İkincisi eksik yanıttır: çekirdeğin ayakta kalması sonucun tam olduğu anlamına gelmez. Üçüncüsü sözleşmedir — üç ad her eklentide tekrarlanır ve sözleşme değiştiği gün beş dosya birlikte değişir.
Özet
- Mikroçekirdek düzeni üç şey ilan eder: eklentisiz koşabilen bir çekirdek, sayısı önceden bilinmeyen eklentiler ve eklentinin karşılaması gereken adları bildiren sözleşme; sözleşme bir bağlantı noktasıdır.
- Gömülü çekirdek 4 eklenti adı taşıyor ve kapanışı 5 dosya; eklentili çekirdek 0 ad taşıyor ve kapanışı 1 dosya. Adlar kapanışı 6 dosya olan bileşim köküne taşındı.
- Eklentisiz çekirdek 3 kg’lık gönderi için 8.500 kuruş üretti, dört eklentiyle 11.800 kuruş çıktı ve dört gönderinin dördünde iki düzenin tutarı aynı.
- Beşinci eklenti iki düzende de 1 dosya ekletti; gömülü çekirdekte 2 satır çıkıp 4 satır girdi, eklentili çekirdek dosyasında 0 satır değişti.
- Koşum anında sözleşmeyi bozan eklentiyle yanıtlanan istek gömülü düzende 0/4, eklentili düzende 4/4 oldu; takma anında sözleşmeyi karşılamayan eklenti reddedildi.
Sonraki Adım
Bu dersin bütün ölçüleri çekirdeğin ne bildiği üzerineydi. Eklentiler birbirini hiç tanımıyordu, ama hepsi çekirdek tarafından biliniyordu: sıranın ne olduğu, kimin çağrılacağı ve sonucun nasıl toplanacağı tek bir yerde duruyordu. Sonraki ders o tek yeri de kaldırır. Birimler ne birbirini ne kendilerini çağıran bir merkezi tanır; ortak bir bilgi alanına yazar, oradan okur ve buluşmayı alanın kendisi sağlar. Bunun adı kara tahtadır ve üç şey ölçülür: birimlerin birbiri hakkında bildiği ad sayısı, aynı sonuca ulaşmak için yapılan gereksiz iş — kaç birim aynı veriyi okudu, kaç tur boşa döndü — ve aynı girdinin her koşumda aynı çıktıyı verip vermediği.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.