Arrayler ve slicelar

Bu bölümün bütün kodlarını burada bulabilirsiniz

Arrayler aynı tipte birden çok elementi bir değişken içerisinde belirli bir sırada saklamanızı sağlar.

Arrayler üzerinde iterasyon yapmanız çok yaygındır.Hadi, Sum fonksiyonunu yapmak için yeni öğrendiğimiz for'u kullanalım. Sum sayıların olduğu bir array alacak ve toplamı dönecek.

Hadi, TDD yeteneklerimizi kullanalım.

İlk olarak test yaz

Çalışmak için yeni bir klasör oluşturun.sum_test.go isminde bir dosya oluşturun ve aşağıdakileri ekleyin:

package main

import "testing"

func TestSum(t *testing.T) {

    numbers := [5]int{1, 2, 3, 4, 5}

    got := Sum(numbers)
    want := 15

    if got != want {
        t.Errorf("got %d want %d given, %v", got, want, numbers)
    }
}

Arraylar sabit bir kapasiteye sahiptirler, bir array değişkenini tanımlarken kapasitesini de belirtiriz. Bir arrayi iki yolla oluşturabiliriz:

  • [N]type{value1, value2, ..., valueN} e.g. numbers := [5]int{1, 2, 3, 4, 5}

  • [...]type{value1, value2, ..., valueN} e.g. numbers := [...]int{1, 2, 3, 4, 5}

Bazen hata mesajlarında girdileri yazdırmak faydalıdır. Burada arraylerde de iyi çalışan %v fotmatlayıcısını değerleri "varsayılan" formatta yazdırması için kullandık.

String formatları hakkında daha fazla bilgi edinin

Testi çalıştırmayı dene

go test komutunu çalıştırdığınızda derleyici ./sum_test.go:10:15: undefined: Sum hatasını verecek.

Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et

sum.go içerisine

package main

func Sum(numbers [5]int) int {
    return 0
}

Şimdi testiniz temiz bir hata mesajı verecek.

sum_test.go:13: got 0 want 15 given, [1 2 3 4 5]

Testi geçecek kadar kod yaz

func Sum(numbers [5]int) int {
    sum := 0
    for i := 0; i < 5; i++ {
        sum += numbers[i]
    }
    return sum
}

Bir arrayin belirli bir indeksindeki değere ulaşmak için array[index] sözdizimini kullanın. Bu durumda, for kullanarak array üzerinde 5 kez iterasyon yaparak array içerisindeki her değeri sum değişkenine ekliyoruz.

Refactor

Kodumuzu temizlemeye yardımcı olması için range anahtar kelimesini tanıtalım

func Sum(numbers [5]int) int {
    sum := 0
    for _, number := range numbers {
        sum += number
    }
    return sum
}

range array üzerinde iterasyon yapmanızı sağlar. range, her iterasyonda indeksı ve indeksin değerini döner. (Blank Identifier) _ karakterini kullanarak indeks değerini yok sayabiliriz.

Arrayler ve tipleri

Arraylarin ilginç bir özelliği, boyutun kendi tipinde kodlanmış olmasıdır. Eğer [4]int arrayini [5]int bekleyen bir fonksiyona gönderirseniz, kod derlenmeyecektir. Bunlar farklı tiplerdir bu işlem aynı int bekleyen bir fonksiyona string göndermek gibidir.

Arraylarin sabit bir uzunluğa sahip olmasının oldukça zahmetli olduğunu düşünüyor olabilirsiniz ve muhtemelen çoğu zaman onları kullanmayacaksınız!

Go, koleksiyonunun boyutunu önemsemediği hatta herhangi bir boyutta olabilecek olan slice tipine sahiptir.

Bir sonraki koşul, farklı boyutlardaki koleksiyonları toplamak olacak.

İlk olarak test yaz

Herhangi bir boyutta koleksiyonlara sahip olmamızı sağlayan slice tipini kullancağız. Sözdizimi arraylere çok benziyor sadece değişkeni tanımlarken boyutunu kaldırın.

myArray := [3]int{1,2,3} yerine mySlice := []int{1,2,3}

func TestSum(t *testing.T) {

    t.Run("collection of 5 numbers", func(t *testing.T) {
        numbers := [5]int{1, 2, 3, 4, 5}

        got := Sum(numbers)
        want := 15

        if got != want {
            t.Errorf("got %d want %d given, %v", got, want, numbers)
        }
    })

    t.Run("collection of any size", func(t *testing.T) {
        numbers := []int{1, 2, 3}

        got := Sum(numbers)
        want := 6

        if got != want {
            t.Errorf("got %d want %d given, %v", got, want, numbers)
        }
    })

}

Dene ve testi çalıştır

Bu derlenmeyecektir

