Go’da Paket Yazımı: Kapsamlı Bir Rehber
Go programlama dili, modülerlik, yeniden kullanılabilirlik ve kod organizasyonu konularında paketlere büyük önem verir. Paketler, Go projelerinin temel yapı taşlarıdır ve büyük ölçekli uygulamaların geliştirilmesini kolaylaştıran, bakımı yapılabilir ve anlaşılır kod tabanları oluşturmaya olanak tanır. Bu makalede, Go’da paketlerin ne olduğunu, nasıl oluşturulduğunu, yönetildiğini ve en iyi uygulamalarla nasıl tasarlanması gerektiğini derinlemesine inceleyeceğiz.
Giriş: Go Paketleri Neden Önemlidir?
Go’da her program, bir paketin parçasıdır. Küçük bir “Merhaba Dünya” uygulaması bile package main adında bir paketin içinde yer alır. Paketler, ilgili fonksiyonları, tipleri, değişkenleri ve sabitleri bir araya getirerek kodun mantıksal birimlere ayrılmasını sağlar. Bu ayrım, aşağıdaki avantajları sunar:
* Modülerlik: Kodu küçük, yönetilebilir parçalara ayırır, bu da her bir parçanın ayrı ayrı geliştirilmesini ve test edilmesini kolaylaştırır.
* Yeniden Kullanılabilirlik: Bir pakette tanımlanan işlevler, farklı projelerde veya aynı projenin farklı yerlerinde kolayca kullanılabilir. Bu, kod tekrarını azaltır ve geliştirme sürecini hızlandırır.
* Organizasyon: Büyük projelerde kodun düzenli ve anlaşılır kalmasını sağlar. Belirli bir işlevselliği ararken, ilgili pakete doğrudan bakılabilir.
* İsim Çakışmalarını Önleme: Her paketin kendi isim alanı vardır, bu da farklı paketlerde aynı isimde fonksiyon veya değişkenlerin bulunmasına izin verirken çakışmaları önler.
Go’nun standart kütüphanesi de aslında bir dizi paketten oluşur (örn. fmt, net/http, io). Bu, Go ekosisteminin pakete dayalı bir felsefeye sahip olduğunun en açık göstergesidir.
Temel Paket Kavramları
Go’da paketlerle çalışmaya başlamadan önce bazı temel kavramları anlamak önemlidir.
package main ve Adlandırılmış Paketler
Her Go uygulaması bir giriş noktasına ihtiyaç duyar ve bu giriş noktası her zaman package main içinde tanımlanan main fonksiyonudur. package main, çalıştırılabilir bir uygulama oluşturmak için kullanılan özel bir pakettir. Diğer tüm paketler ise genellikle bir kütüphane veya yardımcı işlevler kümesi sağlamak üzere adlandırılmış paketlerdir (örn. package utils, package models, package http).
* package main: Uygulamanın çalıştırılabilir giriş noktasıdır. go build komutuyla bir yürütülebilir dosya oluşturulur.
* Adlandırılmış Paketler: Kütüphane olarak kullanılan ve diğer paketler tarafından içe aktarılan paketlerdir. go build ile doğrudan yürütülemezler, ancak go install ile derlenip GOPATH/bin veya GOBIN konumuna yerleştirilebilirler.
import İfadesi
Bir pakette tanımlanan işlevleri veya tipleri başka bir pakette kullanmak için import ifadesi kullanılır. import ifadesi, paketin tam yolunu belirtir.
package main
import (
"fmt" // Standart kütüphane paketi
"math" // Standart kütüphane paketi
"myproject/utils" // Kendi yazdığımız bir paket
)
func main() {
fmt.Println("Merhaba Go!")
fmt.Println("Pi sayısı:", math.Pi)
fmt.Println("Toplam:", utils.Add(5, 3))
}
Yukarıdaki örnekte, fmt ve math standart kütüphane paketleridir. myproject/utils ise bizim projemizin içinde yer alan özel bir pakettir.
Görünürlük (Visibility) Kuralları
Go’da bir elemanın (fonksiyon, değişken, tip, sabit) başka paketlerden erişilebilir olup olmadığını belirleyen basit bir kural vardır:
* Dışa Aktarılmış (Exported): Eğer bir elemanın ismi büyük harfle başlıyorsa (örn. MyFunction, MyType, MyVariable), o eleman paket dışından erişilebilir (dışa aktarılmıştır).
* Dışa Aktarılmamış (Unexported): Eğer bir elemanın ismi küçük harfle başlıyorsa (örn. myFunction, myType, myVariable), o eleman yalnızca tanımlandığı paket içinde erişilebilir (dışa aktarılmamıştır).
Bu kural, paketlerin dahili implementasyon detaylarını dışarıya sızdırmadan temiz API’ler sunmasını sağlar.
// utils/math.go
package utils
// Add iki sayıyı toplar ve sonucu döndürür. (Dışa aktarılmış)
func Add(a, b int) int {
return a + b
}
// subtract iki sayıyı çıkarır ve sonucu döndürür. (Dışa aktarılmamış)
func subtract(a, b int) int {
return a - b
}
// main.go
package main
import (
"fmt"
"myproject/utils"
)
func main() {
fmt.Println(utils.Add(10, 5)) // Çalışır, çünkü Add dışa aktarılmıştır.
// fmt.Println(utils.subtract(10, 5)) // Hata verir, çünkü subtract dışa aktarılmamıştır.
}
Paket İsimleri ve Dizin İsimleri
Genellikle bir paketin ismi, içinde bulunduğu dizinin ismiyle aynı olmalıdır. Bu bir zorunluluk değil, bir kuraldır ve Go topluluğunda yaygın olarak kabul görmüştür. Örneğin, utils adlı bir dizin içindeki Go dosyaları package utils olarak başlamalıdır. Ancak, aynı dizin içindeki tüm Go dosyaları aynı package bildirimiyle başlamalıdır. Bir dizin içinde birden fazla paket olamaz.
Paket Yapısı
Go projeleri genellikle belirli bir dizin yapısını takip eder. Go Modülleri ile bu yapı daha esnek hale gelmiştir, ancak temel prensipler aynıdır.
Go Modülleri ve Proje Yapısı
Modern Go projeleri Go Modülleri kullanır. Bir Go modülü, bir veya daha fazla paketin koleksiyonudur ve bağımlılık yönetimini sağlar.
1. Modül Başlatma: Projenizin kök dizininde go mod init komutuyla bir modül başlatılır. genellikle projenizin VCS (örn. GitHub) yoluyla aynıdır, örneğin github.com/kullaniciadi/projeadi. Bu, go.mod dosyasını oluşturur.
2. Paket Dizinleri: Her paket, modül kök dizini altında kendi dizininde yer alır.
myproject/
├── go.mod
├── main.go
├── utils/
│ └── math.go
├── models/
│ └── user.go
└── config/
└── settings.go
Bu yapıda:
* myproject modülünün kök dizinidir.
* main.go, package main içindedir.
* utils/math.go, package utils içindedir.
* models/user.go, package models içindedir.
* config/settings.go, package config içindedir.
Diğer paketlerden bu paketleri içe aktarırken, modül yolu kullanılır:
import "github.com/kullaniciadi/projeadi/utils"
Alt Paketler
Bir paket, mantıksal olarak daha küçük birimlere ayrılabilecek kadar büyüdüğünde, alt paketler oluşturulabilir. Örneğin, bir http paketi altında server, client, middleware gibi alt paketler olabilir.
myproject/
├── go.mod
├── main.go
├── http/
│ ├── server/
│ │ └── server.go // package server
│ ├── client/
│ │ └── client.go // package client
│ └── handlers.go // package http (handlerlar doğrudan http paketi içinde olabilir)
Bu durumda, server.go dosyası package server olarak başlar ve http paketi içinden http/server olarak içe aktarılır. handlers.go ise package http olarak kalır.
Paket Oluşturma Adımları
Şimdi pratik bir örnek üzerinden kendi Go paketinizi nasıl oluşturacağınızı görelim. Basit bir matematik işlemleri paketi oluşturalım.
1. Yeni Bir Modül Başlatın:
Öncelikle, projeniz için bir dizin oluşturun ve içine girin:
mkdir mymathlib
cd mymathlib
go mod init example.com/mymathlib
Bu komut, go.mod dosyasını oluşturacaktır.
2. Paket Dizini Oluşturun:
mymathlib modülü içinde calculator adında bir paket oluşturmak için bir dizin oluşturun:
mkdir calculator
3. Paket Dosyasını Yazın:
calculator dizini içinde calculator.go adında bir dosya oluşturun ve içine aşağıdaki kodu yazın:
// calculator/calculator.go
package calculator
// Add fonksiyonu iki tam sayıyı toplar ve sonucu döndürür.
func Add(a, b int) int {
return a + b
}
// Subtract fonksiyonu iki tam sayıdan birincisinden ikincisini çıkarır.
func Subtract(a, b int) int {
return a - b
}
// Multiply fonksiyonu iki tam sayıyı çarpar.
func Multiply(a, b int) int {
return a * b
}
// Divide fonksiyonu iki tam sayıyı böler ve sonucu döndürür.
// Sıfıra bölme durumunda bir hata döndürür.
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("sıfıra bölme hatası") // fmt paketi import edilmeli
}
return a / b, nil
}
Not: Yukarıdaki Divide fonksiyonunda fmt paketi kullanıldığı için dosyanın başına import "fmt" eklemeyi unutmayın.
4. Paketi Kullanın:
Şimdi, mymathlib modülünün kök dizininde (yani calculator dizininin bir üstünde) main.go adında bir dosya oluşturun ve calculator paketini kullanın:
// main.go
package main
import (
"fmt"
"example.com/mymathlib/calculator" // Kendi paketimizi import ediyoruz
)
func main() {
sum := calculator.Add(10, 5)
fmt.Printf("10 + 5 = %d\n", sum)
diff := calculator.Subtract(10, 5)
fmt.Printf("10 - 5 = %d\n", diff)
product := calculator.Multiply(10, 5)
fmt.Printf("10 * 5 = %d\n", product)
quotient, err := calculator.Divide(10, 2)
if err != nil {
fmt.Printf("Bölme hatası: %s\n", err)
} else {
fmt.Printf("10 / 2 = %d\n", quotient)
}
quotientZero, errZero := calculator.Divide(10, 0)
if errZero != nil {
fmt.Printf("Bölme hatası: %s\n", errZero)
} else {
fmt.Printf("10 / 0 = %d\n", quotientZero)
}
}
5. Uygulamayı Çalıştırın:
mymathlib modülünün kök dizininde go run main.go komutunu çalıştırın:
go run main.go
Çıktı şöyle olacaktır:
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 2 = 5
Bölme hatası: sıfıra bölme hatası
Bu basit örnek, kendi Go paketinizin nasıl oluşturulacağını, dışa aktarılan işlevleri nasıl tanımlayacağınızı ve bunları başka bir pakette nasıl kullanacağınızı göstermektedir.
Paketleri Kullanma
Paketleri kullanırken farklı senaryolarla karşılaşabiliriz.
Yerel Paketleri İçe Aktarma
Yukarıdaki örnekte olduğu gibi, aynı modül içindeki paketleri içe aktarırken, go.mod dosyasında tanımlanan modül yolunu ve paketin göreceli yolunu kullanırız:
import "example.com/mymathlib/calculator"
Standart Kütüphane Paketlerini İçe Aktarma
Go’nun zengin standart kütüphanesindeki paketler doğrudan isimleriyle içe aktarılır:
import "fmt"
import "net/http"
import "os"
Üçüncü Taraf Paketleri İçe Aktarma
Harici (üçüncü taraf) paketleri kullanmak için go get komutunu kullanırız. Bu komut paketi indirir ve go.mod dosyanıza bağımlılık olarak ekler.
go get github.com/gin-gonic/gin
Ardından, bu paketi kodunuzda kullanabilirsiniz:
import "github.com/gin-gonic/gin"
Paket Takma Adları (Aliases)
Bazen iki farklı paketin aynı isme sahip olması veya uzun bir paket ismini kısaltmak istememiz gibi durumlar olabilir. Bu durumda paket takma adları (aliases) kullanırız:
import (
mycalc "example.com/mymathlib/calculator" // calculator paketine mycalc takma adını verdik
"fmt"
)
func main() {
sum := mycalc.Add(20, 10)
fmt.Printf("20 + 10 = %d\n", sum)
}
Boş İçe Aktarma (Blank Import _)
Bazen bir paketi doğrudan kullanmak istemeyiz, ancak paketin init fonksiyonunun çalışmasını isteriz (genellikle veritabanı sürücüleri gibi yan etkileri olan paketler için). Bu durumda boş içe aktarma kullanılır:
import _ "github.com/go-sql-driver/mysql" // Sadece init fonksiyonunu çalıştırır
Bu, paketin dışa aktarılan elemanlarını kodunuzda kullanmanıza izin vermez, ancak paketin başlatma işlemlerini tetikler.
Nokta İçe Aktarma (Dot Import . – Dikkatli Kullanın)
Nokta içe aktarma, paketin dışa aktarılan tüm elemanlarını mevcut paketin isim alanına getirir, böylece paket ismini belirtmeye gerek kalmaz.
import (
. "fmt" // fmt paketini nokta ile içe aktardık
)
func main() {
Println("Merhaba, paket ismi olmadan!") // fmt.Println yerine doğrudan Println kullanıldı
}
Bu, kodun daha kısa görünmesini sağlasa da, isim çakışmalarına yol açabileceği ve kodun okunabilirliğini azaltabileceği için genellikle tavsiye edilmez. Sadece test kodlarında veya çok özel durumlarda kullanılmalıdır.
Paket Yönetimi ve Modüller
Go Modülleri, Go 1.11 ile tanıtılan ve Go projelerinde bağımlılık yönetimini kökten değiştiren bir özelliktir.
go.mod ve go.sum Dosyaları
* go.mod: Bir modülün kök dizininde bulunur ve modülün yolunu, Go sürümünü ve tüm doğrudan ve dolaylı bağımlılıklarını (modül yolları ve sürümleriyle birlikte) listeler.
* go.sum: Her bağımlılığın kriptografik hash’lerini içerir. Bu, indirilen bağımlılıkların değiştirilmediğinden ve güvenli olduğundan emin olmak için kullanılır.
Bağımlılık Ekleme, Yükseltme ve Kaldırma
* Ekleme: Bir paketi kodunuzda import ettiğinizde ve go build veya go run gibi bir komut çalıştırdığınızda, Go otomatik olarak paketi indirir ve go.mod dosyasına ekler. Manuel olarak eklemek için go get kullanabilirsiniz.
* Yükseltme: Bir bağımlılığın yeni bir sürümüne geçmek için go get -u veya go get kullanabilirsiniz.
* Kaldırma: Kodunuzdan bir paketin tüm kullanımlarını kaldırdığınızda, go tidy komutunu çalıştırarak go.mod dosyasından gereksiz bağımlılıkları temizleyebilirsiniz.
go mod tidy komutu, go.mod dosyasını kodunuzda gerçekten kullanılan bağımlılıkları yansıtacak şekilde günceller ve go.sum dosyasını da buna göre düzenler.
İyi Paket Yazım Uygulamaları
Etkili ve sürdürülebilir Go paketleri yazmak için bazı en iyi uygulamaları takip etmek önemlidir.
Tek Sorumluluk Prensibi (Single Responsibility Principle – SRP)
Her paket, tek bir iyi tanımlanmış sorumluluğa sahip olmalıdır. Bir paket çok fazla şey yapmaya çalışıyorsa, muhtemelen birden fazla pakete ayrılmalıdır. Örneğin, bir auth paketi yalnızca kimlik doğrulama ve yetkilendirme ile ilgilenmeli, veritabanı işlemleri veya HTTP sunucusu yönetimiyle değil. Bu, paketlerin daha küçük, odaklanmış ve yönetilebilir olmasını sağlar.
Açık ve Anlaşılır İsimlendirme
* Paket İsimleri: Kısa, küçük harfli ve anlamlı olmalıdır. Genellikle paketin yaptığı işi yansıtan tekil bir isim tercih edilir (örn. http, json, time, errors, strings, math). util veya common gibi genel isimlerden kaçının; bunlar genellikle SRP’nin ihlal edildiğinin bir işaretidir.
* Dışa Aktarılan Elemanlar: Büyük harfle başlamalı ve açıklayıcı olmalıdır (örn. GetUserByID, Config, ErrNotFound).
* Dışa Aktarılmayan Elemanlar: Küçük harfle başlamalı ve sadece paket içinde geçerli olmalıdır.
Dokümantasyon
Go’da dokümantasyon, kodun önemli bir parçasıdır. Her dışa aktarılan eleman (fonksiyon, tip, değişken, sabit) için açıklayıcı yorumlar yazılmalıdır. Bu yorumlar godoc aracı tarafından okunur ve otomatik olarak dokümantasyon oluşturur.
// calculator/calculator.go
// Package calculator provides basic arithmetic operations.
package calculator
// Add adds two integers and returns their sum.
func Add(a, b int) int {
return a + b
}
// Result represents the outcome of an arithmetic operation.
type Result struct {
Value int
Error error
}
Paket seviyesindeki yorumlar, package bildiriminin hemen üstüne yazılmalı ve paketin ne işe yaradığını açıklamalıdır.
Hata Yönetimi
Go’da hatalar, fonksiyonların son dönüş değeri olarak error arayüzü tipiyle döndürülür. Paketlerinizde anlamlı ve açıklayıcı hata tipleri tanımlamak önemlidir. Özel hata tipleri oluşturarak, çağıran kodun hatayı daha spesifik bir şekilde işlemesini sağlayabilirsiniz.
// calculator/calculator.go
package calculator
import "fmt"
// ErrDivisionByZero indicates an attempt to divide by zero.
var ErrDivisionByZero = fmt.Errorf("sıfıra bölme hatası")
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, ErrDivisionByZero
}
return a / b, nil
}
Test Edilebilirlik
İyi tasarlanmış paketler kolayca test edilebilir olmalıdır. Go, yerleşik bir test çerçevesine sahiptir. Her Go paketi için, aynı dizinde _test.go uzantılı bir dosya oluşturularak testler yazılır.
// calculator/calculator_test.go
package calculator
import "testing"
func TestAdd(t *testing.T) {
sum := Add(1, 2)
expected := 3
if sum != expected {
t.Errorf("Add(1, 2) beklenen %d, alınan %d", expected, sum)
}
}
func TestDivide(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Errorf("Divide(10, 0) bir hata döndürmeliydi, ancak döndürmedi.")
}
if err != ErrDivisionByZero {
t.Errorf("Divide(10, 0) beklenen hata %v, alınan %v", ErrDivisionByZero, err)
}
result, err := Divide(10, 2)
if err != nil {
t.Errorf("Divide(10, 2) hata döndürmemeliydi: %v", err)
}
if result != 5 {
t.Errorf("Divide(10, 2) beklenen 5, alınan %d", result)
}
}
Testleri çalıştırmak için paket dizininde go test komutu kullanılır.
Bağımlılık Yönetimi (Minimize Dependencies)
Paketlerinizin mümkün olduğunca az harici bağımlılığa sahip olması iyi bir uygulamadır. Her yeni bağımlılık, projenizin karmaşıklığını ve potansiyel güvenlik açıklarını artırır. Eğer bir işlevsellik Go standart kütüphanesi ile sağlanabiliyorsa, harici bir kütüphane kullanmaktan kaçının.
API Tasarımı
Paketinizin dışa aktardığı arayüz (API) basit, sezgisel ve kararlı olmalıdır. Dışa aktarılan fonksiyonların ve tiplerin kullanımı kolay olmalı ve gelecekteki değişikliklerden en az etkilenmelidir. Bir paketin API’si, o paketin “sözleşmesi”dir.
Görünürlük (Export Only What’s Necessary)
Sadece diğer paketler tarafından kullanılması gereken elemanları dışa aktarın. Dahili implementasyon detaylarını dışa aktarmaktan kaçının. Bu, paketin iç yapısını değiştirirken dışarıdaki kodu bozma riskini azaltır ve paketin API’sini temiz tutar.
Gelişmiş Konulara Kısa Bir Bakış
Go paketleri hakkında daha derinlemesine bilgi edinmek isteyenler için bazı ileri düzey konular şunlardır:
* Build Tags: Farklı işletim sistemleri veya mimariler için farklı kod parçalarını derlemek üzere kullanılır. Örneğin, // +build linux etiketi, o dosyanın yalnızca Linux’ta derlenmesini sağlar.
* Internal Paketler: Go 1.4 ile tanıtılan internal dizini, bir paketin içindeki kodun sadece o modül içinde kullanılabilmesini sağlar, dış modüllerden erişilemez. Örneğin, myproject/internal/cache paketi, sadece myproject modülü içindeki diğer paketler tarafından kullanılabilir.
* CGO: Go kodundan C kodunu çağırmak veya C kodunu Go’ya bağlamak için kullanılır. Özellikle sistem seviyesi programlama veya mevcut C kütüphanelerini kullanmak gerektiğinde faydalıdır.
* Kod Üretimi (Code Generation): stringer gibi araçlar veya özel go generate komutları kullanarak kodunuzu otomatik olarak üretebilirsiniz. Bu, tekrar eden kod yazma ihtiyacını azaltır ve hataları önler.
Örnek Uygulama: Bir Görev Yöneticisi Modülü
Çoklu paketlerin nasıl bir araya geldiğini göstermek için basit bir görev yöneticisi modülü oluşturalım.
taskmanager/
├── go.mod
├── main.go
├── task/
│ └── task.go
└── storage/
└── memory.go
1. go.mod dosyasını başlatın:
mkdir taskmanager
cd taskmanager
go mod init example.com/taskmanager
2. task paketi (task/task.go):
// task/task.go
package task
import (
"fmt"
"time"
)
// Task represents a single task in the task manager.
type Task struct {
ID string
Description string
IsCompleted bool
CreatedAt time.Time
}
// NewTask creates a new Task instance.
func NewTask(id, description string) *Task {
return &Task{
ID: id,
Description: description,
IsCompleted: false,
CreatedAt: time.Now(),
}
}
// Complete marks the task as completed.
func (t *Task) Complete() {
t.IsCompleted = true
}
// String provides a string representation of the Task.
func (t *Task) String() string {
status := "Beklemede"
if t.IsCompleted {
status = "Tamamlandı"
}
return fmt.Sprintf("ID: %s, Açıklama: '%s', Durum: %s, Oluşturulma: %s",
t.ID, t.Description, status, t.CreatedAt.Format("2006-01-02 15:04:05"))
}
3. storage paketi (storage/memory.go):
Bu paket, görevleri bellekte saklayacak basit bir depolama mekanizması sağlar.
// storage/memory.go
package storage
import (
"fmt"
"sync"
"example.com/taskmanager/task" // task paketini import ediyoruz
)
// Store defines the interface for task storage.
type Store interface {
AddTask(t *task.Task) error
GetTask(id string) (*task.Task, error)
GetAllTasks() []*task.Task
UpdateTask(t *task.Task) error
DeleteTask(id string) error
}
// MemoryStore is an in-memory implementation of the Store interface.
type MemoryStore struct {
tasks map[string]*task.Task
mu sync.RWMutex // Eşzamanlı erişim için mutex
}
// NewMemoryStore creates a new MemoryStore.
func NewMemoryStore() *MemoryStore {
return &MemoryStore{
tasks: make(map[string]*task.Task),
}
}
// AddTask adds a new task to the store.
func (s MemoryStore) AddTask(t task.Task) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.tasks[t.ID]; exists {
return fmt.Errorf("ID %s zaten mevcut", t.ID)
}
s.tasks[t.ID] = t
return nil
}
// GetTask retrieves a task by its ID.
func (s MemoryStore) GetTask(id string) (task.Task, error) {
s.mu.RLock()
defer s.mu.RUnlock()
t, exists := s.tasks[id]
if !exists {
return nil, fmt.Errorf("ID %s ile görev bulunamadı", id)
}
return t, nil
}
// GetAllTasks retrieves all tasks in the store.
func (s MemoryStore) GetAllTasks() []task.Task {
s.mu.RLock()
defer s.mu.RUnlock()
allTasks := make([]*task.Task, 0, len(s.tasks))
for _, t := range s.tasks {
allTasks = append(allTasks, t)
}
return allTasks
}
// UpdateTask updates an existing task.
func (s MemoryStore) UpdateTask(t task.Task) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.tasks[t.ID]; !exists {
return fmt.Errorf("ID %s ile görev bulunamadı, güncellenemedi", t.ID)
}
s.tasks[t.ID] = t
return nil
}
// DeleteTask deletes a task by its ID.
func (s *MemoryStore) DeleteTask(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.tasks[id]; !exists {
return fmt.Errorf("ID %s ile görev bulunamadı, silinemedi", id)
}
delete(s.tasks, id)
return nil
}
4. main paketi (main.go):
// main.go
package main
import (
"fmt"
"log"
"example.com/taskmanager/storage" // storage paketini import ediyoruz
"example.com/taskmanager/task" // task paketini import ediyoruz
)
func main() {
// Bellek tabanlı depolama oluştur
store := storage.NewMemoryStore()
// Yeni görevler oluştur
task1 := task.NewTask("1", "Go paketi yazma makalesini bitir")
task2 := task.NewTask("2", "Alışveriş yap")
task3 := task.NewTask("3", "Spor yap")
// Görevleri depolamaya ekle
if err := store.AddTask(task1); err != nil {
log.Fatalf("Görev eklenirken hata oluştu: %v", err)
}
if err := store.AddTask(task2); err != nil {
log.Fatalf("Görev eklenirken hata oluştu: %v", err)
}
if err := store.AddTask(task3); err != nil {
log.Fatalf("Görev eklenirken hata oluştu: %v", err)
}
fmt.Println("--- Tüm Görevler ---")
printAllTasks(store)
// Bir görevi tamamla
task1.Complete()
if err := store.UpdateTask(task1); err != nil {
log.Fatalf("Görev güncellenirken hata oluştu: %v", err)
}
fmt.Printf("\n--- Görev '%s' tamamlandı ---\n", task1.Description)
printAllTasks(store)
// Bir görevi sil
if err := store.DeleteTask("2"); err != nil {
log.Fatalf("Görev silinirken hata oluştu: %v", err)
}
fmt.Println("\n--- Görev 'Alışveriş yap' silindi ---")
printAllTasks(store)
// Belirli bir görevi getir
retrievedTask, err := store.GetTask("3")
if err != nil {
log.Fatalf("Görev getirilirken hata oluştu: %v", err)
}
fmt.Printf("\n--- Getirilen Görev: %s ---\n", retrievedTask)
}
func printAllTasks(store storage.Store) {
tasks := store.GetAllTasks()
if len(tasks) == 0 {
fmt.Println("Hiç görev yok.")
return
}
for _, t := range tasks {
fmt.Println(t)
}
}
Bu örnek, task ve storage olmak üzere iki farklı paketin nasıl tanımlandığını ve main paketinde nasıl bir araya getirildiğini gösteriyor. task paketi görev nesnesinin yapısını ve davranışlarını tanımlarken, storage paketi bu görevleri saklama ve yönetme sorumluluğunu üstleniyor. Bu, sorumlulukların ayrılmasına ve kodun daha modüler olmasına olanak tanır.
Çalıştırmak için taskmanager dizininde:
go run main.go
Sonuç
Go’da paketler, kod organizasyonunun, yeniden kullanılabilirliğin ve sürdürülebilirliğin temelini oluşturur. Modüler Go uygulamaları geliştirmek için paketlerin nasıl çalıştığını, nasıl oluşturulduğunu ve en iyi uygulamalarla nasıl tasarlanması gerektiğini anlamak hayati öneme sahiptir. Tek sorumluluk prensibini benimseyerek, açık isimler kullanarak, iyi dokümantasyon sağlayarak ve test edilebilir kod yazarak, hem kendi projelerinizde hem de açık kaynak topluluğunda değerli ve bakımı kolay Go paketleri oluşturabilirsiniz. Go Modülleri ile bağımlılık yönetimi de büyük ölçüde basitleşmiş ve Go geliştirme deneyimini daha keyifli hale getirmiştir. Bu rehberdeki bilgileri kullanarak, Go’da sağlam ve ölçeklenebilir uygulamalar geliştirmek için ilk adımı atmış olacaksınız.