İçeriğe geç
academia.sh

Ders 10 / 19

Sınırlı Bağlam

Bir modelin geçerli olduğu alanın sınırı: aynı sözcüğün iki anlamı tek sınıfta birleştirildiğinde sınıfın taşıdığı alan sayısının ve hiçbir kullanımda birlikte geçmeyen alan çiftlerinin sayılması, iki bağlamın değişmezlerinin tek sınıfta kaç nesneyi reddettiğinin ölçülmesi ve sınırın çizilmesinin ücreti değiştirmediğinin gösterilmesi.

İçindekiler

Alan Modeli konusu anemik model eleştirisiyle kapandı. O konu boyunca tek bir modelin iç düzeni kuruldu — kimlik hangi nesnede, değişmez hangi sınırın içinde, davranış nereye ait — ve her karar modelin alanla hizasıyla ölçüldü. Sorulmayan tek soru şuydu: bu, hangi alanın modeli?

Soru boş değil. Gönderi ücretlendirme ve yönlendirme kitaplığında gonderi sözcüğü iki takımın ağzında iki farklı nesneyi adlandırıyor. Ücretlendirme takımı için gönderi; ağırlık, hacim, bölge ve beyan değeri taşıyan fiyatlanabilir bir birimdir. Teslimat operasyonu takımı için gönderi; rotası, aktarma noktaları ve durum zaman damgaları olan fiziksel bir pakettir. Sözcük aynı olduğu için tek bir sınıf yazılır ve o sınıf iki işi birden yapmaya zorlanır. Bu ders o zorlamanın bedelini iki sayıyla ölçer: sınıfın taşıdığı alan sayısı ve hiçbir kullanımda birlikte geçmeyen alan çiftlerinin sayısı.

Sınırlı Bağlam Bir Dil Sınırıdır

Bir modelin ve o modelin dilinin geçerli olduğu alana sınırlı bağlam (bounded context) denir. Sınırın içinde her ad tek bir şeyi gösterir; dışında aynı ad başka bir şeyi gösterebilir ve bu bir tutarsızlık değildir.

Tasarım İlkeleri kursundaki modül, bileşen ve politika–ayrıntı sınırları, sınırın nereye çizileceğini değişim sıklığı ve birlikte değişme ile yanıtlıyordu; sınırlı bağlam ise bir dil sınırıdır ve bir adın anlamının değiştiği yerden geçer, birlikte paketlenen dosya kümesinden değil. İki bağlam aynı bileşende de durabilir; ayrımı yapan şey paketleme değil, sözlüktür.

Her Yerde Aynı Dil dersinde kurulan kural burada bir koşul kazanır: dilin her yerde aynı olması, bir bağlamın içinde her yerde aynı olması anlamına gelir. İki takımın sözlüğü karşılaştırıldığında ortaklık sanıldığından azdır.

Sözcük Ücretlendirme bağlamı Teslimat operasyonu bağlamı
gonderi ağırlık, hacim, bölge ve beyan değeri taşıyan fiyatlanabilir birim rota, aktarma sayısı ve durum geçmişi taşıyan fiziksel paket
bolge ücret katsayısını veren tarife bölgesi karşılığı yok; yerine dağıtım merkezi kodu var
tarife kademe ve katsayı tablosu karşılığı yok
rota karşılığı yok çıkış, aktarma ve dağıtım noktalarının dizisi
teslimat karşılığı yok durum ve zaman damgası kaydı

Beş sözcükten yalnız biri iki bağlamda da geçiyor ve o bir sözcük iki farklı nesneyi adlandırıyor. Aynı alanın iki düzenlenişi bu tablodan çıkar: sözcüğü tek sınıfta birleştiren düzen ve sınırı sözcüğün anlamının değiştiği yerden geçiren düzen.

Tek Sınıfta Birleştirilmiş Düzen

Birinci düzende iki sözlük tek sınıfta toplanıyor. Sınıf her iki tarafın alanlarını, her iki tarafın davranışını ve her iki tarafın değişmezlerini taşıyor. Değişmezler dogrula işlevinde toplanmış; ücretlendirme tarafından beşi U, teslimat tarafından beşi T önekiyle işaretli.

