İçeriğe geç
academia.sh

Ders 06 / 12

Suçlama ve Satır Tarihçesi

Suçlamanın bedeli o dosyaya dokunan işleme sayısıdır ve aynı tarihçede 8, 36, 209 ve 787 adıma çıkar; ikili aramanın 11 adımının yanında bu ikinci bir büyüme biçimidir, ve tarama ne kadar uzarsa uzasın yanıt dosyanın satır sayısıyla sınırlı kalır.

İçindekiler

Önceki ders tarihçeye tek bir soru sordu ve yanıtı bir işleme olarak aldı: kusur şurada girdi. Aramanın bedeli adım birimiyle sayıldı ve tarihçe seksen kat büyürken altıdan on bire çıktı.

Bu dersin sorusu başka bir yerden geliyor. Elde bir kusur belirtisi yok; elde okunan bir dosya ve içinde anlaşılmayan bir satır var. Sorulan şey o satırın oraya neden yazıldığı. Suçlama (blame) bu soruya doğrudan yanıt vermez ama en yakın vekili verir: o satırı en son hangi işleme yazdı. Aynı tarihçe, aynı depo — ama bedel bu kez bambaşka bir biçimde büyüyor.

Suçlamanın Yanıtladığı Soru

Suçlama, bir dosyanın her satırı için tek bir işleme adı üretir. Ürettiği ad, o satırın şimdiki hâlini yazan son işlemedir. Bu ayrım dersin tamamını taşır: yanıt “bu satırı kim yazdı” değil, “bu satıra en son kim dokundu” sorusunun yanıtıdır. İkisi çoğu zaman aynıdır, ama ayrıldıklarında suçlama ayrıldığını haber vermez.

Yanıtı üretmek için araç dosyanın tarihçesini sondan başa yürür. Her adımda o işlemenin dosyada neyi değiştirdiğine bakar; hâlâ sahipsiz kalan satırlardan hangilerini o işleme yazmışsa onları işaretler ve kalanlarla bir önceki işlemeye geçer. Bütün satırlar sahiplenince tarama biter — ya da dosyaya dokunan işlemeler tükenince. İlk kurulan satırlar tarihçenin başında sahiplenilir, en son değişenler ise daha ilk adımlarda; tarama sırası bu yüzden dosyanın değil, tarihçenin sırasıdır.

Buradan bedelin birimi çıkıyor. Bir adım, o dosyaya dokunan bir işlemedir. Dosyaya dokunmayan işlemeler taramaya girmez; tarihçenin geri kalanı suçlama açısından yok hükmündedir. Bu yüzden suçlamanın bedeli tarihçenin uzunluğuna değil, dosyanın tarihçedeki payına bağlıdır.

Neden Yarılanamıyor

Önceki dersin aramasında bedeli düşüren şey tekdüzelikti: tek bir yüklem tarihçe boyunca bir kez değişiyordu ve her yanıt adayların yarısını eliyordu. Suçlamada böyle bir yüklem yok. Sorulan şey bir eşiğin nerede aşıldığı değil, kırk ayrı satırın her birinin en son nerede yazıldığı. Kırk ayrı sorunun kırk ayrı yanıtı var ve hiçbiri diğerinin aday kümesini daraltmıyor.

Yarılama bir ikinci nedenle de kapalı: yanıtın eksiksiz olması isteniyor. Arama tek bir işlemeyi bulup durabilirdi, çünkü aradığı tek bir noktaydı. Suçlama duramaz; sahipsiz tek bir satır kaldığı sürece taramaya devam etmek zorunda. Bu yüzden suçlama, tarihçeye sorulan sorular arasında tam tarama gerektiren sınıfa girer.

# öğretilen komutlar ve örnek döküm — çalıştırılmamıştır

git blame ayar.py
git blame -L 12,18 ayar.py          # yalnız belirli satır aralığı
git blame -w ayar.py                # boşluk değişikliklerini yok say
git blame --ignore-rev a1b2c3d ayar.py

