Maps

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

Arrayler & slicelar bölümünde, değerleri nasıl saklayacağımızı gördünüz. Şimdi, key ile değerleri nasıl sakladığımıza ve hızlıca bakabildiğimizi inceleyeceğiz.

Mapler, öğeleri sözlüğe benzer şekilde saklamanızı sağlar. key'i kelime, value'yu tanım olarak düşünebilirsiniz. Mapleri öğrenmenin kendi sözlüğümüz oluşturmaktan daha iyi yolu nedir?

İlk olarak, kelimelerin ve anlamlarının olduğu bir sözlüğümüzün olduğunu varsayalım, eğer bir kelimeye bararsak sözlük bize onun anlamını dönmeli.

İlk olarak test yaz

dictionary_test.go içerisinde

package main

import "testing"

func TestSearch(t *testing.T) {
    dictionary := map[string]string{"test": "this is just a test"}

    got := Search(dictionary, "test")
    want := "this is just a test"

    if got != want {
        t.Errorf("got %q want %q given, %q", got, want, "test")
    }
}

Map tanımlamak array tanımlamaya benziyor. Aradaki fark, map keywordu ile başlıyor ve iki tip gereklidir. İlki key tipi, [] içerisine yazılır. İkincisi value tipi,[]'in sağına yazılır.

Key özeldir. Sadece kıyaslanabilir (comparable) tip olabilir. Eğer iki keyin eşitliğini söyleyemezsek doğru değeri aldığımızdan emin olamayız. Kıyaslanabilir (Comparable) tipler dil özellikleri (language spec) bölümünde detaylıca açıklanmıştır.

Value tipi herhangi bir tip olabilir. Başka bir map bile olabilir.

Bu testteki her şey tanıdık gelmeli.

Testi çalıştırmayı dene

go test komutunu çalıştırdığımızda derleyici ./dictionary_test.go:8:9: undefined: Search hatası ile başarısız olacaktır.

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

dictionary.go içerisinde

Şimdi testiniz temiz bir hata mesajı verecektir

dictionary_test.go:12: got '' want 'this is just a test' given, 'test'.

Testi geçecek kadar kod yaz

Map'ten value almak Array'den value almak ile aynıdır map[key].

Refactor

İmplementasyonu daha genel yapmak için assertStrings yardımcısını oluşturdum.

Özel tip ile kullanma

Map etrafında yeni bir tür oluşturarak ve Search'ü metod haline getirerek sözlüğümüzün kullanımını iyileştirebiliriz.

dictionary_test.go içerisinde:

Daha tanımlamadığımız, Dictionary tipini kullanmaya başladık. Sonra Dictionary instanceında Search'ü çağırdık.

assertStrings değiştirmemize gerek yok.

dictionary.go içerisinde:

map'i wrapper eden Dictionary tipini oluşturduk. Özel tip tanımı ile Search metodunu oluşturabiliriz.

İlk olarak test yaz

Basit arama implemente etmesi oldukçta kolay ama aradığımız kelime sözlükte yoksa ne olacak?

Aslında hiçbir şey almayacağız. Bu iyi çünkü program çalışmaya devam edecek ama daha iyi bir yöntem var. Fonksiyon kelimenin sözlükte olmadığınu rapor edebilir. Bu şekilde, kullanıcı, kelimenin var olup olmadığını veya sadece bir tanımının olup olmadığını merak etmez (bu bir sözlük için pek kullanışlı görünmeyebilir. Ancak, bu, diğer kullanım durumlarında anahtar olabilecek bir durumdur).

Go'da bu durumu handle etmek için ikinci bir argüman olan Error tipi döndürülür.

assertion'a gönderdiğimiz gibi Errorler .Error() metodu ile stringe dönüştürülebilir. Ayrıca nil durumdunda assertStrings'i if ile .Error() çağırmaktan koruyoruz.

Dene ve testi çalıştır

Derlenmeyecektir

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

Şimdi testiniz daha anlaşılır hata mesajı ile başarısız olacaktır.

dictionary_test.go:22: expected to get an error.

Write enough code to make it pass

Bunu geçebilmek için mapin ilginç bir özelliğini kullanıyoruz. İki değer dönebilir. İkinci değer boolean yani key'in başarılı bir şekilde döndüğünü belirtir.

Bu özellik tanımı olmayan ve sözlükte olmayan kelimelerin farkını ayırt etmemizi sağlar.

Refactor

Search fonksiyonundaki sihirli hatayı değişkene atayarak kurtulabiliriz. Daha iyi bir testimizin olmasını sağlayacak.

Yeni bir yardımcı oluşturarak testlerimizi basitleştirdik ve ErrNotFound kullanmaya başladık bu sayede ileride hata mesajını değiştirsek bile testimiz başarısız olmayacaktır.

İlk olarak test yaz

Sözlükte arama yapmak için harika bir yolumuz var. Ne yazık ki, sözlüğe yeni kelimeler ekleyebileceğimiz bir yol yok.

Bu testte, sözlüğün geçerliliğini kolaylaştırmak için Search fonksiyonunun kullanışlı hale getiriyoruz.

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

dictionary.go içerisinde

Şimdi testiniz başarısız olmalı

Testi geçecek kadar kod yaz

Map'e ekleme yapmak array'a oldukça benzer. Sadece keyi belirlemeniz ve değerini value'ya atamanız gerekli.

Pointerlar, kopyalar, et al

Maplerin ilginç bir özelliği de adreslerini göndermeden düzenleme yapabilmeniz(örn &myMap)

Bu onları "reference tip" gibi hissettiriyor ancak Dave Cheney anlattığı gibi değiller.

Map value'su runtime.hmap structurena bir pointerdır.

Bir mapi fonksiyon/metoda'a gönderdiğinizde sadece kopyalıyorsunuz, pointer kısmını, verileri içeren temel veri yapısını değil.

Mapler hakkında şaşırtıcı olan valueları nil olabilir. nil map okuğunuzda boş bir map gibi davranır ama nil mape yazma işlemi çalışma anı (runtime) hatasına sebep olur. Mapler hakkında daha fazlasını burada okuyabilirsiniz.

Bu nedenle, asla boş bir map oluşturmayın:

Bunun yerine, yukarıda yaptığımız gibi boş bir map oluşturabilirsiniz veya map oluşturmak için make keywordünü oluşturabilirsiniz:

İki yöntem de boş hash map oluşturur ve dictionary'e işaret eder. Bu sayede çalışma zamanı (runtime) hatası almazsınız.

Refactor

Refactor yapmamız için çok fazla bir şey yok ancak test biraz basitleştirme kullanabilir.

kelime ve tanım için değişkenler oluşturduk ve tanım assertionını kendi yardımcı fonksiyonuna taşıdık.

Add iyi gözüküyor. Ancak, Eklemeye çalıştığımız değer zaten mevcutsa ne olacağını düşünmedik!

Eğer value varsa map hata fırlatmayacak. Onun yerine, yeni değeri eskisinin üzerine yazacak. Pratikte kullanışlı ancak fonksiyon ismi doğru yapmaz . Add var olan değeri değiştirmemeli. Sadece sözlüğe yeni kelimeyi eklemeli.

İlk olarak test yaz

Bu test için, Add fonksiyonun hata ,ErrWordExists, döndürmesi için değiştirdik bu sayede yeni hata değişkenine karşı doğrulama yapıyoruz. Ayrıca önceki testi, assertError fonksiyonunda olduğu gibi nil hatası için düzenledik.

Testi çalıştırmayı dene

Derleyici hata verecektir çünkü Add için bir değer dönmüyoruz.

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

dictionary.go içinde

Şimdi iki hata alıyoruz. Hala valueyu değiştiryor ve hata için nil dönüyoruz.

Testi geçecek kadar kod yaz

Hata ile eşleşmesi için switch ifadesini kullanıyoruz. switche sahip olmak Search'ün ErrNotFound'tan başka hata dönmesi durumlar için ekstra güvenlik sağlar.

