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.