# çıktının biçimi: işleme kimliği, rol, tarih alanı, satır no, satırın kendisi
a1b2c3d (yazan     <tarih alanı> 12) esik = ayar.oku("esik")
7d4e5f6 (inceleyen <tarih alanı> 13)     if esik is None:
a1b2c3d (yazan     <tarih alanı> 14)         esik = VARSAYILAN

Satır aralığı seçeneği bedeli düşürmez, yalnız çıktıyı kısaltır: araç yine dosyanın tarihçesini yürümek zorundadır. Bedeli gerçekten düşüren tek şey dosyanın tarihçedeki payının küçük olmasıdır.

Çıktının her satırı üç şey taşır: satırı en son yazan işlemenin kimliği, o işlemenin üstverisinden gelen rol ve zaman alanı, ve satırın kendisi. Yanıtın adres gösterdiği yer bir kişi değil bir işlemedir; kişi bilgisi işlemenin üstverisinden okunur ve suçlamanın ürettiği bir bulgu değildir. Bu ayrımın ekip içindeki karşılığı — bulgunun kime ve nasıl iletildiği — Kod İnceleme ve Ekip Süreci kursunun konusudur; burada suçlama bir ölçüm aracıdır, bir sorumluluk aracı değil.

Ölçümün varsayımları:

  • TA17 — Tarihçe ortak kurgudan üretilir; her işleme beş dosyadan birine dokunur ve dosya seçimi kurgunun kendisidir. Kurgu ve kâhin bu derste de değiştirilmez.
  • TA18 — İzlenen dosya ayar.py; ölçüm dört ölçekte aynı dosyayı izler.
  • TA19 — Bir adım, izlenen dosyaya dokunan bir işlemedir. Dosyaya dokunmayan işlemeler sayılmaz.
  • TA20 — Dosya 40 satırdır ve satır sayısı ölçek boyunca sabittir; taşınma, bölünme ve dosya adı değişikliği ölçümün dışındadır.
  • TA21 — İçerik değiştiren her dokunuş kırk satırdan birini yeniden yazar. Bir dokunuşun birden çok satıra dokunması ölçümde sayılmaz; bu, gizlenen işleme sayısını eksik tahmin eden bir varsayımdır.
  • TA22 — İkinci bölümde tek değişen şey bir biçim geçişinin eklenmesidir: tarihçenin tam ortasındaki işleme dosyanın kırk satırının kırkını da yeniden yazar, içeriğini değiştirmeden.
  • TA23 — Suçlamanın yanıtı satır başına tek bir işlemedir. Aracın satırı önceki hâlinden izleyip izlemediği ölçüme girmez; ölçülen şey yanıtın kendisidir.

Ölçüm

"""Suclamanin bedeli: bir adim, dosyaya dokunan bir islemedir.

Bolum 1 - ayni tarihcede iki aramanin adim sayisi.
Bolum 2 - bir bicim gecisinin satir tarihcesine etkisi.
"""
TOHUM = 20260814
DOSYALAR = ("olcum.py", "rapor.py", "kimlik.py", "ayar.py", "belge.md")
IZLENEN, SATIR, TOHUM_SATIR = "ayar.py", 40, 20260815


def uretec(tohum):
    d = tohum % 2147483646 + 1

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def tarihce(n, tohum=TOHUM):
    """n islemelik dogrusal tarihce; her isleme bir dosyaya dokunuyor."""
    r, liste = uretec(tohum), []
    for i in range(n):
        dosya = DOSYALAR[r(5)]
        ikili = r(11) == 0
        liste.append({"no": i + 1, "dosya": dosya, "ikili": ikili,
                      "nesne": 2 + (40 if ikili else 0)})
    return liste


def kusurlu_isleme(t):
    """Kahin: kusuru getiren isleme, kurguyu biz yazdigimiz icin bilinir."""
    return len(t) * 3 // 4


def ikili_arama(t):
    hedef, alt, ust, adim = kusurlu_isleme(t), 1, len(t), 0
    while alt < ust:
        orta = (alt + ust) // 2
        adim += 1
        if orta >= hedef:
            ust = orta
        else:
            alt = orta + 1
    return adim


def suclama(t, dosya):
    """Bir dosyanin satir tarihcesi: o dosyaya dokunan islemeler taranir."""
    return sum(1 for x in t if x["dosya"] == dosya)


def son_yazarlar(t, dosya, bicim_konumu=None):
    """Her satirin son yazari; bicim gecisi butun satirlari yeniden yazar."""
    r, yazar = uretec(TOHUM_SATIR), [0] * SATIR
    for x in t:
        if x["no"] == bicim_konumu:
            yazar = [x["no"]] * SATIR
        if x["dosya"] == dosya:
            yazar[r(SATIR)] = x["no"]
    return yazar


OLCEKLER = (50, 200, 1000, 4000)
print(f"{'işleme':>7s} {'ikili adım':>11s} {'dokunan işleme':>15s} "
      f"{'gösterilen':>11s} {'gizlenen':>9s}")
for n in OLCEKLER:
    t = tarihce(n)
    tarama = suclama(t, IZLENEN)
    ayri = len(set(son_yazarlar(t, IZLENEN)) - {0})
    print(f"{n:7d} {ikili_arama(t):11d} {tarama:15d} {ayri:11d} "
          f"{tarama - ayri:9d}")

print()
print(f"{'işleme':>7s} {'geçişten sonraki dokunuş':>25s} "
      f"{'biçimi gösteren satır':>22s} {'ayrı işleme':>12s}")
