Pythonic Kod ve Clean Code
Kod yazmak, bir kitap yazmak gibidir. Teknik olarak çalışan bir kod yazmak, gramer kurallarına uyan bir metin yazmak gibi. Ama güzel, okunabilir, akıcı bir kod yazmak — iyi edebiyat yazmak gibi.
Martin Fowler'ın meşhur sözü var: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." Bilgisayarın anlayacağı kodu herkes yazar. İyi programcı insanın anlayacağı kodu yazar.
Python bu felsefenin en güçlü temsilcisidir. Dil tasarımından itibaren okunabilirlik ön plandadır. Bu derste "Pythonic" kod yazmayı, yaygın anti-pattern'lerden kaçınmayı ve temiz kod prensiplerini öğreneceğiz.
Pythonic Code Nedir?
"Pythonic" terimi, Python topluluğunun bir koda baktığında "bu güzel Python" demesidir. Teknik olarak doğru ama Python ruhuna aykırı koda ise "un-Pythonic" denir.
Basit bir örnek:
# Un-Pythonic (Java/C tarzı)
names = ["ahmet", "mehmet", "ayşe"]
upper_names = []
for i in range(len(names)):
upper_names.append(names[i].upper())
# Pythonic
upper_names = [name.upper() for name in names]İkisi de aynı işi yapar. Ama ikincisi hem daha kısa, hem daha okunabilir, hem de daha hızlı. Python'ın "ruhu" bu.
import this — Zen of Python
Python'un felsefesi bir Easter egg'de gizli. Terminal'e import this yazarsan:
import thisThe Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!Bunları tek tek inceleyelim — en önemlileri:
"Beautiful is better than ugly" — Güzel, çirkinden iyidir
# Çirkin
if (x>0 and x<100 and y>0 and y<100):
pass
# Güzel
if 0 < x < 100 and 0 < y < 100:
passPython zincirleme karşılaştırma destekler. Bunu kullan.
"Explicit is better than implicit" — Açık, kapalıdan iyidir
# Kapalı (ne yaptığı belirsiz)
from os import *
data = open("file.txt").read()
# Açık
from os import path, listdir
with open("file.txt") as f:
data = f.read()import * ne geldiğini bilmezsin. Açıkça import et.
"Readability counts" — Okunabilirlik önemlidir
# Okunması zor
result = {k:v for k,v in sorted(((k,len(list(g))) for k,g in itertools.groupby(sorted(data))),key=lambda x:-x[1])}
# Okunabilir
sorted_data = sorted(data)
grouped = itertools.groupby(sorted_data)
counts = {key: len(list(group)) for key, group in grouped}
result = dict(sorted(counts.items(), key=lambda x: -x[1]))Bir satıra sığdırmak "akıllılık" değil. Okunabilirlik her şeyin önünde gelir.
"Errors should never pass silently" — Hatalar sessizce geçmemeli
# KÖTÜ — hata yutma
try:
value = dangerous_operation()
except:
pass # Hiçbir şey olmasın...
# İYİ — en azından logla
try:
value = dangerous_operation()
except SpecificError as e:
logger.error(f"İşlem başarısız: {e}")
value = default_valueEAFP vs LBYL
Bu iki kısaltma Python dünyasında önemlidir.
LBYL — Look Before You Leap (Atlamadan önce bak)
EAFP — Easier to Ask Forgiveness than Permission (İzin almaktansa affı dilemek daha kolay)
# LBYL — Önce kontrol et, sonra yap
if "key" in my_dict:
value = my_dict["key"]
else:
value = "default"
# EAFP — Yap, sorun olursa yakala (Pythonic!)
try:
value = my_dict["key"]
except KeyError:
value = "default"
# Veya en Pythonic'i:
value = my_dict.get("key", "default")Neden EAFP tercih edilir? Çünkü Python'da exception handling ucuzdur ve race condition riskini azaltır:
# LBYL — Race condition riski!
import os
if os.path.exists("file.txt"): # Kontrol anında var
with open("file.txt") as f: # Ama açarken silinmiş olabilir!
data = f.read()
# EAFP — Daha güvenli
try:
with open("file.txt") as f:
data = f.read()
except FileNotFoundError:
data = ""Kontrol ile kullanım arasında dosya silinebilir. EAFP'de bu sorun yok.
Anti-Patterns — Yapmamanız Gerekenler
1. range(len()) Yerine enumerate()
Bu en yaygın anti-pattern'dir. C/Java'dan gelen alışkanlık:
fruits = ["elma", "armut", "muz"]
# ❌ Anti-pattern
for i in range(len(fruits)):
print(f"{i}: {fruits[i]}")
# ✅ Pythonic
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# Başlangıç indeksi de verebilirsin
for i, fruit in enumerate(fruits, start=1):
print(f"{i}: {fruit}")enumerate hem daha okunabilir, hem index hatası riski yok.
2. dict[key] Yerine dict.get()
config = {"host": "localhost", "port": 5432}
# ❌ KeyError riski
timeout = config["timeout"] # KeyError!
# ✅ Varsayılan değerle güvenli erişim
timeout = config.get("timeout", 30)Eğer key'in var olduğundan eminsen, dict[key] kullan. Emin değilsen, get() kullan.
3. if x == True Yerine if x
is_active = True
items = [1, 2, 3]
name = "Python"
# ❌ Gereksiz karşılaştırma
if is_active == True:
pass
if len(items) > 0:
pass
if name != "":
pass
# ✅ Pythonic
if is_active:
pass
if items: # Boş liste falsy
pass
if name: # Boş string falsy
passPython'da falsy değerler: False, None, 0, "", [], {}, set(), (). Geri kalan her şey truthy.
4. Mutable Default Argument — Sinsi Bug
Bu Python'ın en meşhur tuzaklarından biri:
# ❌ BUG! Mutable default argument
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("elma")) # ['elma']
print(add_item("armut")) # ['elma', 'armut'] WTF?!
print(add_item("muz")) # ['elma', 'armut', 'muz'] NEDEN?!Default liste tüm çağrılar arasında paylaşılır! Fonksiyon tanımlandığında bir kez oluşturulur.
# ✅ Doğru yol
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item("elma")) # ['elma']
print(add_item("armut")) # ['armut'] — Her çağrıda yeni liste⚠️ Dikkat: Bu sadece liste değil, her mutable default argüman için geçerli:
dict,set, custom objeler... Default parametre olarak sadece immutable değerler (None,int,str,tuple) kullan.
5. Broad Except — Her Şeyi Yakalama
# ❌ Çok geniş except
try:
result = process(data)
except:
print("Bir hata oldu")
# ❌ Biraz daha iyi ama hâlâ kötü
try:
result = process(data)
except Exception:
print("Bir hata oldu")
# ✅ Spesifik exception
try:
result = process(data)
except ValueError as e:
print(f"Geçersiz değer: {e}")
except ConnectionError as e:
print(f"Bağlantı hatası: {e}")
retry()except: her şeyi yakalar — KeyboardInterrupt dahil! Ctrl+C ile bile programı durduramayabilirsin.
except Exception: biraz daha iyi ama yine de hatanın ne olduğunu bilmiyorsun. Spesifik ol.
6. Diğer Anti-Patterns
# ❌ Gereksiz else after return
def check_age(age):
if age >= 18:
return "Yetişkin"
else:
return "Çocuk"
# ✅ Early return
def check_age(age):
if age >= 18:
return "Yetişkin"
return "Çocuk"
# ❌ İç içe if'ler (arrow anti-pattern)
def process(user):
if user:
if user.is_active:
if user.has_permission:
return do_work(user)
return None
# ✅ Guard clause'lar
def process(user):
if not user:
return None
if not user.is_active:
return None
if not user.has_permission:
return None
return do_work(user)Guard clause'lar (erken dönüş) kodu düzleştirir. İç içe if yerine ters koşullarla erken dönmek okunabilirliği artırır.
DRY, KISS, YAGNI — Üç Altın Kural
DRY — Don't Repeat Yourself
Kendini tekrar etme. Aynı kodu iki yere yazmak, gelecekte "birini güncelle, diğerini unut" hatasına yol açar.
# ❌ DRY ihlali
def validate_email(email):
if not email:
raise ValueError("Email boş olamaz")
if "@" not in email:
raise ValueError("Geçersiz email formatı")
return True
def validate_username(username):
if not username:
raise ValueError("Username boş olamaz")
if len(username) < 3:
raise ValueError("Username en az 3 karakter olmalı")
return True
# ✅ Ortak mantığı çıkar
def validate_not_empty(value, field_name):
if not value:
raise ValueError(f"{field_name} boş olamaz")
def validate_email(email):
validate_not_empty(email, "Email")
if "@" not in email:
raise ValueError("Geçersiz email formatı")
return True
def validate_username(username):
validate_not_empty(username, "Username")
if len(username) < 3:
raise ValueError("Username en az 3 karakter olmalı")
return TrueKISS — Keep It Simple, Stupid
Basit tut. Karmaşık çözümler değil, basit çözümler güzeldir.
# ❌ Over-engineered
class StringReverser:
def __init__(self, strategy="default"):
self.strategy = strategy
def reverse(self, s):
if self.strategy == "default":
return self._default_reverse(s)
elif self.strategy == "recursive":
return self._recursive_reverse(s)
def _default_reverse(self, s):
return s[::-1]
def _recursive_reverse(self, s):
if len(s) <= 1:
return s
return self._recursive_reverse(s[1:]) + s[0]
# ✅ KISS
def reverse_string(s):
return s[::-1]String'i tersine çevirmek için sınıf yazmaya gerek yok!
YAGNI — You Ain't Gonna Need It
İhtiyacın olmayan şeyi yapma. "İleride lazım olur" diye yazılan kodun %90'ı hiç kullanılmaz.
# ❌ YAGNI ihlali — "belki lazım olur"
class User:
def __init__(self, name, email):
self.name = name
self.email = email
self.phone = None # Belki lazım olur
self.address = None # Belki lazım olur
self.preferences = {} # Belki lazım olur
self.login_history = [] # Belki lazım olur
self.friends = [] # Belki lazım olur
def export_to_xml(self): # Hiçbir yerde XML kullanmıyoruz
pass
def send_sms(self): # SMS sistemi yok
pass
# ✅ Sadece ihtiyacın olanı yaz
class User:
def __init__(self, name, email):
self.name = name
self.email = emailGerçekten ihtiyaç olduğunda eklersin. O güne kadar, az kod = az bakım = az bug.
SOLID Prensipleri Python'da
SOLID, nesne yönelimli programlamanın 5 temel prensibidir. Python'da bunlar biraz daha basit uygulanır.
S — Single Responsibility (Tek Sorumluluk)
Her sınıf/fonksiyon tek bir iş yapmalı.
# ❌ Çok fazla sorumluluk
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def save_to_database(self): # Veritabanı işi
pass
def send_email(self): # Email işi
pass
def generate_report(self): # Raporlama işi
pass
# ✅ Her sınıf tek iş
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserRepository:
def save(self, user):
pass
class EmailService:
def send_welcome(self, user):
pass
class UserReportGenerator:
def generate(self, user):
passO — Open/Closed (Açık/Kapalı)
Genişlemeye açık, değişikliğe kapalı.
# ❌ Her yeni şekil için if eklemek gerekiyor
def calculate_area(shape):
if shape["type"] == "circle":
return 3.14 * shape["radius"] ** 2
elif shape["type"] == "rectangle":
return shape["width"] * shape["height"]
# Yeni şekil = yeni elif = mevcut kodu değiştirme
# ✅ Yeni şekil eklemek mevcut kodu değiştirmez
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# Yeni şekil? Yeni sınıf yaz, eski koda dokunma
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.heightD — Dependency Inversion (Bağımlılık Tersine Çevirme)
Somut sınıflara değil, soyutlamalara bağlan.
# ❌ Somut bağımlılık
class OrderService:
def __init__(self):
self.db = PostgresDatabase() # Sıkı bağlı!
def create_order(self, order):
self.db.insert("orders", order)
# ✅ Soyutlamaya bağlan
class OrderService:
def __init__(self, db): # Herhangi bir DB olabilir
self.db = db
def create_order(self, order):
self.db.insert("orders", order)
# Kullanım
service = OrderService(PostgresDatabase())
# veya test için:
service = OrderService(MockDatabase())💡 İpucu: SOLID prensiplerini dogmatik olarak takip etme. Python'da her şeyi sınıf yapmak zorunda değilsin. Bazen basit bir fonksiyon, karmaşık bir sınıf hiyerarşisinden daha iyi çözümdür. "Simple is better than complex."
Clean Function Yazma
Fonksiyonlar kodun yapı taşlarıdır. Temiz fonksiyon yazmak, temiz kod yazmanın temelidir.
Anlamlı İsimler
# ❌ Kötü isimlendirme
def proc(d, t):
return d * (1 + t/100)
# ✅ İyi isimlendirme
def calculate_price_with_tax(price, tax_rate):
return price * (1 + tax_rate / 100)Fonksiyon adı ne yaptığını söylemeli. proc ne demek? İşle? İşlem? 6 ay sonra hatırlar mısın?
Tek Sorumluluk
# ❌ Çok fazla iş yapan fonksiyon
def process_order(order_data):
# Validate
if not order_data.get("items"):
raise ValueError("No items")
if not order_data.get("customer"):
raise ValueError("No customer")
# Calculate totals
subtotal = sum(item["price"] * item["qty"] for item in order_data["items"])
tax = subtotal * 0.18
total = subtotal + tax
# Save to database
db.insert("orders", {"total": total, "customer": order_data["customer"]})
# Send email
send_email(order_data["customer"]["email"], f"Sipariş: {total} TL")
return total
# ✅ Her fonksiyon tek iş
def validate_order(order_data):
if not order_data.get("items"):
raise ValueError("No items")
if not order_data.get("customer"):
raise ValueError("No customer")
def calculate_order_total(items, tax_rate=0.18):
subtotal = sum(item["price"] * item["qty"] for item in items)
tax = subtotal * tax_rate
return subtotal + tax
def save_order(total, customer):
db.insert("orders", {"total": total, "customer": customer})
def notify_customer(email, total):
send_email(email, f"Sipariş: {total} TL")
def process_order(order_data):
validate_order(order_data)
total = calculate_order_total(order_data["items"])
save_order(total, order_data["customer"])
notify_customer(order_data["customer"]["email"], total)
return totalİkinci versiyonda process_order bir orkestra şefi gibi — kendi iş yapmıyor, yönetiyor.
Parametre Sayısı
# ❌ Çok fazla parametre (5+)
def create_user(name, email, age, city, country, phone, role):
pass
# ✅ Dataclass veya dict kullan
from dataclasses import dataclass
@dataclass
class UserData:
name: str
email: str
age: int
city: str = ""
country: str = "TR"
phone: str = ""
role: str = "user"
def create_user(data: UserData):
pass3'ten fazla parametre varsa, bir yapıya sarmayı düşün.
Erken Dönüş
# ❌ Tek return noktası (eski alışkanlık)
def get_discount(user):
discount = 0
if user.is_premium:
if user.years > 5:
discount = 30
else:
discount = 20
else:
if user.orders > 10:
discount = 10
return discount
# ✅ Erken dönüş
def get_discount(user):
if user.is_premium and user.years > 5:
return 30
if user.is_premium:
return 20
if user.orders > 10:
return 10
return 0Code Smell'ler — Kokular
"Code smell" terimi kötü tasarımın belirtileridir. Kod çalışır ama bir şeyler "kokar."
God Class — Her Şeyi Yapan Sınıf
# ❌ God Class — 50+ metod, 1000+ satır
class Application:
def connect_database(self): ...
def run_migrations(self): ...
def handle_request(self): ...
def render_template(self): ...
def send_email(self): ...
def process_payment(self): ...
def generate_report(self): ...
def resize_image(self): ...
# ... 42 metod dahaÇözüm: Sorumlulukları farklı sınıflara dağıt.
Magic Numbers — Sihirli Sayılar
# ❌ Bu 0.18 ne? 30 ne?
if total > 1000:
discount = total * 0.18
if quantity > 30:
discount += 50
# ✅ Sabitleri tanımla
TAX_RATE = 0.18
BULK_DISCOUNT_THRESHOLD = 30
HIGH_VALUE_ORDER_MIN = 1000
BULK_BONUS_DISCOUNT = 50
if total > HIGH_VALUE_ORDER_MIN:
discount = total * TAX_RATE
if quantity > BULK_DISCOUNT_THRESHOLD:
discount += BULK_BONUS_DISCOUNTSayıların ne anlama geldiği artık açık.
Long Function — Çok Uzun Fonksiyonlar
Bir fonksiyon 50+ satırsa muhtemelen çok fazla iş yapıyordur. 20 satır iyi bir hedef. Scroll yapmadan okuyabilmelisin.
Boolean Blindness
# ❌ True/False ne anlama geliyor?
process_order(order, True, False, True)
# ✅ Named parameters kullan
process_order(
order,
send_notification=True,
apply_discount=False,
priority_shipping=True
)Refactoring Örnekleri — Before → After
Örnek 1: Koşul Sadeleştirme
# BEFORE
def get_shipping_cost(order):
if order.total >= 200:
if order.is_member:
return 0
else:
return 10
else:
if order.is_member:
return 15
else:
return 25
# AFTER
SHIPPING_COSTS = {
(True, True): 0, # high_value + member
(True, False): 10, # high_value + non-member
(False, True): 15, # low_value + member
(False, False): 25, # low_value + non-member
}
def get_shipping_cost(order):
is_high_value = order.total >= 200
return SHIPPING_COSTS[(is_high_value, order.is_member)]Örnek 2: Tekrarlanan Kod Çıkarma
# BEFORE
def export_csv(users):
lines = ["name,email,role"]
for user in users:
if user.is_active:
lines.append(f"{user.name},{user.email},{user.role}")
return "\n".join(lines)
def export_json(users):
result = []
for user in users:
if user.is_active:
result.append({"name": user.name, "email": user.email, "role": user.role})
return json.dumps(result)
# AFTER
def get_active_users(users):
"""Ortak filtreleme mantığını çıkar"""
return [user for user in users if user.is_active]
def user_to_dict(user):
"""Ortak dönüşüm mantığını çıkar"""
return {"name": user.name, "email": user.email, "role": user.role}
def export_csv(users):
active = get_active_users(users)
lines = ["name,email,role"]
lines.extend(f"{u.name},{u.email},{u.role}" for u in active)
return "\n".join(lines)
def export_json(users):
active = get_active_users(users)
return json.dumps([user_to_dict(u) for u in active])Örnek 3: Koşul → Polimorfizm
# BEFORE
def calculate_pay(employee):
if employee.type == "full_time":
return employee.salary
elif employee.type == "part_time":
return employee.hours * employee.hourly_rate
elif employee.type == "contractor":
return employee.hours * employee.hourly_rate * 1.2
elif employee.type == "intern":
return employee.stipend
# AFTER
from dataclasses import dataclass
from abc import ABC, abstractmethod
class Employee(ABC):
@abstractmethod
def calculate_pay(self):
pass
@dataclass
class FullTimeEmployee(Employee):
salary: float
def calculate_pay(self):
return self.salary
@dataclass
class PartTimeEmployee(Employee):
hours: float
hourly_rate: float
def calculate_pay(self):
return self.hours * self.hourly_rate
@dataclass
class Contractor(Employee):
hours: float
hourly_rate: float
def calculate_pay(self):
return self.hours * self.hourly_rate * 1.2⚠️ Dikkat: Refactoring yaparken testlerin olsun! Her adımda testleri çalıştır. Test yoksa, önce test yaz, sonra refactor yap. Aksi halde bir şeyi düzeltirken başka bir şeyi bozabilirsin.
Python-Spesifik Güzellikler
Unpacking
# Tuple unpacking
point = (3, 4)
x, y = point
# Swap
a, b = b, a
# Star unpacking
first, *rest = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]
first, *middle, last = [1, 2, 3, 4, 5]
# first = 1, middle = [2, 3, 4], last = 5Context Manager ile Kaynak Yönetimi
# ❌
f = open("file.txt")
data = f.read()
f.close() # Ya exception olursa? close çağrılmaz!
# ✅
with open("file.txt") as f:
data = f.read()
# Otomatik kapanır, exception olsa bileWalrus Operator (Python 3.8+)
# ❌ İki kez hesaplama
data = get_data()
if len(data) > 10:
print(f"{len(data)} items") # len tekrar hesaplanıyor
# ✅ Walrus ile
if (n := len(get_data())) > 10:
print(f"{n} items")
# Dosya okumada
while line := f.readline():
process(line)Ternary Expression
# ❌ 4 satır
if age >= 18:
status = "yetişkin"
else:
status = "çocuk"
# ✅ 1 satır
status = "yetişkin" if age >= 18 else "çocuk"Özet
Pythonic code Python'ın ruhuna uygun, okunabilir ve zarif koddur —
import thisile Zen of Python'u okuEAFP (try/except) Python'da LBYL (if kontrolü) yerine tercih edilir
Anti-pattern'lerden kaçın:
range(len())yerineenumerate(), mutable default argument, broad exceptDRY, KISS, YAGNI prensipleri kodu basit, tekrarsız ve gereksiz karmaşıklıktan uzak tutar
Clean function: tek sorumluluk, anlamlı isim, az parametre, erken dönüş
Code smell'ler (god class, magic numbers, long function) kötü tasarımın belirtileridir — refactoring ile temizle
AI Asistan
Sorularını yanıtlamaya hazır