mkdir -p birlesik ucretlendirme teslimat
// birlesik/gonderi.mjs — iki baglamin alanlarini ve degismezlerini tek sinifta tasiyan gonderi
const BOLGELER = ["yakin", "orta", "uzak"];
const DURUMLAR = ["kabul", "aktarmada", "dagitimda", "teslim-edildi"];
const BOS = {
  gonderiNo: null, agirlikGram: null, hacimDesimetreKup: null, ucretBolgesi: null,
  beyanDegeriKurus: null, tarifeKodu: null, sozlesmeNo: null, indirimOrani: null,
  tasiyiciKodu: null, rotaNoktalari: null, aktarmaSayisi: null, sonDurum: null,
  durumGecmisi: null, teslimAlanAd: null, teslimZamani: null,
};

export class Gonderi {
  constructor(v) { Object.assign(this, BOS, v); }
  ucret(tarife) {
    const esasGram = Math.max(this.agirlikGram, this.hacimDesimetreKup * 200);
    const birim = tarife[this.tarifeKodu][this.ucretBolgesi];
    return Math.round((esasGram / 1000) * birim * (1 - this.indirimOrani));
  }
  teslimIsaretle(ad, zaman) {
    this.teslimAlanAd = ad; this.teslimZamani = zaman; this.sonDurum = "teslim-edildi";
    this.durumGecmisi = [...this.durumGecmisi, ["teslim-edildi", zaman]];
  }
  dogrula() {
    const ihlal = [];
    if (!(Number.isInteger(this.agirlikGram) && this.agirlikGram > 0)) ihlal.push("U1-agirlik");
    if (!(this.hacimDesimetreKup > 0)) ihlal.push("U2-hacim");
    if (!BOLGELER.includes(this.ucretBolgesi)) ihlal.push("U3-bolge");
    if (!Number.isInteger(this.beyanDegeriKurus)) ihlal.push("U4-beyan");
    if (!(this.indirimOrani >= 0 && (this.indirimOrani === 0 || this.sozlesmeNo !== null))) ihlal.push("U5-indirim");
    if (!(this.rotaNoktalari?.length >= 2)) ihlal.push("T1-rota");
    if (this.aktarmaSayisi !== (this.rotaNoktalari?.length ?? 0) - 2) ihlal.push("T2-aktarma");
    if (!DURUMLAR.includes(this.sonDurum)) ihlal.push("T3-durum");
    if (this.durumGecmisi?.at(-1)?.[0] !== this.sonDurum) ihlal.push("T4-gecmis");
    if (this.sonDurum === "teslim-edildi" && this.teslimAlanAd === null) ihlal.push("T5-teslim");
    return ihlal;
  }
}

Sınıfın kurucusundaki BOS nesnesi dikkate değer: her alanın varsayılanı null. Bu bir gerçekleştirim ayrıntısı değil, birleştirmenin zorunlu sonucudur. Ücretlendirme kaydında rota yoktur, teslimat kaydında beyan değeri yoktur; tek sınıf ikisini de kabul edecekse her alanı yokluğa açmak zorundadır.

Sınırın Çizildiği Düzen

İkinci düzende sınır sözcüğün anlamının değiştiği yerden geçiyor. İki dizin, iki Gonderi sınıfı, iki değişmez kümesi. Aynı adın iki dosyada bulunması bir yineleme değildir; iki farklı nesnenin iki farklı bağlamdaki adıdır.

// ucretlendirme/gonderi.mjs — ucretlendirme baglaminda gonderi: fiyatlanabilir birim
const BOLGELER = ["yakin", "orta", "uzak"];

export class Gonderi {
  constructor(v) {
    Object.assign(this, { sozlesmeNo: null, indirimOrani: 0 }, v);
  }
  ucret(tarife) {
    const esasGram = Math.max(this.agirlikGram, this.hacimDesimetreKup * 200);
    const birim = tarife[this.tarifeKodu][this.ucretBolgesi];
    return Math.round((esasGram / 1000) * birim * (1 - this.indirimOrani));
  }
  dogrula() {
    const ihlal = [];
    if (!(Number.isInteger(this.agirlikGram) && this.agirlikGram > 0)) ihlal.push("U1-agirlik");
    if (!(this.hacimDesimetreKup > 0)) ihlal.push("U2-hacim");
    if (!BOLGELER.includes(this.ucretBolgesi)) ihlal.push("U3-bolge");
    if (!Number.isInteger(this.beyanDegeriKurus)) ihlal.push("U4-beyan");
    if (!(this.indirimOrani >= 0 && (this.indirimOrani === 0 || this.sozlesmeNo !== null))) ihlal.push("U5-indirim");
    return ihlal;
  }
}
// teslimat/gonderi.mjs — teslimat operasyonu baglaminda gonderi: fiziksel paket
const DURUMLAR = ["kabul", "aktarmada", "dagitimda", "teslim-edildi"];