Refactor

Çok fazla refactor yapmamıza gerek yok ancak hata kullanımlarımız arttıkça bir kaç değişiklik yapabiliriz.

Hataları sabit yaptık; Bu error interfacinden implemente eden kendi DictionaryErr tipimizi zorunlu kılıyor. Dave Cheney'nin hazırladığı muhteşem makaleden daha fazlasını okuyabilirsiniz. Basitçe söylemek gerekirse, hataları tekrar kullanılabilir ve değiştirilemez hale getirir.

Sıradaki, kelimenin anlamını güncelleyen Update fonksiyonunu oluşturalım.

İlk olarak test yaz

Update, Add çok yakından ilişkili ve sonraki implementasyonumuz olacak.

Dene ve testi çalıştır

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

Bunun gibi bir hata ile nasıl baş edebileceğimizi biliyoruz. Fonksiyonumuzu tanımlamamız gerekli.

Bununla, kelimenin tanımını değiştirmemiz gerektiğini görebiliyoruz.

Testi geçecek kadar kod yaz

Add ile olan issueyu düzelttiğimizde bunu nasıl yapacağımızı görmüştük. Adde gerçekten benzeyen bir şey implemente edelim.

Basit bir değişiklik olduğu için bu konuda yapmamız gereken herhangi bir refactoring yok. Ancak, Add ile aynı sorunu yaşıyoruz. Eğer yeni bir kelime gönderirsek Update onu sözlüğe ekleyecek.

İlk olarak test yaz

Kelimenin olmadığı durumlar için başka bir error tipi ekledik. Ayrıca error değeri dönmesi için Update'i değiştirdik.

Dene ve testi çalıştır

Bu sefer 3 hata aldık ama nasıl çözeceğimizi biliyoruz.

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

Yeni error tipimizi ekledik ve nil error dönüyoruz.

Bu değişiklikler sayesinde daha anlaşılır hata alıyoruz:

Testi geçecek kadar kod yaz

Bu fonksiyon, dictionaryi güncelleme ve hata döndürme dışında Add ile neredeyse aynı.

Update için yeni bir hata tanımlamayla ilgili not

ErrNotFound'u tekrar kullanabilirdik ve yenrine bir hata eklemezdik. Ancak, bir güncelleme başarısız olduğunda kesin bir hataya sahip olmak genellikle daha iyidir.

Spesifik hatalar ne olup bittiği hakkında daha fazla bilgi verir. Web uygulamasında bir örnek:

ErrNotFound oluştuğunda kullanıcıyı yeniden yönlendirebilirsiniz ama ErrWordDoesNotExist hatasını görüntülersiniz..

Sonraki, sözlükte bir kelime silmek için Delete fonksiyonu oluşturalım.

İlk olarak test yaz

Testimiz Dictionary ve bir kelime oluşturur daha sonra silindiğini kontrol eder.

Testi çalıştırmayı dene

go test komutunu çalıştırarak :

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

Bunu ekledikten sonra, test bize kelimeyi silmediğimizi söylüyor.

Testi geçecek kadar kod yaz

Go built-in olarak mapler üzerinde çalışan delete fonksiyonuna sahip. İki argüman alır. İlki map ve ikincisi kaldırılacak olan key.

delete fonksiyonu bir şey döndürmez ve Delete metodunu aynı düşünceye dayandırdık. Olmayan bir değeri silmenin bir etkisi olmadığından, Update ve Add gibi API'yı hatalar ile karmaşıklaştırmamıza gerek yok.

Özetlersek

Bu bölümde çok konudan bahsettik. Sözlüğümüze tam bir CRUD (Create, Read, Update ve Delete) API'yı oluşturduk. Süreç boyunca öğrendiklerimiz:

  • Map oluşturma

  • Map içerisinde item arama

  • Mape yeni item ekleme

  • Mapte item güncelleme

  • Mapten item silme

  • Hatalar hakkında öğrendiklerimiz

    • Constant hatalar oluşturma

    • Error wrappers yazma

Last updated

Was this helpful?