İçeriğe geç

Temiz Kod — İsimler, Fonksiyonlar, Yorumlar

Başlangıç 9 dk Çok sık karşılaşılır

30 saniyede özet

Temiz kod bir zevk meselesi değil, bir zaman meselesi. Kod bir kez yazılır, onlarca kez okunur, ve okuyan çoğu zaman altı ay sonraki sensin.

Birinin yazdığı kodu açtın ve ne yaptığını anlamak yarım saatini aldı. “Temiz kod” dediğimiz şeyin ölçüsü tam olarak bu: anlamak ne kadar sürüyor?

Aynı iş, iki isim seçimi: fark okuyanın saatinde çıkar.
Adım adım oku
  1. Kod bir kez yazılır.
  2. Ama ekipteki herkes tarafından, aylar boyunca defalarca okunur.
  3. d, x, getData gibi belirsiz isimler her okuyanı tanıma geri döndürür; her okuma uzar.
  4. daysSinceLastLogin, findUsersActiveSince gibi isimler ne yaptığını söyler; kod bir bakışta anlaşılır.
  1. Bayt: Bir arkadaşımın kodunu açtım, ne yaptığını anlamak yarım saatimi aldı.

  2. Sen: Kod çalışıyor muydu peki?

  3. Bayt: Kusursuz çalışıyordu. Sorun okumaktı: d, x, getData...

  4. Bayt: Temiz kodun ölçüsü güzellik değil, anlamanın ne kadar sürdüğü!

İsim, en ucuz dokümantasyondur

Bir isim iki soruya cevap verebilir: ne yapıyor, ve nasıl yapıyor. İkincisini söyleyen isimler, uygulamayı değiştirdiğin anda yalana dönüşür.

İsim ne kadar çalışıyor?
// ne anlattığı belirsiz
List<User> getData(int x)
// nasıl yaptığını anlatıyor — uygulama değişince yalan olur
List<User> getUsersWithHashMapCache(int days)
// ne yaptığını anlatıyor
List<User> findUsersActiveSince(int days)

Kısalık bir erdem değil. d yazmak daysSinceLastLogin yazmaktan hızlıdır ama okuyan her seferinde tanıma geri dönmek zorunda kalır.

Kafam karıştı, daha basit anlat

İyi bir isim, kutunun üstündeki etiket gibidir: içini açmadan ne olduğunu söyler. “Nasıl” yapıldığını anlatan etiket ise tarif değişince yalan söylemeye başlar.

Hızlı kontrolBaşlangıç

Hangi isim daha iyidir ve neden?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Satır satır: bir fonksiyonu okunur hâle getirmek

Aşağıdaki fonksiyon çalışıyor. Sorun doğruluğu değil, bir şeyi anlamak için tamamını okumak zorunda olman.

Bu fonksiyonun ne yaptığını anlaman kaç saniye sürüyor? Hangi satırda kaybettin? Cevabı göster

Çoğu okuyucu üçüncü if’te kaybeder. Sebep karmaşıklık değil — üç farklı soyutlama seviyesiKodun ne kadar "ne yapılacak" ne kadar "nasıl yapılacak" anlattığı. Bir fonksiyonda ikisi karışırsa okuyucu iki düzeyi aynı anda takip etmek zorunda kalır.Sözlükte gör →nin (döngü, iş kuralı, biçimlendirme) aynı fonksiyonda olması.

Üç seviye, tek fonksiyon

Rapor.java
1String rapor(List<Order> orders) {
2 StringBuilder sb = new StringBuilder();
şu an çalışan satır for (Order o : orders) {
4 if (o.getStatus() == Status.CANCELLED) continue;
5 if (o.getTotal() > 1000) {
6 sb.append("VIP ");
7 }
8 sb.append(o.getId()).append(": ")
9 .append(String.format("%.2f", o.getTotal()))
10 .append("\n");
11 }
12 return sb.toString();
13}

Debug

Adım 1/6

okuyucu Birinci seviye: bir koleksiyon üzerinde geziliyor. Buraya kadar sorun yok.

seviye
= yineleme
Java 21UTF-8LF3:1

Sol/sağ ok tuşlarıyla da gezebilirsin.

Buradaki kural tek soyutlama seviyesi: bir fonksiyon ya ne yapılacağını anlatır (“raporu hazırla”), ya da nasıl yapılacağını (“şu listeyi dön”), ikisini birden değil.

Bu kuralı bir bankanın ay sonu hesap işletim ücretinde görelim. Kimden ücret alınacağı, ne kadar alınacağı ve ekstreye nasıl yazılacağı burada ayrı, adı olan parçalarda duruyor.

Derinleş · Ay sonu hesap işletim ücreti: okunur hâle getirmek 4 dosya · ~78 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/com/bank/fee/ MaintenanceFeeJob.java Ana akış yalnızca ne yapıldığını söyler; detay, adı olan metotlarda.

