Ana içeriğe geç
Kesit Python
Temel · Dosya ve hata

Temiz kod ve PEP 8

PEP 8 adlandırma, boşluk ve import kurallarını, docstring yazmayı, sihirli sayılardan kaçınmayı, DRY ve erken dönüşü öğrenir; dağınık kodu davranışını bozmadan yeniden düzenler, ruff ve black ile stili otomatiğe bağlarsın.

Uygulamada aç, 18 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden önemli?

Kod bir kez yazılır, onlarca kez okunur: kod incelemesinde (code review), hata ayıklarken, altı ay sonra yeni özellik eklerken. Ilgaz Bank'ın faiz modülünü yazan kişi izne çıktığında, def f(a, b, c) ve içinde * 0.0175 gibi açıklamasız sayılar gören ekip arkadaşı bir günü yalnızca "bu ne yapıyor" sorusuna harcar. Temiz kod (clean code), çalışan kodun bir üst basamağıdır: doğru çalışan ve kolay anlaşılan kod.

Python topluluğunun ortak stil kılavuzu PEP 8'dir (Python Enhancement Proposal 8). Kuralların çoğu zevk meselesi gibi görünür ama asıl amaç tutarlılıktır: herkes aynı biçimde yazınca göz biçime değil içeriğe odaklanır. Değişken adlandırmanın temellerini "Değişkenler" dersinde gördün; burada bütün bir programa bakıyoruz.

Adlandırma kuralları

Ne?BiçimÖrnek
Değişken, fonksiyon, parametre, modülsnake_caseaylik_taksit, hesap_ozeti(), rapor.py
SınıfPascalCase (CapWords)KrediBasvurusu, Siparis
Sabit (modül düzeyinde, değişmeyen)UPPER_CASEKDV_ORANI, MAX_DENEME
"İçeride kullan" işareti_ ile başla_yardimci(), _onbellek
Kaçınılacakl, O, I tek harfleri; yerleşik adlarl = 1 (1 mi l mi?), list = [...]

Anlamlı ad ise kuraldan da önemlidir. Adın uzunluğu kapsamıyla orantılı olsun: üç satırlık bir döngüde i yeterlidir, ama modül boyunca yaşayan bir değişken d olamaz. Mantıksal (bool) değerlere soru gibi okunan adlar ver: odendi_mi, stok_var. Fonksiyon adı bir fiil olsun: hesapla, getir, dogrula.

# Önce: ne yaptığını anlamak için kodu çözmen gerekir
def f(l):
    t = 0
    for x in l:
        if x[1]:
            t += x[0]
    return t

# Sonra: adlar kodu anlatır
def odenen_toplam(faturalar):
    toplam = 0
    for tutar, odendi_mi in faturalar:
        if odendi_mi:
            toplam += tutar
    return toplam

faturalar = [(1200, True), (450, False), (300, True)]
print(f(faturalar), odenen_toplam(faturalar))
Türkçe ad, Türkçe karakter?Python 3 adlarda ş, ğ, ı gibi harflere izin verir (öğrenci_sayısı = 5 çalışır). Yine de çoğu ekip klavye, arama ve araç uyumu için adları ASCII yazar: ogrenci_sayisi. Bu uygulamada da öyle yapıyoruz. Uluslararası ekiplerde adlar genellikle İngilizcedir; önemli olan bir projede tek dil seçmektir.

Girinti, boşluk ve satır uzunluğu

KDV_ORANI = 0.20
KARGO_UCRETI = 39.90
INDIRIM_ESIGI = 1000


def sepet_toplami(ara_toplam, uye_mi):
    indirim = 0.05 if uye_mi and ara_toplam >= INDIRIM_ESIGI else 0
    genel_toplam = (
        ara_toplam * (1 - indirim) * (1 + KDV_ORANI)
        + KARGO_UCRETI
    )
    return round(genel_toplam, 2)


print(sepet_toplami(1200, uye_mi=True))
print(sepet_toplami(400, uye_mi=False))

Operatörün satır başında olması (+ KARGO_UCRETI) PEP 8'in güncel önerisidir: matematikte olduğu gibi gözün hangi işlemin yapıldığını satır başında görür.

Import sırası

Dosyanın en üstünde, docstring'den hemen sonra, üç grup hâlinde ve aralarında birer boş satırla:

"""Anadolu Market günlük ciro raporu."""

import csv                      # 1) standart kütüphane
import datetime as dt
from pathlib import Path

import pandas as pd             # 2) üçüncü taraf (pip ile kurulanlar)

from rapor.ayarlar import KDV_ORANI   # 3) projenin kendi modülleri

Her grup kendi içinde alfabetiktir; her satırda tek modül içe aktarılır (import os, sys değil). from modul import * hangi adların geldiğini gizlediği için kullanılmaz. Bu sırayı elle tutmana gerek yok: ruff (ruff check --select I --fix) ya da isort senin yerine dizer.

Yorum ve docstring

İyi yorum kodun ne yaptığını değil neden öyle yaptığını anlatır. # i'yi 1 artır gürültüdür; # Banka hafta sonu çalışmadığı için valörü cumaya çekiyoruz altın değerindedir. Kod değişip yorum değişmezse yorum yalan söylemeye başlar; bu yüzden kodu kendini anlatacak kadar açık yaz, yorumu iş kuralı ve tuhaflıklar için sakla.

Docstring, fonksiyonun, sınıfın ya da modülün ilk satırına yazılan üç tırnaklı metindir. Yorumdan farkı: Python onu saklar, help() ve editörler gösterir. İlk satır tek cümlelik özettir; ardından boş satır ve ayrıntılar gelir. İki yaygın biçim vardır:

def aylik_taksit(anapara, yillik_faiz, vade_ay):
    """Eşit taksitli kredinin aylık taksitini hesaplar.

    Args:
        anapara: Çekilen kredi tutarı (TL).
        yillik_faiz: Yıllık faiz oranı, 0.42 gibi ondalık.
        vade_ay: Taksit sayısı.

    Returns:
        2 ondalığa yuvarlanmış aylık taksit.

    Raises:
        ValueError: vade_ay pozitif değilse.
    """
    if vade_ay <= 0:
        raise ValueError("vade pozitif olmalı")
    r = yillik_faiz / 12
    if r == 0:
        return round(anapara / vade_ay, 2)
    return round(anapara * r / (1 - (1 + r) ** -vade_ay), 2)


print(aylik_taksit(100_000, 0.42, 12))
print(aylik_taksit.__doc__.splitlines()[0])

Yukarıdaki Google stilidir (Args / Returns / Raises). Veri bilimi kütüphanelerinde (numpy, pandas) yaygın olan NumPy stili başlıkları çizgiyle ayırır:

def ortalama_sepet(tutarlar):
    """Ortalama sepet tutarını döndürür.

    Parameters
    ----------
    tutarlar : list of float
        Sipariş tutarları.

    Returns
    -------
    float
        Ortalama; liste boşsa 0.0.
    """

Hangisini seçtiğin değil, projede tek birini tutarlı kullanman önemlidir. Tip ipuçları (type hints) da belgelemenin parçasıdır: def aylik_taksit(anapara: float, yillik_faiz: float, vade_ay: int) -> float yazınca docstring'de türleri tekrar etmene gerek kalmaz.

Sihirli sayılardan kaçın

Kodun ortasındaki açıklamasız sayı "sihirli sayı"dır (magic number). if puan > 1250 satırında 1250'nin ne olduğunu bilen tek kişi yazandır. Sayıyı adı olan bir sabite taşı: hem anlam kazanır hem de değiştiğinde tek yerden güncellenir.

# Önce
def kart_limiti(gelir):
    if gelir > 85000:
        return gelir * 3
    return gelir * 1.5

# Sonra
YUKSEK_GELIR_ESIGI = 85_000
YUKSEK_GELIR_CARPANI = 3
STANDART_CARPAN = 1.5

def kart_limiti_temiz(aylik_gelir):
    if aylik_gelir > YUKSEK_GELIR_ESIGI:
        return aylik_gelir * YUKSEK_GELIR_CARPANI
    return aylik_gelir * STANDART_CARPAN

print(kart_limiti(90000) == kart_limiti_temiz(90000), kart_limiti_temiz(40000))

0, 1, -1 gibi bariz değerler ve range(len(x)) gibi kullanımlar sabite çevrilmez; kural "anlamı açık olmayan sayı"lar içindir. 85_000 yazımı da bir stil inceliğidir: alt çizgi sayıyı okunur yapar, değerini değiştirmez.

Küçük fonksiyon, DRY, erken dönüş

Tek sorumluluk: bir fonksiyon tek iş yapsın ve adı o işi söylesin. Adında "ve" geçiyorsa (oku_ve_hesapla_ve_yazdir) bölme zamanıdır. Kabaca ekrana sığmayan fonksiyon, büyük olasılıkla fazla iş yapıyordur.

DRY (Don't Repeat Yourself, kendini tekrar etme): aynı mantık iki yerde yazılırsa bir gün yalnızca birinde güncellenir ve iki yer birbirini tutmaz. Üç kez kopyalanmış bir blok, bir fonksiyonun habercisidir.

Erken dönüş (early return, guard clause): iç içe if merdiveni yerine geçersiz durumları başta eleyip ana yolu düz bırak.

# Önce: iç içe merdiven
def kredi_karari_eski(yas, gelir, borc_orani):
    if yas >= 18:
        if gelir >= 20000:
            if borc_orani <= 0.5:
                return "ön onay"
            else:
                return "red: borç"
        else:
            return "red: gelir"
    else:
        return "red: yaş"

# Sonra: erken dönüş
def kredi_karari(yas, gelir, borc_orani):
    if yas < 18:
        return "red: yaş"
    if gelir < 20000:
        return "red: gelir"
    if borc_orani > 0.5:
        return "red: borç"
    return "ön onay"

for basvuru in [(17, 50000, 0.1), (30, 15000, 0.1), (30, 40000, 0.7), (30, 40000, 0.2)]:
    print(kredi_karari(*basvuru), kredi_karari_eski(*basvuru) == kredi_karari(*basvuru))

Zen of Python

Python'ın tasarım felsefesi 19 kısa ilkeyle yorumlayıcının içine gömülüdür. Çalıştır ve oku:

import this

En çok alıntılananlar: Beautiful is better than ugly (güzel, çirkinden iyidir), Explicit is better than implicit (açık olan örtük olandan iyidir: from x import * bu yüzden kötüdür), Readability counts (okunabilirlik önemlidir), Errors should never pass silently (hatalar sessizce geçmemeli: çıplak except: pass bu yüzden kötüdür), There should be one obvious way to do it (bir işi yapmanın tek bariz yolu olmalı).

ruff ve black: stili makineye bırak

Kuralları ezberlemek yerine araçlara uygulat. black bir biçimlendiricidir (formatter): kodu tek ve tartışmasız bir stile sokar. ruff ise çok hızlı bir denetleyicidir (linter): kullanılmayan import, tanımsız ad, etkisiz ifade, import sırası gibi yüzlerce kuralı denetler; ruff format ile black uyumlu biçimlendirme de yapar.

pip install ruff black
ruff check .              # sorunları listele
ruff check . --fix        # düzeltilebilenleri düzelt
ruff format .             # biçimlendir (black uyumlu)
black rapor.py            # ya da yalnızca black

Ayarlar proje kökündeki pyproject.toml içinde tutulur, böylece ekipteki herkes aynı kuralları kullanır:

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I", "N", "B"]   # pycodestyle, pyflakes, import, adlandırma, bugbear
Kendi bilgisayarında deneruff ve black tarayıcıda çalışmaz; terminalde pip install ruff ile kur. VS Code'da "Ruff" eklentisini kurup "Format on Save" açarsan her kayıtta dosya kendiliğinden düzelir. Bu derste bazı denetimleri Python'ın kendi re ve ast modülleriyle nasıl yapabileceğini alıştırmalarda göreceksin.
Biçim tartışmasını kapat"Bu virgülden sonra boşluk olmalı mı" tartışması kod incelemesinde vakit kaybıdır. Formatter'ı ekip kuralı yapınca bu yorumlar tamamen ortadan kalkar ve inceleme mantığa odaklanır.
Gerçek işteÇoğu şirkette kod, ana dala birleşmeden önce otomatik denetimden geçer: pre-commit kancası commit anında ruff ve black'i çalıştırır, CI hattı (GitHub Actions gibi) stil hatası olan değişikliği kırmızıya boyar. Kod incelemesinde en sık yazılan yorumlar da bu dersin konularıdır: "bu sayının adı ne olmalı?", "bu fonksiyon iki iş yapıyor", "bu blok şurada da var". İş görüşmelerinde verilen ev ödevlerinde (take-home) çalışan ama dağınık kod, çalışan ve temiz koda neredeyse her zaman kaybeder.
Uzmanlaş

Tutarlılık, kuraldan önce gelir

PEP 8'in kendisi bir bölümüne "A Foolish Consistency is the Hobgoblin of Little Minds" başlığını koyar: proje içi tutarlılık PEP 8'den, fonksiyon içi tutarlılık proje kuralından önemlidir. Eski bir kod tabanına camelCase ile yazılmışsa, tek fonksiyonu snake_case'e çevirmek karmaşayı artırır; ya hepsini (bir araçla, ayrı bir değişiklikte) çevirirsin ya da mevcut stile uyarsın.

Bilişsel karmaşıklık

Kodun zorluğunu satır sayısı değil, okurun aklında aynı anda tutması gereken şey sayısı belirler. Her iç içe if, her döngü, her bayrak (flag) değişkeni bu yükü artırır. Erken dönüş, küçük yardımcı fonksiyonlar ve anlamlı ara değişkenler (uygun_mu = yas >= 18 and gelir >= ESIK) yükü azaltır. ruff'ın C901 kuralı karmaşıklığı sayıp sınırı aşan fonksiyonları işaretler.

Yeniden düzenleme (refactoring) güvenli yapılır

Refactor, davranışı değiştirmeden yapıyı iyileştirmektir. Güvenli yolu: önce mevcut davranışı test et, küçük bir adım at, testleri çalıştır, tekrarla. Bu dersin refactor alıştırmaları tam olarak bunu yapar: testler hem eski davranışın korunduğunu hem de yapının düzeldiğini denetler. Orta seviyedeki "Test yazma" dersinde bu güvenlik ağını kendin kuracaksın.

Alıştırmalara geç (18)