← Kursa Dön
📄 Text · 15 min

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 this
The 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:
    pass

Python 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_value

EAFP 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
    pass

Python'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 True

KISS — 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 = email

Gerç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):
        pass

O — 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.height

D — 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):
    pass

3'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 0

Code 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_DISCOUNT

Sayı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 = 5

Context 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 bile

Walrus 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 this ile Zen of Python'u oku

  • EAFP (try/except) Python'da LBYL (if kontrolü) yerine tercih edilir

  • Anti-pattern'lerden kaçın: range(len()) yerine enumerate(), mutable default argument, broad except

  • DRY, 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