export class Gonderi {
  constructor(v) {
    Object.assign(this, { teslimAlanAd: null, teslimZamani: null }, v);
  }
  teslimIsaretle(ad, zaman) {
    this.teslimAlanAd = ad; this.teslimZamani = zaman; this.sonDurum = "teslim-edildi";
    this.durumGecmisi = [...this.durumGecmisi, ["teslim-edildi", zaman]];
  }
  dogrula() {
    const ihlal = [];
    if (!(this.rotaNoktalari?.length >= 2)) ihlal.push("T1-rota");
    if (this.aktarmaSayisi !== (this.rotaNoktalari?.length ?? 0) - 2) ihlal.push("T2-aktarma");
    if (!DURUMLAR.includes(this.sonDurum)) ihlal.push("T3-durum");
    if (this.durumGecmisi?.at(-1)?.[0] !== this.sonDurum) ihlal.push("T4-gecmis");
    if (this.sonDurum === "teslim-edildi" && this.teslimAlanAd === null) ihlal.push("T5-teslim");
    return ihlal;
  }
}

İki sınıfın karşılaştırılabilmesi için ölçüm verisi gerekiyor: iki takımın kendi kayıtlarından aynı üç gönderi ve ücretlendirmenin tarife tablosu.

// veri.mjs — iki takimin kendi kayitlarindan ayni uc gonderi ve ortak tarife
export const TARIFE = {
  standart: { yakin: 1800, orta: 2400, uzak: 3200 },
  agir: { yakin: 1400, orta: 1900, uzak: 2600 },
};

export const UCRET_SATIRLARI = [
  { gonderiNo: "G-1041", agirlikGram: 2400, hacimDesimetreKup: 9, ucretBolgesi: "orta",
    beyanDegeriKurus: 180000, tarifeKodu: "standart", sozlesmeNo: "S-77", indirimOrani: 0.12 },
  { gonderiNo: "G-1042", agirlikGram: 700, hacimDesimetreKup: 2, ucretBolgesi: "yakin",
    beyanDegeriKurus: 0, tarifeKodu: "standart", sozlesmeNo: null, indirimOrani: 0 },
  { gonderiNo: "G-1043", agirlikGram: 18000, hacimDesimetreKup: 60, ucretBolgesi: "uzak",
    beyanDegeriKurus: 950000, tarifeKodu: "agir", sozlesmeNo: "S-12", indirimOrani: 0.2 },
];

export const TESLIMAT_SATIRLARI = [
  { gonderiNo: "G-1041", tasiyiciKodu: "T-A", rotaNoktalari: ["34-mrk", "06-akt", "06-dgt"],
    aktarmaSayisi: 1, sonDurum: "dagitimda",
    durumGecmisi: [["kabul", 1], ["aktarmada", 2], ["dagitimda", 3]] },
  { gonderiNo: "G-1042", tasiyiciKodu: "T-B", rotaNoktalari: ["34-mrk", "34-dgt"],
    aktarmaSayisi: 0, sonDurum: "kabul", durumGecmisi: [["kabul", 1]] },
  { gonderiNo: "G-1043", tasiyiciKodu: "T-A", rotaNoktalari: ["34-mrk", "35-akt", "65-akt", "65-dgt"],
    aktarmaSayisi: 2, sonDurum: "aktarmada", durumGecmisi: [["kabul", 1], ["aktarmada", 2]] },
];

Alanların ve Alan Çiftlerinin Sayılması

Birinci ölçü sınıfın taşıdığı alan sayısıdır. İkinci ölçü daha ayırt edicidir: iki alan hiçbir kullanımda birlikte okunmuyorsa aynı sınıfta durmalarının gerekçesi yoktur. Aşağıdaki betik altı kullanım senaryosunu ve her senaryonun okuduğu alanları taşıyor, sonra her alan çiftinin en az bir senaryoda birlikte geçip geçmediğini sayıyor.