./sum_test.go:22:13: cannot use numbers (type []int) as type [5]int in argument to Sum

Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et

Bizimde yapabileceğimiz, buradaki problem

  • Sum ın argümanını arrayden slicelara alarak var olan API'yı bozmak. Bunu yaptığımızda muhtemelen başkasının gününü mahfedeceğiz çünkü diğer testimiz derlenmeyecek!

  • Yeni bir fonksiyon oluşturmak

Bizim durumumuzda, fonksiyonumuzu başka hiç kimse kullanmıyor, bu yüzden bakımı yapılacak iki fonksiyona sahip olmak yerine, sadece bir fonksiyona sahip olalım.

func Sum(numbers []int) int {
    sum := 0
    for _, number := range numbers {
        sum += number
    }
    return sum
}

Eğer testleri çalıştırmayı denerseniz derlenmeyecektir, ilk testin çalışabilmesi için array yerine slice'a çevirmelisiniz.

Testi geçecek kadar kod yaz

Burada yapmamız gereken tek şey derleyici hatalarını gidermek ve testlerin geçmesini sağlamak!

Refactor

Sum fonksiyonunu önceden düzenlemiştik - Tek yaptığımız arrayler yerine slicelar yazmak, ekstra bir değişikliğe gerek yok. Yeniden düzenleme aşamasında test kodumuzu ihmal etmememiz gerektiğini unutmayın - Sum testlerimizi daha da geliştirebiliriz.

func TestSum(t *testing.T) {

    t.Run("collection of 5 numbers", func(t *testing.T) {
        numbers := []int{1, 2, 3, 4, 5}

        got := Sum(numbers)
        want := 15

        if got != want {
            t.Errorf("got %d want %d given, %v", got, want, numbers)
        }
    })

    t.Run("collection of any size", func(t *testing.T) {
        numbers := []int{1, 2, 3}

        got := Sum(numbers)
        want := 6

        if got != want {
            t.Errorf("got %d want %d given, %v", got, want, numbers)
        }
    })

}

Testlerimizin değerini sorgulamak önemlidir. Mümkün olduğu kadar çok test olması bir amaç olmamalı ancak kod tabınında (code base) mümkün olduğu kadar tatminkarlık olmalı. Çok fazla testin olması gerçek bir probleme dönüşebilir ve bakımı için ekstra yük olabilir. Her testin bir maliyeti vardır.

Bizim durumumuzda, bu fonksiyon için iki test olması gereksiz. Eğer bir slice için belirli bir boyutta çalışıyorsa (makul ölçüde) her boyuttaki slice için çalışacaktır.

Go'nun dahili test aracı kapsama aracı (coverage tool) özelliğini içerir. %100 kapsama için çabalamak nihai hedefiniz olmamalıdır, kapsama aracı kodunuzun testler tarafından kapsanmayan alanlarını belirlemenizde size yardımcı olabilir. TDD konusunda katıysanız, 100%'e yakın kapsama oranına sahip olmanız çok olasıdır.

Çalıştırmayı deneyin

go test -cover

Görmelisiniz

PASS
coverage: 100.0% of statements

Şimdi testlerden birini silin ve kapsama oranını bir daha kontrol edin.

Artık iyi test edilmiş bir fonksiyonumuz olduğu için mutluyuz sonraki mücadeleye girmeden önce yapmış olduğunuz harika işi teslim (commit) etmelisiniz.

SumAll isminde yeni bir fonksiyona ihtiyacımız var. Bu fonksiyon çeşitli sayıda sliceı parametre olarak alacak, yeni bir slice dönecek ve bu slice her sliceın toplamını içerecek.

Örneğin

SumAll([]int{1,2}, []int{0,9}) fonksiyonu []int{3, 9} döndürmeli

veya

SumAll([]int{1,1,1}) fonskiyonu []int{3} döndürmeli

İlk olarak test yaz

func TestSumAll(t *testing.T) {

    got := SumAll([]int{1, 2}, []int{0, 9})
    want := []int{3, 9}

    if got != want {
        t.Errorf("got %v want %v", got, want)
    }
}

Dene ve testi çalıştır

./sum_test.go:23:9: undefined: SumAll

Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et

Bizim testimize göre SumAll fonksiyonunu tanımlamalıyız.

Go çeşitli sayıda argüman alan (variadic functions) yazmanıza izin verir.

func SumAll(numbersToSum ...[]int) (sums []int) {
    return
}

Bu geçerlidir ancak testlerimiz hala derlenmeyecektir!

./sum_test.go:26:9: invalid operation: got != want (slice can only be compared to nil)

Go slicelar ile eşittir operatörünü kullanmanıza izin vermez. Her got ve want slicelarını iterate etmesi için bir fonksiyon yazabilirsiniz ancak kolaylık olması için, herhangi iki değişkenin aynı olup olmadığını görmemizde kullanışlı olan, reflect.DeepEqual kullanabiliriz.

func TestSumAll(t *testing.T) {

    got := SumAll([]int{1, 2}, []int{0, 9})
    want := []int{3, 9}

    if !reflect.DeepEqual(got, want) {
        t.Errorf("got %v want %v", got, want)
    }
}

(DeepEqual fonksiyonuna erişebilmek için dosyanın en yukarısında import reflect ekli olduğundan emin olun)

Bunu belirtmek önemli, reflect.DeepEqual "type safe" güvenli tipte değildir - saçma bir şey yapsanızda kodunuz derlenecektir. Uygulamada bunu görmek için geçici olarak testi değiştirin:

func TestSumAll(t *testing.T) {

    got := SumAll([]int{1, 2}, []int{0, 9})
    want := "bob"

    if !reflect.DeepEqual(got, want) {
        t.Errorf("got %v want %v", got, want)
    }
}

Burada yaptığımız şey bir slice ile bir string'ı kıyaslamak. Bu kıyaslamayı yapmak mantıklı değil ancak test derleniyor! reflect.DeepEqual slicelar ile diğer şeyleri kıyaslamak için kullanışlı, kullanırken dikkatli olmalısınız.

Testi eski haline getirin ve çalıştırın. Aşağıdakine benzer bir test çıktısı elde etmelisiniz

sum_test.go:30: got [] want [3 9]

Testi geçecek kadar kod yaz

Burada yapmamız gerekn varargs üzerinde iterate etmek, Sum fonksiyonu ile toplamı hesaplamak, sonucu döneceğimiz olan slice'a eklemek

func SumAll(numbersToSum ...[]int) []int {
    lengthOfNumbers := len(numbersToSum)
    sums := make([]int, lengthOfNumbers)

    for i, numbers := range numbersToSum {
        sums[i] = Sum(numbers)
    }

    return sums
}

Öğrenilecek birçok yeni şey!

Slice oluşturmak için yeni bir yol. make, üzerinde çalışacağımız numbersToSum'ın len ile elde ettiğimiz uzunluğunu kullanarak slice'ın kapasitesini belirlememizi sağlar.

Slicelar da arrayler gibi mySlice[N] indeksteki değere erişebilir veya = ile yeni bir değer atayabilirsiniz.

Testler şimdi geçmeli.

Refactor

Daha önce bahsettiğimiz gibi, sliceların kapasitesi vardır. Eğer slicesınızın boyutu 2 ve mySlice[10] = 1 işlemini yapmaya çalışırsanız çalışma sırasında (runtime) hatası alırsınız.

Bunun yanı sıra, append fonksiyonuna slice ve yeni bir değer göndererek yeni bir slice elde edebilirsiniz.

func SumAll(numbersToSum ...[]int) []int {
    var sums []int
    for _, numbers := range numbersToSum {
        sums = append(sums, Sum(numbers))
    }

    return sums
}

Bu uyarlamada, kapasite hakkında daha az endişeliyiz. Boş bir slice olan sums ile başlıyoruz ve Sum fonksiyonunun sonucunu ekliyoruz.

Sıradaki koşulumuz SumAll fonksiyonunu SumAllTails'a çevirmek, bu sayede her sliceın "tails" değerini hesaplayacağız. Tail (kuyruk), koleksiyonun ilk değeri ("kafa") haric tüm değerlerdir.

İlk olarak test yaz

func TestSumAllTails(t *testing.T) {
    got := SumAllTails([]int{1, 2}, []int{0, 9})
    want := []int{2, 9}

    if !reflect.DeepEqual(got, want) {
        t.Errorf("got %v want %v", got, want)
    }
}

Dene ve testi çalıştır

./sum_test.go:26:9: undefined: SumAllTails

Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et

FonksiyonuSumAllTails olarak yeniden isimlendir ve testi tekrar çalıştır

sum_test.go:30: got [3 9] want [2 9]

Testi geçecek kadar kod yaz

func SumAllTails(numbersToSum ...[]int) []int {
    var sums []int
    for _, numbers := range numbersToSum {
        tail := numbers[1:]
        sums = append(sums, Sum(tail))
    }

    return sums
}

Slicelar dilimlenebilir! Sözdizimi slice[low:high]. Eğer : bir tarafında ki değeri kaldırırsanız o taraf haric her değeri kapsar. Bizin durumumuzda, numbers[1:] ile "1. indeksten sona kadar" almasını söylüyoruz. Slicelar hakkında başka testler yazmak için biraz zaman harcamak ve daha aşina olmak için slice operatörüyle denemeler yapmak isteyebilirsiniz.

Refactor

Bu sefer refactor için fazla bir şey yok.

Fonksiyonumuza boş bir slice gönderdiğimizde ne olacağını düşündünüz mü? Boş bir slice'ın "tail" (kuyruğu) nedir? Go'ya boş bir slice'ın myEmptySlice[1:] bütün elemanlarını almasını söylerseniz ne olur?

İlk olarak test yaz

func TestSumAllTails(t *testing.T) {

    t.Run("make the sums of some slices", func(t *testing.T) {
        got := SumAllTails([]int{1, 2}, []int{0, 9})
        want := []int{2, 9}

        if !reflect.DeepEqual(got, want) {
            t.Errorf("got %v want %v", got, want)
        }
    })

    t.Run("safely sum empty slices", func(t *testing.T) {
        got := SumAllTails([]int{}, []int{3, 4, 5})
        want := []int{0, 9}

        if !reflect.DeepEqual(got, want) {
            t.Errorf("got %v want %v", got, want)
        }
    })

}

Dene ve testi çalıştır

panic: runtime error: slice bounds out of range [recovered]
    panic: runtime error: slice bounds out of range

O hayır! Test derlendi ancak çalışma sırasında (runtime) hata verdi. Derleme zamanı hataları bizim dostumuzdur çünkü çalışan yazılımlar yapmamıza yardım ediyorlar, çalışma zamanı hatalar düşmanımız çünkü kullanıcıları etkiliyorlar.

Testi geçecek kadar kod yaz

func SumAllTails(numbersToSum ...[]int) []int {
    var sums []int
    for _, numbers := range numbersToSum {
        if len(numbers) == 0 {
            sums = append(sums, 0)
        } else {
            tail := numbers[1:]
            sums = append(sums, Sum(tail))
        }
    }

    return sums
}

Refactor

Testimizde assertion etrafında yine tekrarlı kodlar var, öyleyse bunları bir fonksiyona çıkaralım.

func TestSumAllTails(t *testing.T) {

    checkSums := func(t testing.TB, got, want []int) {
        t.Helper()
        if !reflect.DeepEqual(got, want) {
            t.Errorf("got %v want %v", got, want)
        }
    }

    t.Run("make the sums of tails of", func(t *testing.T) {
        got := SumAllTails([]int{1, 2}, []int{0, 9})
        want := []int{2, 9}
        checkSums(t, got, want)
    })

    t.Run("safely sum empty slices", func(t *testing.T) {
        got := SumAllTails([]int{}, []int{3, 4, 5})
        want := []int{0, 9}
        checkSums(t, got, want)
    })

}

Bunun kullanışlı bir yan etkisi, kodumuza biraz tip güvenliği eklemesidir. Eğer geliştirici kazara yeni bir test eklerse checkSums(t, got, "dave") derleyici onların çalışmasını durduracak.

$ go test
./sum_test.go:52:21: cannot use "dave" (type string) as type []int in argument to checkSums

Özetlersek

Ele alınanlar

  • Arrayler

  • Slicelar

    • Slice oluşturmanın çeşitli yolları

    • sabit kapasiteli oldukları ama append kullanarak nasıl yenisini oluşturulacağı

    • Sliceları nasıl dilimleyeceğini!

  • len ile array veya slice'ın uzunluğunu elde etmeyi

  • Test kapsama aracını (coverage tool)

  • reflect.DeepEqual kullanmanın neden kullanışlı olduğu ama kodumuzun tip güvenliğini (type-safety) düşürebileceği

Sliceları ve arraylari integerlar ile kullandık ancak diğer tiplerle de çalışabilirler, arrayler/sliceların kendileri de dahil. Eğer ihtiyacınız varsa [][]string ile tanımlayabilirsiniz.

Slicelar hakkında derinlemesine bir bakış için Go blog'una bakabilirsiniz. Okuyarak öğrendiklerinizi pekiştirmek için daha fazla test yazmayı deneyin.

Test yazmaktan başka Go ile deneme yapmanın bir başka kullanışlı yolu da Go playgrounddır. Soru sormanız gerekirse kodunuzu kolayca paylaşabilirsiniz ve çoğu şeyi deneyebilirsiniz. Slice ile deneyler yapabilmeniz için Go playground'u hazırladım.

Array'in dilimlenmesi ve slice'ın değiştirilmesinin orjinal diziye nasıl etkilediğinin örneği; "Kopya" slice orjinal arraye etki etmeyecektir. Neden büyük bir slice'ı dilimledikten sonra slice'ın kopyasını almak neden iyi bir fikirdir başka bir örnek.

Bu sayfa @bariscanyilmaz tarafından çevrildi.

Last updated