Clean Code: Kodunu Okunabilir ve Sürdürülebilir Yapmanın Pratik Yolları
Kodunu yazdın, 6 ay sonra geri döndüğünde ne anladığını hatırlıyor musun?
Şu senaryoyu yaşadın mı hiç? Bir projeye başlıyorsun, her şey harika gidiyor. 6 ay sonra bir bug fix için geri dönüyorsun ve kendi yazdığın kodu anlamak için saatler harcıyorsun. İşte clean code tam bu noktada hayat kurtarıyor. Clean code sadece 'güzel görünen kod' değil, okunabilir, anlaşılır ve sürdürülebilir kod yazma sanatıdır.
Clean Code Nedir ve Neden Önemli?
Clean code, Robert C. Martin'in (Uncle Bob) ortaya attığı bir kavram. Temel felsefe şu: Kodunuzu yazarken, sadece bilgisayarın anlayacağı şekilde değil, insanların da anlayacağı şekilde yazın. Çünkü yazılım geliştirme hayat döngüsünün %80'i kod okumak ve anlamakla geçiyor.
Gerçek bir örnek vereyim. Bir fintech startup'ında çalışıyordum. Payment processing modülü o kadar karmaşıktı ki, yeni bir developer'ın sistemi anlaması 2 hafta sürüyordu. Code review'lar saatler alıyor, basit değişiklikler beklenmedik sonuçlar doğuruyordu. Clean code prensiplerini uyguladıktan sonra, aynı modülde yeni feature'lar eklemek günlerden saatlere indi.
İsimlendirme: Kodun İlk İzlenimi
Değişken, fonksiyon ve class isimleri kodunuzun ilk izlenimidir. Kötü isimlendirme, kod okumayı İstanbul trafiğinde araba kullanmaya benzetir.
Kötü örnek:
def p(u):
return u * 1.18
İyi örnek:
def calculate_price_with_tax(unit_price: float) -> float:
TAX_RATE = 1.18
return unit_price * TAX_RATE
İsimlendirme kuralları:
- Değişken isimleri ne yaptığını açıkça anlatmalı
- Boolean değişkenler 'is_', 'has_', 'can_' ile başlamalı
- Fonksiyon isimleri fiil + nesne formatında olmalı
- Magic number'ları sabit olarak tanımla
Fonksiyonlar: Tek İş Yap, İyi Yap
Fonksiyonlar clean code'un temel taşıdır. Single Responsibility Principle (SRP) burada devreye giriyor. Bir fonksiyon sadece bir iş yapmalı ve onu iyi yapmalı.
Kötü örnek - God function:
def process_user_data(user_data):
# Validation
if not user_data.get('email') or '@' not in user_data['email']:
raise ValueError("Invalid email")
# Database operation
user = User.create(**user_data)
# Email sending
send_welcome_email(user.email)
# Analytics
track_user_signup(user.id)
return user
İyi örnek - Bölünmüş fonksiyonlar:
def validate_user_data(user_data: dict) -> bool:
required_fields = ['email', 'name', 'password']
return all(field in user_data for field in required_fields)
def create_user_in_db(user_data: dict) -> User:
return User.create(**user_data)
def send_welcome_notifications(user: User) -> None:
send_welcome_email(user.email)
track_user_signup(user.id)
def register_user(user_data: dict) -> User:
if not validate_user_data(user_data):
raise ValueError("Invalid user data")
user = create_user_in_db(user_data)
send_welcome_notifications(user)
return user
Fonksiyon yazarken şu kuralları takip et:
- Fonksiyon 20 satırdan uzun olmamalı
- Maximum 3 parametre almalı
- Side effect'leri minimumda tut
- Pure function'lar yazmaya çalış
Comment Yerine Self-Documenting Code
Çoğu developer comment'leri sever ama clean code felsefesi şunu söyler: Kodun kendisi kendini açıklamalı. Eğer comment yazman gerekiyorsa, muhtemelen kodun yeterince açık değil.
Kötü örnek:
# Check if user is eligible for discount
if u.age > 65 and u.income < 50000:
d = 0.1
İyi örnek:
def is_eligible_for_senior_discount(user: User) -> bool:
SENIOR_AGE_THRESHOLD = 65
LOW_INCOME_THRESHOLD = 50000
return (user.age > SENIOR_AGE_THRESHOLD and
user.income < LOW_INCOME_THRESHOLD)
if is_eligible_for_senior_discount(user):
discount_rate = 0.1
Comment yazman gereken durumlar:
- Complex business logic açıklaması
- Neden belli bir approach seçildiği
- TODO'lar ve FIXME'ler
- Public API documentation
Error Handling: Fail Fast, Fail Clearly
Error handling clean code'un en önemli parçalarından biri. Kullanıcıya ve developer'a net hata mesajları vermek debugging süresini ciddi şekilde kısaltır.
Kötü örnek:
try:
result = process_payment(order)
# ...
except:
print("Error occurred")
İyi örnek:
class PaymentError(Exception):
"""Base exception for payment-related errors"""
pass
class InsufficientFundsError(PaymentError):
"""Raised when user doesn't have enough funds"""
pass
class PaymentProcessingError(PaymentError):
"""Raised when payment processor fails"""
pass
def process_payment(order: Order) -> PaymentResult:
if not order.user.has_sufficient_funds(order.total_amount):
raise InsufficientFundsError(
f"User {order.user.id} has insufficient funds for amount {order.total_amount}"
)
try:
return payment_gateway.charge(order)
except PaymentGatewayError as e:
raise PaymentProcessingError(
f"Payment gateway error for order {order.id}: {str(e)}"
) from e
Testability: Clean Code ve Unit Testing
Clean code yazmak unit test yazmayı kolaylaştırır. Test edilebilir kod yazmak için dependency injection ve interface segregation prensiplerini kullan.
# Tightly coupled - test etmesi zor
class OrderProcessor:
def __init__(self):
self.email_service = EmailService()
self.database = Database()
def process_order(self, order):
# ... complex logic
self.email_service.send_confirmation(order.user.email)
self.database.save_order(order)
# Loose coupling - test etmesi kolay
class OrderProcessor:
def __init__(self, email_service: EmailService, database: Database):
self.email_service = email_service
self.database = database
def process_order(self, order: Order) -> None:
# ... logic
self.email_service.send_confirmation(order.user.email)
self.database.save_order(order)
# Test
class MockEmailService:
def send_confirmation(self, email):
pass
class MockDatabase:
def save_order(self, order):
pass
def test_order_processor():
email_service = MockEmailService()
database = MockDatabase()
processor = OrderProcessor(email_service, database)
# Test logic here
Code Smells ve Refactoring
Code smell'ler kötü kodun erken belirtileridir. Bunları tanımayı öğren ve düzenli olarak refactor et.
Sık karşılaşılan code smell'ler:
- Long Method: Çok uzun fonksiyonlar
- Large Class: Çok fazla responsibility'si olan class'lar
- Duplicate Code: Aynı kodu birden fazla yerde kullanmak
- Feature Envy: Bir method'un başka class'ın data'sıyla çok fazla işi olması
- Primitive Obsession: Her şeyi primitive type'larla temsil etmek
Refactoring örneği - Primitive Obsession:
# Before
class Order:
def __init__(self, amount: float, currency: str):
self.amount = amount
self.currency = currency
# After
class Money:
def __init__(self, amount: float, currency: str):
self.amount = amount
self.currency = currency
def add(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("Currencies don't match")
return Money(self.amount + other.amount, self.currency)
class Order:
def __init__(self, total: Money):
self.total = total
Pratik Alışkanlıklar ve Araçlar
Clean code yazmak bir alışkanlık meselesi. İşte günlük workflow'una ekleyebileceğin pratik adımlar:
1. Linter ve Formatter Kullan:
- Python: black, flake8, pylint
- JavaScript/TypeScript: ESLint, Prettier
- Git pre-commit hook'ları kur
2. Code Review Kültürü Geliştir:
- Her PR'da clean code aspect'lerine bak
- Constructive feedback ver
- Boy scout rule: Code'u check-in ettiğinde daha temiz bırak
3. Refactoring Zamanı Ayır:
- Her sprint'te %10-20 refactoring time planla
- Technical debt'i takip et
- Legacy code'u incremental olarak iyileştir
Sonuç: Clean Code Bir Yatırımdır
Clean code yazmak ilk başta daha fazla zaman alıyormuş gibi görünebilir. Ama uzun vadede bu zamanın kat kat fazlasını kazanırsın. Debugging süresi azalır, yeni feature'lar daha hızlı eklenir, code review'lar daha verimli geçer.
Bugün başla: Mevcut projenden bir dosya seç ve şu adımları uygula:
- İsimlendirmeleri düzelt
- Uzun fonksiyonları böl
- Magic number'ları sabit yap
- Error handling'i iyileştir
- Bir unit test yaz
Unutma: Clean code bir varış noktası değil, bir yolculuktur. Her gün küçük adımlarla daha iyi kod yazabilirsin.