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

Code Review'da İletişim: Yorum Dili, Conventional Comments, Anlaşmazlık ve Review Yorgunluğu

Mert Özen 14 Kas 2026 12 dk 16 görüntülenme
Code Review'da İletişim: Yorum Dili, Conventional Comments, Anlaşmazlık ve Review Yorgunluğu

İyi bir review yorumu kişiye değil koda hitap eder, gerekçesini söyler ve merge'i engelleyip engellemediğini açıkça belirtir; Conventional Comments bunu issue, suggestion, question, praise gibi etiketler ve blocking/non-blocking dekoratörleriyle yapar. Yazı kupon PR'ındaki yorumları bu biçimde yeniden yazıyor, nit'in dozunu, yazarın cevabını, anlaşmazlıkta izlenecek yolu ve review yorgunluğunu anlatıyor.

Sürüm notu: Bu yazı kavramsaldır; Conventional Comments'in conventionalcomments.org'daki Kasım 2026 hâli ve Google Engineering Practices'in review rehberi esas alınmıştır. Örnek yorumlar bir önceki yazıdaki gerçek kupon pull request'i (PR) üzerinedir.

Önce Tahmin Et

Bir önceki yazıda Ayşe, sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) kupon PR'ında bir N+1 sorunu buldu: indirim hesaplanırken sepetteki her kitap için veritabanına ayrı bir sorgu gidiyordu. Aynı bulguyu iki farklı biçimde yazdığını düşün.

Birinci yorum: "Döngüde sorgu atılır mı ya? Bunu daha önce de konuşmuştuk."

İkinci yorum: "issue (blocking): CalculateDiscountAsync her satır için Books'a ayrı sorgu atıyor; beş kitaplık sepette GET isteği sekiz SQL komutu üretiyor (log'u PR'a ekledim). Kategori bilgisi GetByIdsAsync ile zaten geliyor; BookInfo'ya CategoryId eklersek hem sorgu kalkar hem Catalog'un tablosuna doğrudan erişim. Sen ne dersin?"

İki yorum da aynı satırı gösteriyor. Hangisi daha hızlı düzeltilir? Hangisinin altında üç mesajlık bir tartışma başlar? Ve daha zor soru: Emre ilk yorumu okuduktan sonra bir sonraki PR'ını Ayşe'ye mi gönderir, yoksa başka bir reviewer mı arar?

Koda Yorum, Kişiye Değil

Review yazılı bir konuşma. Yüz yüze konuşmada ses tonu ve yüz ifadesi cümleyi yumuşatır; yazıda bunların hiçbiri yok ve okuyan kişi boşluğu kendi o günkü ruh hâliyle doldurur. Bu yüzden review yorumlarında birkaç alışkanlık, yorumun içeriği kadar önemli.

Koddan bahset, kişiden değil. "Burada hata yapmışsın" yerine "bu method her satırda sorgu atıyor". Google'ın rehberi aynı şeyi önerir: geliştiricinin neden öyle yaptığını sormak yerine kodun ne yaptığını ve bunun neden sorun olduğunu anlat. "Neden böyle yaptın?" sorusu yazıda çoğu zaman bir suçlama gibi okunur.

Gerekçe ver. "Bunu değiştir" bir emir; "bunu değiştir, çünkü sipariş transaction'ının içinde de çalışıyor" bir bilgi. Gerekçe, yazarın bir dahaki sefere aynı hatayı kendi başına fark etmesini sağlar. Gerekçe bir doküman ya da bir ölçümse linkini ya da çıktısını ekle; Ayşe'nin sekiz SQL komutunu gösteren log'u, tartışılabilir bir görüşü tartışılmaz bir veriye çevirdi.

Emin değilsen soru sor, ama gerçek bir soru sor. "Burada eski sürüm migration'dan sonra çalışırsa ne olur?" iyi bir soru, çünkü cevabını Ayşe de tam bilmiyor. "Bunun yanlış olduğunu biliyorsun, değil mi?" ise soru kılığında bir eleştiri.

Çözüm önerirken kararı yazara bırakabilirsin. Google'ın rehberi, düzeltmenin sorumluluğunun yazarda olduğunu, reviewer'ın çözümü tasarlamak zorunda olmadığını ama yardımcı olması gerektiğini söyler; bazen sorunu göstermek yazara daha çok şey öğretir, bazen doğrudan bir kod önerisi zaman kazandırır.

İyi olanı da söyle. Ayşe'nin ApplyCouponRequest'teki StringLength(32)'nin veritabanı kolonuyla aynı olduğunu not etmesi küçük bir şey, ama Emre'ye bu özenin fark edildiğini ve beklendiğini söylüyor. Övgü, gerekçesiyle yazıldığında bir sonraki PR'da tekrarlanan bir davranışa dönüşür.

Aynı yorumların zayıf ve güçlü hâlleri, Türkçe konuşan bir ekipte; son satırdaki LGTM (looks good to me; "bana uygun görünüyor" anlamındaki onay kısaltması) dahil:

ZayıfDaha iyi
Bu yanlış.Tutar kuponu uygun tutardan büyük olunca toplam eksiye düşüyor; 19,90'lık sepette WELCOME25 ile denedim.
Neden DateTime.Now kullandın?DateTime.Now yerel saat dönüyor, kupon saatleri UTC (eşgüdümlü evrensel saat); İstanbul'da kupon saat 21'den sonra geçersiz görünüyor. TimeProvider'ı kullanabilir miyiz?
Bu testi baştan yaz.Test beklenen değeri implementation'daki formülle hesaplıyor; formül yanlışsa test de yeşil kalır. 48,90 gibi elle hesaplanmış bir değer yazalım mı?
Ben olsam böyle yazmazdım.Bir alternatif: hesaplamayı saf bir fonksiyona çekersek testte veritabanı gerekmez. Mevcut hâli de çalışıyor, karar senin.
LGTMOnay. Migration'ı kupon öncesi siparişi olan bir veritabanında denedim, backfill doğru çalışıyor.

Son satır önemli. Tek kelimelik bir onay ile neyin kontrol edildiğini söyleyen bir onay aynı düğmeye basılarak verilir, ama ikincisi yazara ve PR'ı ileride okuyacak kişiye neyin güvenilir olduğunu söyler.

Conventional Comments: Yorumun Niyetini Etiketlemek

Bir önceki yazının tuzaklarından biri, N+1'in, stored XSS'in (cross-site scripting; sayfaya zararlı script enjekte edilmesi) ve bir isim önerisinin aynı tonda yazılmasıydı: yazar hangisinin merge'i engellediğini bilemez. Conventional Comments, Paul Slaughter'ın yayımladığı, Creative Commons Attribution 3.0 lisanslı küçük bir standart; her yorumun başına niyetini söyleyen bir etiket koyar:

<label> [decorations]: <subject>

[discussion]

Etiketlerin kısa anlamları, bizim cümlelerimizle:

  • praise: gerçekten iyi bulduğun bir şey; her review'da en az bir tane olması önerilir.
  • nitpick: küçük, zevke bağlı bir istek; doğası gereği engellemez.
  • suggestion: bir iyileştirme önerisi; ne önerdiğini ve neden önerdiğini yazarsın.
  • issue: belirli bir sorun; mümkünse bir öneriyle birlikte.
  • todo: küçük ama yapılması gereken bir iş; nitpick'ten farkı isteğe bağlı olmaması.
  • question: emin olmadığın bir endişe; cevap "sorun yok" da olabilir.
  • thought: review sırasında aklına gelen, engellemeyen bir fikir.
  • chore: kabulden önce yapılması gereken süreç işi (ör. changelog, dokümantasyon).
  • note: okuyanın bilmesi gereken, engellemeyen bir bilgi.

Etiketin yanına parantez içinde bir dekoratör gelebilir: (blocking) bu çözülmeden PR kabul edilmez, (non-blocking) engellemez, (if-minor) değişiklik küçükse yap demek. Spesifikasyon dekoratörleri az kullanmayı öneriyor; bir yoruma üç dört dekoratör yığmak okunabilirliği bozar.

Ayşe'nin kupon PR'ındaki yorumları bu biçimde şöyle görünüyor:

issue (blocking): CalculateDiscountAsync her satır için ayrı sorgu atıyor ve Catalog'un tablosuna doğrudan gidiyor. Beş kitaplık sepette GET sekiz SQL komutu üretiyor. BookInfo'ya CategoryId ekleyip indirimi elimizdeki satırlardan hesaplayalım.

issue (blocking): CouponRules bu PR'da değişmedi ama tutar kuponu uygun tutarı aşabiliyor. Kırmızıya dönen iki testi aşağıya ekledim; Math.Min yeterli görünüyor.

issue (blocking): Kupon açıklamasında bypassSecurityTrustHtml var; açıklama veritabanından geliyor. [innerHTML]'i sanitizer'la kullanırsak <strong> kalıyor, onerror düşüyor. Spec dosyası ekte.

issue: Sipariş log'unda müşterinin e-postası var. Müşteri numarası siparişi bulmaya yetiyor.

suggestion: Kupon testlerinde beklenen değerleri elle hesaplanmış sayılar olarak yazalım; şu an test formülü tekrar ediyor ve yuvarlama biçimi koddan farklı.

question (non-blocking): Deploy sırasında eski sürüm bir süre sipariş alırsa Subtotal sıfır kalır. Bizim deploy'umuzda bu oluyor mu? Olmuyorsa PR açıklamasına bir satır yeter.

praise: ApplyCouponRequest'teki StringLength(32) kolon uzunluğuyla aynı, validation ile şema tutarlı. Güzel.

nitpick (non-blocking): FindValidAsync'teki normalized değişkeni, OrderService'teki normalizedEmail gibi normalizedCode olabilir.

Etiketlerin İngilizce, açıklamanın Türkçe olması bilinçli bir tercih: etiket bir sözleşme, araçların ve ekibin aynı anlamı paylaşması için sabit kalıyor; açıklama ise ekibin konuştuğu dilde. Emre bu listeye bakınca neyin merge'i engellediğini, neyin isteğe bağlı olduğunu ve neyin sadece bir soru olduğunu tek bakışta görüyor.

"nit"in Anlamı ve Dozu

Nit, İngilizcedeki nitpick'in kısaltması: önemsiz bir ayrıntı üzerine yapılan küçük düzeltme isteği. Google'ın rehberi bu tür yorumların başına "Nit:" yazmayı önerir ve iki benzer etiket daha sayar: "Optional" (ya da "Consider") iyi olabilecek ama zorunlu olmayan fikirler için, FYI (for your information; "bilgin olsun" anlamında) bu PR'da beklenmeyen ama gelecekte bilinmesinde fayda olan bilgiler için. Ortak amaç, yazarın her yorumu zorunlu sanmasını önlemek.

Nit'in bir dozu var. Bir review'da bir iki nit makul. On tane nit iki şeyden birini söyler: ya eksik bir araç var (biçim, isim biçimi ya da import sırası gibi şeyler bir formatter'ın ya da analyzer'ın işidir) ya da reviewer kendi zevkini ekibin kuralı gibi uyguluyor. İlk durumda doğru hamle yorum yazmak değil, aracı kurmak; Coding Conventions ve Ekip Standartları serisi bunun içindi. İkinci durumda nit'leri tek bir özet yoruma toplayıp "hepsi isteğe bağlı" demek yazarın yükünü azaltır.

Bir de zamanlaması var. Yeni katılan birinin ilk PR'ına on nit yazmak, ona öğrettiğinden fazlasını korkutur. Google'ın makalesi, şirkete yeni katılanların değişikliklerinin deneyimlilere göre iki kattan fazla yorum aldığını gösteriyor; bu doğal, ama yorumların ağırlıklı olarak bloklayıcı sorunlara ve gerçek öğrenme fırsatlarına ayrılması gerekir.

Yazar Olarak Yorumlara Cevap Vermek

Review'un yarısı yazarın cevabı. Emre'nin her yoruma bir cevap bırakması, Ayşe'nin ikinci turda neyin yapıldığını tahmin etmeye çalışmasını önler. Kısa cevaplar yeterli: "Düzeltildi, 548de4c" ya da "Migration'a backfill ekledim, açıklamaya deploy notunu yazdım". Commit hash'i, reviewer'ın ilgili değişikliği doğrudan açmasını sağlar.

Katılmadığın bir yorum varsa bunu gerekçesiyle söyle ve kararı açık bırak. Google'ın rehberi reviewer'a da aynı açıklığı öneriyor: yazar çoğu zaman kodu reviewer'dan daha iyi bilir; geçerli bir noktası varsa reviewer bunu kabul eder ve konuyu bırakır. Burada önemli olan, anlaşmazlığın kişisel bir mesele değil, iki farklı teknik gerekçe olarak masada durması.

"Sonra düzeltirim" cevabına dikkat. Google'ın deneyimi, sonraya bırakılan temizliklerin çoğunun hiç yapılmadığı yönünde; rehber, temizliğin kod merge edilmeden yapılmasını ya da hemen bir issue açılıp yazarın kendine atamasını öneriyor. Bookstore'da kural basit: "sonra" diyorsan issue numarasını da yaz.

Konuşmayı kim kapatır? GitHub'da her yorum bir conversation olarak "resolve" edilebilir. Bookstore'da düzeltmeyi yapan yazar, düzeltmenin yorumun istediği şey olduğundan eminse conversation'ı kapatır; tartışma içeren yorumları ise yorumu yazan kişi kapatır. Beşinci yazıda göreceğimiz ruleset ayarı, açık conversation varken merge'i engelleyebiliyor; bu da kimin neyi kapattığının önemini artırıyor.

Anlaşmazlıkta İzlenecek Yol

Emre, BookInfo'ya CategoryId eklenmesine itiraz ediyor: Ordering module'ünün kategori kavramını bilmesini istemiyor; ileride Catalog ayrı bir servise taşınırsa bu alan bir bağımlılık daha olacak. Ayşe ise Ordering'in Catalog'un tablosuna doğrudan gitmesinin daha büyük bir bağımlılık olduğunu düşünüyor. İki gerekçe de makul. Bu durumda izlenecek yol sırayla şöyle.

Önce veri ve doküman. Teknik bir gerçek ya da bir ölçüm, görüşten ağır basar. Sorgu sayısı ölçülmüş durumda; module sınırı kuralı CatalogService'in yorumunda yazılı. Stil konusunda son söz ekibin style guide'ıdır; guide bir şey söylemiyorsa, mevcut kodun tutarlılığı ya da yazarın tercihi geçerli olur. İki tasarım eşit derecede geçerliyse Google'ın rehberi yazarın tercihini kabul etmeyi öneriyor.

Sonra senkron konuşma. Üç mesajdan uzun süren bir yazılı tartışma, on beş dakikalık bir görüşmeyle genelde biter. Burada kural, sonucun PR'a yazılması: "Görüştük, CategoryId'yi BookInfo'ya ekliyoruz; Catalog ayrıldığında contract'ta zaten olacak bir alan." Toplantıda verilip kayda geçmeyen karar, altı ay sonra yeniden tartışılır.

Sonra karar sahibi. İki kişi anlaşamazsa kararı kimin vereceği önceden bellidir: Bookstore'da tech lead, yoksa module'ün sahibi. Bu bir yenilgi değil; PR'ın anlaşmazlık yüzünden takılı kalmaması için konmuş bir kural.

Ve gerekirse ADR (Architecture Decision Record; mimari bir kararı bağlamı ve sonuçlarıyla kaydeden kısa doküman). Anlaşmazlık bu PR'ı aşıyorsa, yani "module'ler birbirinin verisini hangi yoldan okur?" gibi genel bir soruya dönüşüyorsa, PR'da çözülmez; kısa bir karar kaydı yazılır ve PR o karara göre düzeltilir. Nygard template'iyle ve MADR (Markdown Architectural Decision Records; Markdown tabanlı ADR formatı) ile nasıl yazılacağı Architecture Decision'lar serisinin "ADR Yazmak: Nygard Şablonu, MADR 4.0 ve Y-Statement ile Karar Kaydı" yazısında, kararların ekipte nasıl yaşatıldığı aynı serinin "Kararları Yaşatmak: Decision Log, Architecture Review ve Advice Process" yazısında.

Review Yorgunluğu ve Darboğaz

Bookstore'da iki geliştirici var; Emre'nin her PR'ı Ayşe'ye, Ayşe'ninkiler Emre'ye gidiyor. Ekip büyüdüğünde en sık görülen tablo farklı: review'ların yarısından fazlası aynı bir iki kişide toplanır, genelde kodu en iyi bilen senior'larda. Bu kişiler gününün yarısını review'a ayırır, kendi işleri gecikir, review'ları da yüzeyselleşir. Diğerleri bekler.

Yorgunluğun ilk belirtisi bekleme süresi. Bir iş günü hedefi tutmamaya başladıysa ve bekleyen PR'lar hep aynı kişide duruyorsa, darboğaz o kişidir. İkinci belirti LGTM refleksi: onay sayısı artar, yorum sayısı düşer, büyük PR'lar dakikalar içinde onaylanır. Bacchelli ve Bird'ün mülakatlarında geçen şikâyet tam bu: review'u yapılmış saymak için kolay bulunan şeylere bakmak.

İşe yarayan önlemler basit. Review için gün içinde iki sabit zaman aralığı ayırmak, odaklanmış çalışmayı bölmeden bir iş günü hedefini tutturur. Reviewer'ı rotasyonla seçmek yükü dağıtır ve kod bilgisini yayar; GitHub'da ekip için otomatik review ataması bunu yapabiliyor, beşinci yazıda ayarına bakacağız. Bir kişinin aynı anda açık tuttuğu PR sayısına sınır koymak, review edilmeyi bekleyen iş yığınını küçültür. Ve en etkilisi, önceki yazının konusu: küçük PR. 80 satırlık bir PR on dakikada okunur; 600 satırlık PR akşama kalır.

Junior'ın senior kodunu review etmesi de bu yükü dağıtmanın bir yolu, ama başka bir kazancı daha var. Junior'ın "bu kısmı anlamadım" yorumu değerli bir bulgu: codebase'i en yeni bilen kişi anlamıyorsa, altı ay sonra kodu açan herkes de zorlanacak. Google'ın rehberi de review'da yazılan açıklamaların gelecekteki okuyucuya bir şey kazandırmadığını, anlaşılmayan kodun genelde daha açık hâle getirilmesi gerektiğini söyler. Senior'ın burada yapacağı şey açıklamayı yorumda bırakmak değil, kodu ya da ismi değiştirmek.

Üretimde Dikkat

Farklı saat dilimlerinde ya da farklı saatlerde çalışan ekiplerde her review turu bir günlük gecikme demek. Burada iki alışkanlık işe yarar: yorumların hepsini tek seferde göndermek (GitHub'da "Start a review" ile yorumları biriktirip tek bildirimle göndermek) ve küçük kalan yorumlarla onay vermek. Yazar düzeltmeleri yapar ve ikinci tur beklemeden merge eder.

Ekip dili de bir karar. Türkçe konuşan bir ekipte review yorumlarını Türkçe yazmak doğal ve hızlıdır; açık kaynağa ya da yabancı bir ekiple ortak bir repo'ya katkıda İngilizce gerekir. Önemli olan, kod ve commit mesajları için seçilen dille yorum dilinin karışmaması ve etiketlerin her durumda sabit kalması.

Review'larda yapay zekâ araçlarının yazdığı yorumlar da var artık. Onların tonunu ve yerini AI Destekli Geliştirme serisinin "AI'ın Yazdığı Kodu Review Etmek ve Doğrulamak: Tipik Hatalar, Checklist ve AI Reviewer'lar" yazısında konuştuk; burada tek not şu: bir bot yorumunu kabul ya da reddetmek de bir cevap gerektirir, sessizce bırakılan bot yorumları insan yorumlarını görünmez hâle getirir.

Sık Düşülen Tuzaklar

Sessiz onay. "LGTM" yazıp diff'i açmadan onaylamak, review sözleşmesini kâğıt üzerinde bırakır. Bir onay neyin kontrol edildiğini en azından bir cümleyle söylemeli.

Kişisel stil tercihini dayatmak. "Ben LINQ (Language Integrated Query; C#'ın sorgu ifadeleri) yerine döngüyü tercih ederim" bir tercih, bir kural değil. Ekibin guide'ında yoksa ve kod sağlığını kötüleştirmiyorsa, yorum en fazla etiketli bir nitpick olur.

Alay ve abartı. "Harika, yine N+1" cümlesi yazının içinde ironiyi taşımaz ama kırgınlığı taşır. Ciddi bir sorun ciddi ve sade bir cümleyle yazılır.

Bütün yorumları bloklayıcı saymak. Etiketsiz yirmi yorum, yazara yirmi zorunlu iş gibi görünür. Neyin engellediğini açıkça yazmak, hem yazarın hem reviewer'ın zamanını korur.

Yazılı tartışmayı uzatmak. Aynı konuda üçüncü mesaja gelindiyse konuşma senkron devam etmeli ve sonucu PR'a yazılmalı.

Küçük Bir Deneme

★☆☆ Son yazdığın beş review yorumunu tekrar oku. Kaçı kişiye, kaçı koda hitap ediyor? Kaçında gerekçe var? Gerekçesiz olanları gerekçeli olarak yeniden yaz.

★★☆ Bir sonraki review'unda bütün yorumları Conventional Comments etiketleriyle yaz. Yazar, hangi yorumların merge'i engellediğini sormadan anlayabildi mi? Review'un sonunda en az bir praise yazdın mı?

★★★ Ekibinin son bir aydaki PR'larında reviewer dağılımını çıkar: her kişi kaç review yaptı, ilk review'a kadar ortalama kaç saat geçti? Yük bir iki kişide toplanıyorsa, rotasyon ve review saatleri için ekibe bir öneri yaz; önerini de bir PR olarak açıp review'a sun.

Yorumların dili ve anlaşmazlık yönetimi insanın işi. Ama review sürecinin bir kısmı, kimin hangi dosyayı review edeceği, kaç onay gerektiği, açık yorum varken merge'in engellenmesi, platformun ayarlarıyla kurulur. Serinin son yazısında GitHub'a geçiyoruz: CODEOWNERS, rulesets, suggested changes, merge queue, bot'lar ve review sürecini ölçmek.