Forum: Ders Arası RSS
Birim testler ve keşiflerim
replicate işlevi hakkında
zafer #1
Üye Tem 2009 tarihinden beri · 700 mesaj · Konum: Ankara
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Konu adı: Birim testler ve keşiflerim
Merhaba,

Ali üstad tarafından yazılan ve benim yeniden düzenleyip projeme eklediğim bir metod var aşağıda veriyorum.
string AralikliOlarak(string menuSecenegi, int deger, string doldurmaDizgisi, int genislik)
{
    size_t toplamUzunluk = menuSecenegi.length;
    string aralik = replicate(doldurmaDizgisi, genislik - toplamUzunluk);
    
    return menuSecenegi ~ aralik ~ to!string(deger);
}

Isınma amaçlı bu metoda unittest yazmaya niyetlendim. İlk testimi yazdım ama program bir türlü açılmıyor (Windows ortamında) Neyse uğraşa uğraşa sonunda hatayı buldum. Yazdığım test içindeki ifade replicate() metodunun dolduracağı genislik bilgisini aldığı ikinci değeri sıfırdan küçük bir değer oluyordu ve program patlıyordu tabi, burada anlatmak istediğim bazen bir şeyin iyi olduğunu biliyorsunuz ama başınıza gelene kadar önemini kavrayamıyorsunuz. Birim testler önemli ve çok değerliler.

Diğer taraftan sanırım değer döndürmeyen metodlar için birim testleri yazamıyoruz doğru mu?

/********************************************
Returns an array that consists of $(D s) (which must be an input
range) repeated $(D n) times. This function allocates, fills, and
returns a new array. For a lazy version, refer to $(XREF range, repeat).
*/
ElementEncodingType!S[] replicate(S)(S s, size_t n) if (isDynamicArray!S)
{
    alias ElementEncodingType!S[] RetType;
 
    // Optimization for return join(std.range.repeat(s, n));
    if (n == 0)
        return RetType.init;
    if (n == 1)
        return cast(RetType) s;
    auto r = new Unqual!(typeof(s[0]))[n * s.length];
    if (s.length == 1)
        r[] = s[0];
    else
    {
        immutable len = s.length, nlen = n * len;
        for (size_t i = 0; i < nlen; i += len)
        {
            r[i .. i + len] = s[];
        }
    }
    return cast(RetType) r;
}

Ayrıca replicate() metodunun kendi içinde genişlik değerinin kontrolü yapması gerekmez mi diye soruyorum? replicate() kodunu incelemek istedim ama bilmediğim o kadar çok şeyle karşılaştım ki bu sebele buraya taşıdım belki birlikte inceleriz. Bu arada array.d modülünde iki tane replicate() metodu var ama benim program bu metoda gidiyor nereden biliyoruz! gdb öğrendik ya :)
https://github.com/zafer06 - depo
acehreli (Moderatör) #2
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Yazdığım test içindeki ifade replicate() metodunun dolduracağı genislik bilgisini aldığı ikinci değeri sıfırdan küçük bir değer oluyordu ve program patlıyordu tabi

Harika! :)

Birim testler önemli ve çok değerliler.

Hem de nasıl! :)

Diğer taraftan sanırım değer döndürmeyen metodlar için birim testleri yazamıyoruz doğru mu?

Teknik olarak yazamadığımız doğru değil. Aslında unittest blokları programda herhangi bir yere yazılabilirler. Derleyici basitçe o blokları da işletiyor (veya seçenek etkin değilse işletmiyor).

Değer döndürmeyen işlevlerin en azından yan etkileri vardır. Onlar da dolaylı olarak öyle test edilebilirler. Hatta bazen hatasız olarak işleyebilmesi bile test etmek için yeterli olabilir.

Burada akla bir soru gelebilir: ya işlevin yan etkisi kolayca denetlenemiyorsa? Sanırım bunun yanıtı şu: "eğer birim testlerini koddan önce yazarsanız tasarımlarınız daha iyi olur. Örneğin birim testini yazarken işlevin kolayca test edilebilir olmasını sağlamak ilerisi için kullanışlı olacaktır. Bunlar benim fikirlerim değil, TDD (test driven development) yönteminin savunduğu konulardan birisidir.

Ayrıca replicate() metodunun kendi içinde genişlik değerinin kontrolü yapması gerekmez mi diye soruyorum?

İki genişlik değerini düşünebiliriz:

/* ... */ replicate(S)(S s, size_t n)
/* ... */

1) s.length'e güvenilebilir çünkü s bir dizi olduğuna göre .length niteliği doğrudur. Dizi işlemleri bunu garanti ederler.

2) n'e güvenip güvenmeme konusu biraz daha karmaşık ve ilginç. Örneğin bu işlev aşağıdaki diziyi indeksler n'i kullanıyor:

/* ... */[n * s.length]

Ama replicate kendisi n'nin değerini doğrudan kullanmadığı için, eğer bir sorun varsa dizi indeksleme işlemi sırasında farkedilecektir. replicate'in "kendi edindiğim bu parametre değerini çağırdığım bir işleve göndermeden önce denetleyeyim" demesi fazlaca bir denetim kabul edilebilir.

Biz kendi C projelerimizde bunu uyguluyoruz: aracı işlevler parametrelerin NULL olup olmadığını yalnızca kendileri o göstergenin gösterdiğine erişirken kullanıyorlar. Eğer parametre olduğu gibi başka bir işleve gönderiliyorsa bu işlev ayrıca denetlemiyor.

Ayrıca Phobos daha yeterince olgun değil. Kod ileride değişebilir. :)

Son olarak, n'nin türü size_t olduğundan sıfırdan büyük olduğundan da eminiz tabii.

replicate() kodunu incelemek istedim ama bilmediğim o kadar çok şeyle karşılaştım ki bu sebele buraya taşıdım belki birlikte inceleriz.

Her tarafını açıklıyorum. :)

// (1)
/********************************************
Returns an array that consists of $(D s) (which must be an input
range) repeated $(D n) times. This function allocates, fills, and
returns a new array. For a lazy version, refer to $(XREF range, repeat).
*/
 
//       (2)                 (3)      (4)              (5)
ElementEncodingType!S[] replicate(S)(S s, size_t n) if (isDynamicArray!S)
{
 
    // (6)
    alias ElementEncodingType!S[] RetType;
 
    // (7) 
    // Optimization for return join(std.range.repeat(s, n));
 
    // (8)
    if (n == 0)
        return RetType.init;
 
    // (9)
    if (n == 1)
        return cast(RetType) s;
 
    // (10)  (11) (12)       (13)         (14)
    auto r = new Unqual!(typeof(s[0]))[n * s.length];
 
    // (15)
    if (s.length == 1)
        r[] = s[0];
    else
    {
        // (16)
        immutable len = s.length, nlen = n * len;
        for (size_t i = 0; i < nlen; i += len)
        {
            r[i .. i + len] = s[];
        }
    }
 
    // (17)
    return cast(RetType) r;
}

(1) Açıklaması:

s'nin n kere tekrarlanmasından oluşan bir dizi döndürür (s bir giriş aralığı olmalıdır). Bu işlev yeni bir dizi için yer ayırır, onun içini doldurur, ve döndürür. Tembel olarak işleyeni için bkz. std.range.repeat.

(2) Dönüş türü: Elemanları S'nin eleman türü olan bir dinamik dizi (veya "dilim")

(3) İşlev şablonunun ismi replicate, tek şablon parametresine S diyoruz

(4) İşlevin parametreleri: S türünde s ve size_t türünde n

(5) "Sevgili derleyici, bu şablonu yalnızca S'nin dinamik dizi olduğu durumlarda göze al."

(6) Uzun uzun ElementEncodingType!S[] yazmak yerine kısaca RetType diyeceğiz

(7) Açıklama şunu ifade ediyor: Burada return join(std.range.repeat(s, n)) de yazabilirdik ama o işlev çağrısı için hız bile kaybetmeyelim diye 0 ve 1 durumlarında sonucu hemen döndüreceğiz:

(8) 0 kere tekrarlanacaksa boş dizi döndürüyoruz (dizilerin .init niteliği o dizi türünde boş dizidir)

(9) 1 kere tekrarlanacaksa s'nin kendisini döndürüyoruz

Oradaki cast(RetType) tür dönüşümüne neden gerek olduğundan emin değilim. (?)

(10) Türünü açıkça yazmak istemediğimiz r isminde bir değişken tanımlıyoruz. Bu değişkenin bir dizi olduğunu da sağ taraftaki türünden biliyoruz tabii ki.

(11) Herhalde daha hızlı olacağını ölçmüş olduklarından, bu diziyi tekrar tekrar sonuna ekleyerek oluşturmak yerine gereken bütün yeri baştan ayırıp içini ondan sonra dolduracak biçimde oluşturmak istiyorlar. Onun için new ile yeterince büyük bir yer ayrılıyor.

(12) Bu yerin eleman türünü (13)'te s[0] ile belirleyecekler ama önce türün olası const, immutable, veya shared belirteçlerinden kurtulmak istiyorlar. std.traits.Unqual, türün saf halini verir (unqualified).

(13) typeof(s[0]), ilk elemanın türünü verir; (12)'de gördüğümüz gibi Unqual bunun saf halini üretir.

(14) Yeni dizinin uzunluğu n * s.length olacak; zaten bütün amaç bu :)

(15) Tekrarlanacak olan dizinin uzunluğu tek olduğunda, sonuç dizinin bütün elemanlarını bu tek değere eşitliyoruz. Sol taraftaki r[] kullanımına dikkat edin. Bu konu şurada "Bütün elemanlar üzerindeki işlemler" başlığında var:

  http://ddili.org/ders/d/dilimler.html

(16) Sonuç diziyi parça parça asıl dizinin elemanlarına eşitliyoruz

(17) r'nin türü artık const, immutable, veya shared taşımadığı için sonucun türünü tekrar döndürmek istediğimiz asıl dizi yapıyoruz.

Bu arada array.d modülünde iki tane replicate() metodu var ama benim program bu metoda gidiyor nereden biliyoruz! gdb öğrendik ya :)

İki replicate işlev şablonunun şablon kısıtlamaları farklı (aşağıda S, tekrar edilecek aralığı ifade ediyor):

  • birisi "S bir dinamik dizi ise" durumuna uyuyor:

if (isDynamicArray!S)

isDynamicArray D.ershane'de geçmiyormuş; std.traits'te buldum.

  • diğeri ise "S bir giriş aralığıysa (yani bir InputRange ise) ama dinamik dizi değilse" durumuna uyuyor:

if (isInputRange!S && !isDynamicArray!S)

Şablon kısıtlamaları şu bölümde "Şablon kısıtlamaları" başlığı altında geçiyor:

  http://ddili.org/ders/d/sablonlar_ayrintili.html

Ali
zafer #3
Üye Tem 2009 tarihinden beri · 700 mesaj · Konum: Ankara
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Bu harika ve detaylı cevap için çok teşekkürler Ali. Gerçekten bir konuyu bir bilenden öğrenmek kadar güzel bir şey yok, hem konu tam olarak kavranıyor hemde insanın ufku açılıyor.

Bu arada aklıma takılan bazı konular;

1) s.length'e güvenilebilir çünkü s bir dizi olduğuna göre .length niteliği doğrudur. Dizi işlemleri bunu garanti ederler.

Diziler için haklısın ama benim yeniden düzenlediğim kodda o bölümü string olarak değiştirmiştim ve dolayısıyla .length niteliğine artık güvenemeyiz öyle değil mi? Şimdi ne olacak?

Son olarak, n'nin türü size_t olduğundan sıfırdan büyük olduğundan da eminiz tabii.

Bundan gerçekten emin miyiz? Benim buradan okuduğum (hatalı olabilirim tabi) ben metod olarak size_t türünde bir değişken ile yani sıfırdan büyük bir değer ile çalışıyorum. Sana da buna riayet etmeni tavsiye ederim :) şeklinde ama benim gibi dikkatsizler metod bağıra bağıra size_t derken sıfırdan küçük değer gönderebiliyor  :blush:

Her tarafını açıklıyorum.

Ali sana yakın bir yerlerde olsam akşam kimseye söz verme akşam yemeğin benden deyip sana bir yemek ısmarlardım. :) İnan şu metodun detaylı açıklaması çok makbule geçti.
https://github.com/zafer06 - depo
acehreli (Moderatör) #4
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
zafer:
1) s.length'e güvenilebilir çünkü s bir dizi olduğuna göre .length niteliği doğrudur. Dizi işlemleri bunu garanti ederler.

Diziler için haklısın ama benim yeniden düzenlediğim kodda o bölümü string olarak değiştirmiştim ve dolayısıyla .length niteliğine artık güvenemeyiz öyle değil mi?

Onu ben de düşünmüştüm ama bu işlevin gözüyle bakarsak, string'ler de dizi olduklarından onun UTF-8 karakterleri de o kadar kere tekrarlanacak demektir. Asıl dizinin içinde hep geçerli UTF-8 kodlamaları varsa, yani örneğin ç'yi oluşturan iki UTF-8'de geçiyorsa sonuç string de geçerli olacaktır:

import std.stdio;
import std.array;
import std.utf;
 
void main()
{
    auto asıl = "abcç";
    auto sonuç = replicate(asıl, 2);
 
    writefln("asıl: %s, .length: %s, count: %s)",
             asıl, asıl.length, count(asıl));
    writefln("sonuç: %s, .length: %s, count: %s",
             sonuç, sonuç.length, count(sonuç));
}

Çıktısına göre herşey yolunda:

asıl: abcç, .length: 5, count: 4)
sonuç: abcçabcç, .length: 10, count: 8


Ama asıl dizi olarak örneğin ç'yi oluşturan UTF-8 kodlarından yalnızca birincisini içeren bir dilim kullansaydık durum kötü olurdu. Çünkü tekrarlama sırasından o UTF-8 kodundan sonra bir 'a' gelirdi ve ya geçersiz ya da istenmeyen bir Unicode karakteri oluşurdur.

Ne yazık ki (aslında ne iyi ki :) ) bir kere std.array'i ekleyince ve o da std.range'i eklediği için, ç'yi ortadan kesen dizgi üretmek zor oluyor. Onunla fazla uğraşmayacağım.

Son olarak, n'nin türü size_t olduğundan sıfırdan büyük olduğundan da eminiz tabii.

metod bağıra bağıra size_t derken sıfırdan küçük değer gönderebiliyor  :blush:

Ama sıfırdan küçük değerler yine de size_t'ye dönüştürülürler ve size_t olarak çok büyük bir değer gelir:

import std.stdio;
 
void foo(size_t i)
{
    writeln(i);
}
 
void main()
{
    foo(-1);
}

size_t'nin ulong olduğu ortamda çıktısı:

18446744073709551615

Adet gibi kavramları işaretsiz türlerle göstermenin doğru olduğunu savunanlara karşı güzel bir örnektir çünkü parametre size_t olunca ne yazık ki böyle bir hata denetlenemez. size_t yerine long olsaydı senin de söylediğin gibi çok kolay denetlenirdi:

import std.exception;
import std.stdio;
 
void foo(int i)
{
    enforce(i > 0, "Sıfırdan büyük olmalıdır");
    writeln(i);
}
 
void main()
{
    foo(-1);
}

Ben de işaretsiz türlerin yalnızca gerçekten gereken durumlarda kullanılmaları gerektiklerini savunurum. D.ershane'de de "aksine bir neden yoksa tamsayılar için int kullanabilirsiniz" demiştim.

İşaretsiz tür kullanmanın başka bir sorunu, işaretsiz türlerin aritmetik işlemlerde kullanıldıklarında ya hata yapılacağı ya da hata olmasın diye garip durumlara düşüleceğidir:

import std.stdio;
 
void foo(size_t i, size_t j)
{
    writeln("farkları: ", i - j);
}
 
void main()
{
    foo(2, 1);
}

Çıktısı:

farkları: 1

foo(1, 2) diye çağrılsa sonuç yanlış çıkar:

farkları: 18446744073709551615

Neyse işte... :)

akşam yemeğin benden

Çok teşekkürler! :) Şimdilik en erken önümüzdeki yaz gibi görünüyor. ;)

Ali
zafer #5
Üye Tem 2009 tarihinden beri · 700 mesaj · Konum: Ankara
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Bilgiler için teşekkürler, bu konuyu daha fazla irdelemeye gerek yok. Öğrendiklerimizi tecrübe olarak bir tarafa yazalım  :)

    akşam yemeğin benden
Çok teşekkürler! :) Şimdilik en erken önümüzdeki yaz gibi görünüyor. ;)

Ne diyeyim Ali programlamadaki hünerini yazılarda da göstermişsin, cümleyi öyle güzel yerinden kesmişsin ki otomatikman alacaklı oldun :) Olurda görüşürsek alacağın olsun unutma :)
https://github.com/zafer06 - depo
Doğrulama Kodu: VeriCode Lütfen resimde gördüğünüz doğrulama kodunu girin:
İfadeler: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Özel Karakterler:
Forum: Ders Arası RSS
Bağlı değilsiniz. · Şifremi unuttum · ÜYELİK
This board is powered by the Unclassified NewsBoard software, 20100516-dev, © 2003-10 by Yves Goergen
Şu an: 2017-11-19, 06:07:39 (UTC -08:00)