GitHub Actions ile Lint ve Format Kontrolü: Kod Kalitesini Otomatikleştirin

Kod kalitesi meselesi, bir projenin büyüklüğü ne olursa olsun her zaman öncelikli gündem maddesi olmalı. “Çalışıyor ya, ne olmuş?” diyebilirsiniz, ama ekip büyüdükçe, yeni geliştiriciler projeye dahil oldukça ve kod tabanı genişledikçe tutarsız formatlama ve lint hataları gerçek bir kaosa dönüşüyor. İşte tam bu noktada GitHub Actions devreye giriyor: her push veya pull request anında otomatik olarak lint ve format kontrolü yaparak sorunları insan gözü değmeden yakalamak mümkün oluyor.

Neden Lint ve Format Kontrolü Önemli?

Düşünün, beş kişilik bir ekipte çalışıyorsunuz. Biri tab kullanıyor, diğeri iki boşluk, bir başkası dört boşluk. Python dosyasında kullanılmayan import’lar var, JavaScript kodunda noktalı virgül tutarsızlıkları var. Code review sırasında asıl mantık hataları yerine bu tür şeylerle zaman kaybediyorsunuz.

Lint araçları kodu statik olarak analiz eder ve potansiyel hataları, kötü pratikleri veya stil sorunlarını işaretler. Format araçları ise kodu otomatik olarak belirli bir stile uygun hale getirir. Bu ikisini CI/CD pipeline’ına entegre ettiğinizde:

  • Kod tutarlılığı otomatik olarak sağlanır
  • Code review süreci sadece iş mantığına odaklanabilir
  • Teknik borç birikmeden önlenir
  • Yeni ekip üyeleri projeye daha kolay adapte olur
  • Üretim ortamına potansiyel runtime hataları geçmez

Temel GitHub Actions Kavramları (Hızlı Hatırlatma)

Lint workflow’larına geçmeden önce temel yapıyı hatırlayalım. GitHub Actions, .github/workflows/ dizinindeki YAML dosyalarını okur. Her workflow bir veya daha fazla job içerir, her job ise step’lerden oluşur.

# Proje dizin yapısı
myproject/
├── .github/
│   └── workflows/
│       ├── lint.yml
│       └── format-check.yml
├── src/
├── tests/
└── package.json

Bir workflow’un temel anatomisi şöyle:

name: Code Quality Check

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint
        run: npm run lint

Bu basit yapı bile çok şey yapıyor: kodu checkout ediyor, Node.js kuruyor, bağımlılıkları yüklüyor ve ESLint çalıştırıyor.

Python Projeleri için Kapsamlı Lint Workflow’u

Python projelerinde genellikle birden fazla araç bir arada kullanılır. flake8 veya pylint lint için, black format için, isort import sıralaması için, mypy ise tip kontrolü için kullanılır.

name: Python Code Quality

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

jobs:
  python-quality:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12']
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      
      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install flake8 black isort mypy pylint
          pip install -r requirements.txt
          if [ -f requirements-dev.txt ]; then
            pip install -r requirements-dev.txt
          fi
      
      - name: Run Black (format check)
        run: black --check --diff src/ tests/
      
      - name: Run isort (import sort check)
        run: isort --check-only --diff src/ tests/
      
      - name: Run flake8 (lint)
        run: |
          flake8 src/ tests/ 
            --max-line-length=88 
            --extend-ignore=E203,W503 
            --count 
            --statistics
      
      - name: Run mypy (type check)
        run: mypy src/ --ignore-missing-imports

Burada dikkat çeken birkaç nokta var. matrix stratejisi ile birden fazla Python versiyonunda test ediyoruz. cache step’i ile pip paketlerini önbellekliyoruz, bu özellikle büyük projelerde ciddi zaman tasarrufu sağlıyor. black --check modu dosyaları değiştirmiyor, sadece format hatası olup olmadığını raporluyor.

JavaScript/TypeScript Projeleri için ESLint ve Prettier

Frontend veya Node.js projelerinde ESLint ve Prettier kombinasyonu neredeyse standart haline geldi.

