Ders 18 / 19
Benimseme Ölçümü
Depo taramasıyla kapsam yüzdesinin ekip bazında hesaplanması, trafikle ağırlıklı kapsamla farkı, sapma listesinin yerel kopya ve katalog adayı olarak sınıflandırılması ve sezgisel eşlemenin sınırları.
İçindekiler
Önceki dersin modelinde “yerel kopya” bir varsayımdı: kuyrukta bekleyen işin belirli bir bölümünün sistemden ayrılacağı varsayıldı. Gerçek bir kuruluşta bu sayı varsayılmaz, ölçülür.
Benimseme ölçümünün amacı bir başarı raporu üretmek değildir. Sistemin nerede kullanılmadığını bulmak, sistemin ne eksiği olduğunu söyler; her yerel kopya, ya kataloğun bir boşluğuna ya bir bulunabilirlik sorununa ya da bir katkı yolunun tıkanıklığına işaret eder.
Neyin Ölçüleceği
Benimseme için sorulabilecek en zayıf soru “ekipler sistemi kullanıyor mu” sorusudur; yanıtı her zaman “evet”tir. Ölçülebilir soru şudur: arayüzdeki bileşen çağrılarının yüzde kaçı sistemden geliyor?
Bu oran kapsam yüzdesidir ve iki farklı yolla ölçülebilir:
- Durağan tarama. Kaynak metindeki içe aktarımlar ve bileşen çağrıları sayılır. Her çağrı yeri bir birim sayılır; nadiren açılan bir yönetim ekranındaki çağrı, ana sayfadaki çağrıyla eşit ağırlıktadır.
- Çalışma zamanı telemetrisi. Gerçekten oluşturulan bileşen örnekleri sayılır ve ekranın görüntülenme sayısıyla ağırlıklanır.
İkisi farklı sorulara yanıt verir ve ikisi de gereklidir. Durağan tarama bakım yükünü ölçer: kaç yerde kod değiştirileceğini söyler. Trafikle ağırlıklı ölçüm kullanıcı etkisini ölçer: sistemin görünen arayüzün ne kadarını ürettiğini söyler.
Taramanın Yapılması
Aşağıdaki program üç ürün ekibinin yedi dosyasını tarar. Her dosyanın içe aktarımlarını çözümler, bileşen çağrılarını sayar, her çağrıyı sistem paketinden mi yoksa yerel bir dosyadan mı geldiğine göre sınıflandırır ve iki kapsam yüzdesini birlikte hesaplar. Ardından yerel bileşenleri kataloğa göre sınıflandırır.
// benimseme.mjs — depo taramasiyla kapsam yuzdesi, sapma listesi ve katalog adaylari // Kaynak dosyalar. Yol, ilk bolumuyle ekibi verir. Sayi, o ekranin donem basina // goruntulenme sayisidir; calisma zamani agirligi buradan gelir. Gorunum tanimlari // oge(bilesen, ozellikler, ...cocuklar) bicimindedir. const DEPO = [ { yol: "urun-katalog/arama-sayfasi.js", goruntulenme: 48200, kaynak: ` import { AramaAlani, Kart, Rozet, Dugme, Sayfalama, BosDurum } from "@sistem/bilesenler"; import { FiltrePaneli } from "../bilesenler/filtre-paneli"; export const AramaSayfasi = () => oge("div", {}, oge(AramaAlani), oge(FiltrePaneli), oge(Kart, {}, oge(Rozet), oge(Dugme)), oge(Kart, {}, oge(Rozet), oge(Dugme)), oge(BosDurum), oge(Sayfalama));` }, { yol: "urun-katalog/kayit-sayfasi.js", goruntulenme: 31400, kaynak: ` import { Kart, Rozet, Dugme, KirintiYolu, Sekmeler } from "@sistem/bilesenler"; import { KayitKarti } from "../bilesenler/kayit-karti"; export const KayitSayfasi = () => oge("div", {}, oge(KirintiYolu), oge(KayitKarti), oge(Sekmeler), oge(Kart, {}, oge(Rozet), oge(Dugme), oge(Dugme)));` }, { yol: "urun-katalog/odunc-sayfasi.js", goruntulenme: 9800, kaynak: ` import { Dugme, BildirimSeridi, Iskelet } from "@sistem/bilesenler"; import { OzelDugme } from "../bilesenler/ozel-dugme"; import { YuklemeCubugu } from "../bilesenler/yukleme-cubugu"; export const OduncSayfasi = () => oge("div", {}, oge(BildirimSeridi), oge(Iskelet), oge(OzelDugme), oge(OzelDugme), oge(Dugme), oge(YuklemeCubugu));` }, { yol: "urun-uyelik/uye-girisi.js", goruntulenme: 22600, kaynak: ` import { MetinAlani, Dugme, Baglanti, BildirimSeridi } from "@sistem/bilesenler"; export const UyeGirisi = () => oge("form", {}, oge(MetinAlani), oge(MetinAlani), oge(Dugme), oge(Baglanti), oge(BildirimSeridi));` }, { yol: "urun-uyelik/uye-profili.js", goruntulenme: 7300, kaynak: ` import { Kart, MetinAlani, OnayKutusu, Dugme } from "@sistem/bilesenler"; import { DurumRozeti } from "../bilesenler/durum-rozeti"; import { OzelDugme } from "../bilesenler/ozel-dugme"; export const UyeProfili = () => oge("div", {}, oge(Kart, {}, oge(DurumRozeti), oge(MetinAlani), oge(OnayKutusu)), oge(OzelDugme), oge(Dugme));` }, { yol: "urun-yonetim/raf-yonetimi.js", goruntulenme: 1900, kaynak: ` import { Dugme, MetinAlani, KipPencere } from "@sistem/bilesenler"; import { VeriTablosu } from "../bilesenler/veri-tablosu"; import { FiltrePaneli } from "../bilesenler/filtre-paneli"; import { BasitAcilirListe } from "../bilesenler/basit-acilir-liste"; export const RafYonetimi = () => oge("div", {}, oge(FiltrePaneli), oge(BasitAcilirListe), oge(VeriTablosu), oge(KipPencere, {}, oge(MetinAlani), oge(Dugme)));` }, { yol: "urun-yonetim/kullanici-yonetimi.js", goruntulenme: 1200, kaynak: ` import { Dugme, MetinAlani } from "@sistem/bilesenler"; import { VeriTablosu } from "../bilesenler/veri-tablosu"; import { OzelKipPencere } from "../bilesenler/ozel-kip-pencere"; import { DurumRozeti } from "../bilesenler/durum-rozeti"; export const KullaniciYonetimi = () => oge("div", {}, oge(VeriTablosu), oge(DurumRozeti), oge(DurumRozeti), oge(OzelKipPencere, {}, oge(MetinAlani), oge(Dugme)));` }, ]; const SISTEM_PAKETI = "@sistem/bilesenler"; // Katalogdaki bilesenlerin kod tarafindaki adlari; yerel kopya saptamasi bunlara bakar. const KATALOG = ["Dugme", "Baglanti", "MetinAlani", "OnayKutusu", "Rozet", "Kart", "Sayfalama", "AramaAlani", "BosDurum", "KipPencere", "Iskelet", "Sekmeler", "AcilirListe", "BildirimSeridi", "KirintiYolu"]; // Bir dosyadaki ice aktarimlari cozumle: ad -> kaynak. function iceAktarimlar(kaynak) { const harita = new Map(); for (const m of kaynak.matchAll(/import\s*\{([^}]+)\}\s*from\s*"([^"]+)"/g)) { for (const ad of m[1].split(",").map((s) => s.trim()).filter(Boolean)) harita.set(ad, m[2]); } return harita; } // Kullanim sayimi: oge() cagrisinin ilk argumani buyuk harfle basliyorsa bilesendir. function kullanimlar(kaynak) { const sayim = new Map(); for (const m of kaynak.matchAll(/\boge\(\s*([A-Z][A-Za-z0-9]*)/g)) sayim.set(m[1], (sayim.get(m[1]) ?? 0) + 1); return sayim; } const ekipler = new Map(); const yerelToplam = new Map(); for (const dosya of DEPO) { const ekip = dosya.yol.split("/")[0]; const ith = iceAktarimlar(dosya.kaynak); const kul = kullanimlar(dosya.kaynak); const e = ekipler.get(ekip) ?? { sistem: 0, yerel: 0, agirlikliSistem: 0, agirlikliYerel: 0 }; for (const [ad, adet] of kul) { const kaynak = ith.get(ad); if (!kaynak) continue; const sistemMi = kaynak === SISTEM_PAKETI; e[sistemMi ? "sistem" : "yerel"] += adet; e[sistemMi ? "agirlikliSistem" : "agirlikliYerel"] += adet * dosya.goruntulenme; if (!sistemMi) { const y = yerelToplam.get(ad) ?? { adet: 0, ekipler: new Set() }; y.adet += adet; y.ekipler.add(ekip); yerelToplam.set(ad, y); } } ekipler.set(ekip, e); } console.log("ekip sistem yerel kapsam trafikle ağırlıklı kapsam"); let toplamSistem = 0, toplamYerel = 0, agSistem = 0, agYerel = 0; for (const [ekip, e] of ekipler) { toplamSistem += e.sistem; toplamYerel += e.yerel; agSistem += e.agirlikliSistem; agYerel += e.agirlikliYerel; const kapsam = (e.sistem / (e.sistem + e.yerel)) * 100; const agirlikli = (e.agirlikliSistem / (e.agirlikliSistem + e.agirlikliYerel)) * 100; console.log( `${ekip.padEnd(15)} ${String(e.sistem).padStart(6)} ${String(e.yerel).padStart(7)} ` + `${(kapsam.toFixed(1) + "%").padStart(8)} ${(agirlikli.toFixed(1) + "%").padStart(26)}` ); } console.log( `${"TOPLAM".padEnd(15)} ${String(toplamSistem).padStart(6)} ${String(toplamYerel).padStart(7)} ` + `${(((toplamSistem / (toplamSistem + toplamYerel)) * 100).toFixed(1) + "%").padStart(8)} ` + `${(((agSistem / (agSistem + agYerel)) * 100).toFixed(1) + "%").padStart(25)}` ); // Sapma listesi: yerel bilesenin katalogda karsiligi var mi? const karsilikBul = (yerelAd) => KATALOG.find((k) => yerelAd.includes(k)) ?? null; console.log("\nsapma listesi"); console.log("yerel bileşen kullanım ekip katalog karşılığı sınıf"); const adaylar = []; for (const [ad, y] of [...yerelToplam].sort((a, b) => b[1].adet - a[1].adet)) { const karsilik = karsilikBul(ad); const sinif = karsilik ? "yerel kopya" : (y.ekipler.size >= 2 ? "katalog adayı" : "ürüne özgü"); if (sinif === "katalog adayı") adaylar.push(ad); console.log( `${ad.padEnd(20)} ${String(y.adet).padStart(9)} ${String(y.ekipler.size).padStart(6)} ` + `${(karsilik ?? "-").padEnd(19)} ${sinif}` ); } const kopyaAdedi = [...yerelToplam].filter(([ad]) => karsilikBul(ad)).reduce((t, [, y]) => t + y.adet, 0); console.log(`\nyerel kopya kullanımı : ${kopyaAdedi} (kataloğa geçirilebilir)`); console.log(`katalog adayı yerel bileşenler: ${JSON.stringify(adaylar)}`); console.log(`kopyalar kataloğa geçseydi kapsam: %${(((toplamSistem + kopyaAdedi) / (toplamSistem + toplamYerel)) * 100).toFixed(1)}`);
ekip sistem yerel kapsam trafikle ağırlıklı kapsam urun-katalog 18 5 78.3% 85.7% urun-uyelik 9 2 81.8% 90.7% urun-yonetim 5 7 41.7% 43.5% TOPLAM 32 14 69.6% 85.7% sapma listesi yerel bileşen kullanım ekip katalog karşılığı sınıf OzelDugme 3 2 Dugme yerel kopya DurumRozeti 3 2 Rozet yerel kopya FiltrePaneli 2 2 - katalog adayı VeriTablosu 2 1 - ürüne özgü KayitKarti 1 1 Kart yerel kopya YuklemeCubugu 1 1 - ürüne özgü BasitAcilirListe 1 1 AcilirListe yerel kopya OzelKipPencere 1 1 KipPencere yerel kopya yerel kopya kullanımı : 9 (kataloğa geçirilebilir) katalog adayı yerel bileşenler: ["FiltrePaneli"] kopyalar kataloğa geçseydi kapsam: %89.1
İki Kapsam Sayısının Ayrımı
Toplam satırında iki sayı arasında 16,1 puanlık fark var: durağan kapsam yüzde 69,6,
trafikle ağırlıklı kapsam yüzde 85,7. Fark tek bir ekipten geliyor. urun-yonetim ekibinin
kapsamı yüzde 41,7 ile listedeki en düşük değer, ama ekranlarının görüntülenme sayısı
diğerlerinin yanında küçük kalıyor; ağırlıklı hesapta neredeyse görünmüyor.
İki sayının hangisinin kullanılacağı, sorulan soruya bağlıdır. Kullanıcının gördüğü arayüzün ne kadarının sistemden geldiği sorulduğunda ağırlıklı sayı doğrudur. Sistemin bakım yükü ve kırıcı bir değişikliğin kaç yeri etkileyeceği sorulduğunda durağan sayı doğrudur; sürümleme dersindeki göç maliyeti hesabı da bu ikinci sayıya dayanır.
Yalnızca ağırlıklı sayının raporlanması, düşük trafikli ürünlerin sistemden tamamen
kopmasını gizler. urun-yonetim ekibi bu tabloda yüzde 41,7’de duruyor ve durumu
düzelmezse sistem bu üründe hiç kullanılmayacak. Ağırlıklı sayıya bakan bir yönetişim
kararı bu ekibi hiç görmez.
Sapma Listesinin Sınıflandırılması
Sapma listesi yerel bileşenleri üç sınıfa ayırıyor ve her sınıfın ayrı bir işi var.
Yerel kopya — katalogda karşılığı olan bir bileşen. OzelDugme ile DurumRozeti iki
ayrı ekipte kullanılıyor; ikisi de sistemde zaten var olan bir bileşenin yerine yazılmış.
Bu satırlar bir soru üretir: kopya neden yazıldı? Yanıt genellikle üç şeyden biridir —
sistem bileşeni istenen bir özelliği vermiyordu, kaçış deliği yetersizdi, ya da katkı yolu
önceki derste görülen kuyruğa takılmıştı. Yanıt bulunmadan kopya kaldırılırsa aynı kopya
başka adla geri döner.
Katalog adayı — karşılığı olmayan ama iki ayrı ekipte kullanılan bileşen.
FiltrePaneli bu sınıfta görünüyor. İlk dersin kapsam ölçütü tam olarak bu durumu arıyordu:
birden çok ekipte yinelenen bir parça, kataloğa girmeye adaydır.
Ürüne özgü — karşılığı olmayan ve tek ekipte kullanılan bileşen. VeriTablosu ve
YuklemeCubugu bu sınıfta. Bunlar sapma değildir; ürünün kendi parçalarıdır ve kataloğa
alınmaları yalnızca inceleme yükü ekler.
Son satır, sapmanın kapatılmasının kapsama etkisini veriyor: yerel kopyaların dokuzu sisteme geçirilseydi kapsam yüzde 69,6’dan yüzde 89,1’e çıkardı. Bu sayı bir hedef değil, bir tavan ölçüsüdür: kopyaları kapatmanın en iyi durumdaki getirisini gösterir ve bu işin diğer işlerle karşılaştırılmasını sağlar.
Sezgisel Eşlemenin Sınırı
Rapor bir programın ürettiği listedir ve programın kullandığı eşleme kuralı sezgiseldir: yerel adın içinde bir katalog adı geçiyorsa kopya sayılır. Bu kural iki yerde yanılıyor ve iki yanılma da öğreticidir.
KayitKarti satırı Kart bileşeninin kopyası olarak işaretlenmiş. Oysa ilk derste
görüldüğü gibi kayıt kartı, bibliyografik bir kaydın hangi alanlarını hangi sırayla
gösterdiğine karar veren, ürüne özgü bir parçadır. Adının içinde “Kart” geçmesi, kart
bileşeninin kopyası olduğunu göstermez; onu kullanıyor olması daha olasıdır. Sezgisel
eşlemenin ürettiği liste bir sonuç değil, bir inceleme kuyruğudur.
FiltrePaneli satırındaki yanılma daha ilginçtir. Program bunu katalog adayı olarak
işaretliyor, çünkü katalogda “FiltrePaneli” adında bir şey yok. Oysa ilk dersteki katalog
tablosunda suzgec-paneli diye bir kayıt var — üstelik tek ekipte ve dört yerde
kullanıldığı için kapsam ölçütünü karşılamıyordu. İki tablo yan yana konduğunda gerçek
sorun görünür hâle gelir: katalogda kimsenin kullanmadığı bir süzgeç paneli duruyor, iki
ekip ise kendi filtre panelini yazmış.
Bu bir kapsam sorunu değil, bulunabilirlik sorunudur ve kaynağı adlandırma dersinde tanımlanan eş anlamlılıktır. Çözümü yeni bir bileşen eklemek değil, var olan bileşenin eş anlamlı adlarını sözlüğe yazmak ve arama yüzeyini bu sözlükle beslemektir. Ölçüm bu sonuca ancak iki raporun birlikte okunmasıyla varır; tek başına hiçbir sayı bunu söylemez.
Ölçümün Kendi Maliyeti
Benimseme ölçümü sürekli çalışan bir iştir ve iki tuzağı vardır.
Birincisi, kapsam yüzdesinin hedef hâline getirilmesidir. Ekiplerin başarısı bu sayıyla ölçülürse, ürüne özgü bileşenler zorla kataloğa itilir ve katalog ilk dersteki kapsam ölçütünü kaybeder. Kapsam yüzdesi bir tanı aracıdır; hedef, sapmanın nedenlerinin kapatılmasıdır.
İkincisi, taramanın eksik saymasıdır. Yukarıdaki program içe aktarım ve çağrı sayar; bir bileşenin sistem bileşenini sarmalayarak yeniden yayımlaması ya da bir ekranın sistem bileşenini kullanıp biçemini tümüyle ezmesi bu sayıma girmez. İkisi de sapmadır ve ayrı denetimler ister: sarmalayan bileşenlerin listesi ve sistem bileşenlerine dışarıdan yazılan biçem kurallarının sayımı.
Özet
- Benimseme, “sistem kullanılıyor mu” sorusuyla değil, bileşen çağrılarının yüzde kaçının sistemden geldiği sorusuyla ölçülür.
- Durağan tarama bakım yükünü, trafikle ağırlıklı ölçüm kullanıcı etkisini verir; yalnız ağırlıklı sayı raporlandığında düşük trafikli ürünlerin sistemden kopması gizlenir.
- Sapma listesi üç sınıfa ayrılır: yerel kopya kapatılacak bir boşluğa, katalog adayı yeni bir bileşene, ürüne özgü olan ise hiçbir işe işaret eder. Yerel kopyanın neden yazıldığı bulunmadan kopya kaldırılırsa aynı kopya başka adla geri döner.
- Ad benzerliğine dayanan eşleme sezgiseldir ve yanılır; ürettiği liste bir sonuç değil inceleme kuyruğudur.
- Katalogda kullanılmayan bir bileşenle ekiplerin yazdığı yerel karşılığı yan yana görüldüğünde sorun kapsam değil bulunabilirliktir; çözümü eş anlamlı adların kaydedilmesidir.
- Kapsam yüzdesi hedefe dönüştürülürse katalog kapsam ölçütünü kaybeder; ölçüm bir tanı aracıdır.
Sonraki Adım
Bu dersin taraması kod tarafında kaldı: sistem bileşeninin kullanılıp kullanılmadığına bakıldı, ama o bileşenin taşıdığı değerlerin tasarım kitaplığındakiyle aynı olup olmadığına bakılmadı. Bir ekip sistem düğmesini kullanıyor olabilir ve düğmenin dolgu rengi tasarım dosyasındakinden farklı olabilir; kapsam yüzdesi bunu yüzde yüz olarak sayar. Sonraki ders araç zincirini kurar: tasarım aracından çıkan belirteç dışa aktarımıyla koddaki belirteç tanımını karşılaştıran bir fark raporu üretir, adları ve renk biçimlerini normalleştirerek sahte farkları eler ve kalan gerçek sapmayı sınıflandırır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.