Boolean Parametreler Neden Kod Kokar
Bir fonksiyonu boolean parametre ile çağırdığınızda, çağrı noktasında ne olduğunu anlamak için fonksiyon tanımına bakmanız gerekir. processUser(user, true) gördüğünüzde true neyi ifade ediyor? E-posta gönderilsin mi, veritabanına yazılsın mı, cache'lensin mi? Cevap kod tabanının başka bir yerinde.
Boolean parametre genellikle fonksiyonun içinde if bloğu demektir. Bu da fonksiyonun iki farklı iş yaptığı anlamına gelir. Tek sorumluluk ilkesini ihlal eder, test edilebilirliği düşürür ve her yeni davranış için parametre sayısını artırma baskısı yaratır.
Çağrı noktasındaki belirsizlik
Aşağıdaki iki çağrıyı karşılaştırın:
// Kötü: boolean parametre
saveOrder(order, true);
// İyi: açık method adı
saveOrderAndNotify(order);İlk örnekte true parametresinin anlamını bilmiyorsunuz. Fonksiyon imzasına veya dokümantasyona bakmanız gerekiyor. İkinci örnekte method adı neyin olduğunu söylüyor.
Gerçek bir kod tabanından örnek:
def export_report(report_id, include_details, send_email, compress):
report = fetch_report(report_id)
if include_details:
report = enrich_with_details(report)
output = generate_output(report)
if compress:
output = compress_data(output)
if send_email:
email_report(output)
return output
# Çağrı noktası
export_report(123, True, False, True)Bu çağrıyı okuduğunuzda üç boolean'ın sırasını ve anlamını hatırlamanız imkansız. Kod review'da veya altı ay sonra bu koda döndüğünüzde her seferinde fonksiyon tanımına gideceksiniz.
Fonksiyon iki iş yapıyor
Boolean parametre, fonksiyonun davranışını değiştiren bir switch gibi çalışır. Bu da fonksiyonun birden fazla sorumluluğu olduğunun göstergesidir.
public void processPayment(Payment payment, boolean isRetry) {
if (isRetry) {
logRetryAttempt(payment);
skipFraudCheck(payment);
} else {
runFraudCheck(payment);
}
chargeCard(payment);
if (isRetry) {
updateRetryMetrics();
} else {
updateNormalMetrics();
}
}Bu fonksiyon aslında iki farklı akışı yönetiyor: ilk ödeme ve retry. Test etmek için tüm kombinasyonları düşünmeniz gerekiyor. Yeni bir boolean parametre eklendiğinde (isInternational gibi) karmaşıklık katlanarak artar.
Daha iyi tasarım:
public void processPayment(Payment payment) {
runFraudCheck(payment);
chargeCard(payment);
updateNormalMetrics();
}
public void retryPayment(Payment payment) {
logRetryAttempt(payment);
chargeCard(payment);
updateRetryMetrics();
}Şimdi her fonksiyon tek bir akıştan sorumlu. Test senaryoları net, çağrı noktası okunabilir.
Alternatif yaklaşımlar
1. Ayrı fonksiyonlar
En basit çözüm: boolean'ın her değeri için ayrı fonksiyon.
// Önce
func SaveUser(user User, sendEmail bool) error {
if err := db.Save(user); err != nil {
return err
}
if sendEmail {
return mailer.SendWelcome(user)
}
return nil
}
// Sonra
func SaveUser(user User) error {
return db.Save(user)
}
func SaveUserAndSendWelcome(user User) error {
if err := SaveUser(user); err != nil {
return err
}
return mailer.SendWelcome(user)
}2. Strategy pattern veya enum
Boolean yerine enum kullanmak okunabilirliği artırır:
enum ExportFormat {
JSON,
CSV,
XML
}
// Kötü
exportData(data, true, false); // true ve false ne anlama geliyor?
// Daha iyi ama yine de parametre sayısı fazla
exportData(data, ExportFormat.JSON, Compression.ENABLED);
// En iyi: configuration object
exportData(data, {
format: ExportFormat.JSON,
compression: true,
includeMetadata: false
});Configuration object yaklaşımı ölçeklenebilir. Yeni seçenek eklemek mevcut çağrıları bozmaz ve her alanın adı açıkça görünür.
3. Builder pattern
Karmaşık konfigürasyonlar için builder okunabilirliği maksimize eder:
class ReportExporter {
private var includeDetails = false
private var compress = false
private var emailRecipients: List<String> = emptyList()
fun withDetails(): ReportExporter {
includeDetails = true
return this
}
fun withCompression(): ReportExporter {
compress = true
return this
}
fun sendTo(recipients: List<String>): ReportExporter {
emailRecipients = recipients
return this
}
fun export(reportId: Int): Report {
// export logic
}
}
// Çağrı noktası
ReportExporter()
.withDetails()
.withCompression()
.sendTo(listOf("admin@example.com"))
.export(123)Boolean'ın kabul edilebilir olduğu durumlar
Her kural için istisna vardır. Boolean parametre kabul edilebilir olduğu iki durum:
| Durum | Örnek | Neden kabul edilebilir |
|-------|-------|------------------------|
| Yaygın bilinen konvansiyon | sort(items, reverse=True) | Sıralama yönü evrensel bir konsept |
| Private/internal method | private void validateFields(bool isStrict) | Sadece sınıf içinden çağrılıyor, kapsam dar |
Ama bunlar bile dikkatle kullanılmalı. reverse yerine descending daha açık olabilir. Private method büyüdükçe refactor edilmeli.
Mevcut kodu refactor ederken
Boolean parametreli bir fonksiyonu değiştirirken geriye dönük uyumluluk sorunu çıkar. Yaklaşım:
- Yeni, daha açık fonksiyonları oluştur
- Eski fonksiyonu
@Deprecatedolarak işaretle - Eski implementasyonu yeni fonksiyonları çağıracak şekilde değiştir
- Çağrı noktalarını kademeli olarak güncelle
// Eski
[Obsolete("Use SaveUser or SaveUserWithNotification")]
public void SaveUser(User user, bool notify) {
if (notify) {
SaveUserWithNotification(user);
} else {
SaveUser(user);
}
}
// Yeni
public void SaveUser(User user) {
repository.Save(user);
}
public void SaveUserWithNotification(User user) {
SaveUser(user);
notifier.SendWelcome(user);
}Bu yaklaşım mevcut kodu bozmadan kademeli geçiş sağlar.
Özet
- Boolean parametre çağrı noktasında belirsizlik yaratır;
processUser(user, true)çağrısı tek başına anlamsızdır - Fonksiyonun birden fazla sorumluluk taşıdığının işaretidir; her boolean bir
ifbloğu ve iki farklı akış demektir - Ayrı fonksiyonlar, configuration object veya builder pattern okunabilirliği artırır ve test edilebilirliği iyileştirir
- Mevcut boolean parametreli fonksiyonları deprecate ederek ve yeni alternatifleri sunarak kademeli refactor yapın