// alan-say.mjs — alan sayisi, kullanim basina okunmayan alan ve birlikte hic gecmeyen alan cifti
import { Gonderi as Birlesik } from "./birlesik/gonderi.mjs";
import { Gonderi as UcretGonderi } from "./ucretlendirme/gonderi.mjs";
import { Gonderi as TeslimatGonderi } from "./teslimat/gonderi.mjs";
import { UCRET_SATIRLARI, TESLIMAT_SATIRLARI } from "./veri.mjs";

const KULLANIM = [
  ["ucretHesapla", ["gonderiNo", "agirlikGram", "hacimDesimetreKup", "ucretBolgesi", "tarifeKodu"]],
  ["indirimUygula", ["gonderiNo", "tarifeKodu", "sozlesmeNo", "indirimOrani"]],
  ["sigortaPrimi", ["gonderiNo", "beyanDegeriKurus", "ucretBolgesi"]],
  ["rotaAta", ["gonderiNo", "tasiyiciKodu", "rotaNoktalari", "aktarmaSayisi"]],
  ["aktarmaKaydet", ["gonderiNo", "rotaNoktalari", "aktarmaSayisi", "sonDurum", "durumGecmisi"]],
  ["teslimIsaretle", ["gonderiNo", "sonDurum", "durumGecmisi", "teslimAlanAd", "teslimZamani"]],
];

const alanlari = (Sinif, satir) => Object.keys(new Sinif(satir));

function olc(ad, alanlar, senaryolar) {
  let cift = 0, birlikte = 0;
  for (let i = 0; i < alanlar.length; i += 1) {
    for (let j = i + 1; j < alanlar.length; j += 1) {
      cift += 1;
      if (senaryolar.some(([, o]) => o.includes(alanlar[i]) && o.includes(alanlar[j]))) birlikte += 1;
    }
  }
  console.log(`${ad}: ${alanlar.length} alan, ${cift} alan cifti, ${senaryolar.length} senaryo`);
  for (const [senaryo, okunan] of senaryolar) {
    const icerde = okunan.filter((a) => alanlar.includes(a)).length;
    console.log(`  ${senaryo.padEnd(15)} okunan ${String(icerde).padStart(2)}, okunmayan ${String(alanlar.length - icerde).padStart(2)}`);
  }
  console.log(`  hic birlikte gecmeyen cift = ${cift - birlikte} / ${cift}`);
  return alanlar;
}

const hepsi = olc("birlesik", alanlari(Birlesik, {}), KULLANIM);
const ucret = alanlari(UcretGonderi, UCRET_SATIRLARI[0]);
const teslimat = alanlari(TeslimatGonderi, TESLIMAT_SATIRLARI[0]);
const yalnizUcret = ucret.filter((a) => !teslimat.includes(a));
const yalnizTeslimat = teslimat.filter((a) => !ucret.includes(a));
console.log(`  bunun ${yalnizUcret.length * yalnizTeslimat.length}'i baglam disi cift (${yalnizUcret.length} x ${yalnizTeslimat.length})`);
olc("ucretlendirme", ucret, KULLANIM.filter(([, o]) => o.every((a) => ucret.includes(a))));
olc("teslimat", teslimat, KULLANIM.filter(([, o]) => o.every((a) => teslimat.includes(a))));
console.log(`iki baglamda ortak alan = ${hepsi.filter((a) => ucret.includes(a) && teslimat.includes(a)).join(", ")}`);
node alan-say.mjs
birlesik: 15 alan, 105 alan cifti, 6 senaryo
  ucretHesapla    okunan  5, okunmayan 10
  indirimUygula   okunan  4, okunmayan 11
  sigortaPrimi    okunan  3, okunmayan 12
  rotaAta         okunan  4, okunmayan 11
  aktarmaKaydet   okunan  5, okunmayan 10
  teslimIsaretle  okunan  5, okunmayan 10
  hic birlikte gecmeyen cift = 68 / 105
  bunun 49'i baglam disi cift (7 x 7)
ucretlendirme: 8 alan, 28 alan cifti, 3 senaryo
  ucretHesapla    okunan  5, okunmayan  3
  indirimUygula   okunan  4, okunmayan  4
  sigortaPrimi    okunan  3, okunmayan  5
  hic birlikte gecmeyen cift = 11 / 28
teslimat: 8 alan, 28 alan cifti, 3 senaryo
  rotaAta         okunan  4, okunmayan  4
  aktarmaKaydet   okunan  5, okunmayan  3
  teslimIsaretle  okunan  5, okunmayan  3
  hic birlikte gecmeyen cift = 8 / 28
iki baglamda ortak alan = gonderiNo

Birleşik sınıf 15 alan taşıyor ve altı senaryonun hiçbiri beşten fazlasını okumuyor: her kullanımda en az on alan ilgisiz duruyor. Asıl sayı son satırdadır. 105 alan çiftinin 68’i hiçbir senaryoda birlikte geçmiyor ve bunların 49’u bağlam dışı çift, yani biri ücretlendirmeye, öteki teslimata ait alan çiftleri. Sınır çizildiğinde çift sayısı 105’ten 56’ya, hiç birlikte geçmeyen çift sayısı 68’den 19’a iniyor; 49 bağlam dışı çift tamamen ortadan kalkıyor. İki bağlamın ortak tek alanı gonderiNo, yani kimliğin kendisi.

Değişmezlerin Sayılması

Alan sayımı yapının gevşekliğini gösterir; değişmez sayımı daha keskin bir şey gösterir. Birleşik sınıfın dogrula işlevi on değişmez taşıyor ve bu on değişmezin hepsi her nesne için çalışıyor. Ücretlendirme kaydının rotası olmadığı, teslimat kaydının beyan değeri olmadığı için sonuç baştan bellidir, ama sayılması gerekir.

// ihlal-say.mjs — on degismezin alti gonderide kac ihlal verdigi ve ucretin degisip degismedigi
import { Gonderi as Birlesik } from "./birlesik/gonderi.mjs";
import { Gonderi as UcretGonderi } from "./ucretlendirme/gonderi.mjs";
import { Gonderi as TeslimatGonderi } from "./teslimat/gonderi.mjs";
import { TARIFE, UCRET_SATIRLARI, TESLIMAT_SATIRLARI } from "./veri.mjs";

const birlesikNesneler = [...UCRET_SATIRLARI, ...TESLIMAT_SATIRLARI].map((s) => new Birlesik(s));
let toplam = 0, gecen = 0;
console.log("birlesik sinif (10 degismez)");
for (const g of birlesikNesneler) {
  const ihlal = g.dogrula();
  toplam += ihlal.length;
  if (ihlal.length === 0) gecen += 1;
  console.log(`  ${g.gonderiNo}  ihlal ${ihlal.length}  ${ihlal.join(" ") || "-"}`);
}
console.log(`  gecen nesne = ${gecen} / ${birlesikNesneler.length}, toplam ihlal = ${toplam}`);

const ayrilmis = [...UCRET_SATIRLARI.map((s) => new UcretGonderi(s)),
                  ...TESLIMAT_SATIRLARI.map((s) => new TeslimatGonderi(s))];
let t2 = 0, g2 = 0;
console.log("ayrilmis siniflar (5 + 5 degismez)");
for (const g of ayrilmis) {
  const ihlal = g.dogrula();
  t2 += ihlal.length;
  if (ihlal.length === 0) g2 += 1;
}
console.log(`  gecen nesne = ${g2} / ${ayrilmis.length}, toplam ihlal = ${t2}`);

let ayrilan = 0;
for (const s of UCRET_SATIRLARI) {
  const a = new Birlesik(s).ucret(TARIFE), b = new UcretGonderi(s).ucret(TARIFE);
  if (a !== b) ayrilan += 1;
  console.log(`  ${s.gonderiNo}  birlesik ${a}  ayrilmis ${b}`);
}
console.log(`ayrilan ucret = ${ayrilan} / ${UCRET_SATIRLARI.length}`);
node ihlal-say.mjs
birlesik sinif (10 degismez)
  G-1041  ihlal 4  T1-rota T2-aktarma T3-durum T4-gecmis
  G-1042  ihlal 4  T1-rota T2-aktarma T3-durum T4-gecmis
  G-1043  ihlal 4  T1-rota T2-aktarma T3-durum T4-gecmis
  G-1041  ihlal 5  U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim
  G-1042  ihlal 5  U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim
  G-1043  ihlal 5  U1-agirlik U2-hacim U3-bolge U4-beyan U5-indirim
  gecen nesne = 0 / 6, toplam ihlal = 27
ayrilmis siniflar (5 + 5 degismez)
  gecen nesne = 6 / 6, toplam ihlal = 0
  G-1041  birlesik 5069  ayrilmis 5069
  G-1042  birlesik 1260  ayrilmis 1260
  G-1043  birlesik 37440  ayrilmis 37440
ayrilan ucret = 0 / 3

Altı gerçek gönderinin hiçbiri birleşik sınıfın değişmezlerini geçmiyor: 0/6, toplam 27 ihlal. Aynı altı gönderi, aynı on değişmez iki sınıfa dağıtıldığında 6/6 geçiyor ve ihlal sayısı sıfır. Değişmezlerin metni değişmedi, yalnız uygulandıkları alan değişti.

Son üç satır ayrımın davranışı bozmadığını gösteriyor: üç gönderinin ücreti iki düzende de aynı. Sınır çizmek modeli iki parçaya böler, hesabı değiştirmez.

Koşullu Değişmezin Gizlediği Şey

Birleşik sınıfı kurtarmanın bilinen bir yolu vardır: değişmezleri koşullu yazmak. Rota denetimi yalnız rota alanı doluysa çalışsın, ağırlık denetimi yalnız ağırlık doluysa çalışsın. Bu, ihlal sayısını sıfıra indirir ama bir şeyi kaybeder: koşul, hangi bağlamın nesnesiyle çalışıldığını sınıfın içine bir veri olarak geri koyar. Değişmez artık “gönderinin rotası en az iki noktadır” demez, “eğer bu nesne teslimat tarafından geldiyse rotası en az iki noktadır” der. İlki alan uzmanının cümlesidir, ikincisi değildir.

Sınırın nereden geçeceğini bulmanın yordamı buradan çıkar: bir sınıfın değişmezlerinden hangilerinin ancak koşul altında yazılabildiğine bakılır. Koşul bir bağlam ayrımını gizliyorsa, sınır o koşulun yerinden geçer.

Özet

  • Sınırlı bağlam, bir modelin ve dilinin geçerli olduğu alandır; bileşen sınırı gibi birlikte paketlenen dosyalardan değil, bir adın anlamının değiştiği yerden geçer.
  • İki bağlamın sözlüğünde beş sözcükten yalnız biri ortaktı ve o sözcük iki farklı nesneyi adlandırıyordu; iki sınıfın ortak tek alanı gonderiNo çıktı.
  • Birleşik sınıf 15 alan taşıdı, altı senaryonun hiçbiri beşten fazlasını okumadı; 105 alan çiftinin 68’i hiçbir senaryoda birlikte geçmedi ve bunların 49’u bağlam dışı çiftti.
  • Sınır çizildiğinde alan çifti 105’ten 56’ya, hiç birlikte geçmeyen çift 68’den 19’a indi; bağlam dışı 49 çift ortadan kalktı.
  • On değişmez tek sınıfta altı gerçek gönderinin altısını da reddetti (0/6, 27 ihlal); aynı değişmezler iki sınıfa dağıtıldığında 6/6 geçti ve üç gönderinin ücreti değişmedi.
  • Değişmezi koşullu yazmak ihlali sıfırlar ama bağlam ayrımını sınıfın içine veri olarak geri koyar; koşulun yeri sınırın yerini gösterir.

Sonraki Adım

Sınır çizildi ve iki bağlam kendi sözlüğüne kavuştu. Ne var ki iki bağlam birbirinden habersiz çalışamaz: ücretlendirme bir gönderinin fiyatını hesaplarken teslimat o gönderiyi yola çıkarır, teslim edildiğinde ücretlendirmenin bunu bilmesi gerekir. Sınır bir duvar değil, bir geçittir ve geçidin kaç tane olduğu, hangi yöne baktığı, kimin kimin sözlüğüne uyduğu tasarım kararıdır. Sonraki ders bağlamlar arası ilişkileri bir çizge olarak yazar, ilişki türlerini yön ve bağımlılık cinsinden ayırt eder, sonra iki haritada aynı ölçüyü koşturur: bir bağlam değiştiğinde kaç bağlamda dosya düzenlenir ve bu sayı ilişkiler adlandırıldığında nereye iner.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat