← Kursa Dön
📄 Text · 15 min

Linting ve Kod Kalitesi Araçları

Hiç başkasının yazdığı kodu açıp "bu ne böyle?" dediğin oldu mu? Değişken isimleri anlamsız, import'lar dağınık, bazen boşluk var bazen yok, satır uzunlukları uçmuş... Kod çalışıyor ama okumak işkence. Şimdi bir de tersten düşün: sen yazıyorsun, altı ay sonra açıyorsun ve kendi kodunu tanıyamıyorsun.

İşte linter ve formatter araçları tam bu sorunu çözer. Kodu standartlara otomatik uyduran, potansiyel hataları daha çalıştırmadan yakalayan ve takımdaki herkesin aynı stilde yazmasını sağlayan araçlar. Bu derste Python ekosisteminin en önemli kod kalitesi araçlarını — flake8, black, isort, ruff, mypy — öğrenecek ve bunları pre-commit hook'ları ile CI/CD pipeline'ına entegre edeceksin.


1. Neden Linter Kullanırız?

🏭 Analoji: Fabrikanın Kalite Kontrol Bandı

Bir fabrikayı düşün. Üretim hattının sonunda bir kalite kontrol bandı var. Her ürün bu banttan geçer — boyutu ölçülür, kusurları taranır, standartlara uygunluğu kontrol edilir. Hatalı ürünler yakalanır, geri gönderilir. Bu bant olmasa defolu ürünler müşteriye ulaşır.

Linter da kodun kalite kontrol bandıdır. Her commit'ten önce kodun bu banttan geçer — stil hataları yakalanır, potansiyel bug'lar işaretlenir, standartlara uyum kontrol edilir. Sorun production'a ulaşmadan düzeltilir.

Linter Ne Yapar?

Linter'lar iki temel iş yapar:

  1. Stil kontrolü: Boşluklar, satır uzunlukları, isimlendirme kuralları (PEP 8 uyumu)

  2. Hata tespiti: Kullanılmayan import'lar, tanımsız değişkenler, ulaşılamaz kod, tip hataları

Bunları gözle yakalamak hem zor hem de zaman kaybı. Bir linter bunu milisaniyeler içinde yapar.

Linter Olmadan Ne Olur?

# ❌ Her geliştirici kendi stilinde yazıyor
import os,sys
import json
from pathlib import Path
import requests
import os  # tekrar import!

def hesapla_toplam(liste,vergi_orani):
    toplam=0
    for i in liste:
        toplam+=i
    if vergi_orani>0:
        return toplam*(1+vergi_orani)
    else :
        return toplam

class musteri_bilgisi:
    def __init__(self,ad,soyad):
        self.Ad=ad
        self.Soyad=soyad

Bu kodda en az 10 stil sorunu var: boşluk eksiklikleri, tekrar eden import, PEP 8'e aykırı isimlendirme, gereksiz else, tutarsız attribute isimleri... Kod çalışır ama okunması ve bakımı kabus.


2. flake8: Klasik Linter

flake8, Python'un en yaygın kullanılan linter'ıdır. PyFlakes (mantık hataları), pycodestyle (PEP 8 stil kontrolü) ve McCabe (karmaşıklık analizi) araçlarını bir arada sunar.

Kurulum ve Temel Kullanım

pip install flake8

# Tek dosya kontrol et
flake8 app.py

# Tüm proje
flake8 src/

# Belirli hataları göster
flake8 --select=E,W src/   # Sadece error ve warning

# Belirli hataları yoksay
flake8 --ignore=E501,W503 src/  # Satır uzunluğu ve satır sonu kuralını atla

Yaygın Uyarı Kodları

flake8 her uyarıyı bir kodla raporlar. En sık karşılaşacakların:

KodAçıklamaÖrnek
E501Satır 79 karakterden uzunx = very_long_variable_name + another_long_...
E302Fonksiyon/class önünde 2 boş satır olmalıFonksiyonlar arası tek boş satır bırakmak
E303Fazla boş satır3+ ardışık boş satır
W291Satır sonunda gereksiz boşlukx = 5 (sondaki boşluklar)
F401Import edilmiş ama kullanılmamış modülimport os ama os hiç kullanılmıyor
F841Atanmış ama kullanılmamış değişkenx = 5 ama x hiç kullanılmıyor
E711== None yerine is None kullanif x == None:
E712== True yerine direkt kullanif x == True:if x:

Konfigürasyon

Her seferinde komut satırında parametre geçmek yerine proje kökünde bir konfigürasyon dosyası oluştur:

# .flake8 dosyası (proje kökünde)
[flake8]
max-line-length = 120
max-complexity = 10
exclude =
    .git,
    __pycache__,
    venv,
    migrations,
    .eggs
ignore =
    W503,
    E203
per-file-ignores =
    __init__.py: F401
    tests/*: E501

Önemli ayarlar:

  • max-line-length: Varsayılan 79 çoğu takım için dar. 120 yaygın bir tercih.

  • max-complexity: McCabe karmaşıklık skoru — fonksiyondaki dallanma sayısını ölçer.

  • exclude: Kontrol edilmeyecek dizinler.

  • per-file-ignores: Dosya bazında istisnalar. __init__.py'da kullanılmayan import'lar normal (modül export için), test dosyalarında uzun satırlar da kabul edilebilir.

Alternatif olarak setup.cfg veya tox.ini dosyasında da aynı konfigürasyonu [flake8] bölümü altına yazabilirsin.


3. black: Opinionated Formatter

flake8 sorunları gösterir ama düzeltmez. black ise kodu alır, kendi stiline göre otomatik düzeltir. "Opinionated" (fikirli) olmasının sebebi, tartışmaya yer bırakmaması — black'in bir stili var ve herkes onu kullanır.

Felsefesi

black'in mottosu: "Any color you like, as long as it's black." Stil tartışmaları takımların en verimli zamanını çalar. black bu tartışayı tamamen ortadan kaldırır: herkes aynı formatı kullanır, nokta.

Kurulum ve Kullanım

pip install black

# Tek dosyayı formatla
black app.py

# Tüm projeyi formatla
black src/

# Önizleme — değişiklikleri göster, uygulamadan
black --diff app.py

# Kontrol modu — değişiklik gerekiyorsa hata kodu döner (CI için)
black --check src/

Önce ve Sonra

# ❌ ÖNCE — dağınık formatlama
x = {  'a':37,'b':42,

'c':927}

y = 'hello ''world'
z = 'hello '+'world'
if very_long_variable_name is not None and \
       another_long_variable is not None and \
   yet_another is not None:
    do_something()

def f(a,b,c=5,d={'key':'value'}):
    return a+b*c
# ✅ SONRA — black'in formatladığı hali
x = {"a": 37, "b": 42, "c": 927}

y = "hello " "world"
z = "hello " + "world"
if (
    very_long_variable_name is not None
    and another_long_variable is not None
    and yet_another is not None
):
    do_something()


def f(a, b, c=5, d={"key": "value"}):
    return a + b * c

black'in yaptığı başlıca değişiklikler:

  • Tek tırnak → çift tırnak (tutarlılık)

  • Operatörler etrafına boşluk

  • Uzun satırları akıllıca kırma

  • Fonksiyon/class arası standart boş satırlar

  • Trailing comma (son elemandan sonra virgül) stratejisi

Konfigürasyon

black kasıtlı olarak az ayar sunar. Konfigürasyon pyproject.toml dosyasında yapılır:

# pyproject.toml
[tool.black]
line-length = 120
target-version = ["py311"]
include = '\.pyi?$'
exclude = '''
/(
    \.git
  | \.venv
  | migrations
)/
'''

black ile flake8'i birlikte kullanıyorsan satır uzunluğunu ikisinde de aynı tutmalısın. Ayrıca flake8'in E203 ve W503 uyarılarını kapatman gerekir — black'in formatlaması bu kurallarla çelişir.


4. isort: Import Düzenleyici

Python dosyalarının başındaki import satırları zamanla karmaşıklaşır. isort bunları otomatik sıralar ve gruplar.

PEP 8 Import Sıralaması

PEP 8'e göre import'lar şu sırada olmalı:

  1. Standart kütüphaneos, sys, json

  2. Üçüncü partirequests, pandas, flask

  3. Yerel modüllerfrom myapp import utils

Her grup arasında bir boş satır.

Kurulum ve Kullanım

pip install isort

# Tek dosya
isort app.py

# Tüm proje
isort src/

# Kontrol modu (CI için)
isort --check-only --diff src/

Önce ve Sonra

# ❌ ÖNCE — karışık import'lar
import json
from flask import Flask, jsonify
import os
from myapp.models import User
import requests
from pathlib import Path
import sys
from myapp.utils import helper
from datetime import datetime
# ✅ SONRA — isort'un düzenlediği hali
import json
import os
import sys
from datetime import datetime
from pathlib import Path

import requests
from flask import Flask, jsonify

from myapp.models import User
from myapp.utils import helper

black ile Uyumluluk

isort ve black'in çıktıları çakışabilir. Bunu önlemek için isort'a black profili kullanmasını söyle:

# pyproject.toml
[tool.isort]
profile = "black"
line_length = 120
known_third_party = ["flask", "requests", "pandas"]
known_first_party = ["myapp"]

profile = "black" ayarı isort'un black ile uyumlu formatta çıktı vermesini sağlar. Bu tek satır pek çok uyumsuzluk sorununu çözer.


5. ruff: Tek Araç, Hepsini Yönetir

ruff, Rust ile yazılmış yeni nesil bir Python linter ve formatter. flake8, isort, pyflakes, pycodestyle ve daha birçok aracın kurallarını tek çatı altında toplar — ve onlardan 10-100 kat daha hızlı çalışır.

Neden ruff?

Geleneksel araç seti şöyle görünür:

# Eski yöntem: 4 farklı araç
pip install flake8 black isort pyflakes
flake8 src/
black src/
isort src/

ruff ile:

# Yeni yöntem: tek araç
pip install ruff
ruff check src/     # lint (flake8 + isort + pyflakes + ...)
ruff format src/    # format (black uyumlu)

Kurulum ve Kullanım

pip install ruff

# Lint kontrolü
ruff check .

# Otomatik düzeltme
ruff check --fix .

# Formatlama (black uyumlu)
ruff format .

# Kontrol modu (CI için)
ruff check .
ruff format --check .

Konfigürasyon

# pyproject.toml
[tool.ruff]
line-length = 120
target-version = "py311"

[tool.ruff.lint]
# Hangi kural setleri aktif?
select = [
    "E",    # pycodestyle errors
    "W",    # pycodestyle warnings
    "F",    # pyflakes
    "I",    # isort
    "N",    # pep8-naming
    "UP",   # pyupgrade
    "B",    # flake8-bugbear
    "SIM",  # flake8-simplify
    "RUF",  # ruff-specific rules
]
ignore = ["E501"]  # Satır uzunluğunu formatter'a bırak

[tool.ruff.lint.isort]
known-first-party = ["myapp"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

ruff'ın Hız Farkı

Büyük projelerde fark dramatiktir:

# 1000 dosyalık bir projede
$ time flake8 src/
real    0m4.200s

$ time ruff check src/
real    0m0.040s   # ~100x daha hızlı!

Bu hız farkı özellikle pre-commit hook'larında ve CI/CD pipeline'larında fark yaratır. Geliştiriciler her commit'te saniyeler beklemek yerine anında sonuç alır.

💡 İpucu: Yeni projelerde doğrudan ruff ile başla. Mevcut projelerde flake8 + black + isort üçlüsünden ruff'a geçiş genellikle sorunsuz — kural kodları büyük ölçüde aynı.


6. mypy: Statik Tip Kontrolü

Python dinamik tipli bir dil ama tip hatası (type hint) desteği var. mypy bu tip ipuçlarını analiz eder ve runtime'dan önce hataları yakalar.

Neden Tip Kontrolü?

# ❌ Bu hata runtime'da patlıyor
def hesapla_kdv(fiyat, oran):
    return fiyat * (1 + oran)

# Yanlışlıkla string geçirildi — TypeError runtime'da!
sonuc = hesapla_kdv("100", 0.18)
# ✅ Tip ipuçları ile — mypy derleme zamanında yakalar
def hesapla_kdv(fiyat: float, oran: float) -> float:
    return fiyat * (1 + oran)

sonuc = hesapla_kdv("100", 0.18)  # mypy: Argument 1 has incompatible type "str"

Kurulum ve Kullanım

pip install mypy

# Tek dosya
mypy app.py

# Tüm proje
mypy src/

# Strict mode — en katı kontrol
mypy --strict src/

Konfigürasyon

# pyproject.toml
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true
no_implicit_optional = true
strict_equality = true

# Üçüncü parti kütüphaneler için tip stub'ları eksik olabilir
[[tool.mypy.overrides]]
module = ["requests.*", "pandas.*"]
ignore_missing_imports = true

Yaygın Tip Örüntüleri

from typing import Optional

# Basit tip ipuçları
def selamla(isim: str) -> str:
    return f"Merhaba, {isim}!"

# Optional — None olabilir
def kullanici_bul(id: int) -> Optional[dict]:
    if id <= 0:
        return None
    return {"id": id, "isim": "Ali"}

# Liste ve dict tipleri
def ortalama_hesapla(sayilar: list[float]) -> float:
    return sum(sayilar) / len(sayilar)

def config_oku(ayarlar: dict[str, str]) -> None:
    for anahtar, deger in ayarlar.items():
        print(f"{anahtar} = {deger}")

Strict Mode

--strict flag'i şu kontrolleri aktifleştirir:

  • Tüm fonksiyonlar tip ipucu zorunlu

  • Any tipi uyarı verir

  • Implicit Optional yasaklanır (None dönüyorsan Optional yazmalısın)

  • Dekoratörsüz fonksiyonlar da kontrol edilir

Yeni projelerde strict mode ile başlamak en sağlıklısı. Mevcut projelere strict mode eklemek ise genellikle aşamalı yapılır — önce hata sayısını gör, sonra modül modül düzelt.


7. pre-commit Hooks

Tüm bu araçları "her commit öncesi otomatik çalıştırmak" istersen pre-commit framework'ünü kullanırsın. Git'in pre-commit hook mekanizması üzerine kurulu bir araç.

Kurulum

pip install pre-commit

Konfigürasyon Dosyası

Proje kökünde .pre-commit-config.yaml dosyası oluştur:

# .pre-commit-config.yaml
repos:
  # ruff — lint + format (flake8, isort, black yerine tek araç)
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.6
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  # mypy — tip kontrolü
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.14.1
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]

  # Genel kontroller
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace      # Satır sonu boşlukları
      - id: end-of-file-fixer        # Dosya sonu yeni satır
      - id: check-yaml               # YAML syntax
      - id: check-json               # JSON syntax
      - id: check-added-large-files  # Büyük dosya uyarısı
      - id: debug-statements         # print/pdb kalıntıları

Eğer ruff yerine klasik araçları tercih ediyorsan:

# Alternatif: ayrı araçlarla
repos:
  - repo: https://github.com/pycqa/flake8
    rev: 7.1.1
    hooks:
      - id: flake8
        args: [--max-line-length=120]

  - repo: https://github.com/psf/black
    rev: 24.10.0
    hooks:
      - id: black

  - repo: https://github.com/pycqa/isort
    rev: 5.13.2
    hooks:
      - id: isort

Hook'ları Aktifleştirme

# Hook'ları kur (projeye ilk kez)
pre-commit install

# Tüm dosyalarda elle çalıştır (ilk kurulumda)
pre-commit run --all-files

# Tek bir hook çalıştır
pre-commit run ruff --all-files

# Hook'ları güncelle
pre-commit autoupdate

pre-commit install komutundan sonra her git commit komutu otomatik olarak hook'ları çalıştırır. Hata varsa commit engellenir — düzelt ve tekrar dene.

Tipik İş Akışı

$ git add .
$ git commit -m "Yeni özellik eklendi"

ruff.....................................................Passed
ruff-format..............................................Passed
mypy.....................................................Passed
trailing-whitespace......................................Fixed
end-of-file-fixer.......................................Fixed
check-yaml...............................................Passed

# trailing-whitespace ve end-of-file-fixer dosyaları düzeltti
# Bu yüzden tekrar add + commit gerekiyor
$ git add .
$ git commit -m "Yeni özellik eklendi"
# Bu sefer tüm hook'lar Passed → commit başarılı

⚠️ Dikkat: pre-commit hook'ları yerel makinede çalışır. Bir geliştirici hook'ları kurmazsa (pre-commit install çalıştırmazsa) kontrol atlanır. Bu yüzden CI/CD'de de aynı kontrolleri çalıştırmak kritik — bir sonraki bölümde bunu ele alacağız.


8. CI/CD'de Lint: GitHub Actions

pre-commit yerel makinede çalışır ama son savunma hattı CI/CD pipeline'ıdır. Pull request açıldığında veya main branch'e push yapıldığında otomatik kontrol.

GitHub Actions Workflow

# .github/workflows/lint.yml
name: Lint & Type Check

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4

      - name: Python kur
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Bağımlılıkları kur
        run: |
          python -m pip install --upgrade pip
          pip install ruff mypy
          pip install -r requirements.txt

      - name: Ruff lint kontrolü
        run: ruff check .

      - name: Ruff format kontrolü
        run: ruff format --check .

      - name: mypy tip kontrolü
        run: mypy src/ --ignore-missing-imports

Bu workflow her PR'da ve main/develop push'unda çalışır. Herhangi bir adım başarısız olursa PR merge edilemez (branch protection rules ile birlikte).

pre-commit CI (Alternatif)

pre-commit'in kendi CI servisi de var. .pre-commit-config.yaml zaten projedeyse ekstra workflow yazmaya gerek kalmaz:

# .github/workflows/pre-commit.yml
name: pre-commit

on:
  push:
    branches: [main]
  pull_request:

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: pre-commit/action@v3.0.1

Bu daha kısa ve .pre-commit-config.yaml'deki tüm hook'ları otomatik çalıştırır.


9. Araç Karşılaştırma Tablosu

Hangi aracı ne zaman kullanacağını seçmek için:

Özellikflake8ruffpylintblackmypy
KategoriLinterLinter + FormatterLinterFormatterTip kontrolü
HızOrtaÇok hızlı (Rust)YavaşOrtaYavaş
Kural sayısı~100~800+~400+
Otomatik düzeltme
Formatlama
Import sıralamaPlugin gerekli✅ (dahili)
Tip kontrolüKısmi✅ (tam)
Konfigürasyon.flake8 / setup.cfgpyproject.toml.pylintrcpyproject.tomlpyproject.toml
Öğrenme eğrisiDüşükDüşükYüksekÇok düşükOrta
Topluluk trendiStabilHızla yükseliyorStabilStandartStandart

Önerilen Kombinasyonlar

Yeni proje (2024+):

ruff (lint + format + isort) + mypy (tip kontrolü)

İki araç, tüm ihtiyaçları karşılar. Hızlı, modern, tek konfigürasyon dosyası.

Mevcut proje (geçiş döneminde):

flake8 (lint) + black (format) + isort (import) + mypy (tip kontrolü)

Bu üçlü yıllardır endüstri standardı. ruff'a geçiş zamanla yapılabilir.

Minimal setup (küçük/kişisel proje):

ruff check + ruff format

mypy bile opsiyonel. Hızlı başla, ihtiyaç oldukça ekle.

pylint Notu

pylint bu tabloda var ama bilinçli olarak ayrı tutuyoruz. Çok kapsamlı bir araç — kod stili, hata tespiti, refactoring önerileri, hatta kod tekrarı (duplication) tespiti yapabilir. Ama aşırı "gürültülü"dür: varsayılan konfigürasyonla neredeyse her satıra bir uyarı verir. Ciddi projeler için faydalı ama başlangıç için flake8 veya ruff çok daha pragmatik.


10. Tüm Araçları Bir Arada Kullanma

Gerçek bir projede bu araçlar birlikte çalışır. İşte sıfırdan bir Python projesi kurarken önerilen adımlar:

Proje Konfigürasyonu

Tek bir pyproject.toml dosyasında her şeyi yönetebilirsin:

# pyproject.toml

[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"

# === ruff ===
[tool.ruff]
line-length = 120
target-version = "py311"

[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "UP", "B", "SIM", "RUF"]
ignore = ["E501"]

[tool.ruff.lint.isort]
known-first-party = ["myproject"]

[tool.ruff.format]
quote-style = "double"

# === mypy ===
[tool.mypy]
python_version = "3.11"
warn_return_any = true
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = ["requests.*", "pandas.*"]
ignore_missing_imports = true

Pre-commit + CI/CD

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.6
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.14.1
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: debug-statements

Geliştirici Onboarding

Yeni bir geliştirici projeye katıldığında:

# 1. Repo'yu klonla
git clone https://github.com/team/myproject.git
cd myproject

# 2. Sanal ortam oluştur
python -m venv venv
source venv/bin/activate

# 3. Bağımlılıkları kur
pip install -r requirements.txt
pip install -r requirements-dev.txt  # ruff, mypy, pre-commit

# 4. Pre-commit hook'larını kur
pre-commit install

# 5. İlk kontrol — mevcut durum
pre-commit run --all-files

Bu adımlardan sonra geliştirici her commit'te otomatik kontrol alır. Takımdaki herkes aynı kurallarla çalışır, code review'da stil tartışması olmaz.


Özet

  • Linter'lar kodu çalıştırmadan hataları ve stil sorunlarını yakalar — kalite kontrol bandın.

  • flake8 klasik ve güvenilir bir linter; black tartışmasız bir formatter; isort import düzenleyicisi.

  • ruff bu üç aracın işini tek başına yapar, Rust ile yazıldığı için 10-100x daha hızlıdır — yeni projelerde ilk tercih.

  • mypy statik tip kontrolü sağlar; tip ipuçlarıyla runtime hatalarını derleme zamanında yakalar.

  • pre-commit hook'ları her commit öncesi otomatik kontrol çalıştırır; CI/CD pipeline'ı ise son savunma hattıdır.

  • Modern Python projesinde ruff + mypy + pre-commit + GitHub Actions dörtlüsü endüstri standardı haline gelmiştir.