Mert Özen Her Satırda Daha İleri
Test ve Kalite Code Review — #02

PR Yazarı Olarak Review'u Kolaylaştırmak: Küçük PR, Stacked PR, İyi Açıklama ve Self-review

Mert Özen 14 Kas 2026 15 dk 14 görüntülenme
PR Yazarı Olarak Review'u Kolaylaştırmak: Küçük PR, Stacked PR, İyi Açıklama ve Self-review

Review'u kolaylaştırmanın en etkili yolu PR'ı küçük ve tek amaçlı tutmaktır: 2721 satırlık kupon PR'ının 2059 satırı üretilmiş migration dosyasıydı, kalan kısım refactor, model ve davranış olarak üç stacked PR'a bölününce her biri tek bir soruya indi. Yazı GitHub'ın native stacked PR'larını, PR template'ini, draft ve self-review'u, fixup ile --update-refs ve range-diff kullanarak review turlarını gösteriyor.

Sürüm notu: Bu yazıdaki örnekler .NET 10 (SDK 10.0.402), Entity Framework Core (EF Core; .NET'in veritabanı erişim kütüphanesi) 10.0.15, xUnit.net v3 4.0.3, Angular 22.2 (TypeScript 6.0) ve Git 2.54 ile denenmiştir. GitHub'ın stacked pull request özelliği Kasım 2026'da public preview'dadır; bir GitHub repo'sunda çalıştırılmamış, GitHub Docs'tan anlatılmıştır.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'a (bir online kitapçı) kupon özelliği geliyor: yüzde kuponu (BOOK10), tutar kuponu (SAVE50), yalnız klasikler kategorisinde geçen bir kupon (CLASSIC15) ve sepette kupon kutusu. Emre işi üç günde bitirip tek bir pull request (PR) açıyor. Ayşe'nin önüne gelen tablo şu:

$ git log --oneline main..feature/coupons
7218f7b Coupons + small fixes

$ git diff --shortstat main...feature/coupons
 31 files changed, 2665 insertions(+), 56 deletions(-)

Tahmin et: bu 2721 satırın kaçını bir insanın gerçekten okuması gerekiyor? Ve okunması gereken satırların kaçı kupon özelliğiyle ilgili? Ayşe bu PR'ı bir oturuşta review edebilir mi, yoksa ilk yorumu "bunu nereden okumaya başlayayım?" mı olur?

PR Boyutu Review'u Nasıl Etkiler?

Serinin ilk yazısında Bookstore'un review sözleşmesine "üretilmiş dosyalar hariç en fazla yaklaşık 400 satır" diye yazmıştık. Sayının kaynağı SmartBear'ın Cisco'da yaptığı çalışma: bir seferde 200 ile 400 satırdan fazlası review edildiğinde bulunan kusur oranı belirgin biçimde düşüyor. Google'ın Engineering Practices rehberi kesin bir sınır koymuyor ama yönü aynı: küçük değişiklik daha hızlı ve daha dikkatli review edilir, içine hata gizlemek zordur, reddedilirse daha az emek çöpe gider, geri almak kolaydır. Rehber, reviewer'ın bir değişikliği yalnız büyüklüğü yüzünden geri çevirebileceğini de açıkça söylüyor. Satır sayısı tek ölçü değil: aynı 200 satır tek dosyadaysa okunur, 50 dosyaya dağılmışsa okunmaz.

Emre'nin PR'ına bu gözle bakalım. 2665 satırın büyük kısmını insan yazmadı: EF Core'un her migration için ürettiği designer dosyası ve model snapshot'ı. Bu dosyaları GitHub'ın diff görünümünde kapalı göstermek için repo köküne bir .gitattributes koyduk:

# EF Core'un migration designer dosyaları ve model snapshot'ı üretilmiş dosyalar; GitHub bunları diff'te kapalı gösterir.
src/Bookstore.Api/Data/Migrations/*.Designer.cs linguist-generated
src/Bookstore.Api/Data/Migrations/BookstoreContextModelSnapshot.cs linguist-generated

linguist-generated işaretli dosyalar GitHub'da diff'te varsayılan olarak kapalı gelir ve repo'nun dil istatistiklerine katılmaz. Yerelde de aynı ayrımı pathspec ile yaparsın:

$ git diff --shortstat main...feature/coupons -- . ':(exclude)*.Designer.cs' ':(exclude)*ModelSnapshot.cs'
 28 files changed, 606 insertions(+), 56 deletions(-)

Okunması gereken kısım 662 satır. Sınırın üstünde ama asıl sorun boyut değil, karışım. git diff --stat listesinin Ordering ve çevresine düşen kısmı şöyle:

$ git diff -M --stat=90 main...feature/coupons -- src/Bookstore.Api/Ordering src/Bookstore.Api/Inventory src/Bookstore.Api/Catalog README.md
 README.md                                           |  4 ++
 src/Bookstore.Api/Catalog/CatalogService.cs         |  2 +-
 src/Bookstore.Api/Inventory/InventoryService.cs     |  6 +-
 src/Bookstore.Api/Ordering/Cart.cs                  |  1 +
 src/Bookstore.Api/Ordering/CartContracts.cs         | 19 ++++++
 src/Bookstore.Api/Ordering/CartPricing.cs           | 23 ++++++++
 src/Bookstore.Api/Ordering/Coupon.cs                | 23 ++++++++
 src/Bookstore.Api/Ordering/CouponConfiguration.cs   | 31 ++++++++++
 src/Bookstore.Api/Ordering/CouponRules.cs           | 13 +++++
 src/Bookstore.Api/Ordering/CouponService.cs         | 40 +++++++++++++
 src/Bookstore.Api/Ordering/Order.cs                 |  3 +
 src/Bookstore.Api/Ordering/OrderContracts.cs        | 21 +++++++
 src/Bookstore.Api/Ordering/OrderService.cs          | 30 +++++++---
 src/Bookstore.Api/Ordering/OrderingConfiguration.cs |  4 ++
 src/Bookstore.Api/Ordering/OrderingContracts.cs     | 19 ------
 src/Bookstore.Api/Ordering/OrderingModule.cs        | 96 ++++++++++++++++++++++++-------
 16 files changed, 281 insertions(+), 54 deletions(-)

Bu listede dört farklı türde değişiklik iç içe. Bir refactor: sepet fiyatlandırması OrderingModule ile OrderService'ten CartPricing'e taşınmış. Yeni bir veri modeli: Coupon, configuration'ı ve iki migration. Bir davranış değişikliği: kuponun sepete ve siparişe uygulanması, Angular tarafında kupon kutusu. Ve "hazır buradayken" yapılanlar: OrderingContracts.cs iki dosyaya bölünmüş, InventoryService'te bir değişkenin adı değişmiş, CatalogService'te bir yorum yeniden yazılmış, iki method yeniden adlandırılmış. Ayşe'nin okurken her satır için "bu davranışı değiştiriyor mu, yoksa sadece yer mi değiştirdi?" diye sorması gerekiyor. Commit mesajı da yardım etmiyor: "Coupons + small fixes".

Büyük Özelliği Bölmek: Refactor Ayrı, Davranış Ayrı

Emre işi baştan, review edilebilir parçalar olarak yeniden kuruyor. Kural basit: her parça tek bir şey yapar, tek başına derlenir ve testleri geçer. Google'ın rehberi de refactor'ı özellik değişikliğinden ayırmayı ve birbirine bağlı parçalarda her birinin build'i kırmamasını şart koşuyor.

İlk parça yalnız refactor. Sepet satırlarının fiyatlandırılması hem sepet görünümünde hem sipariş verirken aynı mantıkla yapılıyordu; Emre bunu tek bir fonksiyona topluyor:

using Bookstore.Catalog;

namespace Bookstore.Ordering;

public sealed record PricedLine(BookInfo Book, int Quantity, decimal LineTotal);

public sealed record CartPrice(IReadOnlyList<PricedLine> Lines, decimal Subtotal);

// Sepet görünümü ve sipariş verme aynı fiyatlandırmayı kullanır.
public static class CartPricing
{
    public static CartPrice Price(IEnumerable<CartItem> items, IReadOnlyDictionary<int, BookInfo> books)
    {
        List<PricedLine> lines =
        [
            .. items
                .Select(i => (Book: books[i.BookId], i.Quantity))
                .OrderBy(x => x.Book.Title)
                .Select(x => new PricedLine(x.Book, x.Quantity, OrderItem.CalculateLineTotal(x.Book.Price, x.Quantity)))
        ];
        return new CartPrice(lines, lines.Sum(l => l.LineTotal));
    }
}

Bu PR'ın açıklamasındaki en önemli cümle "davranış değişikliği yok". Reviewer'ın işi bu iddiayı kontrol etmek: mevcut sipariş testleri değişmeden geçiyor mu, yeni testler yalnız taşınan mantığı mı kapsıyor? Refactor tek başına geldiğinde bu kontrol kolay; kupon kodunun arasında geldiğinde neredeyse imkânsız.

İkinci parça veri modeli: Coupon entity'si, configuration'ı, dört örnek kuponu ekleyen AddCoupons migration'ı ve indirim hesabını yapan saf bir fonksiyon, CouponRules. Bu PR merge edildiğinde hiçbir kullanıcı bir şey görmez; tablo ve kod hazırdır ama hiçbir endpoint onu kullanmaz. Migration'ın ayrı PR'da gelmesinin bir faydası daha var: şema değişikliği, onu kullanan koddan önce production'a çıkabilir. Bunun neden önemli olduğunu Deployment Stratejileri serisinin "Migration'ların Deploy'daki Yeri: Expand-Contract, EF Core Migration Bundle ve Geriye Uyumlu Şema" yazısında anlattık.

Üçüncü parça davranış: kupon uygulama endpoint'i, sepet ve sipariş görünümündeki indirim ve Angular'daki kupon kutusu. Bu parça bir feature flag'in arkasında. Endpoint flag kapalıyken 404 döner, flag yalnız Development ortamında açık:

// PUT /api/carts/{cartId}/coupon  { "code": "BOOK10" }
private static async Task<Results<Ok<CartView>, NotFound, ProblemHttpResult>> ApplyCoupon(
    Guid cartId, ApplyCouponRequest request, IConfiguration config,
    BookstoreContext db, ICatalogService catalog, CouponService coupons, TimeProvider clock, CancellationToken ct)
{
    if (!config.GetValue<bool>("Features:Coupons"))
    {
        return TypedResults.NotFound();
    }
    // ...
}
"Features": {
  "Coupons": true
}

Flag sayesinde yarım iş main'e girebilir ve PR'lar özelliğin tamamlanmasını beklemeden merge edilebilir. Flag türleri ve flag borcunun yönetimi Çözüm Desenleri serisinin "Feature Flag'leri Kodda Tasarlamak: Toggle Türleri, Microsoft.FeatureManagement ve Flag Borcu" yazısında; burada configuration'dan okunan basit bir boolean yeterli.

Peki "hazır buradayken" yapılanlar? Dosya bölme ve isim değişiklikleri bu özellikten çıkarıldı. Gerçekten gerekiyorlarsa ayrı ve davranış değiştirmeyen küçük bir PR olarak gelirler; gerekmiyorlarsa hiç gelmezler. Üç parçanın boyutları:

$ git diff --shortstat main...coupons/1-cart-pricing -- . ':(exclude)*.Designer.cs' ':(exclude)*ModelSnapshot.cs'
 4 files changed, 70 insertions(+), 17 deletions(-)
$ git diff --shortstat coupons/1-cart-pricing...coupons/2-coupon-model -- . ':(exclude)*.Designer.cs' ':(exclude)*ModelSnapshot.cs'
 6 files changed, 175 insertions(+)
$ git diff --shortstat coupons/2-coupon-model...coupons/3-apply-coupon -- . ':(exclude)*.Designer.cs' ':(exclude)*ModelSnapshot.cs'
 15 files changed, 332 insertions(+), 17 deletions(-)

Toplam satır neredeyse aynı; değişen, her PR'ın tek bir soruya cevap vermesi. Birinci PR "fiyat aynı mı kaldı?", ikinci PR "model ve kurallar doğru mu?", üçüncü PR "kupon sepete doğru uygulanıyor mu?".

Stacked PR'lar: Branch'leri Üst Üste Kurmak

Üç parça birbirine bağlı: ikinci parça CartPricing'i görmeden yazılamaz, üçüncü parça Coupon'u. Emre birincinin review'unu bekleyip boş durmak istemiyor. Çözüm stack: her branch bir öncekinin üstünde kurulur ve her PR'ın base'i altındaki branch'tir.

$ git log --oneline --graph --decorate -4
* ec2e486 (HEAD -> coupons/3-apply-coupon) Apply coupons to carts and orders behind Features:Coupons
* c09eaa2 (coupons/2-coupon-model) Add Coupon entity, seed coupons and CouponRules
* ae17f10 (coupons/1-cart-pricing) Extract CartPricing from cart view and order placement
* 0e86d53 (main) Bookstore reference app

GitHub'da bunu yapmanın iki yolu var. Eski yol elle: ikinci PR'ı açarken base olarak coupons/1-cart-pricing'i seçersin, böylece diff yalnız ikinci parçayı gösterir. Birinci PR merge edilip branch'i silindiğinde GitHub, o branch'i base alan açık PR'ları otomatik olarak merge edilen PR'ın base'ine, yani main'e çevirir. Birinci PR squash ile merge edildiyse ikinci branch hâlâ birincinin eski commit'lerini taşır ve onu yeni main'in üstüne rebase etmen gerekir. Üst üste branch'leri tek komutla güncelleyen git rebase --update-refs'i Git & GitHub serisinin "Git rebase: Geçmişi Yeniden Yazmak ve Güvenle Push'lamak" yazısında anlattık.

Yeni yol GitHub'ın kendi stacked pull request desteği. 30 Temmuz 2026'da public preview olarak açıldı. Dokümana göre davranışı şöyle (bu kısım çalıştırılmadı):

  • Her PR yalnız kendi katmanının diff'ini gösterir; PR'ın üstündeki stack haritası katmanın yerini gösterir.
  • Stack'teki her PR, altındaki branch'in değil, stack'in base'inin (burada main) kurallarıyla değerlendirilir: zorunlu review'lar, status check'ler, CODEOWNERS. main'i hedefleyen Actions workflow'ları her katmanda çalışır.
  • Bir PR, kendisi ve altındaki tüm PR'lar şartları sağladığında merge edilebilir. En üstteki hazır PR'ı merge etmek, altındaki merge edilmemiş katmanları da tek işlemde main'e alır.
  • Alt katmanlar merge edildiğinde üstteki PR'lar açık kalır, otomatik olarak rebase edilir ve yeniden hedeflenir. Diff değişmediyse onaylar rebase'den sonra da geçerli kalır.
  • Sınırlar: bütün branch'ler aynı repo'da olmalı, fork'tan stack kurulamaz; GitHub Desktop desteklemiyor; bypass yetkisi yalnız en alttaki PR'da işe yarıyor. Duyuruda merge queue desteğinin kademeli açıldığı yazıyordu.

Komut satırı tarafı bir GitHub CLI extension'ı. Kurulum ve dokümanda adı geçen komutlar:

gh extension install github/gh-stack
gh stack init --base main
gh stack rebase
gh stack push

Native stack'in asıl kazancı reviewer tarafında: Ayşe hangi katmanı açarsa yalnız onu görür ve katmanları ayrı ayrı onaylayabilir. Stack'in bir bedeli de var: alt katmanda bir şey değişirse üst katmanların hepsi yeniden rebase edilir. Stack ne kadar yüksekse bu bedel o kadar artar; üç dört katmanı geçen bir stack genelde işin yanlış bölündüğünü gösterir.

İyi Bir PR Açıklaması

Küçük bir diff bile bağlam olmadan okununca soru üretir. Bookstore'un PR template'i .github/pull_request_template.md'de duruyor; GitHub yeni PR açılırken açıklama kutusunu bununla doldurur:

## Why

<!-- Bu PR hangi problemi ya da story'yi çözüyor? Issue'ya link ver. -->

## What changed

<!-- Davranış değişikliğini iki üç cümleyle anlat. Bilerek dışarıda bırakılanı da yaz. -->

## How it was tested

<!-- Çalıştırdığın komutlar, eklediğin testler, elle yaptığın kontroller. -->

## Screenshots

<!-- Yalnız UI değişikliklerinde: önce ve sonra. Değilse bu bölümü sil. -->

## Risk and rollback

<!-- Migration, feature flag, configuration. Bir sorun olursa nasıl geri alırız? -->

## Notes for reviewers

<!-- Okumaya nereden başlanmalı, neyi atlayabilir, stack'te bu PR'ın altında ve üstünde ne var? -->

Template'in her başlığı reviewer'ın soracağı bir soruya önceden cevap. Emre'nin üçüncü PR için yazdığı açıklama:

## Why

Customers can enter a coupon code in the cart (story BS-214). Selin wants BOOK10, SAVE50,
CLASSIC15 and WELCOME25 for the December campaign.

## What changed

- PUT and DELETE /api/carts/{cartId}/coupon apply or remove a coupon.
- The cart view and the order now have Subtotal, Discount and CouponCode.
- Coupon box on the cart page.
- Not in this PR: usage limits per customer, coupons for institutional orders.

## How it was tested

- dotnet test: 17 passed (2 new tests in CouponTests).
- npm test in web/bookstore-web: passed.
- Manually: BOOK10 and CLASSIC15 on a cart with Dune and Emma.

## Risk and rollback

- Behind Features:Coupons, true only in appsettings.Development.json.
- Migration ApplyCoupons adds nullable CouponCode to Carts and Orders, Subtotal and Discount to Orders.
  Rollback: switch the flag off; the columns can stay.

## Notes for reviewers

Stack: 1/3 CartPricing refactor, 2/3 coupon model, this is 3/3.
Start with CouponService.cs and OrderingModule.ApplyCoupon. The migration Designer file is generated.

"Not in this PR" satırı küçük ama değerli: reviewer'ın "kullanım limiti nerede?" diye yorum yazmasını önler ve kapsamı açıkça sınırlar. "How it was tested" bölümünde komutun kendisi ve sonucu var, "test ettim" cümlesi değil. "Notes for reviewers" ise okuma sırasını veriyor; üçüncü yazıda reviewer'ın bu sırayı nasıl kullandığını göreceğiz.

Self-review ve Review'u Ne Zaman İstemeli?

Emre PR'ı önce draft olarak açıyor. Draft PR'da CI çalışır ama PR merge edilemez ve CODEOWNERS'taki sahiplere review isteği gitmez; isteğin gitmesi PR'ın "ready for review" işaretlendiği ana kalır. Bu arada Emre kendi diff'ini, Ayşe'nin göreceği ekranda, onun gözüyle okuyor. Kendi editöründe okumakla GitHub'ın diff ekranında okumak arasında fark var; ikincisinde unuttuğun bir debug satırı, yanlışlıkla commit'lenmiş bir dosya ya da konuyla ilgisiz bir değişiklik çok daha kolay göze çarpar.

Review istemeden önceki kontroller kısa:

$ git diff --check main...coupons/3-apply-coupon
$ dotnet test
Test run summary: Passed!
  total: 17
  failed: 0
  succeeded: 17
  skipped: 0
  duration: 2s 048ms
$ cd web/bookstore-web && npm test -- --watch=false
 Test Files  1 passed (1)
      Tests  1 passed (1)

git diff --check hiçbir şey yazmadığında satır sonunda boşluk ya da conflict marker kalmamış demektir. Testler yerelde geçiyor. Yine de "ready for review" düğmesine CI yeşile döndükten sonra basılır; kırmızı CI ile review istemek, reviewer'ın zamanını çözülmemiş bir sorunun üstüne harcamak demek.

Self-review'un asıl kazancı kendi kodunda soru üretmek. "Bu satırı neden yazdım?" sorusunun cevabı koddan anlaşılmıyorsa ya bir isim değişmeli ya bir yorum eklenmeli ya da cevap PR açıklamasına yazılmalı. Bu soruyu sen sormazsan reviewer soracak ve bir tur kaybedilecek.

Review Turları: Yorumları Kaybetmeden Güncellemek

Ayşe birinci PR'a bir yorum bırakıyor: CartPricing satır toplamlarını tek tek yuvarlıyor, bu iş kuralı Requirements Analysis serisinde konuşulmuştu ama hiçbir test bunu korumuyor. Emre testi stack'in en üstünde, birinci commit'i hedefleyen bir fixup commit'i olarak yazıyor ve stack'i tek komutla yeniden kuruyor:

$ git commit --quiet --fixup=ae17f10
$ git rebase -i --autosquash --update-refs main
Successfully rebased and updated refs/heads/coupons/3-apply-coupon.
Updated the following refs with --update-refs:
	refs/heads/coupons/1-cart-pricing
	refs/heads/coupons/2-coupon-model
$ git log --oneline --graph --decorate -4
* ce9a98f (HEAD -> coupons/3-apply-coupon) Apply coupons to carts and orders behind Features:Coupons
* 3c3a74b (coupons/2-coupon-model) Add Coupon entity, seed coupons and CouponRules
* 05f87fe (coupons/1-cart-pricing) Extract CartPricing from cart view and order placement
* c139989 (main) Add pull request template, mark EF Core generated files

Fixup commit'i birinci PR'ın commit'ine katıldı ve üç branch de yeni commit'lere taşındı. Bu arada Ayşe PR template'ini ayrı küçük bir PR ile main'e merge etmişti; stack onun üstüne de taşındı. Remote'taki üç branch'i güncellemek için artık force push gerekiyor; bunu --force-with-lease ile yapmanın nedeni Git & GitHub serisinin rebase yazısında. Reviewer'a ne değiştiğini göstermenin en temiz yolu git range-diff: iki commit aralığını karşılaştırır ve yalnız farkı gösterir:

$ git range-diff 0e86d53..pr1-round1 main..coupons/1-cart-pricing
1:  ae17f10 ! 1:  05f87fe Extract CartPricing from cart view and order placement
    @@ tests/Bookstore.Api.Tests/CartPricingTests.cs (new)
     +        Assert.Empty(price.Lines);
     +        Assert.Equal(0m, price.Subtotal);
     +    }
    ++
    ++    [Fact]
    ++    public void Each_line_is_rounded_before_the_subtotal_is_summed()
    ++    {
    ++        Dictionary<int, BookInfo> books = new()
    ++        {
    ++            [1] = new BookInfo(1, "9780141439518", "Pride and Prejudice", 0.125m),
    ++            [2] = new BookInfo(2, "9780141439587", "Emma", 0.125m)
    ++        };
    ++
    ++        CartPrice price = CartPricing.Price(
    ++            [new CartItem { BookId = 1, Quantity = 1 }, new CartItem { BookId = 2, Quantity = 1 }], books);
    ++
    ++        // 0.125 is rounded to 0.13 on each line; rounding only the sum would give 0.25.
    ++        Assert.Equal(0.26m, price.Subtotal);
    ++    }
     +}

pr1-round1, review'dan önceki commit'e koyduğumuz bir tag; main arada ilerlediği için iki aralığın base'i farklı ama range-diff bunu dert etmiyor ve yalnız eklenen testi gösteriyor. Çift ++ ile başlayan satırlar "yeni diff'te eklenmiş, eskisinde olmayan satır" demek.

Burada bir tercih var. Force push, GitHub'da eski commit'lere bağlı yorumların "outdated" olmasına ve reviewer'ın "son review'dan beri ne değişti?" görünümünün zayıflamasına yol açar. Stack yoksa en sade yol review sürerken geçmişi yeniden yazmamak: düzeltmeleri yeni commit olarak eklemek, PR'ı squash merge ile kapatmak. Stack varsa alt katmandaki değişiklik zaten rebase gerektirir; o zaman range-diff çıktısını PR'a yorum olarak eklemek ya da native stack'in "Rebase stack" düğmesini kullanmak bağlamı korur. Interactive rebase ve squash seçeneklerinin karşılaştırması Git & GitHub serisinin "Git Interactive Rebase ve Squash: PR Öncesi Geçmişi Toparlamak" yazısında.

Üretimde Dikkat

Stack'in katmanları ayrı ayrı production'a çıkabilir; bu iyi, ama sıranın anlamı olmalı. AddCoupons migration'ı ikinci katmanda; o katman merge edilip deploy edildiğinde veritabanında yeni bir tablo oluşur ve hiçbir kod onu kullanmaz. Üçüncü katmanın migration'ı ise mevcut Orders tablosuna kolon ekliyor. Bu tür değişikliklerin, eski sürüm hâlâ çalışırken uygulanabilir olması gerekir; üçüncü yazıda bu migration'ın review'da yakalanan bir sorununu göreceğiz.

Feature flag'in bir de kapanış tarihi olmalı. Kupon özelliği production'da açıldıktan ve bir süre sorunsuz çalıştıktan sonra flag'i ve onu kontrol eden if'i silen PR açılmazsa, flag kalıcı bir configuration'a dönüşür. PR açıklamasındaki "Risk and rollback" bölümü bu temizlik işinin de yeri: flag'in ne zaman kalkacağını oraya yazmak, unutulmasını zorlaştırır.

Bir de ölçüm. PR'ların boyutu ve ilk review'a kadar geçen süre, ekibin bu yazıdaki alışkanlıkları gerçekten edinip edinmediğini gösterir. Beşinci yazıda bu sayıları GitHub verisinden çıkaran küçük bir araç yazacağız.

Sık Düşülen Tuzaklar

"Küçük düzeltmeler" başlıklı büyük PR. Açılıştaki PR'ın başlığı "Coupons + small fixes" idi ve küçük düzeltmeler, asıl değişikliği okumayı zorlaştıran kısmın ta kendisiydi. Bir PR'ın başlığında "ve" ya da "+" varsa, büyük ihtimalle iki PR'dır.

Review sürerken habersiz force push. Reviewer yorum yazarken branch'in geçmişi değişirse, yorumlar eski satırlara bağlı kalır ve "neye cevap verilmişti?" sorusu ortaya çıkar. Rebase gerekiyorsa PR'a bir cümle yaz ve range-diff'i ekle.

Katmana göre değil, dosya türüne göre bölmek. "Önce bütün entity'ler, sonra bütün service'ler, sonra bütün controller'lar" diye bölünen PR'lar tek başına anlamsızdır ve çoğu zaman tek başına derlenmez. Her katman çalışan, test edilmiş bir adım olmalı.

Açıklamada diff'i tekrar etmek. "CouponService.cs eklendi, Order.cs değişti" cümlesi reviewer'ın zaten gördüğü şeyi söyler. Açıklamanın işi diff'in göstermediğini anlatmak: neden, neyin bilerek dışarıda kaldığı, nasıl test edildiği.

Üretilmiş dosyaları gözden kaçırmak ya da tersine, onları gerekçe yapmak. Migration designer dosyaları okunmaz ama migration'ın kendisi (Up ve Down) mutlaka okunur. "Satırların çoğu üretilmiş" demek, üretilmemiş kısmın review'unu atlatmaz.

Küçük Bir Deneme

★☆☆ Kendi repo'nda son merge edilen beş PR için git diff --shortstat'i üretilmiş dosyaları hariç tutarak çalıştır. Hangileri 400 satırın üstünde? Bu PR'ların başlığında "ve" geçiyor mu?

★★☆ Repo'na bir PR template'i ekle ve bir sonraki PR'ını onunla aç. "Not in this PR" satırını yazarken zorlandıysan, kapsam henüz net değil demektir.

★★★ Üç commit'lik küçük bir stack kur (refactor, model, davranış), en alttaki commit'e bir fixup yaz ve git rebase -i --autosquash --update-refs main ile stack'i güncelle. Sonra git range-diff ile reviewer'a göstereceğin farkı üret ve çıktıyı kendin okuyarak neyin değiştiğini anlatabildiğini kontrol et.

Emre işini review edilebilir parçalara ayırdı ve her parçanın açıklamasını yazdı. Sıra Ayşe'de. Bir sonraki yazıda reviewer koltuğuna geçiyoruz: bir PR'ı hangi sırayla okumak gerekir, .NET ve Angular kodunda neye bakılır ve kupon PR'ının üçüncü katmanında review'un gerçekte ne bulduğu.