İçeriğe geç
academia.sh

Ders 15 / 19

Sürümleme ve Yayın

Bileşen arayüzünde neyin kırıcı sayıldığı, iki anlık görüntünün karşılaştırılmasından sürüm numarasının türetilmesi, göç kılavuzunun üretilmesi ve göç maliyetinin çağrı yeriyle ölçülmesi.

İçindekiler

Belgedeki özellik tablosunun arayüzle karşılaştırılması bir soruyu açıkta bırakmıştı: dugme bileşenine genislik özelliğinin eklenmesi ile rozet bileşeninden ikonlu özelliğinin kaldırılması aynı ağırlıkta değildir. Birincisi hiçbir kullanımı bozmaz, ikincisi o özelliği veren her yeri bozar.

Sürümleme bu ağırlık farkını bir sayıya çevirir. Modüller, Araçlar ve Ekosistem kursunda tanıtılan anlamlı sürümleme, bir paketin sürüm numarasını değişikliğin sınıfına bağlar. Bileşen kataloğunda uygulanabilmesi için tek bir şeye ihtiyaç vardır: bileşen arayüzünde neyin kırıcı sayıldığının kesin listesi.

Bileşen Arayüzünde Kırıcı Olan

Bir bileşenin sözleşmesi girdi özelliklerinden ibaret değildir, ama ölçülebilir bölümü odur. Aşağıdaki değişiklikler kırıcıdır:

  • Bileşenin kaldırılması. En görünür olanı; içe aktarım hemen kırılır.
  • Özelliğin kaldırılması. O özelliği veren çağrılar sessizce etkisiz kalır.
  • Özelliğin adının değişmesi. Kaldırma ile eklemenin birleşimi gibi görünür ama göç adımı farklıdır.
  • İsteğe bağlı özelliğin zorunlu hâle gelmesi. Özelliği vermeyen her çağrı bozulur.
  • Varsayılan değerin değişmesi. Kaynak metnini hiç değiştirmemiş her çağrı farklı davranış görür.
  • Değer kümesinin daralması. Kalkan değeri veren çağrılar tanımsız duruma düşer.

Bunların dışında kalan iki sınıf uyumludur. Ekleme: yeni bileşen, isteğe bağlı yeni özellik, değer kümesinin genişlemesi. Yama: arayüzü değiştirmeyen davranış düzeltmesi.

Listede olmayan ama önemli bir kalem var: belge ağacının yapısı ve sınıf adları. Bir bileşenin ürettiği düğüm ağacına dışarıdan CSS yazılabiliyorsa, o ağaç da sözleşmenin parçasıdır ve değişmesi kırıcıdır. Katalog bu yüzden ya ağacı sözleşmeye dahil eder ya da dahil olmadığını açıkça yazar; ikisinden birini yapmayan sistem, her yayında adı konmamış kırıcı değişiklikler üretir.

Farkın Hesaplanması

Aşağıdaki program kataloğun iki anlık görüntüsünü alır, yukarıdaki kuralları uygular, değişiklikleri sınıflandırır, sürüm numarasını türetir ve göç kılavuzunu üretir. Göç maliyeti, her değişikliğin etkilediği çağrı yeri sayısıyla ölçülür.

// surum.mjs — iki katalog anlik goruntusunu karsilastirip surum numarasi turetme

// Bir anlik goruntu: bilesen -> ozellik adi -> { deger kumesi, varsayilan, zorunlu }
const ESKI = {
  surum: "3.4.2",
  bilesen: {
    dugme:        { tur: { deger: ["birincil", "ikincil", "sessiz"], varsayilan: "ikincil", zorunlu: false },
                    boyut: { deger: ["kucuk", "orta"], varsayilan: "orta", zorunlu: false },
                    simge: { deger: null, varsayilan: null, zorunlu: false } },
    rozet:        { ton: { deger: ["notr", "olumlu", "uyari", "olumsuz"], varsayilan: "notr", zorunlu: false },
                    boyut: { deger: ["kucuk", "orta"], varsayilan: "orta", zorunlu: false },
                    ikonlu: { deger: null, varsayilan: false, zorunlu: false } },
    "acilir-liste": { boyut: { deger: ["kucuk", "orta"], varsayilan: "orta", zorunlu: false },
                    durum: { deger: null, varsayilan: null, zorunlu: false } },
    "kip-pencere": { boyut: { deger: ["orta", "genis"], varsayilan: "orta", zorunlu: false },
                    baslik: { deger: null, varsayilan: null, zorunlu: false } },
    "bildirim-seridi": { ton: { deger: ["bilgi", "olumlu", "uyari", "olumsuz"], varsayilan: "bilgi", zorunlu: false },
                    kapatilabilir: { deger: null, varsayilan: true, zorunlu: false } },
    "yukleme-gostergesi": { boyut: { deger: ["kucuk", "orta"], varsayilan: "orta", zorunlu: false } },
    iskelet:      { satir: { deger: null, varsayilan: 3, zorunlu: false } },
  },
};

const YENI = {
  bilesen: {
    dugme:        { tur: { deger: ["birincil", "ikincil", "sessiz", "tehlikeli"], varsayilan: "ikincil", zorunlu: false },
                    boyut: { deger: ["kucuk", "orta"], varsayilan: "orta", zorunlu: false },
                    "simge-adi": { deger: null, varsayilan: null, zorunlu: false },
                    genislik: { deger: ["icerik", "tam"], varsayilan: "icerik", zorunlu: false } },
    rozet:        { ton: { deger: ["notr", "olumlu", "uyari", "olumsuz"], varsayilan: "notr", zorunlu: false },
                    boyut: { deger: ["kucuk", "orta"], varsayilan: "kucuk", zorunlu: false } },
    "acilir-liste": { boyut: { deger: ["kucuk", "orta"], varsayilan: "orta", zorunlu: false },
                    durum: { deger: null, varsayilan: null, zorunlu: false },
                    aramali: { deger: null, varsayilan: false, zorunlu: false },
                    coklu: { deger: null, varsayilan: false, zorunlu: false } },
    "kip-pencere": { boyut: { deger: ["orta", "genis"], varsayilan: "orta", zorunlu: false },
                    baslik: { deger: null, varsayilan: null, zorunlu: true },
                    kapatilabilir: { deger: null, varsayilan: true, zorunlu: false } },
    "bildirim-seridi": { ton: { deger: ["olumlu", "uyari", "olumsuz"], varsayilan: "uyari", zorunlu: false },
                    kapatilabilir: { deger: null, varsayilan: true, zorunlu: false } },
    iskelet:      { satir: { deger: null, varsayilan: 3, zorunlu: false } },
    "sayi-alani": { boyut: { deger: ["kucuk", "orta"], varsayilan: "orta", zorunlu: false } },
  },
};

// Ad degisikligi elle bildirilir; yoksa kaldirma + ekleme olarak gorunur.
const YENIDEN_ADLANDIRMA = [{ bilesen: "dugme", eski: "simge", yeni: "simge-adi" }];

// Cagri yeri sayilari: goc maliyetini bu sayilar belirler.
const KULLANIM = { dugme: 412, rozet: 188, "acilir-liste": 57, "kip-pencere": 45, "bildirim-seridi": 63, "yukleme-gostergesi": 31, iskelet: 38 };
// Ozellik basina cagri yeri sayisi (o ozelligi acikca veren yerler).
const OZELLIK_KULLANIMI = { "dugme.simge": 96, "rozet.ikonlu": 24, "rozet.boyut": 61, "bildirim-seridi.ton": 63, "kip-pencere.baslik": 40 };

const esitKume = (a, b) => JSON.stringify(a) === JSON.stringify(b);
const adlandirmaBul = (bilesen, ad, yon) =>
  YENIDEN_ADLANDIRMA.find((r) => r.bilesen === bilesen && r[yon] === ad);

const degisiklikler = [];
const ekle = (sinif, bilesen, aciklama, gocAdimi, etki) =>
  degisiklikler.push({ sinif, bilesen, aciklama, gocAdimi, etki });

for (const [ad, eskiOzellikler] of Object.entries(ESKI.bilesen)) {
  const yeniOzellikler = YENI.bilesen[ad];
  if (!yeniOzellikler) {
    ekle("kirici", ad, "bileşen kaldırıldı", "yerine iskelet bileşeni kullanılır", KULLANIM[ad] ?? 0);
    continue;
  }
  for (const [ozAd, eskiOz] of Object.entries(eskiOzellikler)) {
    const adDegisimi = adlandirmaBul(ad, ozAd, "eski");
    if (adDegisimi) {
      ekle("kirici", ad, `özellik adı değişti: ${ozAd} → ${adDegisimi.yeni}`,
        `${ozAd} yazan her çağrı ${adDegisimi.yeni} olur`, OZELLIK_KULLANIMI[`${ad}.${ozAd}`] ?? 0);
      continue;
    }
    const yeniOz = yeniOzellikler[ozAd];
    if (!yeniOz) {
      ekle("kirici", ad, `özellik kaldırıldı: ${ozAd}`, `${ozAd} veren çağrılardan kaldırılır`,
        OZELLIK_KULLANIMI[`${ad}.${ozAd}`] ?? 0);
      continue;
    }
    if (!eskiOz.zorunlu && yeniOz.zorunlu) {
      ekle("kirici", ad, `özellik zorunlu hâle geldi: ${ozAd}`, `${ozAd} vermeyen çağrılara değer eklenir`,
        (KULLANIM[ad] ?? 0) - (OZELLIK_KULLANIMI[`${ad}.${ozAd}`] ?? 0));
    }
    if (eskiOz.varsayilan !== yeniOz.varsayilan) {
      ekle("kirici", ad, `varsayılan değişti: ${ozAd} ${JSON.stringify(eskiOz.varsayilan)} → ${JSON.stringify(yeniOz.varsayilan)}`,
        `${ozAd} vermeyen çağrılara eski değer açıkça yazılır`,
        (KULLANIM[ad] ?? 0) - (OZELLIK_KULLANIMI[`${ad}.${ozAd}`] ?? 0));
    }
    if (eskiOz.deger && yeniOz.deger && !esitKume(eskiOz.deger, yeniOz.deger)) {
      const kalkan = eskiOz.deger.filter((d) => !yeniOz.deger.includes(d));
      const gelen = yeniOz.deger.filter((d) => !eskiOz.deger.includes(d));
      if (kalkan.length) {
        ekle("kirici", ad, `değer kümesi daraldı: ${ozAd} ${JSON.stringify(kalkan)} kalktı`,
          `${JSON.stringify(kalkan)} veren çağrılar başka değere taşınır`, OZELLIK_KULLANIMI[`${ad}.${ozAd}`] ?? 0);
      }
      if (gelen.length) ekle("ekleme", ad, `değer kümesi genişledi: ${ozAd} ${JSON.stringify(gelen)} eklendi`, "-", 0);
    }
  }
  for (const ozAd of Object.keys(yeniOzellikler)) {
    if (eskiOzellikler[ozAd]) continue;
    if (adlandirmaBul(ad, ozAd, "yeni")) continue;
    const sinif = yeniOzellikler[ozAd].zorunlu ? "kirici" : "ekleme";
    ekle(sinif, ad, `${sinif === "kirici" ? "zorunlu " : ""}özellik eklendi: ${ozAd}`,
      sinif === "kirici" ? `${ozAd} her çağrıya eklenir` : "-", sinif === "kirici" ? (KULLANIM[ad] ?? 0) : 0);
  }
}
for (const ad of Object.keys(YENI.bilesen)) {
  if (!ESKI.bilesen[ad]) ekle("ekleme", ad, "bileşen eklendi", "-", 0);
}

const SIRA = { kirici: 0, ekleme: 1, yama: 2 };
degisiklikler.sort((a, b) => SIRA[a.sinif] - SIRA[b.sinif] || b.etki - a.etki);

console.log("sınıf     bileşen             değişiklik");
for (const d of degisiklikler) console.log(`${d.sinif.padEnd(9)} ${d.bilesen.padEnd(19)} ${d.aciklama}`);

const sayim = { kirici: 0, ekleme: 0, yama: 0 };
for (const d of degisiklikler) sayim[d.sinif]++;
console.log(`\nkırıcı: ${sayim.kirici}   ekleme: ${sayim.ekleme}   yama: ${sayim.yama}`);

// Surum turetimi: en agir sinif hangi bilesenin artacagini belirler.
function sonrakiSurum(mevcut, sayim) {
  const [ana, ikincil, yama] = mevcut.split(".").map(Number);
  if (sayim.kirici > 0) return `${ana + 1}.0.0`;
  if (sayim.ekleme > 0) return `${ana}.${ikincil + 1}.0`;
  return `${ana}.${ikincil}.${yama + 1}`;
}
console.log(`mevcut sürüm: ${ESKI.surum}   türetilen sürüm: ${sonrakiSurum(ESKI.surum, sayim)}`);
console.log(`kırıcılar ayıklansaydı: ${sonrakiSurum(ESKI.surum, { kirici: 0, ekleme: sayim.ekleme, yama: sayim.yama })}`);

// Goc kilavuzu: yalniz kirici degisiklikler, etkiledigi cagri yeri sayisiyla.
console.log("\n— GÖÇ KILAVUZU —");
let toplamEtki = 0;
for (const d of degisiklikler.filter((x) => x.sinif === "kirici")) {
  toplamEtki += d.etki;
  console.log(`* ${d.bilesen}: ${d.aciklama}\n    yapılacak: ${d.gocAdimi}\n    etkilenen çağrı yeri: ${d.etki}`);
}
console.log(`\ntoplam göç maliyeti: ${toplamEtki} çağrı yeri`);
console.log(`kırıcı değişiklik başına ortalama: ${(toplamEtki / sayim.kirici).toFixed(1)} çağrı yeri`);
sınıf     bileşen             değişiklik
kirici    rozet               varsayılan değişti: boyut "orta" → "kucuk"
kirici    dugme               özellik adı değişti: simge → simge-adi
kirici    bildirim-seridi     değer kümesi daraldı: ton ["bilgi"] kalktı
kirici    yukleme-gostergesi  bileşen kaldırıldı
kirici    rozet               özellik kaldırıldı: ikonlu
kirici    kip-pencere         özellik zorunlu hâle geldi: baslik
kirici    bildirim-seridi     varsayılan değişti: ton "bilgi" → "uyari"
ekleme    dugme               değer kümesi genişledi: tur ["tehlikeli"] eklendi
ekleme    dugme               özellik eklendi: genislik
ekleme    acilir-liste        özellik eklendi: aramali
ekleme    acilir-liste        özellik eklendi: coklu
ekleme    kip-pencere         özellik eklendi: kapatilabilir
ekleme    sayi-alani          bileşen eklendi

kırıcı: 7   ekleme: 6   yama: 0
mevcut sürüm: 3.4.2   türetilen sürüm: 4.0.0
kırıcılar ayıklansaydı: 3.5.0

— GÖÇ KILAVUZU —
* rozet: varsayılan değişti: boyut "orta" → "kucuk"
    yapılacak: boyut vermeyen çağrılara eski değer açıkça yazılır
    etkilenen çağrı yeri: 127
* dugme: özellik adı değişti: simge → simge-adi
    yapılacak: simge yazan her çağrı simge-adi olur
    etkilenen çağrı yeri: 96
* bildirim-seridi: değer kümesi daraldı: ton ["bilgi"] kalktı
    yapılacak: ["bilgi"] veren çağrılar başka değere taşınır
    etkilenen çağrı yeri: 63
* yukleme-gostergesi: bileşen kaldırıldı
    yapılacak: yerine iskelet bileşeni kullanılır
    etkilenen çağrı yeri: 31
* rozet: özellik kaldırıldı: ikonlu
    yapılacak: ikonlu veren çağrılardan kaldırılır
    etkilenen çağrı yeri: 24
* kip-pencere: özellik zorunlu hâle geldi: baslik
    yapılacak: baslik vermeyen çağrılara değer eklenir
    etkilenen çağrı yeri: 5
* bildirim-seridi: varsayılan değişti: ton "bilgi" → "uyari"
    yapılacak: ton vermeyen çağrılara eski değer açıkça yazılır
    etkilenen çağrı yeri: 0

toplam göç maliyeti: 346 çağrı yeri
kırıcı değişiklik başına ortalama: 49.4 çağrı yeri

Sürüm Numarasının Türetilmesi

Sürüm numarası bir karar değil, bir sonuçtur. Program yedi kırıcı değişiklik bulduğu için 3.4.2 sürümü 4.0.0’a çıkıyor. Kırıcılar ayıklanıp yalnızca eklemeler yayımlansaydı sürüm 3.5.0 olurdu; bu ikinci satır, sürüm numarasını tartışılabilir bir konu olmaktan çıkarıp yayın kapsamının sonucu hâline getirir.

Bu ayrım pratik bir seçenek üretir. Yedi kırıcı değişiklikten hiçbiri yayına zorunlu değilse, ekleme yayını hemen çıkabilir ve kırıcılar bir sonraki ana sürüme biriktirilebilir. Kırıcı değişiklikleri biriktirmek, her birini ayrı ana sürümle yayımlamaktan ucuzdur: kullanan ekipler bir kez göç eder, yedi kez değil.

Ad değişikliği satırı türetimin sınırını gösteriyor. simge özelliğinin simge-adi olması, program tarafından ancak elle bildirildiği için ad değişikliği olarak tanınabiliyor. Bildirilmeseydi iki ayrı satır üretilirdi: “özellik kaldırıldı: simge” ve “özellik eklendi: simge-adi”. Sürüm numarası yine 4.0.0 çıkardı, ama göç kılavuzu yanlış olurdu; kullanan kişi kaldırma ile eklemenin aynı özelliğin iki hâli olduğunu bilemezdi. Fark alma otomatik, niyet bildirimi elle yapılır.

Göç Maliyetinin Ölçülmesi

Göç kılavuzu, kırıcı değişikliklerin çağrı yeri sayısına göre sıralanmış hâlidir ve bu sıralama iki kararı besler.

Birincisi, hangi kırıcı değişikliğin gerçekten değdiğidir. rozet bileşeninin boyut varsayılanının değişmesi 127 çağrı yerini etkiliyor; ikonlu özelliğinin kaldırılması yalnızca 24. Aynı yayında duran iki değişikliğin maliyeti beş kattan fazla farklı. Varsayılan değişikliğinin gerekçesi bu 127 sayısına değmiyorsa, değişiklik geri çekilir ve kırıcı listesinden düşer.

İkincisi, göçün nasıl yapılacağıdır. Listedeki adımların bir bölümü mekaniktir: ad değişikliği, bir dizgi değiştirmedir ve kod dönüştürücüyle otomatikleştirilebilir. Bir bölümü ise karar ister: bildirim-seridi bileşeninin bilgi tonunu veren 63 çağrının hangisinin olumlu, hangisinin uyari olacağına bakmadan karar verilemez. Kılavuzun bu iki sınıfı ayırması, göç süresinin tahminini gerçekçi kılar.

Son satır ilginç bir durum gösteriyor: bildirim-seridi bileşeninin varsayılan değişikliğinin etkisi sıfır. Nedeni, o bileşeni kullanan 63 çağrının hepsinin ton değerini zaten açıkça vermesi. Değişiklik sözleşme gereği kırıcıdır — sürüm numarasını yükseltir — ama göç maliyeti yoktur. Sözleşme ile maliyet ayrı şeylerdir; sürüm numarası sözleşmeye, yayın planı maliyete bakar.

Yayın Biçimi

Kataloğun tek paket olarak mı yoksa bileşen başına ayrı paketler olarak mı yayımlanacağı, yukarıdaki sayıların dağılımını değiştirir.

Tek paket yayımlandığında bir bileşendeki kırıcı değişiklik tüm paketin ana sürümünü yükseltir. Yukarıdaki örnekte rozet yüzünden iskelet bileşenini kullanan ekip de 4.0.0 sürümüne geçmek zorunda kalır; kendisi için hiçbir şey değişmediği hâlde bir göç kararı vermesi gerekir. Buna karşılık bağımlılık yönetimi tektir, sürümler arası uyum sorunu çıkmaz.

Bileşen başına paket yayımlandığında her bileşen kendi hızında ilerler ve göç kararları ayrışır. Bedeli, bağımlılık ağacında aynı sistemin farklı sürümlerinin yan yana bulunabilmesidir; iki bileşen aynı belirteç katmanının farklı sürümlerine bağlanırsa tutarlılık, sistemin var oluş nedeni, kaybolur.

Seçim, iki sayının karşılaştırılmasıyla yapılır: gereksiz göçe zorlanan çağrı yeri sayısı ile yan yana duran sürümlerden doğan tutarsızlık sayısı. Katalog küçük ve bileşenler birbirine sıkı bağlıyken tek paket ucuzdur; katalog büyüdükçe ve bileşenler arası bağ gevşedikçe ayrı paketler ucuzlar.

Kullanımdan Kaldırma Penceresi

Kırıcı değişiklik doğrudan yapılmaz; önce kullanımdan kaldırma işareti konur, sonra kaldırma yapılır. Aradaki süre bir penceredir ve takvimle değil yayınla ölçülür: “bir ana sürüm boyunca” ifadesi, “üç ay” ifadesinden daha kullanışlıdır, çünkü yayın kadansı değişse bile anlamını korur.

Pencerenin işlevi, göçün yayın anına yığılmasını önlemektir. simge özelliği bir ana sürüm önce kullanımdan kaldırılmış olsaydı, 96 çağrı yerinin bir bölümü zaten taşınmış olurdu ve 4.0.0 yayınındaki göç maliyeti daha düşük çıkardı. Kullanımdan kaldırma işareti konurken uyarı metninin göç adımını içermesi de bu yüzden gereklidir; uyarı yalnızca “bu özellik kaldırılacak” diyorsa hiçbir göçü hızlandırmaz.

Özet

  • Bileşen arayüzünde kırıcı olanlar sayılabilir bir listedir: bileşenin kaldırılması, özelliğin kaldırılması veya adının değişmesi, zorunlu hâle gelme, varsayılanın değişmesi ve değer kümesinin daralması.
  • Sürüm numarası bir karar değil, yayına giren en ağır değişiklik sınıfının sonucudur; kırıcıları biriktirip tek ana sürümde yayımlamak, her birini ayrı yayımlamaktan ucuzdur.
  • Fark alma otomatikleştirilebilir ama ad değişikliği gibi niyet bildirimleri elle yapılmalıdır; bildirilmezse kılavuz yanlış olur.
  • Göç maliyeti etkilenen çağrı yeri sayısıyla ölçülür ve mekanik adımlarla karar gerektiren adımların ayrılması gerekir.
  • Sözleşme ile maliyet ayrı şeylerdir: göç maliyeti sıfır olan bir değişiklik de sürüm numarasını yükseltir.
  • Tek paket ile bileşen başına paket arasındaki seçim, gereksiz göçe zorlanan çağrı yeri sayısı ile yan yana duran sürümlerden doğan tutarsızlık sayısı karşılaştırılarak yapılır.

Sonraki Adım

Bu dersin ürettiği sürüm ve göç kılavuzu, değişikliklerin nereden geldiğini sormadı; arayüz tanımı iki kez okundu ve arada bir şey değişmişti. Değişikliği kimin önerdiği, kimin incelediği ve hangi ölçütle kabul edildiği açık kaldı. dugme bileşenine genislik özelliğinin eklenmesi kimin kararıydı ve bu karar kaç günde verildi? Sonraki ders katkı sürecini bir durum makinesi olarak kurar, geçersiz geçişleri saptar ve öneriden yayına kadar geçen süreyi aşamalara ayırarak ölçer.

İ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