Ders 10 / 15
Dal Stratejileri
Dalın ne kadar yaşadığı tarihçeye yazılanı belirler: aynı on iki işleme dal açılmadan 12, dört işlemede dönen düzende 15, her işlemede dönen düzende 24 işleme bırakır — ve ayrışma penceresi kapandığında birleştirme çakışması hiç doğmaz.
İçindekiler
Önceki derste biçim seçimi tek bir birleşme için yapıldı: şu dal ana dala nasıl alınsın. Ekipler bu kararı her seferinde yeniden vermez; bir kez verip kural hâline getirirler.
Kural yalnız birleşme anını kapsamaz. Dalların ne kadar yaşayacağını, kaç tane olacağını ve ne sıklıkla ana dala döneceğini de belirler. Bu dersin sorusu şudur: aynı on iki işlemelik gelişim ayrı dal düzenleriyle yürütüldüğünde tarihçeye ne kalır — ve dal ömrünün kısalması hangi soruyu ortadan kaldırır.
Sürekli tümleştirmenin derleme, sınama ve dağıtım hattı açısından tartışması bu kursun konusu değildir; Sürekli Tümleştirme ve Teslimat kursunda kurulur ve burada tekrarlanmaz. Ölçülen tek şey, stratejinin tarihçeye ne bıraktığıdır.
Dal Ömrü Bir Sayıdır
Dal stratejilerinin farkı tek bir büyüklüğe indirgenebilir: bir dal ana dala dönmeden önce kaç işleme biriktirir? Bu sayı düzenin bütün öbür özelliklerini belirler.
Uzun ömürlü dal düzeninde sayı büyüktür. Dal bir işin tamamı bitene kadar açık kalır; haftalarca yaşayabilir, onlarca işleme taşıyabilir. Kısa ömürlü dal düzeninde sayı küçüktür; dal bir günden az yaşar, birkaç işleme taşır ve kapanır. Uçta bir düzen daha vardır: dal hiç açılmaz ve herkes doğrudan ana çizgiye işler.
Üç düzen de aynı işi yaptırır ve aynı kodu üretir. Ayrıldıkları yer, tarihçeye bıraktıkları kayıt ile dalların birbirinden ne kadar uzaklaştığıdır.
Kısa ömürlü düzenin günlük döngüsü birkaç komuta iner ve her turda aynen tekrarlanır.
# öğretilen komut, örnek döküm — çalıştırılmamıştır $ git switch -c olcum/esik ana $ git commit -am "olcum: esik degerini yazdir" $ git switch ana $ git merge --no-ff olcum/esik $ git branch -d olcum/esik
Beşinci satır düzenin ayırt edici parçasıdır: dal, işi ana dala girdikten hemen sonra kapatılır. Kapatılmayan kısa dallar birikir ve dal listesi bir işaretçi çöplüğüne döner — kayıt tarihçede kapanış işlemesi olarak zaten durduğu için işaretçinin kalması hiçbir soruyu yanıtlamaz.
Uzun ömürlü dallar tek bir sınıf değildir. Bir işi taşıyan özellik dalı işi bitince kapanır; bir yayımlanmış sürümü taşıyan dal ise kapanmaz, aylarca yaşar ve yalnız düzeltme alır. İkincisinde ayrışma tasarım gereğidir — o dalın ana daldan uzaklaşması bir kusur değil, amaçtır. Bu yüzden düzeltmeler oraya ve geliştirme çizgisine ayrı ayrı gitmek zorunda kalır; dördüncü dersteki tek işleme kopyalama en sık burada kullanılır ve kopyanın ödettiği yinelenme de burada birikir.
Adlandırma düzeni burada belirleyici olur. Kısa ömürlü düzende dal sayısı işin büyüklüğüyle değil, dönüş sıklığıyla artar; bir işin birbirini izleyen turları ayrı ayrı dallar olur. Dal adları bu turları aynı işe bağlayacak biçimde kurulmamışsa, tarihçedeki dal kayıtları çoğalır ama hiçbiri işin tamamını göstermez. Adlandırmanın tarihçenin birinci sorusuna katkısı bu kursun üçüncü dersinde kurulmuştu; burada aynı katkı dal sayısıyla çarpılır.
Ayrışma ve Çakışma Penceresi
İkinci dersin çakışması kurgunun üçüncü adımında doğdu: olcum ile rapor aynı dosyaya
dokundu. Ama çakışmanın doğması için aynı dosyaya dokunmak yetmez. İki değişikliğin
paralel geliştirilmiş olması gerekir — yani ikincisi başladığında birincisi henüz ana
dala girmemiş olmalıdır.
Birincisi zaten girmişse ikinci dal onu taban olarak görür. O zaman ortada iki taraflı bir değişiklik yoktur; tek taraflı bir değişiklik vardır ve araç kararı kendiliğinden verir. Aynı satır iki kez değişmiştir ama sırayla, çakışmadan.
Buradan dal ömrünün ikinci etkisi çıkar: dal ne kadar uzun yaşarsa ana daldan o kadar çok işleme kadar ayrışır ve iki işin aynı dosyaya paralel dokunma olasılığı o kadar büyür. Ayrışma bir zaman değil, bir işleme sayısıdır; ve düzenin sayısına eşittir.
Bu, kısa ömürlü dal düzeninin asıl gerekçesidir. Gerekçe “çakışmayı çözmek kolaylaşır” değildir; çakışmanın hiç doğmamasıdır. Buna karşılık kısa dal, yarım kalmış işin ana dalda durmasını gerektirir — yarım işin görünmeden durabilmesi için çalışma zamanında kapatılabilir olması gerekir ve o düzeneğin kendisi Sürekli Tümleştirme ve Teslimat kursunun konusudur.
Ölçümün varsayımları:
- BR36 — Dal düzeni tek bir sayıyla temsil edilir: bir dal ana dala dönmeden önce kaç işleme biriktirir. Sıfır, dalın hiç açılmaması demektir.
- BR37 — Her dönüş bir kapanış işlemesi yazar. Düzen dört işlemede dönüyorsa bu, birinci derste ölçülen birleştirme işlemesi biçiminin kendisidir.
- BR38 — Kısa ömürlü düzende her tur ayrı bir daldır ve ayrı bir dal kaydı taşır; aynı işin turları arasında bağ kuran bir alan yoktur.
- BR39 — Ayrışma, bir dalın ana daldan uzak kaldığı en çok işleme sayısıdır ve düzenin sayısına eşittir.
- BR40 — İki dokunuşun paralel sayılması, ikincisinin dalı açıldığında birincisinin ana dala henüz girmemiş olmasıdır. Birleştirme çakışması yalnız paralel dokunuşlarda doğar.
- BR41 — Zaman değerleri kurgunun kendi saatidir, ortama bağlı bir damga değildir; yalnız sıralama için kullanılır.
- BR42 — Altı sorunun tanımı değiştirilmez; her düzen için ayrı ayrı çalıştırılır.
- BR43 — Kümenin çözünürlüğü on iki işlemede 1/12, altı soruda 1/6.
Ölçüm
"""Dal stratejisi: dal omru tarihceye ne birakir. Bolum 1 - dort dal duzeni: dal acmadan, bir, iki ve dort islemede bir donen. Bolum 2 - ayni ortak dosyaya dokunan iki isleme paralel mi kaliyor. """ TOHUM = 20260813 DALLAR = ("olcum", "rapor", "kimlik") DOSYALAR = {"olcum": "olcum.py", "rapor": "rapor.py", "kimlik": "kimlik.py"} KUSURLU = ("rapor", 2) ORTAK_DOSYA = "ayar.py" DUZENLER = ((0, "dal açılmıyor"), (1, "her işlemede dönüş"), (2, "iki işlemede dönüş"), (4, "dört işlemede dönüş")) def uretec(tohum): d = tohum % 2147483646 + 1 def r(n): nonlocal d d = (d * 48271) % 2147483647 return d % n return r def gelisim(): r, kayit, zaman = uretec(TOHUM), [], 0 for adim in range(1, 5): for dal in DALLAR: zaman += 1 + r(3) dosya = (ORTAK_DOSYA if adim == 3 and dal in ("olcum", "rapor") else DOSYALAR[dal]) kayit.append({"dal": dal, "sira": adim, "dosya": dosya, "kusurlu": (dal, adim) == KUSURLU, "zaman": zaman}) return kayit def bosalt(grup, dal, tur): """Bir turun islemeleri ve kapanis islemesi.""" ad = f"{dal}#{tur}" y = [{**x, "dal": ad, "is": dal, "dal_kaydi": ad, "zaman_korundu": True} for x in grup] y.append({"dal": ad, "is": dal, "sira": 0, "dosya": None, "kusurlu": False, "zaman": grup[-1]["zaman"], "dal_kaydi": ad, "birlesme": True, "zaman_korundu": True}) return y def tarihce(kayit, k): """k islemede bir ana dala donen dal duzeni. k=0: dal hic acilmaz.""" sirali = sorted(kayit, key=lambda y: y["zaman"]) if k == 0: return [{**x, "is": x["dal"], "dal_kaydi": None, "zaman_korundu": True} for x in sirali] t, tampon, tur = [], {d: [] for d in DALLAR}, {d: 0 for d in DALLAR} for x in sirali: tampon[x["dal"]].append(x) if len(tampon[x["dal"]]) == k: tur[x["dal"]] += 1 t.extend(bosalt(tampon[x["dal"]], x["dal"], tur[x["dal"]])) tampon[x["dal"]] = [] for d in DALLAR: if tampon[d]: tur[d] += 1 t.extend(bosalt(tampon[d], d, tur[d])) return t def s1_gruplama(t): return all(x.get("dal_kaydi") for x in t if not x.get("birlesme")) def s2_kusur(t): kusurlu = [x for x in t if x["kusurlu"]] return len(kusurlu) == 1 and not kusurlu[0].get("sikisik") def s3_sira(t): return all(x.get("zaman_korundu") for x in t) def s4_dosya(t): return all(isinstance(x["dosya"], str) or x.get("birlesme") for x in t) def s5_kapsam(t): yerler = {} for i, x in enumerate(t): if x.get("birlesme"): continue yerler.setdefault(x["dal"], []).append(i) return all(y[-1] - y[0] == len(y) - 1 for y in yerler.values()) def s6_cakisma(t): return any("secilen" in x for x in t) SORULAR = (("özellik gruplaması", s1_gruplama), ("kusurun yalıtımı", s2_kusur), ("gerçek sıra", s3_sira), ("dosya izi", s4_dosya), ("dalın bütünlüğü", s5_kapsam), ("çakışma kararı", s6_cakisma)) def giris_ani(kayit, k, dal, sira): """O islemenin ana dala girdigi an.""" grup = [x for x in kayit if x["dal"] == dal] if k == 0: return grup[sira - 1]["zaman"] return grup[min(((sira - 1) // k + 1) * k, len(grup)) - 1]["zaman"] def tur_baslangici(kayit, k, dal, sira): """O islemenin bulundugu turun dalinin acildigi an.""" grup = [x for x in kayit if x["dal"] == dal] if k == 0: return grup[sira - 1]["zaman"] return grup[((sira - 1) // k) * k]["zaman"] kayit = gelisim() dokunus = [(k["dal"], k["sira"]) for k in kayit if k["dosya"] == ORTAK_DOSYA] onceki, sonraki = dokunus[0], dokunus[1] print(f"gerçek gelişim: {len(kayit)} işleme | {ORTAK_DOSYA} dosyasına dokunan: " f"{dokunus}") print() print(f"{'dal düzeni':<22s} {'işleme':>6s} {'kapanış':>7s} {'dal kaydı':>9s}" f" {'iş başına dal':>13s} {'yanıtlanan':>10s}") for k, ad in DUZENLER: t = tarihce(kayit, k) kap = sum(1 for x in t if x.get("birlesme")) kayitlar = {x["dal_kaydi"] for x in t if x["dal_kaydi"]} print(f"{ad:<22s} {len(t):6d} {kap:7d} {len(kayitlar):9d}" f" {(len(kayitlar) / len(DALLAR) if kayitlar else 0):13.1f}" f" {sum(s(t) for _, s in SORULAR):10d}") print() print(f"{'dal düzeni':<22s} {'ayrışma':>7s} {'önceki dokunuş girer':>20s}" f" {'sonraki dal açılır':>18s} {'paralel':>7s}") for k, ad in DUZENLER: girer = giris_ani(kayit, k, onceki[0], onceki[1]) acilir = tur_baslangici(kayit, k, sonraki[0], sonraki[1]) print(f"{ad:<22s} {k:7d} {girer:20d} {acilir:18d}" f" {('evet' if girer > acilir else 'hayır'):>7s}")
gerçek gelişim: 12 işleme | ayar.py dosyasına dokunan: [('olcum', 3), ('rapor', 3)]
dal düzeni işleme kapanış dal kaydı iş başına dal yanıtlanan
dal açılmıyor 12 0 0 0.0 3
her işlemede dönüş 24 12 12 4.0 5
iki işlemede dönüş 18 6 6 2.0 5
dört işlemede dönüş 15 3 3 1.0 5
dal düzeni ayrışma önceki dokunuş girer sonraki dal açılır paralel
dal açılmıyor 0 17 19 hayır
her işlemede dönüş 1 17 19 hayır
iki işlemede dönüş 2 23 19 evet
dört işlemede dönüş 4 23 4 evet
Tarihçeye Ne Bıraktılar
Üst tablonun son sütunu ilk bakışta şaşırtıcıdır: üç dal düzeni de altı sorunun 5’ini yanıtlıyor. Dört işlemede dönen düzen birinci dersteki birleştirme işlemesi biçiminin kendisidir ve beklenen sayıyı veriyor; ama her işlemede dönen düzen de, iki işlemede dönen düzen de aynı 5’i veriyor. Altı soru dal düzenlerini birbirinden ayırmıyor. Dal açılmayan düzen ise 3’te kalıyor — dal kaydı hiç yazılmadığı için özellik gruplaması ve dalın bütünlüğü düşüyor, sonuç ileri sarmanın sonucuna eşitleniyor.
Düzenleri ayıran şey soldaki sütunlardır. Aynı on iki işlemelik gelişim, dört işlemede dönen düzende 15, iki işlemede dönende 18, her işlemede dönende 24 işleme bırakıyor. Kapanış işlemesi sayısı sırasıyla 3, 6 ve 12. Son düzende tarihçenin yarısı kapanış işlemesidir: yirmi dört kayıttan on ikisi hiçbir dosyaya dokunmaz. Bu, önceki derste ölçülen 3/12’lik kapanış bedelinin dört katıdır ve dal sayısıyla doğrusal büyür — dönüş sıklığı iki katına çıktığında kapanış sayısı da iki katına çıkar.
Dal kaydı sütunu ikinci maliyeti veriyor. Her işlemede dönen düzende 12 ayrı dal kaydı var ve üç işe bölündüğünde iş başına 4.0 dal düşüyor. Bu sayı, tarihçenin bir işi tek bir kayıtta göstermediği anlamına gelir: özellik gruplaması sorusu evet der ama gösterdiği grup bir iş değil, bir turdur. Soru dal düzeyinde yanıtlanır ve dal, işten küçülmüştür.
Buradan bir uyarı çıkar. Altı soru bir tümleştirme biçimini ölçmek için kurulmuştu ve o işi yapar; bir dal düzenini ölçmek için tek başına yetmez. Düzenin maliyeti soruların dışında, kapanış oranı ile iş başına dal sayısında durur. Ölçünün nereye kadar geçerli olduğunu bilmek, ölçünün kendisi kadar önemlidir.
Ayrışmanın Ödettiği
Alt tablo öbür yöne bakıyor. ayar.py dosyasına dokunan iki işleme her düzende aynı iki
işlemedir; değişen, ikincisinin dalı açıldığında birincisinin ana dalda olup olmadığıdır.
Dört işlemede dönen düzende olcum dalının üçüncü işlemesi ana dala 23 anında giriyor,
rapor dalı ise 4 anında açılmış durumda. İkinci dal çok önce açıldığı için birincinin
değişikliğini hiç görmüyor: iki dokunuş paralel ve çakışma doğuyor. İki işlemede dönen
düzende ayrışma yarıya iniyor ama sonuç değişmiyor; giriş anı 23, dal açılışı 19 ve
dokunuşlar yine paralel.
Her işlemede dönen düzende tablo dönüyor. olcum dalının üçüncü işlemesi yazıldığı anda,
17’de, ana dala giriyor; rapor dalının o turu ise 19’da açılıyor. İkinci dal
birincinin değişikliğini taban olarak görüyor ve paralel hayır. Aynı dosyaya
dokunulmasına rağmen ortada çözülecek bir çakışma yok. Dal açılmayan düzende ayrışma
0’dır ve sonuç aynıdır.
Örüntü şudur: ayrışma penceresi kapandığında çakışma doğmaz. Kısa ömürlü dalın satın aldığı şey çözülmesi kolay çakışmalar değil, hiç doğmayan çakışmalardır. Ödediği şey ise üst tablodadır: on iki kapanış işlemesi ve iş başına dört dal kaydı.
Bir üçüncü maliyet ölçümün dışında kalır ve söylenmesi gerekir. Ayrışmayı düşük tutmak, dalın hızla ana dala dönebilmesine bağlıdır; dönüş bir onay adımından geçiyorsa ayrışma o adımın süresi kadar uzar. Yani dal ömrü yalnız bir yazma alışkanlığı değil, bir inceleme hızıdır. İnceleme akışının kendisi bu kursun sonraki konusunda mekanik olarak kurulur, inceleme ekseni ve kültürü ise ayrı bir kursun konusudur.
İki tablo birlikte okunduğunda strateji kararı yerine oturur. Uzun ömürlü dal tarihçeyi kısa ve iş düzeyinde okunur tutar, ayrışmayı ve çakışmayı biriktirir. Kısa ömürlü dal çakışmayı ortadan kaldırır, tarihçeyi kapanış işlemeleriyle şişirir ve işi turlara böler. İkisinin arasında bir doğru nokta yoktur; hangi maliyetin ödenebilir olduğu ekibin sorusudur. Ölçüm yalnız iki maliyetin aynı anda azaltılamayacağını gösterir.
Özet
- Dal stratejilerinin farkı tek bir büyüklüğe iner: bir dalın ana dala dönmeden önce biriktirdiği işleme sayısı. Bu sayı hem tarihçenin boyunu hem ayrışmayı belirler.
- Aynı on iki işlemelik gelişim dal açılmayan düzende 12, dört işlemede dönende 15, iki işlemede dönende 18, her işlemede dönende 24 işleme bırakır; kapanış sayıları 0, 3, 6 ve 12’dir.
- Üç dal düzeni de altı sorunun 5’ini yanıtlar: altı soru dal düzenlerini ayırmaz. Dal açılmayan düzen 3’te kalır.
- Her işlemede dönen düzende iş başına 4.0 dal kaydı düşer; özellik gruplaması evet der ama gösterdiği grup bir iş değil bir turdur.
- Çakışma yalnız paralel dokunuşlarda doğar; ayrışma 1 ya da 0 olduğunda ikinci dal birincinin değişikliğini taban olarak görür ve çakışma hiç doğmaz.
Sonraki Adım
Bu konunun kurduğu her şey — dört tümleştirme biçimi, çakışmanın çözümü, dal ömrü ve strateji kararı — tek bir depo varsayımıyla ölçüldü. Tarihçe bir yerde duruyordu, birleşmeler orada yapılıyordu, dal kayıtları oraya yazılıyordu.
Gerçek bir ekipte bu varsayım geçerli değildir: tarihçe birden çok kopyada yaşar. Her kişinin kendi deposu vardır, kopyalar birbirinden ayrı ilerler ve ölçülen bütün sorular bir kez daha sorulur — bu kez “hangi kopyada” diye. Sonraki konu buradan başlar ve önce en temel soruyu yanıtlar: bir depo başka bir depoyu nasıl tanır, birden çok kopya nasıl adlandırılır ve ayrı tutulur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.