name: JavaScript Code Quality

on:
  push:
    branches: [ main, develop, 'feature/**' ]
  pull_request:
    types: [ opened, synchronize, reopened ]

jobs:
  eslint:
    name: ESLint Check
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint with annotations
        uses: reviewdog/action-eslint@v1
        with:
          reporter: github-pr-review
          eslint_flags: 'src/**/*.{js,jsx,ts,tsx}'
      
      - name: Run Prettier check
        run: npx prettier --check "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}"
      
      - name: TypeScript type check
        run: npx tsc --noEmit

reviewdog/action-eslint kullanmak önemli bir detay burada. Bu action, lint hatalarını doğrudan pull request’in ilgili satırlarına yorum olarak ekliyor. Yani geliştiriciler hangi satırda ne sorun olduğunu PR üzerinde görebiliyor, ayrıca log dosyasını karıştırmaları gerekmiyor.

Çoklu Dil Desteği: Monorepo Senaryosu

Gerçek dünya projelerinde genellikle monorepo yapısı görüyoruz. Frontend React, backend Python veya Go, ve belki bir microservice daha. Bu durumda her servisi ayrı ayrı kontrol etmek gerekiyor.

name: Monorepo Code Quality

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

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      frontend: ${{ steps.changes.outputs.frontend }}
      backend: ${{ steps.changes.outputs.backend }}
      services: ${{ steps.changes.outputs.services }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            frontend:
              - 'frontend/**'
            backend:
              - 'backend/**'
            services:
              - 'services/**'

  frontend-lint:
    needs: detect-changes
    if: ${{ needs.detect-changes.outputs.frontend == 'true' }}
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: frontend
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: frontend/package-lock.json
      - run: npm ci
      - run: npm run lint
      - run: npm run format:check

  backend-lint:
    needs: detect-changes
    if: ${{ needs.detect-changes.outputs.backend == 'true' }}
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: backend
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install ruff black
      - run: ruff check .
      - run: black --check .

dorny/paths-filter action’ı burada kilit rol oynuyor. Sadece değişen dizinlere ait lint job’ları çalışıyor. 100 servisli bir monorepo’da tek bir dosyayı değiştirdiğinizde tüm servislerin lint’ini çalıştırmak hem zaman hem de kaynak israfı olur.

Pre-commit Hook ile GitHub Actions Entegrasyonu

Profesyonel bir yaklaşım olarak pre-commit framework’ünü hem yerel geliştirme ortamında hem de CI’da kullanabilirsiniz. Bu şekilde yerel ve uzak kontroller tamamen senkronize kalır.

Önce projeye .pre-commit-config.yaml ekleyin:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-json
      - id: check-merge-conflict
      - id: detect-private-key
  
  - repo: https://github.com/psf/black
    rev: 23.12.0
    hooks:
      - id: black
        language_version: python3.11
  
  - repo: https://github.com/pycqa/isort
    rev: 5.13.2
    hooks:
      - id: isort
        args: ["--profile", "black"]
  
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.8
    hooks:
      - id: ruff
        args: [--fix]

Sonra bu config’i GitHub Actions’ta kullanın:

name: Pre-commit Checks

on: [push, pull_request]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Run pre-commit
        uses: pre-commit/[email protected]
        with:
          extra_args: --all-files

Bu yaklaşımın güzelliği şu: geliştiriciler pre-commit install komutunu çalıştırdığında aynı kontroller her commit öncesi yerel olarak da çalışıyor. CI’a geçen hatalar ciddi ölçüde azalıyor.

Hata Raporlama ve Bildirimler

Lint hataları olduğunda sadece build’in kırmızıya dönmesi yeterli değil, geliştiricilere net bildirim göndermek gerekiyor. Özellikle büyük ekiplerde Slack veya email bildirimleri önemli.

name: Code Quality with Notifications

on:
  push:
    branches: [ main ]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup and lint
        id: lint_step
        run: |
          npm ci
          npm run lint 2>&1 | tee lint-output.txt
          echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
      
      - name: Upload lint results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: lint-results
          path: lint-output.txt
          retention-days: 7
      
      - name: Notify Slack on failure
        if: failure()
        uses: slackapi/[email protected]
        with:
          channel-id: 'dev-alerts'
          slack-message: |
            *Lint Kontrolü Başarısız!*
            Repo: ${{ github.repository }}
            Branch: ${{ github.ref_name }}
            Commit: ${{ github.sha }}
            Gönderi: ${{ github.actor }}
            Detaylar: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
      
      - name: Add PR comment on lint failure
        if: failure() && github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '## Lint Kontrolü BaşarısıznLütfen lint hatalarını düzeltin. [Workflow loglarına](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) bakın.'
            })