src/main/java/com/bank/fee/MaintenanceFeeJob.java
@Service
public class MaintenanceFeeJob {
private final MaintenanceFeeProperties rules;
private final FeeLedger ledger;
private final StatementLineFormatter lines;
MaintenanceFeeJob(MaintenanceFeeProperties rules, FeeLedger ledger, StatementLineFormatter lines) {
this.rules = rules;
this.ledger = ledger;
this.lines = lines;
}
// One level only: the flow. Each step is a name you can jump to.
public void chargeMonthlyFees(List<Account> accounts, YearMonth month) {
for (Account account : accounts) {
if (isExemptFromFee(account, month)) continue;
Money fee = rules.monthlyFee();
Money tax = bsmvOn(fee);
ledger.debit(account.iban(), fee.plus(tax), lines.maintenanceFee(month, fee, tax));
}
}
private boolean isExemptFromFee(Account account, YearMonth month) {
return account.isClosed()
|| account.receivesSalaryDeposit()
|| account.averageBalance(month).isAtLeast(rules.feeWaiverBalance());
}
private Money bsmvOn(Money fee) {
return fee.times(rules.bsmvRate()).roundedTo(2, RoundingMode.HALF_UP);
}
}

src/main/java/com/bank/fee/ MaintenanceFeeProperties.java Sihirli sayılar yerine adı olan ayarlar.

src/main/java/com/bank/fee/MaintenanceFeeProperties.java
// Bound from application.yml, e.g. bank.fees.maintenance.fee-waiver-balance.
@ConfigurationProperties(prefix = "bank.fees.maintenance")
public record MaintenanceFeeProperties(
Money monthlyFee,
Money feeWaiverBalance, // average monthly balance at which the fee is waived
BigDecimal bsmvRate) { // banking and insurance transactions tax on fees
}

src/main/java/com/bank/fee/ StatementLineFormatter.java Ekstre satırını yazmak ayrı bir iş; kalan tek yorum nedenini anlatıyor.

src/main/java/com/bank/fee/StatementLineFormatter.java
@Component
public class StatementLineFormatter {
private static final DateTimeFormatter PERIOD = DateTimeFormatter.ofPattern("MM/yyyy");
public StatementLine maintenanceFee(YearMonth month, Money fee, Money tax) {
// The tax goes in the description, not a separate line: the fee disclosure
// shown to the customer quotes fee and tax together, and the two must match.
String text = "Hesap işletim ücreti " + month.format(PERIOD)
+ " (ücret " + fee + ", BSMV " + tax + ")";
return new StatementLine(text, fee.plus(tax));
}
}

src/main/java/com/bank/fee/ FeeUtil.java Şöyle de yazılabilirdi: aynı iş tek fonksiyonda. Çalışır, ama bak okumak ne kadar uzuyor.

src/main/java/com/bank/fee/FeeUtil.java
// The same job in one method: same output, three levels to hold in your head.
public class FeeUtil {
private final LedgerDao ledgerDao;
FeeUtil(LedgerDao ledgerDao) {
this.ledgerDao = ledgerDao;
}
public void calc(List<Account> l, YearMonth m) {
for (Account a : l) {
// skip closed accounts
if (a.isClosed()) continue;
// skip salary customers
if (a.receivesSalaryDeposit()) continue;
// skip if balance is high
if (a.averageBalance(m).value().compareTo(new BigDecimal("50000")) >= 0) continue;
BigDecimal f = new BigDecimal("25.00");
BigDecimal t = f.multiply(new BigDecimal("0.05")).setScale(2, RoundingMode.HALF_UP);
String s = "Hesap işletim ücreti " + m.getMonthValue() + "/" + m.getYear()
+ " (ücret " + f + ", BSMV " + t + ")";
ledgerDao.insert(a.iban(), f.add(t).negate(), s); // currency? nobody knows
}
}
}
Kafam karıştı, daha basit anlat

Bir fonksiyonda hem döngü, hem iş kuralı, hem de biçimlendirme varsa okuyan kaybolur. Her birini adı olan ayrı bir küçük fonksiyona ayır.

Hızlı kontrolOrta

"Tek soyutlama seviyesi" kuralı ne demek?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Koddaki `if (o.getTotal() > 1000)` satırının sorunu nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Simülatör bir fonksiyonu adım adım refactor ediyor ve her adımda iki şeyi ölçüyor: iç içe geçme derinliği ve en uzun fonksiyonun satır sayısı.

Aynı fonksiyon, dört refactor

Tohum 3
  1. Başlangıç
  2. Guard clause
  3. Fonksiyon çıkarıldı
  4. Aşırıya kaçırıldı
Fonksiyon
1
En uzun fonksiyon
14 / 20
İç içe derinlik
3 / 2
Akışı izlemek için bakılacak yer
0

Lint sınırlarına takılıyor

Hız
Adım 0

Şu an ne oldu?

Tek fonksiyon, üç seviye iç içe

14 satır ve 3 seviye derinlik. Okuyucu neyi elediğini, neyi işaretlediğini ve nasıl yazdığını aynı anda takip etmek zorunda. Lint sınırlarına takılıyor.

Aklında kalsın: Sorun satır sayısı değil, tek fonksiyonda birden çok soyutlama seviyesinin olması.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Baştan adımla. Tek fonksiyon, derinlik 3, 14 satır.
  2. Guard clause'a çevir. guard clauseİstisnai durumu başta eleyip erken dönen koşul. `else` merdivenini siler; satır kazandırmaz ama iç içe geçmeyi kaldırır.Sözlükte gör →, else merdivenini siliyor — derinlik düşüyor, satır sayısı neredeyse aynı.
  3. Fonksiyon çıkar. Satır sayısı bölünüyor ama toplam kod artıyor. Takas bu: daha çok isim, daha az anda tutulacak şey.
  4. Aşırıya kaçır. Her şeyi tek satırlık fonksiyonlara böl. Ölçüler “iyi” görünüyor ama akışı takip etmek zorlaştı — temizlik de abartılabilir.
Hızlı kontrolOrta

Statik analiz aracı bir fonksiyona "karmaşıklık: 3, iyi" diyor. Bu kod temiz midir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Yorum, çoğu zaman bir özürdür

Yorumların büyük kısmı, yazılamamış bir ismin yerine geçer:

Yorumu silen refactor
// kullanıcı son 30 gündür giriş yapmadıysa pasif sayılır
if (u.getLastLogin().isBefore(now.minusDays(30))) { ... }
// yorum artık gereksiz — isim onu söylüyor
if (pasifSayilir(u)) { ... }

Ama bazı yorumlar silinemez, çünkü kodda olmayan bir bilgi taşırlar:

Yorum türüKalmalı mıNeden
Neden böyle yapıldı✅“Bu sıra önemli: API başlığı gövdeden önce okumak zorunda.” Koda yazılamaz.
Dış dünyaya referans✅“Bkz. RFC 9457 §3” veya “Satıcı API’si burada 200 ile hata dönüyor.”
Uyarı✅“Bu metot thread-safe değildir.”
Ne yaptığını anlatan❌İsim düzeltilmeli.
Kapalı kod❌Git zaten hatırlıyor.
Kafam karıştı, daha basit anlat

Bir yorum “bu kod şunu yapıyor” diyorsa, çoğu zaman kodun kendisi daha iyi bir isimle aynı şeyi söyleyebilir. Yorumu, kodun anlatamadığı “neden” için sakla.

Hızlı kontrolBaşlangıç

Her yorumu, kalması mı yoksa kodun kendisiyle değiştirilmesi mi gerektiğine göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Kalmalı

Kodda ifade edilemeyen bilgi taşıyor

    Kod düzeltilmeli

    Yorumun söylediğini isim söyleyebilir

      Tuzaklar

      Temizlik de abartılabilir. Üç satırlık bir fonksiyonu üç fonksiyona bölmek okuma maliyetini düşürmez, artırır: artık akışı takip etmek için dosyada zıplaman gerekiyor. Ölçüt her zaman aynı — anlamak kolaylaştı mı?

      Sayı kuralları körü körüne uygulanmaz. “Fonksiyonlar 20 satırı geçmesin” gibi sayılar bir hedef değil, “buraya bir bak” diyen bir alarmdır. 30 satırlık düz bir switch, 8 satırlık üç seviye iç içe koşuldan okunaklıdır.

      erken soyutlamaHenüz bilinmeyen bir gereksinime göre genelleştirme yapmak. Yanlış eksende açılmış bir soyutlama, hiç açılmamış olmasından pahalıdır.Sözlükte gör →. Tek kullanımı olan bir şeyi genelleştirmek, henüz bilmediğin bir gereksinime göre tasarlamaktır. İkinci kullanım geldiğinde soyutlama neredeyse her zaman yanlış yerden çıkar.

      Kendini sına

      Şimşek turu1/5

      İyi bir isim, bir şeyin nasıl yapıldığını değil ne yaptığını söyler.

      Soru 1/3Orta

      Bu kod ne yazdırır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
      YanlisIsim.java
      1class UserService {
      2 private int cagriSayisi = 0;
      3
      4 User getUser(String id) {
      5 cagriSayisi++; // sayac da artiyor
      6 return repo.findById(id);
      7 }
      8
      9 int getCagriSayisi() { return cagriSayisi; }
      10}
      11
      12service.getUser("a");
      13service.getUser("a");
      14System.out.println(service.getCagriSayisi());
      Java 21UTF-8LF

      Aklında kalacak üç şey

      1. 1 Temiz kodun ölçüsü güzellik değil, okuma süresidir: kod bir kez yazılır, onlarca kez okunur.
      2. 2 İyi bir isim ne yaptığını söyler, nasıl yaptığını değil. getUserData bir şey anlatmaz; findActiveUsersByEmail anlatır.
      3. 3 Yorumların çoğu, bulunamamış iyi bir ismin özrüdür. Kodun ne yaptığını isimle anlat; yorumu neden öyle yaptığın için sakla.
      Sonraki kapı Tek bir sınıfa dokundun ve alakasız üç özellik birden bozuldu. Neden? SOLID — Beş Harf, Tek Soru · 10 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.