Thread overview
Deject
May 05, 2013
Salih Dinçer
May 05, 2013
Salih Dinçer
May 07, 2013
Salih Dinçer
May 10, 2013
Salih Dinçer
May 05, 2013

Merhaba,

Ali hocam, yakın bir zaman DConf (<dconf.org/talks/cehreli.html>)'a katılmış ve oradaki izlenimlerini bu forumda dile getirmişti. Paragraf aralarında (geliştiricisi orada bulunduğundan) D ile yazılmış ve sanırım yine D için "Dependency Injection (http://en.wikipedia.org/wiki/Dependency_injection)" yazılımından bahsetti:

https://github.com/bgertzfield/deject

Bu terimi ilk defa duydum ve Türkçesi konusunda Ali hocamdan yardım almalıyım. Anladığım kadarıyla yazılım geliştirirken nesnelerin bağımlı oldukları başka nesneleri bize özet şekilde gösteren bir yazılım? Ha keza notlara baktığımda HTML ve JS kullanılan bir UI olduğunu gördüm. Yani mutfak kısmı D'de yazılırken ürettiği veriyi görsel bir şekilde takip ediyoruz (?), sanırım!

Anlamadığım şeyleri, kodlara bakarak anlayacağımı zannederken kendimi bir an içinde (kodlar basit olsa da) GitHub'da buldum. Başladım, kod takip yapmaya ama git gide (adı Git Hub ya...:) ) bir keşmekeş girdiğimi hissettim...:)

Yahu bir yazılım yaparken niye bu kadar karıştırıyorlar? Örneğin Linker (https://github.com/bgertzfield/deject/blob/master/deject/detail/linker.d):

module deject.detail.linker;

import deject.detail.binding;
import deject.detail.bindingkey;
import deject.detail.bindingmap;

import std.conv;
import std.stdio;

class Linker {
 this(BindingMap newBindingMap) {
   bindingMap = newBindingMap;
 }

 Binding!T requestBinding(T)() {
   BindingKey key = { typeid(T) };
   auto variant = bindingMap[key];
   auto binding = variant.get!(Binding!T);
   if (key !in linkedBindings) {
     binding.attach(this);
     linkedBindings[key] = true;
   }
   return binding;
 }

 private BindingMap bindingMap;
 private bool[BindingKey] linkedBindings;
}

Tamam basit görünüyor ama anlamak için gezinmeye devam etmeliyim...:)

"BindingMap" ne ola ki (?) başlıyoruz yeni bir yolculuğa...

Alıntı (bindingmap.d):

>
> alias Variant[BindingKey] BindingMap;
> ```

>

Evet, bu aslında bir Variant imiş. Eee peki BindingKey'e bakalım şimdi de...:)

Alıntı (bindingkey.d):
>
>
>

struct BindingKey {
TypeInfo typeInfo;
}

>

Aman Allahım, işte burada tosladım çünkü type() ile öğrendiğimiz ve bu nesneyi döndüren TypeInfo ne maksatla kullanıyor olabilir? Düşünüyorum, herhalde nesnelerin kimliklerini en ince ayrıntısına kadar belgelemek için. Peki devam edelim nasıl kullanmış:

   BindingKey key = { typeid(T) }; // Vaouvvv...:)
   auto variant = bindingMap[key]; // Hmm...
   auto binding = variant.get!(Binding!T);  // ?
   if (key !in linkedBindings) {  // Eee, tamam..:)
     binding.attach(this);  // sınıfın attach() işlevinin ne yaptığını bilmemiz gerekiyor...
     linkedBindings[key] = true;  // bu satır 2. bool üyesi => private bool[BindingKey]
   }

Başlangıçta Binding ne ki sorusu akla geliyor. Çünkü tek bilinmeyen o sanki ve meğer Interface imiş:

Alıntı (binding.d):

>
> interface Binding (T) : Provider!T {
>   void attach(Linker linker);
> }
> ```

>

Vallahi burada her şey birbirine bağlanmış ama benim kafadaki bilgileri de bağlamak için zaman vermem gerekecek. Çünkü parçalar gerçekten çok ufak ve neler yaptığını anlamak vakit alacak... :blush:

Özetle; belki herkesin vakit bulamayacağı bu maceraya anlam veremesem de yazılım geliştirirken her şey küçük parçalara bölüp birleştirme (kullanma) geleneğimizin bir eseri olmalı. Yani yöntem doğru olmalı. Belki bu aşamada olmasa da zamanla yazılım geliştirirken büyüyen ve iyice içinde çıkılmaz bir hale gelen projelerin üstesinden gelmek için iyi bir yöntem olabilir. Her ne kadar bu yöntemde kod takibinde zorlansam da...:)

Sevgiler, saygılar...

-- 
[ Bu gönderi, <http://ddili.org/forum>'dan dönüştürülmüştür. ]
May 05, 2013

Pes eder miyim, etmedim tabi...:)

Çünkü devam ettikçe yeni şeyler öğreniyorum! Örneğin benim için en önemlisi şu:
'
dmd test.d detail/*.d -unittest
'
Görüldüğü gibi bir dizin içindeki tüm modülleri derlerken tek tek elle yazmaya gerek yokmuş! Meğer taaa 90'lı yıllarda MS-DOS'da gördüğümüz bir asterix ile işi bitiriyormuşuz. Stephen Kleene (http://en.wikipedia.org/wiki/Kleene_star) insanlık diline yerleştirmiş ya, ruhu şad olsun...:)

detail dizininden çıkınca provider.d ile tanışmalıydım! Çünkü kodun devamı beni oraya götürüyordu. Meğer o da olabildiğince basit bir interface imiş ve nihai sonucu elde etmemizi sağlarmış...

Alıntı:

>
> module deject.provider;
>
> interface Provider (T) {
>   T get();
> }
> ```

>

Eee peki bütün bunlar nerede kullanılıyor; ObjectGraph (<https://github.com/bgertzfield/deject/blob/master/deject/objectgraph.d>) isminde bir sınıfta ve işte unittest'i:

unittest {
auto testModule = new class BindingModule {
void addBindingsToMap(ref BindingMap bindingMap) {
bindingMap[BindingKey(typeid(int))] = new class Binding!int {
int get() { return 42; }
void attach(Linker linker) { }
};
}
};

auto objectGraph = new ObjectGraph!()(testModule);
assert(objectGraph.get!int == 42);
}


Özetle, şimdi taşlar yerine oturuyor. Ama hala anlamadığım neden bu kadar çok dallandırıp budaklandırmış. Her şey tek bir dosya içine gömebilirdi sanırım ama ilerisi için büyük planları olabilir?

Kolay gelsin...

-- 
[ Bu gönderi, <http://ddili.org/forum>'dan dönüştürülmüştür. ]
May 06, 2013

Alıntı (Salih Dinçer):

>

Bu terimi ilk defa duydum

İlgili terimler:

mock: Sahte, yapay (Türkçesini bilen var mı?)

dependency: Bağımlılık

injection: Enjeksiyon

dependency injection: Kodun bağımlı olduğu birimleri o koda dışarıdan vermek (Bizim belirlediğimiz sahte nesneleri alsın ve test sırasında kullansın diye) (Türkçesini bilen var mı?)

deject: Kendi anlamı olsa da, burada DEpendency inJECTion'ından türetilmiş bir isim (deject'in asıl anlamı "moralini bozmak, hevesini kırmak" gibi bir şey ama bu çatıyla hiç ilgisi yok)

Ben'in bu işi nasıl gerçekleştirdiğini anlamak için tabii ki kodlara bakabiliriz ama önce bu kavramın nasıl kullanıldığını anlamak daha iyi olabilir. Kodu indirdim:

'$ git clone git://github.com/bgertzfield/deject.git'

Ben'in DConf'ta gösterdiği sunum klasörlerden birisinde duruyor. Tarayıcınızda açın:

'deject/presentation/index.html'

10 numaralı saydamda test edilmesi zor olan bir kod gösteriyor:

import std.socket;
import std.stdio;

void checkHost(string host) {
 auto ih = new InternetHost;
 if (!ih.getHostByName(host)) {
   logError("No DNS: " ~ host);
   throw new Exception("Host down: " ~ host);
 }
}

void logError(string err) {
 auto f = File("log.txt", "a");
 f.write(err);
}

unittest {
 assertNotThrown(checkHost("dlang.org"));
 assertThrown(checkHost("qlang.org"));
}

Test etmeye çalışılan şu: Acaba kod "dlang.org" sorgulandığında hata atmayacak mı ve "qlang.org" sorgulandığında hata atacak mı? Testin neden zor olduğu 10-18 numaralı saydamlarda var:

  • Ağ bağlantısında sorun olabilir

  • Var olmadığı sandığımız "qlang.org" birisi tarafından alınmış olabilir

  • "log.txt" dosyası yazılamayabilir

  • Test çok uzun sürebilir (birim testlerinin çok çok hızlı olmaları gerek; 30 saniye gibi süreler kabul edilemez)

19 numaralı saydamdaki kod çok daha iyi çünkü dependency injection kullanıyor:

import std.log; // Wishful thinking..
import std.socket;

class HostChecker {
 private InternetHost ih;
 private Logger logger;

 this(InternetHost ih, Logger logger) {
   this.ih = ih;
   this.logger = logger;
 }

 void checkHost(string host) {
   if (!ih.getHostByName(host)) {
     logger.error("No DNS: " ~ host);
     throw new Exception("Host down: " ~ host);
   }
 }
}

Bağımlı olduğu InternetHost nesnesini checkHost()'un içinde artık kendisi oluşturmuyor, dışarıdan kendisine verilen nesneyi kullanıyor. (Bu konuyu daha önceki tartışmalarımızda da bir kaç kere konuşmuştuk: Kurucu içinde File nesnesi oluşturmak yerine o File nesnesinin kurucu parametresi olarak dışarıdan verilmesi çok daha iyidir.)

Bu aşamada artık elimizde kolayca test edilebilen bir kod var. Şimdi test etmeye geçebiliriz.

24 numaralı saydamda dmocks çatısı kullanılıyor. (Ben daha ilerleyen saydamlarda deject'in nasıl çok daha kolay kullanıldığını gösterecek.)

deject'e geçmeden önce yapay test nesnelerinin dmock'ta nasıl kullanıldıklarını görelim:

 mocker
   .expect(ih.getHostByName(host))
   .returns(false);

O kodun anlamı şu: getHostByName("host") çağrıldığında false döndüğü durumu test etmek istiyoruz, lütfen bize bu konuda yardım eder misin. :)

Bir rica daha:

 mocker
   .expect(logger.error(""))
   .repeatAny()
   .ignoreArgs;

Her logger.error("") çağrısını gözardı etmek istiyoruz. (Bir anlamda, test ettiğimiz kod logger.error'ı herhangi bir mesajla çağırabilir. Bizim testimiz bu konuyla ilgilenmiyor...)

Şimdi hazırladığımız yapay nesneyi istediğimiz gibi davranmaya başlatıyoruz:

 mocker.replay();

Ardından teste geçiyoruz:

 auto checker = new HostChecker(ih, logger);
 assertThrown(checker.checkHost(host));

Test ettiğimiz HostChecker kendi işinin parçası olarak 'ih.getHostByName(host)' yapacak ama o çağrı bizim mocker nesnesi tarafından karşılanacak ve o mocker bizim ricamız doğrultusunda o çağrıya false döndürecek.

Böylece biz HostChecker türümüzün var olmayan bir adres geldiğinde gerçekten de hata atacağını test etmiş oluyoruz.

Özetle, yavaşlık gibi bir sürü sorunu olabilen asıl getHostByName() yerine, araya bizim tanımladığımız yapay davranışı sokuşturuyoruz ve hiç dışarıyla etkileşmemiz gerekmeden program içinde HostChecker'ı test etmiş oluyoruz.

Dependency injection denen kavram işte bu...

43 numaralı saydamda D'nin UDA (user defined attribute) olanağının ne kadar güçlü bir olanak olarak ortaya çıktığını görüyoruz. deject çatısı, @Inject ile nitelendirilmiş olan sınıfların çok kolayca test edilmelerini sağlıyor. (UDA DConf'ta başka bir sürü sunumda da ortaya çıktı. Örneğin, Manu'nun Remedy Games'de kullandığı olanaklar da UDA'e dayanıyordu.)

deject'in @Inject ile getirdiği, bu işin külfetini ortadan kaldırmakmış. D'nin derleme zamanı içgözlem (introspection) olanakları sınıfın hangi başka sınıflara bağımlı olduğunu belirlemeyi kolaylaştırıyormuş.

Ama nasıl kullanıldığını tam anlamadım. He he... :) Anlayınca bir örnek gösteririm.

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 07, 2013

Açıklamalar için teşekkürler...

Bir de şu vardı; unutmadan hatırlamak için nakledeyim: https://github.com/Dgame/DAT
Alıntı:

>

DAT
D Analysis Tool

DAT can

  • resolve rvalue references
  • detect unused or underused variables
  • detect unused functions
  • enumerate all detected variables along with their frequency of use.

Usage

Example: DCompiler --file mytest.d --autoref --unused
Or with logging: DCompiler --file mytest.d --autoref --unused --log log.txt

Belki bir gün ayrı bir başlıkta incelemesini yaparız. Kanaatimce büyük projeler için çok faydalı olabilir...

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 10, 2013

Şimdi çok cahilene bir laf (laf ile söz farklıdır, söz değerlidir, laf ise çer çöp anlamı ifade eder) edeceğim; sanki çok büyük ve iddialı bir söz söylemişcesine...:)

"Unittest'leri yazılım geliştirirken yapmak bir zaman kaybıdır!"

Ankara'da TÜTEV'deki D konferansına kadar, unittest kavramını bilsem de pek kullanmıyordum. Peki ne değişti de artık kullanıyorum? Orada bana şöyle demişlerdi:

"Yazılım yazmanın temeli unittest ile başlar. Siz o testi gerçekleyecek kodu yazar ve emin adımlarla ilersiniz. Çünkü geliştirmenizde unittest hata vermiyorsa siz yeterince güvendesiniz demektir."

Elbette bu başlığın konusunda yeterince güvende olmadığımızı ve/veya esnek bir yapı içinde bulunmadığımızı görüyoruz ama ben izninizle bir parantez açmak istiyorum:

Bence unittest'ler yazılımı geliştirdikten sonra onu çalıştığını göstermesi (emin olunması) ve nasıl kullanılabileceği hakkında fikir vermesi için yapılmak zorundalar. İyi şeyler yani, itirazım yok. Ama başlangıçta unittest ile başlamak (bu kaideyi, yazılım dünyasındaki hangi büyüğümüz ilk olarak söylemiş acaba?) zaman kaybı olabilir. Belki nedeni çok basit çünkü yazılım sürekli gelişiyor ama anlatmak istediklerim bambaşka...

Sanki unittest içinde saklı bir kavram var! O da yazılımcıyı kısıtlayan bir şey. Yani siz, ana kodu yazdığınız sırada, unittest içerisine koyduğunuz ve çalıştığını gördüğünüz kodlar sizi farkında olmadan sınırlandırıyor. Çünkü içeriğine ne kadar örnekle doldurabilirsiniz ki (!) kendinizi sonsuz farklı varyasyonda, sınamanız gereken özellikler kümesi içinde bulabilirsiniz. O yüzden ve aynı zamanda vakit kaybetmemek için (çünkü hedef ana kodu yazmaktır), herhalde bir kaç temel öğesi yazılıp geçiliyor olmalı?

Peki, siz temel öğeleri test edip yeterince iyi bir kod yazdığınıza karar veriyor musunuz? Belki bir kaç örnekle test ettiğinizde, daha yolun başında olduğunuzu bile gözlemleyebilirsiniz. Ama yine vakit faktörüne takılıyoruz ve bunu yapmıyoruz. Sözlerimden "bunu yapmalıyız" gibi netice de çıkıyor sanki ama söylemek istediğim bu değil. Offf nasıl söylesem...:)

Şimdi, bence insan beyni kod yazarken bir başka moda giriyor. Tamam, unittest örnekleriniz de bir kod ama anahatar sözcük "development" yani ortada bir sorun var ve bunun kodunu geliştiriyorsunuz. O sırada ilham kapısı ardına kadar açık ve o vakti ana kodu yazmak için geçiriyorsanız, daha çok kazanıyorsunuz. Ama yok kardeşim üşüdüm, cereyan yaptı, kapıyı aralayım derseniz (unittest yazmak için ara verirseniz) kendinizi farkında olmadan kısıtlarsınız.

Ali hocam yukarıdaki kapı örneğini anlamakta zorlanabilir. Ben biraz daha düşüneyim. Belki çok "subjective" olmayan bir örnek bulabilirim ama derdimi anlatabildim sanırım? Bence unittest'ler kodun tamamı, bütün öğeleri ile birlikte yazıldıktan sonra yapılacaklar şeyler...

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 10, 2013

Ben beni kırmadı ve ACCU Silicon Valley grubumuzun bu ayki toplantısının konuşmacısı da oldu. DConf'un aksine dinleyiciler D'yi yeterince tanımadıklarından konuşmasının süresini ikiye katlayarak D ile ilgili çok güzel bir tanıtım da yaptı. (Aslında sık sık beni de sohbete kattığı için konuşmanın baş tarafı biraz iki konuşmacı tarafından verilmiş gibi oldu. :))

Kendisine hem bir mesaj yazarak hem de konuşmadan önce sohbet sırasında deject'in amacını tam olarak anlamadığımı söylemiştim, tekrar özetlemişti ve be yine anlamamıştım. Sonunda neredeyse en son saydama gelindiğinde anladım! :)

deject aslında birim testine yardım eden bir çatı değilmiş. deject, birim testine uygun nitelikte yazılmış olan kodun bir sakıncasını ortadan kaldırıyormuş yalnızca. Bilenler için, aslında Guice ve Dagger'ın D'ye uyarlanmışından başka bir şey değilmiş:

https://code.google.com/p/google-guice/wiki/GettingStarted

http://square.github.io/dagger/

Olay şu:

  • Sınıfın bağımlı olduğu başka türleri sınıfa dışarıdan kurucu parametresi olarak vermenin çok daha yararlı olduğunu biliyoruz. Bu sayede kodun birim test edilebilmesini sağlamış oluyoruz. Bunu anlama konusunda bir sakınca yok herhalde. (Ben bile anlıyorum. :p)

  • Ancak, öyle yapmanın bir sakıncası ortaya çıkıyor: O sınıfı kullanan başka her kodun da bağımlı olunan o nesneleri oluşturması ve dışarıdan vermesi gerekiyor. Baştan bunun sakıncası görülmeyebilir: Kodun bağımlı olduğu belki de tek sınıf vardır ve beş yerde o sınıfın nesnesi oluşturulur ve dışarıdan verilir. Ama kodun değiştiğini ve ikinci bir bağımlılığın daha oluştuğunu düşünelim... Birim testini yazabilmek için o bağımlılığı da kurucu parametresi yapınca o beş yerde birden değişiklik yapmak gerekir. Külfetli...

  • deject, @Inject ile işaretlenmiş olan sınıfları derleme zamanında otomatik olarak tarıyor ve sınıf nesnelerini açıkça new ile oluşturmak gerektirmeden ObjectGraph denen bir yapı kullanarak oluşturuyor:

   // Bu baştan bir kere yapılıyor
   auto og = new ObjectGraph!(foo);
// ...
   // Bu ise ToTest sınıfından bir nesne oluşturuyor
   auto o = og.get!(foo.ToTest)();

Ne yazık ki benim basit denemem deject'in bir hatasını bulduğu için örnek kod yok. Hata giderildiğinde tekrar bakarız...

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 10, 2013

Söylediklerinin hepsi geçerli ve başka insanların da dile getirdikleri kaygılar. Birim testleri tabii ki kısıtlayıcı olmamalı. Herkes kendisine uygun olan bir yöntem uyguluyor. Baştan test yazılabildiği gibi senin yaptığın gibi tasarım oturduktan sonra da yazılabilir. :)

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]