Refactoring 101: Isolation

İlk adım, izolasyon.

Neleri izole etmeliyiz?

Bu soruya cevap verirken dikkat edeceğimiz birkaç nokta var.

  1. Her bir metodun/sınıfın/projenin tek bir çalışma amacı olmalı, ve tek bir amaç için değiştirilmeli/güncellenmeli/genişletilmeli. (SOLID -> S -> Single Responsibility Principle)
  2. Front-end ile back-end’i birbirinden olabildiğince ayırmalıyız. Bu sebeple formlar içerisindeki metotlar ile işin kendisini yapan back-end kodlarını izole etmeliyiz.
    Neye göre karar vereceğiz derseniz, bu adım için kolay bir yöntem var. Front-end ile back-end’i birbirinden ayırırken her zaman kendinize “minimum efor ve back-end’de minimum değişiklik ile yeni bir front-end projesi oluşturabiliyor muyum?” diye sorabilirsiniz. (Örneğin bir web client projeniz var ise onun yanına bir mobil client projesi eklemek gibi düşünebilirsiniz.) Cevap evet olana kadar da ara kütüphaneler ve sınıflar ile izolasyona devam etmelisiniz.
  3. Ne ve Nasıl sorularının cevapları birbirinden izole olmalı. Domain Driven Design konusunda belki de en çok başvurmanız gereken sorudur Ne? sorusu. Bir örnekle açıklayayım.
IEnumerable<string> GetUserNames();

Bu metot NE yapıyor? diye sorduğunuzda vereceğiniz cevap “Tüm kullanıcıların isimlerini getiriyor” olmalı. Ve içeriğindeki kodu incelediğinizde eğer vereceğiniz cevap “EntityFramework ile MyTestDB veritabanına bağlanıp Users tablosu üzerinden User DBSet’ini çekip, bir döngü içerisinde yeni bir string listesine kullanıcı adlarını yazmak ve bunu return etmek.” ise, vay halinize…

Ne sorusuna vereceğiniz cevap hiçbir zaman teknoloji ya da yöntem içermemeli. Benzer şekilde nasıl sorusu sorduğumuz bir metot da Ne sorusunun cevabını verememeli.

Bu bilgiler ışığında projenin ilk versiyonu üzerinde izolasyon çalışmalarına başlayalım.

Arayüz üzerinde herhangi bir değişiklik yapmamaya çalışacağım gerekmedikçe.

Mevcutta arayüzün geldiği nokta şu şekilde:

Mühendislerin dostu, ajansların korkulu rüyası “Çirkin ama işlevsel arayüz”

Formun code-behind tarafında ne gibi değişiklikler olduğu da proje yapısındaki değişimden görülebilir.

Yeni proje yapısı

Helpers ve Models isminde iki yeni klasör eklendi. Yalnızca bu ekran görüntüsünden artık Windows’un kendi File ve Folder sınıflarını kullanmadığımızı, kendimize ait yeni birer FileModel ve FolderModel sınıfı oluşturduğumuzu görebiliriz. Bu iki model sınıfı da ortak bir IDirectoryItem interface’inden türüyor. Gerçek dünyada olduğu gibi nasıl ki File ve Folder bir dosya sisteminin iki elemanı ise, kodda da bu yapıyı kurmalıyız. Domain Driven Design’a yaklaştıkça bu eğilimi daha net görebileceksiniz.

Bir diğer yenilik, DirectoryManager sınıfı. Buradan da anlıyoruz ki dizinler ile ilgili işlemleri yönetmek üzere bir yardımcı sınıf oluşturulup tüm fonksiyonellik buraya taşınmış.

Şimdi forma ait code-behind’a bir göz atalım ve nelerin izole olduğunu görelim.

Eski ve yeni kodları arka arkaya bölüm bölüm ekleyerek nelerin neden değiştiğini aktaracağım.

Eski Form1.cs

FolderBrowserDialog fbd;
        string path;
        string[] folders;
        string[] files;
        public Form1()
        {
            InitializeComponent();
            toolTip1.SetToolTip(listBox1, "Bulunduğu dizini açmak için çift tıklayınız. İşlem menüsü için sağ tıklayınız.");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            fbd = new FolderBrowserDialog();
            fbd.ShowDialog();
            if (fbd.SelectedPath.ToString() != "")
            {
                path = fbd.SelectedPath;
                textBox1.Text = path;
                folders = Directory.GetDirectories(path);
                files = Directory.GetFiles(path);
            }
        }

Yeni Form1.cs

FolderModel _folderModel;
        public Form1()
        {
            InitializeComponent();
            toolTip1.SetToolTip(treeViewResults, "Bulunduğu dizini açmak için çift tıklayınız. İşlem menüsü için sağ tıklayınız.");
        }

        private void buttonBrowse_Click(object sender, EventArgs e)
        {
            using (FolderBrowserDialog fbd = new FolderBrowserDialog())
            {
                DialogResult dr = fbd.ShowDialog();
                if (dr.Equals(DialogResult.OK))
                {
                    string path = fbd.SelectedPath;
                    string name = DirectoryManager.GetName(path);
                    textBoxPath.Text = path;
                    _folderModel = new FolderModel(name, path, null);
                }
            }
        }

İlk göze çarpan global değişkenler olmalı. Eski versiyonda dosyalar, klasörler, FolderBrowserDialog’a kadar pek çok global değişken vardı.

Tüm bunlar yerine yalnızca seçilen klasöre ait bilgileri tutan FolderModel sınıfından bir değişken yeterli.

Bir diğer iyileştirme, isimlendirmeler. button1 gibi bir isimlendirme yerine butonun yaptığı iş ile butonu ve butona ait metotları isimlendirmek kodun okunabilirliğini ve haliyle bakımını kolaylaştıracaktır.

Bir diğer yenilik, Disposable bir sınıf olan FolderBrowserDialog sınıfının kullanımı. Daha önce uygulamanın kapanışında manuel olarak dispose edilen bu sınıf artık browse butonunun click eventinde using ile initialize edilerek kullanılıyor ve görevini tamamladıktan sonra, yani bize seçilen folder’ın yolunu verdikten sonra otomatik olarak dispose ediliyor.

Bir diğer güzellik de, using içerisinde tanımladığımız tüm değişkenler görevini yerine getirdikten sonra dışarıya yalnızca _folderModel sınıfı çıkabiliyor. Ve bizim için gerekli olan tek sınıf bu. Diğer tüm referans ve değer değişkenlerinin ömrü sonlanıyor.

Gelelim klasörleri/dosyaları arama ve listeleme işlevselliğine.

Eski Kod

private void button2_Click(object sender, EventArgs e)
        {
            if (folders != null || files != null)
            {
                if (checkBox1.Checked)
                {
                    yaz(folders, 0);
                    yaz(files, 0);
                }
                else
                {
                    duzYaz(folders);
                    duzYaz(files);
                }
            }
        }

        private void yaz(string[] paths,int degree)
        {
            string newPath;
            string ayirac = "";
            for (int i = 0; i < paths.Length; i++)
            {
                for (int j = 0; j < degree; j++)
                    ayirac += "     ";
                listBox1.Items.Add(ayirac +"-"+ paths[i].ToString());
                newPath = paths[i].ToString();
                if (Directory.Exists(newPath))
                {
                    yaz(Directory.GetDirectories(newPath),degree+1);
                    yaz(Directory.GetFiles(newPath),degree+1);
                }
                ayirac = "";
            }
        }

        private void duzYaz(string[] paths)
        {
            string newPath;
            for (int i = 0; i < paths.Length; i++)
            {
                listBox1.Items.Add(paths[i].ToString());
                newPath = paths[i].ToString();
                if (Directory.Exists(newPath))
                {
                    duzYaz(Directory.GetDirectories(newPath));
                    duzYaz(Directory.GetFiles(newPath));
                }
            }
        }

Yeni Kod

private void buttonStartListing_Click(object sender, EventArgs e)
        {
            Clear();
            bool showCompletePath = checkBoxShowCompletePath.Checked;

            DirectoryManager dm = new DirectoryManager(_folderModel);
            dm.Investigate();

            FillTree(_folderModel, showCompletePath);
        }

        private void FillTree(FolderModel rootFolder, bool showCompletePath)
        {
            TreeNode rootNode = AddNode(null, rootFolder, showCompletePath);
            treeViewResults.Nodes.Add(rootNode);

            FillTree(rootNode, rootFolder, showCompletePath);
        }
        private void FillTree(TreeNode parentNode, FolderModel selectedFolder, bool showCompletePath)
        {
            TreeNode currentNode = AddNode(parentNode, selectedFolder, showCompletePath);

            foreach (var subFolder in selectedFolder.GetSubFolders())
            {
                FillTree(currentNode, subFolder, showCompletePath);
            }

            foreach (var file in selectedFolder.GetFiles())
            {
                AddNode(parentNode, file, showCompletePath);
            }
        }

        private TreeNode AddNode(TreeNode parentNode, IDirectoryItem itemToAdd, bool showCompletePath)
        {
            string nodeText = itemToAdd.Name;
            if (showCompletePath)
            {
                nodeText = itemToAdd.CompletePath;
            }

            if (parentNode != null)
                return parentNode.Nodes.Add(nodeText);

            return new TreeNode(nodeText);
        }

İlk değişiklik listView yerine TreeView kontrolünün kullanılması. Böylelikle eski kodda yer alan indent verme karmaşıklığı tamamen ortadan kalktı. Bir diğer önemli değişiklik tüm directory kodları ve fonksiyonelliğin DirectoryManager sınıfına taşınması. Bu aşamadaki en büyük izolasyon bu sınıf ile gerçekleşiyor.

Son olarak eski kodlarda iterative şekilde kodlanmış ağaç oluşturma fonksiyonları, yeni kodlarda recursive kodlanmış.

Bu aşamada hala tek bir form application üzerinden ilerliyoruz. Domain – Infrastructure – App üçgenini henüz kurmaya başlamadık. Ancak bu adımda yaptığımız izolasyon sayesinde bu ayrımı daha rahat görebiliyoruz artık.

Yeni eklenen modelleri ve DirectoryManager sınıfını github üzerinden inceleyebilirsiniz. Ayrıca bu aşamaya kadarki tüm kodlara yine github üzerindeki aynı repository’den, yazının başlığı ile aynı isimli klasör altında ulaşabilirsiniz.

Bir sonraki adımda Domain ve Infra ayrımını yapıp Inversion of Control ve Dependency Inversion’ı odağımıza alacağız. Ne ve Nasıl‘ı birbirinden ayırmaya devam edeceğiz.

Sıradaki yazı; Refactoring 101: DDD, IoC ve DI

Leave a Reply

Your email address will not be published. Required fields are marked *