Ana içeriğe geç

Bölüm 17/03: Golang Paketi Geliştirmek

Sık kullandığımız yardımcı fonksiyonları bir kütüphane haline getirebiliriz. Böylece başka projelerde de bu paketten faydalanabiliriz. Reusable yani tekrar kullanılabilir kod parçaları oluşturabiliriz.

Paket aslında bir dizin (folder). Örneklerde sık kullandığımız fmt paketi. Hemen kaynak koda bakalım:

$ ls -al "$(go env GOROOT)/src/fmt"
total 236K
drwxr-xr-x 16 vigo admin  512 Aug  4 23:14 .
drwxr-xr-x 73 vigo admin 2.3K Aug  4 23:14 ..
-rw-r--r--  1 vigo admin  15K Aug  4 23:14 doc.go
-rw-r--r--  1 vigo admin 1.7K Aug  4 23:14 errors.go
-rw-r--r--  1 vigo admin 3.7K Aug  4 23:14 errors_test.go
-rw-r--r--  1 vigo admin  12K Aug  4 23:14 example_test.go
-rw-r--r--  1 vigo admin  219 Aug  4 23:14 export_test.go
-rw-r--r--  1 vigo admin  59K Aug  4 23:14 fmt_test.go
-rw-r--r--  1 vigo admin  14K Aug  4 23:14 format.go
-rw-r--r--  1 vigo admin 1.6K Aug  4 23:14 gostringer_example_test.go
-rw-r--r--  1 vigo admin  32K Aug  4 23:14 print.go
-rw-r--r--  1 vigo admin  32K Aug  4 23:14 scan.go
-rw-r--r--  1 vigo admin  40K Aug  4 23:14 scan_test.go
-rw-r--r--  1 vigo admin 1.5K Aug  4 23:14 state_test.go
-rw-r--r--  1 vigo admin  551 Aug  4 23:14 stringer_example_test.go
-rw-r--r--  1 vigo admin 2.2K Aug  4 23:14 stringer_test.go

doc.go’ya bakalım:

$ cat "$(go env GOROOT)/src/fmt/doc.go"
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package fmt implements formatted I/O with functions analogous
to C's printf and scanf.  The format 'verbs' are derived from C's but
are simpler.

# Printing

The verbs:
.
.
.
Note: Fscan etc. can read one character (rune) past the input
they return, which means that a loop calling a scan routine
may skip some of the input.  This is usually a problem only
when there is no space between input values.  If the reader
provided to Fscan implements ReadRune, that method will be used
to read characters.  If the reader also implements UnreadRune,
that method will be used to save the character and successive
calls will not lose data.  To attach ReadRune and UnreadRune
methods to a reader without that capability, use
bufio.NewReader.
*/
package fmt

Kocaaaaman bir comment ve son satırda paketin adı package fmt yazıyor. Bu dizindeki tüm dosyaların package deklarasyon kısmına baksak hepsinde de package fmt yazdığını görürüz. Mantık şu;

paket1/
    paket2/
        paket2.go
    paket1.go

Dikkat ettiyseniz fmt paketini import ederken;

import "fmt"

şeklinde kullanıyoruz. yani;

import foo/bar/baz/go/1.21.0/libexec/src/fmt

gibi bir tanım yok, çünkü go kurulunca otomatik olarak built-in paketlerin nereye kurulduğunu biliyor;

$ go env GOROOT
/opt/homebrew/Cellar/go/1.21.0/libexec

ve paketlerin da src/ altında olduğunu biliyor. Aslında biz import "fmt" dediğimizde go otomatik olarak; /opt/homebrew/Cellar/go/1.21.0/libexec/src/ değerini ekliyor.

stringutils Paketi

Evet, bir pakete ihtiyacımız var. İçinde string’lerle ilgili küçük küçük fonksiyonlar olacak. func Reverse(string)string mesela. Bu paket için nasıl isim vereceğiz?

Paket adını düşünürken hep nasıl import edeceğimi, paket içinden fonksiyonları nasıl çağıracağımı düşünürüm. Eğer python ya da ruby kodu yazıyor olsak; utils.py ya da helper.rb ya da common.py gibi bir dosya yapar geçerdik.

Go’da isimlendirme kurallarında bahsetmiştik;

  • Paketin amacına uygun bir isim olmalı
  • Olası başka paket adlarıyla çakışmamalı. Ben olsam uuid diye bir paket yapmak durumunda kalsam, ayrıştırıcı bir isim düşünürüm: simpleuuid mesela...
  • Paket adı mümkünse tek kelime olsun.

Dedik ya, string’ler için küçük yardımcı fonksiyonlar. Bu bakımdan adına; stringutils diyorum ve bu paketi ben dahil herkes kullanabilsin diye GitHub’a koymayı planlıyorum:

$ cd /path/to/development/
$ mkdir stringutils && cd stringutils

# go mod init github.com/<GITHUB-KULLANICI-ADINIZ>/stringutils
$ go mod init github.com/vigo/stringutils
go: creating new go.mod: module github.com/vigo/stringutils

$ ls
go.mod

$ cat go.mod 
module github.com/vigo/stringutils

go 1.21.0

Modül olarak initialize edilince, go.mod adında bir dosya oluşur. Bu dosya içinde; paketin adı ve paketin bağımlılıkları yazar. Uygulamaya katılacak her bağımlılık sonrası, yani kod içinde kullandığınız her ilave paketin bu dosyada yer alması için, paket kurulumundan sonra mutlaka go mod tidy yapmak gerekiyor!.

Otomatik olarak, paket ekleyip çıkarttıkça go.sum dosyası da güncelleniyor. Bu dosyada geriye dönük uyumluluk adına, kurulan tüm paketlerin ve hatta o paketlerin de bağımlı olduğu paketlerin bir hash-checksum listesi duruyor.

Baze bu go.mod ve go.sum dosyaları canımızı sıkabiliyor. Hatta bazen bu module summary olayını komple kapatıyoruz. GONOSUMDB environment variable’ı sayesinde;

GONOSUMDB="github.com/vigo"

kurulan paketlerden başı github.com/vigo başlayanların go.sum kısmını işleme katma diyoruz.

Bazen kütüphanelerimiz GitHub’a koyarız ama repo sadece bize erişilebilir olur, yani repo PRIVATE olur. Bu tür durumlarda GOPRIVATE environment variable’ı ile build mekanizmalarının private repo’lara ulaşmalarını da sağlıyoruz;

$ GOPRIVATE="github.com/vbyazilim,*.vbyazilim,vigo.io/private"

şeklinde birden fazla domain ve regex kullanımı ile bu ayarlamayı yapıyoruz.

Artık bu kütüphane birileri tarafından kod içinde kullanılacağı zaman github.com/vigo/stringutils üzerinden fonksiyonlara ulaşacaklar.

Şimdi dosyaları oluşturalım:

$ touch stringutils.go stringutils_test.go

Şimdi stringutils.go için;

/*
Package stringutils implements basic string utility functions for demo
purposes only!
*/
package stringutils

// Reverse reverses given string!
func Reverse(s string) string {
    r := []rune(s)
    lr := len(r)
    ss := make([]rune, lr)

    for i := 0; i < lr; i++ {
        ss[lr-1-i] = r[i]
    }

    return string(ss)
}

ve stringutils_test.go için;

package stringutils_test

import (
    "fmt"
    "testing"

    "github.com/vigo/stringutils"
)

func TestReverse(t *testing.T) {
    tcs := map[string]struct {
        input []string
        want  []string
    }{
        "none Turkish letters": {
            input: []string{"hello", "this is vigo"},
            want:  []string{"olleh", "ogiv si siht"},
        },
        "with Turkish letters": {
            input: []string{"uğur", "kırmızı şapka ve ÖĞRENCİ"},
            want:  []string{"ruğu", "İCNERĞÖ ev akpaş ızımrık"},
        },
        "with German letters": {
            input: []string{"Präzisionsmeßgerät"},
            want:  []string{"täregßemsnoisizärP"},
        },
    }

    for name, tc := range tcs {
        t.Run(name, func(t *testing.T) {
            for i, in := range tc.input {
                got := stringutils.Reverse(in)

                if got != tc.want[i] {
                    fmt.Println(len(got), len(tc.want[i]))
                    t.Errorf("want: %v; got: %v", tc.want[i], got)
                }
            }
        })
    }
}

var gs string

func BenchmarkReverse(b *testing.B) {
    var s string
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        s = stringutils.Reverse("merhaba dünya!")
    }

    gs = s
}

func ExampleReverse() {
    fmt.Println(stringutils.Reverse("vigo"))
    // Output: ogiv
}

Hemen testleri çalıştıralım:

$ go test -v # ./... ya da paket adı vermedik!
=== RUN   TestReverse
=== RUN   TestReverse/none_Turkish_letters
=== RUN   TestReverse/with_Turkish_letters
=== RUN   TestReverse/with_German_letters
--- PASS: TestReverse (0.00s)
    --- PASS: TestReverse/none_Turkish_letters (0.00s)
    --- PASS: TestReverse/with_Turkish_letters (0.00s)
    --- PASS: TestReverse/with_German_letters (0.00s)
=== RUN   ExampleReverse
--- PASS: ExampleReverse (0.00s)
PASS
ok      github.com/vigo/stringutils 0.911s

Paket artık dağıtıma hazır?