Pazartesi, Eylül 24, 2012

Ödeme Ekranı Refactoring 1

Altdotnet Türkiye Google grubundan gelen geri bildirimler ışığında SambaPOS üzerinde değişiklikler yapmaya ve yaptığım değişiklikleri dökümante etmeye devam ediyorum. Önceki yazımdan sonra test first geliştirme yapmam önerildi. Hazır yeri gelmişken bu yazıların ilham kaynağı olan ve grupta değerli paylaşımları ile bir çok kişiye yol gösteren Sidar OK dostumuza bir selam gönderelim.

Öncelikle söyleyim bir TDD uzmanı kesinlikle değilim ama takip ettiğim bir konu. Bu yazılarda öncelikli amacım birşeyler öğretmek değil . Amacım öğrenmek ve öğrenirken yaşadıklarımı paylaşmak. İsterseniz siz de yorumlarınızla katkıda bulunabilirsiniz. 

Eveeet konumuz test driven development olayı. Ve belki de test driven meselesinin en kazık konularından biri olan TDD ile geliştirilmeyen bir proje üzerinde test driven development yapmak. Evet bu elbette çok daha zor çünkü muhtelemen sınıflar arasındaki bağımlıklar nedeniyle test etmek istediğimiz sınıfları yapılandırmak (create) bile mümkün olmayacak. Bu nedenle öncelikle test yazmak istediğimiz sınıfı bağımlıklardan kurtarmak ve "Test Edilebilir" hale getirmek gerekecek.

İyi de buna neden katlanalım ? Zaten çalışıyor ne gerek var test edilebilir hale getirip test falan yazmaya?


Bir önceki yazımda da konu ettiğim ödeme ekranına geri dönelim. Yapmak istediğim döviz butonlarından birine basıldığı zaman solda listelenen fiyatları ilgili döviz kuruna göre güncellemekti. Sonuçta basit bir bölme işlemi olduğu için fazla önemsememiştim ama döviz ile ilgili özellikleri yaparken en çok bu kısım ile uğraştım.

Bu liste kullanıcıya ürün seçerek ödeme alma şansı veriyor. Yani aynı masada oturan beş kişi beraber kasaya gelip "ben kola içtim", "ben de pide yedim" diye saymaya başladığında kasiyer kolayca hesap toplamlarını görüp tahsilat yapabiliyor. Buraya kadar güzel ancak programın tasarımı gereği işin içine döviz girince bir kişinin yiyip içtiğini dolar olarak ödemesi diğer kişinin Türk Lirası olarak ödemesi gibi durumlar (case) ortaya çıktı. İşin içine para üstü girdi. Yani benim 12 lira hesabım vardı 15 verdim 3 lira para üstü aldım, arkadaşım turist olduğu için dolar olarak ödemek istedi ve 4 dolarlık hesabını 5 dolar olarak ödedi. Ayrıca 1 lira 80 kuruş para üstü aldı... Kasiyerin gün sonunda kasasını tutturabilmek için her bir para birimininden kasayı ayrı ayrı düzgün hesaplamak gerek. 

Bir de işin içine iskontolar, bahşişleri hesaplamakla ilgili özellikler, kur çarpımından doğan yuvarlamalardan doğan farklar falan girince ipin ucu gerçekten kaçtı ve ben yaptığım hesaplamaların doğru olup olmadığını anlayamayacağım bir noktaya geldim. Test falan yazmamış olduğum için elime hesap makinesini aldım, akla gelecek her durumu tek tek elimle yapıp hesaplayarak kontrol ettim. Allahtan geçmişte yüzlerce kez bu tür hesaplamalar yapan kodlar yazdığım için işin içinden çıkabildim ama bu ekranda yapılabilecek işlemlerin kombinasyonu neredeyse sınırsız olduğu için hatalı sonuç verecek bir işlem yapılabilme şansı var mı bundan emin değilim. Aslında yüzlerce kullanıcı her durumu test edip çıkan problemleri bildiriyor ama ben yapacağım en ufak değişiklikte bile bu hesap makinesi ile doğrulama işini baştan yapmak zorundayım. Aksi takdirde çalışan şeyi bozmuş olurum. Kötü bir durum.

İşte TDD bu problemle başedebilmemizi sağlayan bir yötem. Tam olarak yaptığımız kodu doğru yazıp yazmadığımızı anlamamız için değil. Daha çok çalışan şeyi bozmamak için kullanılan bir yöntem.

Tamam test yazılması gerektiğine ikna olduk ama nasıl yapacağız? Bu ürün seçme işi ilk yazıldığında üç beş satırdan oluışan basit bir özellikti. Bu ekrana eklenen her özellikten sonra bikaç satır daha kod eklendi ve sonuçta elimizde geliştirmesi ve bakımı zor bir kod yığını var. Nereden başlayacağız? 

Öncelikle bir kod parçası inceleyerek problemli noktalara bakalım. Aşağıdaki kod soldaki her bir satırı temsil eden MergedItem sınıfı. Problemli noktaları comment ekleyerek açıkladım.



MergedItem aslında bir ViewModel sınıfı. Ancak zaman içerisinde hızlı kod yazma gerekliliklerinden (!) dolayı normalde bir modelde durması gereken kodlar da içine karışmış. Bu sınıf sadece görüntülenecek Label'ı ve Font görünümünü belirlemesi gereken bir sınıf olduğu halde bazı hesaplamalar da yapıyor.

Normalde ViewModel sınıflarını test etmek zordur çünkü bir MVVM framework'ü kullanıyorsanız çeşitli nedenlerle framework'e bağımlılıklar oluşur ve test yazmak için test context'i içinde framework'ü de kullanıma hazır hale getirmek gerekebilir. Aslında ViewModel sınıfları test edilmesi şart olan özellikler pek içermezler. Sonuçta temel görevi Model sınıfı ile View sınıfı arasında köprü vazifesi görmektir. Asıl önemli kısımlar model içindedirler ve bu nedenle MergedItem sınıfından Model sınıfını ayırmak ve test ile garantiye almak gerekiyor.

Ayrıca bu MergedItem kolleksiyonunu tutan ve kolleksiyon oluşturma işlerini yapan sınıf da PaymentEditorViewModel sınıfı ve bu kısmı ilgilendiren model sınıfını da ayırıp test ile garantilemek gerek.

İşe öncelikle isimlendirmeyi düzelterek başladım. MergedItem yerine Selector isimini ve kolleksiyonu yöneten sınıfa da OrderSelector ismini vermeyi tercih ettim. Testin yazılımı sürecine fazla girmeyeceğim ancak ilk olarak bir belgenin OrderSelector sınıfına aktarılarak Selector sınıflarının oluşturulması ile başladım ve eklemek istediğim her case için bir test yazarak devam ettim. Yazdığım her bir testten sonra yeni sınıflarımın özellikleri birer birer oluştu ve 10 kadar case'i test ederek sınıflarımı tamamladım.  https://github.com/emreeren/SambaPOS-3/blob/master/Samba.Modules.PaymentModule.Tests/PaymentOrderSelectorTests.cs adresinden testlere yukarıdan aşağı bakarak özellikleri ne sırada eklediğim ve sınıfın nasıl kullanılacağı görülebiliyor.

Sonuçta MergedItem sınıfı yerine geçecek Selector sınıfının son hali şu şekilde oldu.


Özelliklerin arabirimlere bağlanması ve diğer görsel işler de SelectorViewModel sınıfı içinde yapılıyor. Bu sınıfın görünümü de şu şekilde.






Eğer dikkatinizi çektiyse bu sınıf çok enteresan özellikler içermiyor. ViewModel sınıfları için test yazılmasa da olur dememin nedeni buydu. Belki Description özelliğinin doğru olup olmadığı veya diğer değerlerlerin doğru formatlanıp formatlanmadığı test edilebilir ama döviz hesaplamalarının doğru olup olmadığı kadar kritik bir konu değil.

Böylelikle iki farklı işi birden yapan bir sınıfı ikiye bölmüş olduk. Veri tutma ve değiştirme işlemlerini bir sınıfa, görünümle ilgili sorumlulukları ise başka bir sınıfa vermiş olduk. Belki bu ayırma işlemini kodu gözle inceleyerek ve yorumlarda belirttiğim yerleri başka bir sınıfa taşıyarak da yapabilirdik ancak burada test yönelimli hareket ettiğimiz için bölünme sihirli bir şekilde kendiliğinden oluşmuş oldu. Bu arada testlerimizi de yazmış olduk. Hatta sürekli dikkat çeken bir görsel problem de arada çözüldü ama onun nasıl çözüldüğünü tam anlamadım. Demek ki bazı kurallara uygun hareket edince yazdığımız kod daha başarılı oluyor.

PaymentEditorViewModel sınıfından çıkan kısımları da commentleyerek submit ettim. https://github.com/emreeren/SambaPOS-3/blob/16d7123ceaf49e0d54621082ed492a35e5eaabb5/Samba.Modules.PaymentModule/PaymentEditorViewModel.cs adresinde görülebilir.

Bu sınıftan ayrıştırdığımız OrderSelector sınıfı da şurada https://github.com/emreeren/SambaPOS-3/blob/16d7123ceaf49e0d54621082ed492a35e5eaabb5/Samba.Modules.PaymentModule/OrderSelector.cs

OrderSelector sınıfı da gayet temiz ve anlaşılır oldu. Ancak PaymentEditorViewModel sınıfı halen fazla büyük ve daha refactoring istiyor. 

Devam edeceğiz.

Commit : https://github.com/emreeren/SambaPOS-3/commit/16d7123ceaf49e0d54621082ed492a35e5eaabb5

4 yorum:

efkan dedi ki...

Emre Bey merhaba,

yaptığınız her çalışma Türk Yazılım sektörüne değer katıyor. Kodlamalarınızı buraya taşıyor olmanız eminim çok kişinin işini kolaylaştıracaktır.

Başarılarınızın devamını dilerim.

Shibby dedi ki...

1 kelle paça 9 tl olur mu ayıp ya

Emre Eren dedi ki...

İki yıl kadar önceki ilk kullanıcımızın menüsü bu. Şimdi kaç paradır kim bilir :)

Adsız dedi ki...

Hocam deneyimlerini paylasman biz yazilim muh ogrencileri icin harika oluyor. Allah gonlune gore versin gercekten duacinim..