for n in OLCEKLER:
    t = tarihce(n)
    y = son_yazarlar(t, IZLENEN, bicim_konumu=n // 2)
    sonraki = sum(1 for x in t if x["dosya"] == IZLENEN and x["no"] > n // 2)
    print(f"{n:7d} {sonraki:25d} {sum(v == n // 2 for v in y):22d} "
          f"{len(set(y) - {0}):12d}")
 işleme  ikili adım  dokunan işleme  gösterilen  gizlenen
     50           6               8           8         0
    200           7              36          25        11
   1000           9             209          40       169
   4000          11             787          40       747

 işleme  geçişten sonraki dokunuş  biçimi gösteren satır  ayrı işleme
     50                         3                     37            4
    200                        16                     25           16
   1000                       120                      3           38
   4000                       375                      0           40

Aynı Depo, İki Ayrı Büyüme

İlk iki sütun aynı tarihçenin iki sorusunu yan yana koyuyor. Tarihçe 50’den 4000’e çıkarken ikili aramanın adımı 6’dan 11’e çıkıyor; aynı tarihçede tek bir dosyanın suçlaması 8 adımdan 787 adıma. Seksen kat büyüyen tarihçede birinci soru iki kat bile pahalılaşmıyor, ikincisi doksan sekiz kat pahalılaşıyor.

Fark araçların hızından gelmiyor; sorulan sorunun biçiminden geliyor. İkili arama tek bir eşiği arıyor ve her yanıt adayların yarısını eliyor. Suçlama kırk ayrı satırın sahibini arıyor ve hiçbir yanıt diğerinin aday kümesini daraltmıyor, üstelik yanıtın eksiksiz olması gerekiyor. Aynı depo, aynı tarihçe, iki ayrı büyüme biçimi — ve ikisini ayıran şey aracın gerçekleştirimi değil, sorunun kendisidir.

Bu, kursun ölçü ekseninin ikinci kez ödendiği yerdir: bedel tarihçenin uzunluğundan değil, işlemin dokunduğu kümeden çıkar. İkili arama tarihçenin yarısını bir adımda eliyor; suçlama dosyanın tarihçedeki payının tamamına dokunmak zorunda. Suçlamanın payı kurguda beşte birdir, ve 787 ÷ 4000 oranı tam olarak bunu gösteriyor.

Ölçümün çözünürlüğünü de aynı yer belirliyor. Dört ölçekte ayırt edilebilen en küçük fark bir işlemedir, yani sırasıyla 1/50, 1/200, 1/1000 ve 1/4000. Suçlamanın sekiz adımdan yedi yüz seksen yediye çıkması bu bandın çok üstünde bir farktır ve ölçüm onu rahatça taşır; buna karşılık iki dosyanın payları arasındaki birkaç adımlık ayrım — kurguda beş dosyanın payları birbirine yakındır — bu kümeyle savunulamaz. Ölçümün söylediği şey hangi dosyanın daha pahalı olduğu değil, payın büyümesiyle bedelin aynı oranda büyüdüğüdür.

Bunun pratikteki karşılığı şudur: suçlamayı hızlandırmanın yolu aracı değiştirmek değil, dosyayı küçültmektir. Beş yüz kez dokunulan bir dosya ikiye bölünürse iki dosyanın suçlaması ayrı ayrı yaklaşık iki yüz elli adımda biter. Dosyanın büyüklüğü bir okunabilirlik sorunu olduğu kadar bir tanı bedeli sorunudur.

Gösterilen ve Gizlenen

Son iki sütun daha rahatsız edici bir şey söylüyor. Dört bin işlemelik tarihçede suçlama 787 işleme tarıyor ama yanıtında yalnız 40 ayrı işleme görünüyor; 747 işleme dosyaya dokunmuş ve yanıtta hiç görünmüyor. Bin işlemelik tarihçede gizlenen sayı 169, iki yüz işlemede 11, elli işlemede 0.

Gösterilen sütununun 40’ta durması bir tavandır ve nereden geldiği açık: dosya kırk satır, suçlama satır başına bir ad veriyor, dolayısıyla en çok kırk ayrı ad üretebiliyor. Tarama uzadıkça bedel büyüyor ama yanıtın büyüklüğü büyümüyor. Elli işlemede tarama sekiz, yanıt sekiz — biri birine. Dört binde tarama yedi yüz seksen yedi, yanıt kırk — yirmi adıma bir ad.

Bunun anlamı, suçlamanın bir özet olmasıdır. Dosyanın satır tarihçesinin tamamı değil, o tarihçenin son katmanıdır. Bir satırın kaç kez değiştiği, kaç ayrı işlemenin ona dokunduğu, önceki hâllerinin ne olduğu suçlamanın yanıtında yoktur — ve olmadığı da yazmaz. Yanıtı okuyan, gizlenen yedi yüz kırk yedi işlemenin farkında olmaz.

Tam satır tarihçesi ayrı bir sorudur ve ayrı bir alt komutla sorulur: bir satır aralığının bütün geçmişini isteyen sorgu, o aralığa dokunan her işlemeyi sırasıyla verir. Bedeli suçlamayla aynı taramadır, ama yanıtın büyüklüğü artık tavana takılmaz. Doğru araç seçiminin ölçütü budur: son katman mı isteniyor, bütün katmanlar mı?

Taşınan Satır

Ölçüm dosyanın kırk satırının yerinde kaldığını varsayıyor (TA20). Gerçek bir tarihçede satırlar taşınır: bir işlev başka bir dosyaya alınır, bir blok aynı dosyanın başka yerine kaydırılır. Suçlama açısından bu, satırın yeniden yazılması demektir. Taşıyan işleme taşınan bütün satırların son yazarı olur ve satırın gerçek geçmişi bir dosya sınırının ötesinde kalır.

Araç bunu bir ölçüde geri alabilir. Aynı işlemenin içinde yeri değişmiş satırları izleyen bir ayar vardır, dosyalar arasında taşınmayı izleyen bir başkası. İkisi de taramayı genişletir, çünkü artık yalnız izlenen dosyanın tarihçesi değil, satırın gelmiş olabileceği dosyaların tarihçesi de yürünmek zorundadır. Bedel bu yüzden ölçümde saydığımızın üstüne çıkar: tarama tek bir dosyanın payıyla sınırlı kalmaz.

Buradan pratik bir kural çıkıyor. Taşıma ile değişiklik aynı işlemede birleştirilirse satırın geçmişi orada kesilir — araç, taşınan satırın yeni yerinde ne kadarının korunduğunu ancak ikisi ayrıldığında güvenle çıkarabilir. Taşımayı ayrı bir işlemede kaydetmek, ileride sorulacak soruya yanıt bırakmanın en ucuz yoludur: saf taşıma işlemesi atlama listesine yazılabilir, taşımayla değişikliği birleştiren işleme yazılamaz.

Biçim Geçişinin Ödettiği

Alt tablo tarihçenin ortasına tek bir biçim geçişi koyuyor: içeriği değiştirmeyen, ama dosyanın kırk satırının kırkını da yeniden yazan bir işleme. Sonuç ölçeğe göre sert biçimde değişiyor.

Elli işlemelik tarihçede geçişten sonra dosyaya yalnız 3 kez dokunulmuş; suçlamanın yanıtında 37 satır biçim geçişini gösteriyor ve geriye 4 ayrı işleme kalıyor. Dosyanın görünür yazarlığı tek bir işlemeye toplanmış durumda. Dört bin işlemede ise geçişten sonra 375 dokunuş olmuş ve biçim geçişini gösteren satır 0; yanıtta yine 40 ayrı işleme var.

Örüntü şudur: biçim geçişinin zararı ölçekle değil, geçişten sonraki dokunuş sayısıyla belirlenir. Geçiş, üstünden yeterince iş geçmemiş bir dosyada suçlamanın yanıtını tümüyle kendi üstüne toplar. Ve zararın en ağır olduğu yer, dosyanın en az dokunulan yeridir — yani suçlamaya en çok başvurulacak, en az anlaşılan satırlar.

Bunun iki karşılığı vardır ve ikisi de suçlamanın kendisinde durur. Birincisi boşluk değişikliklerini yok saymaktır; yalnız girinti ve hizalama değişmişse satır sahipsiz sayılır ve tarama bir önceki işlemeye geçer. İkincisi belirli işlemelerin taramada atlanmak üzere işaretlenmesidir; toplu biçim geçişleri bir listeye yazılır ve suçlama o işlemeleri hiç görmemiş gibi davranır. İkisi de aracın ayarıdır ve tarihçeyi değiştirmez — bu kursta tarihçeyi değiştirmeden çözülen ilk sorundur.

Özet

  • Suçlama satır başına tek bir ad üretir ve o ad satırı en son yazan işlemedir; “kim yazdı” ile “en son kim dokundu” ayrıldığında araç bunu haber vermez.
  • Bedelin birimi adımdır ve bir adım, izlenen dosyaya dokunan bir işlemedir; tarihçe 50’den 4000’e çıkarken bedel 8’den 787’ye yükselir.
  • Aynı tarihçede ikili arama 6 adımdan 11’e çıkar; sorulan sorunun biçimi tek başına iki ayrı büyüme üretir.
  • Tarama uzarken yanıt büyümez: dört bin işlemede 787 işleme taranır, yanıtta 40 ayrı işleme görünür, 747 işleme gizlenir. Suçlama bir özet, tam satır tarihçesi ise ayrı bir sorgudur.
  • Taşınan satır suçlama açısından yeniden yazılmış sayılır; taşımayı ayrı bir işlemede kaydetmek satırın geçmişini korumanın en ucuz yoludur.
  • Tarihçenin ortasındaki tek bir biçim geçişi, üstünden az iş geçmiş bir dosyada 37 satırın yanıtını kendi üstüne toplar; boşluk yok sayma ve işleme atlama listesi bunu tarihçeye dokunmadan geri alır.

Sonraki Adım

Bu iki ders de tarihçeye sonradan soru sordu. Kusur çoktan girmişti, satır çoktan yazılmıştı; araçlar yalnızca olmuş bitmiş bir kaydı okudu ve bedeli okuma tarafında ödedi. Oysa her işleme, tarihçeye girmeden önce birkaç ayrı andan geçer: hazırlık alanından işlemeye, işlemeden itmeye, itmeden karşı tarafın kabulüne. Sonraki ders bu anlara bakar — bir denetimin hangi anda araya girebildiği, o anda hangi işlemin hâlâ engellenebilir olduğu, ve engel geç konduğunda bedelin kimin üstünde kaldığı.

İ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