PR üzerine otomatik yorum bırakmak harika bir pratik. Geliştirici PR’a bakıyor, hata nerede anında görüyor, log dosyasını aramak zorunda kalmıyor.

Performans Optimizasyonu: Cache Stratejileri

Büyük projelerde bağımlılık kurulumu ciddi zaman alabilir. Doğru cache stratejisi ile bu süreyi dramatik şekilde kısaltabilirsiniz.

name: Optimized Lint Pipeline

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js with aggressive caching
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Cache ESLint results
        uses: actions/cache@v4
        with:
          path: .eslintcache
          key: eslint-${{ runner.os }}-${{ hashFiles('**/.eslintrc*', '**/package-lock.json') }}-${{ github.sha }}
          restore-keys: |
            eslint-${{ runner.os }}-${{ hashFiles('**/.eslintrc*', '**/package-lock.json') }}-
            eslint-${{ runner.os }}-
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint with cache
        run: npx eslint src/ --cache --cache-location .eslintcache --ext .js,.jsx,.ts,.tsx
      
      - name: Cache mypy results  
        uses: actions/cache@v4
        with:
          path: .mypy_cache
          key: mypy-${{ runner.os }}-${{ hashFiles('**/*.py') }}
          restore-keys: |
            mypy-${{ runner.os }}-
      
      - name: Run mypy with cache
        run: mypy src/ --cache-dir .mypy_cache

ESLint’in --cache seçeneği sadece değişen dosyaları tekrar lint ediyor. Binlerce dosyalı bir projede bu fark çok büyük olabiliyor, 5 dakikadan 30 saniyeye düşebiliyor.

Gerçek Dünya Senaryosu: E-ticaret Projesi Pipeline’ı

Bir e-ticaret platformu düşünün: React frontend, Django backend, ve birkaç Python microservice. Bu proje için production-ready bir workflow nasıl görünmeli?

name: E-Commerce Platform Quality Gate

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

env:
  PYTHON_VERSION: '3.11'
  NODE_VERSION: '20'

jobs:
  # Tüm job'ların geçmesi gereken kalite kapısı
  quality-gate:
    runs-on: ubuntu-latest
    needs: [frontend-quality, backend-quality, security-lint]
    if: always()
    steps:
      - name: Check all quality jobs
        run: |
          if [[ "${{ needs.frontend-quality.result }}" != "success" || 
                "${{ needs.backend-quality.result }}" != "success" || 
                "${{ needs.security-lint.result }}" != "success" ]]; then
            echo "Kalite kontrolleri başarısız. Merge engellendirebilir."
            exit 1
          fi
          echo "Tüm kalite kontrolleri başarılı!"

  frontend-quality:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./frontend
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
          cache-dependency-path: frontend/package-lock.json
      - run: npm ci
      - run: npm run lint:strict
      - run: npm run format:check
      - run: npm run typecheck

  backend-quality:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./backend
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: Install quality tools
        run: |
          pip install ruff black isort mypy django-stubs
          pip install -r requirements.txt
      - run: ruff check . --select ALL --ignore ANN,D
      - run: black --check .
      - run: isort --check-only .
      - run: mypy apps/ --strict

  security-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}
      - name: Run Bandit security linter
        run: |
          pip install bandit
          bandit -r backend/ -ll -ii 
            --exclude backend/tests/ 
            -f json -o bandit-report.json || true
      - name: Upload security report
        uses: actions/upload-artifact@v4
        with:
          name: security-lint-report
          path: bandit-report.json

Bu pipeline’da dikkat edin: quality-gate job’u diğer tüm job’ların sonucunu kontrol ediyor. Branch protection rule’larında bu job’u zorunlu hale getirirseniz, kalite kontrollerinden geçmeyen hiçbir PR merge edilemiyor.

Branch Protection ile Entegrasyon

Workflow’ları yazdınız, çalışıyor. Ama geliştiriciler bypass edip direkt push yapabiliyorsa ne anlamı var? GitHub branch protection rules ile bu workflow’ları zorunlu hale getirmek şart.

GitHub repository settings üzerinden yapılan branch protection ayarları için şu noktalar önemli:

  • Require status checks to pass before merging seçeneğini aktif edin
  • Zorunlu hale getireceğiniz job isimlerini “Required status checks” listesine ekleyin
  • Require branches to be up to date before merging seçeneği ile her PR’ın main ile güncel olmasını zorunlu kılın
  • Include administrators seçeneği ile admin kullanıcıların da bu kuralları atlatamamasını sağlayın

Unutmayın, bu kurallar olmadan en güzel workflow’ların bile pratik değeri oldukça sınırlı kalır.

Yaygın Sorunlar ve Çözümleri

Gerçek projelerde karşılaşılan bazı tipik sorunlar ve çözüm yolları:

Problem 1: Lint hataları çok fazla, nereden başlayacağız?

Mevcut projeye lint entegre ediyorsanız yüzlerce hata çıkabilir. Bunu aşmak için kademeli yaklaşım işe yarıyor. Önce sadece yeni dosyaları kontrol eden bir workflow yazın, eski dosyalar için --diff-filter=A gibi seçenekler kullanın.

Problem 2: Farklı işletim sistemlerinde farklı davranış

Windows geliştirici makinelerde CRLF satır sonları, Linux CI’da LF bekler. .gitattributes dosyasında * text=auto ayarı ve .editorconfig bu sorunu büyük ölçüde çözer.

Problem 3: Cache sorunları

Bazen cache bozuluyor ve workflow garip hatalar veriyor. Cache key’e tarih ekleyerek veya Actions sekmesinden cache’i manuel silerek bu sorunu aşabilirsiniz.

Problem 4: Çok yavaş çalışan lint

Paralel job’lar kullanın, lint ve format kontrolünü ayrı job’lara bölün, cache stratejinizi optimize edin. Büyük projelerde lint işlemini dosya bazında bölerek paralel çalıştırmak da mümkün.

Sonuç

GitHub Actions ile lint ve format kontrolü entegrasyonu, ilk kurulumda belki birkaç saatinizi alıyor ama uzun vadede kazandırdığı zaman ve kalite tartışılmaz. Ekipteki her geliştirici aynı standartlara uyuyor, code review süreçleri iş mantığına odaklanıyor, üretim ortamına giden kod çok daha temiz oluyor.

Başlangıç olarak en basit şeyle başlayın: tek bir lint tool, tek bir workflow. Sonra kademeli olarak genişletin. Mükemmel pipeline ilk günden çalışmaz, ama çalışan bir pipeline birkaç saatte kurulabilir.

Özellikle şunu vurgulamak istiyorum: bu araçlar geliştiriciyi kısıtlamak için değil, onu korumak için var. Gece 2’de aceleyle yazılan bir kod, sabah lint’ten geçmek zorunda kalınca biraz daha dikkatli yazılıyor. Bu da hem geliştirici hem de kullanıcı için kazanç.

Son olarak, hangi araçları seçeceğiniz projenize ve ekinize göre değişir ama süreci otomatikleştirmek her zaman manuel kontrolden daha güvenilir. CI/CD pipeline’ı insan hatalarını ortadan kaldırmaz, ama ciddi ölçüde azaltır ve bu bile başlı başına büyük bir değer.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir