İçeriğe geç
Muhammet Şafak
en
Diller 2 dk okuma

Go'da ilk CLI aracımı yazmak

Yeni öğrendiğim Go'yu gerçek bir CLI aracı yazarak pekiştirdim: dilin pratikteki hissi ve standart kütüphaneyle ne kadar uzağa gidilebildiği.

Kapak görseli — karanlıkta duran klavyede tek bir mor aydınlatılmış Enter tuşu, kenarına Go gopher figürlü mor yuvarlak rozet yaslanmış

Bu yazı 7 yıl önce yazıldı — o günün koşullarını yansıtır.


Nisan ayında Go öğrenmeye karar verdiğimde “küçük ama gerçek bir şey yazacağım” demiştim. Teorik öğrenme, yani yalnızca dokümantasyon okuyup playground’da denemeler yapmak, bir noktadan sonra kısırlaşıyor. Dili gerçekten anlamak için bir ihtiyacı karşılaması gerekiyor.

Seçtiğim araç basit ama kullandığım bir şey: proje dizinlerini tarayıp .env.example dosyası olan ama .env dosyası olmayan projeleri listeleyen bir CLI. Geliştirme makinemde onlarca proje klasörü var ve zaman zaman bir projeyi açıp “neden çalışmıyor?” diye düşünürken .env dosyasının eksik olduğunu görüyorum. Bu küçük araca ihtiyacım vardı.

Yapı ve paket organizasyonu

Go’da proje organizasyonu PHP’den farklı. main paketi çalıştırılabilir binary üretiyor; başka paketler ise kütüphane. Küçük bir CLI için tek dosya da yeter ama mantığı ayırmayı tercih ettim.

env-checker/
  main.go
  scanner/
    scanner.go

scanner paketi dizin tarama mantığını, main.go ise argüman işleme ve çıktıyı barındırıyor.

Dizin tarama

Go’nun standart kütüphanesi filepath.Walk ile ağaç gezme işini hallediyor. Dış paket yok.

package scanner

import (
    "io/ioutil"
    "os"
    "path/filepath"
)

type Result struct {
    Path      string
    HasExample bool
    HasEnv     bool
}

func Scan(root string) ([]Result, error) {
    var results []Result

    entries, err := ioutil.ReadDir(root)
    if err != nil {
        return nil, err
    }

    for _, entry := range entries {
        if !entry.IsDir() {
            continue
        }

        dir := filepath.Join(root, entry.Name())
        hasExample := fileExists(filepath.Join(dir, ".env.example"))
        hasEnv := fileExists(filepath.Join(dir, ".env"))

        if hasExample {
            results = append(results, Result{
                Path:       dir,
                HasExample: true,
                HasEnv:     hasEnv,
            })
        }
    }

    return results, nil
}

func fileExists(path string) bool {
    _, err := os.Stat(path)
    return err == nil
}

PHP’den gelince append fonksiyonu ve nil dilimleri biraz farklı hissettiriyor. Go’da boş bir slice nil; append her çağrıda yeni bir dilim döndürüyor, yerinde değiştirmiyor.

main.go ve çıktı

package main

import (
    "fmt"
    "os"
    "env-checker/scanner"
)

func main() {
    root := "."
    if len(os.Args) > 1 {
        root = os.Args[1]
    }

    results, err := scanner.Scan(root)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Hata: %v\n", err)
        os.Exit(1)
    }

    missing := 0
    for _, r := range results {
        if !r.HasEnv {
            fmt.Printf("EKSIK: %s\n", r.Path)
            missing++
        }
    }

    if missing == 0 {
        fmt.Println("Tüm projelerde .env mevcut.")
    } else {
        fmt.Printf("\n%d projede .env eksik.\n", missing)
    }
}

os.Args ile argüman almak, fmt.Fprintf(os.Stderr, ...) ile standart hata çıktısı vermek — bunların hepsi standart kütüphanede, sıfır bağımlılık.

Derleme ve dağıtım

Beni en çok etkileyen şeylerden biri: go build komutu tek bir çalıştırılabilir dosya üretiyor. PHP’de bir CLI aracı paylaşmak istediğinizde ya Composer paketi yapıyorsunuz ya da PHP kurulu bir ortam gerekiyor. Go ile üretilen ikili, hiçbir bağımlılık olmadan çalışıyor.

go build -o env-checker .
# Oluşan ikili dosyayı /usr/local/bin'e koyup her yerden çağırabilirsiniz

Linux ve macOS için cross-compilation de dahili:

GOOS=linux GOARCH=amd64 go build -o env-checker-linux .

Bu, CLI araçları için Go’nun ciddi bir avantajı. Dağıtmak, paylaşmak, farklı sistemlerde çalıştırmak son derece kolay. Bir araç yapıp başkasına yollamak istediğinizde “Go kurulu mu?” diye sormak zorunda kalmıyorsunuz.

Go’nun bu deneyimden çıkardığım dersleri

err her yerde. Alışmak zaman aldı ama artık anlıyorum: hata akışı explicit olunca ne olabileceğini görmezden gelmek zorlaşıyor. PHP’de bir exception’ı yakalamayı unuttunuz mu? Sessizce kaçabiliyor. Go’da err’i kontrol etmezseniz lint uyarı veriyor, meslektaşlar code review’da fark ediyor.

Standart kütüphane gerçekten zengin. Bu küçük araç için tek dış bağımlılık kullanmadım. Dosya işlemleri, string biçimleme, argüman işleme — hepsi dahili.

Structural typing farklı. Go’da interface’ler açıkça “implement” edilmiyor. Bir struct gerekli metodları içeriyorsa, o interface’i otomatik sağlıyor. PHP’den gelen biri için bu sezgisel değil ama esneklik sağlıyor. scanner.Result tipini bir interface’e bağlamadan, ileride farklı çıktı formatları için aynı veriyi kullanmak mümkün — yapıyı değiştirmeden.

Sürtünmesiz geçti demek istemem. İlk günlerde en çok zorlandığım şey, PHP’de her şey için kullandığım associative array’lerin Go’daki karşılığı olan map’in tek bir anahtar ve tek bir değer tipiyle sınırlı olması oldu; farklı tipte alanlar taşıyan veriyi önceden bir struct olarak tanımlamak gerekiyor. Başta yavaşlatıcı geldi, ama birkaç gün sonra bunun bir kısıt değil bir disiplin olduğunu fark ettim: verinin şeklini baştan düşünmek zorunda kalıyorsunuz. PHP’de “sonra bir alan daha eklerim” diye bıraktığım belirsizlikleri, Go bana en baştan kapattırdı.

Küçük bir araç ama Go’nun hissini anlamak için yeterliydi. Yeni bir dili gerçek bir ihtiyaç üzerinden öğrenmenin, dokümantasyon okumaktan kat kat verimli olduğunu bir kez daha gördüm. Sonraki adım: dilin concurrency modelini kavramak.

Bu konudaki deneyler

Dile özgü serileştirme yerine dondurulmuş bir JSON zarfı; polyglot kuyruk standardı BabelQueue'ya dönüştü.

Şu an ne yapıyor

Dondurulmuş JSON zarfı dört dilde — PHP, Python, Go, Node.js — aynı baytları okutuyor; sidecar ya da broker eklentisi gerekmiyor. Polyglot kuyruk kuran ekipler spesifikasyonu ve SDK'ları bugün kullanabilir.

Açık kaynak JSON Redis RabbitMQ +4 daha
Mart 2026 — Haziran 2026

Diff'i makineden çıkarmadan bir modele denetleten, sağlayıcıdan bağımsız Go CLI; CommitBrief ürününe dönüştü.

Şu an ne yapıyor

Diff hiç makineden çıkmadan yerel kod incelemesi yapıyor; `Provider` arayüzü sayesinde Anthropic, OpenAI, Gemini ve Ollama aynı sözleşmeyle takılıyor. İmzalı binary'ler yayında — diff'ini bir bulut sağlayıcısına vermek istemeyen herkes kurabilir.

Açık kaynak Go Cobra go-git +4 daha
Ocak 2026 — Nisan 2026
Etiketler: #Go
Uzmanlık: Go Geliştirici
Paylaş:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi