Refactoring’i Detaylı Bir Örnek İle İnceleme Part 2

Selamlar,

Bu makalede, Refactoring serisine kaldığımız yerden devam edeceğiz. Part 1’e buradan erişebilirsiniz.

Kodun Fonksiyonlara Dağtılması : Kodun son hali aşağıdaki gibidir. Şu anki yapı, ilk haline göre çok daha dağıtık ve anlaşılırdır. Tüm işler fonksiyonlara taşınmıştır. Ana methodda yapılacak tek iş, ilgili fonksiyonun çağrılmasıdır.

  • getAmounth()
  • findCourse()
  • calculateVolumeCredit()
  • calculateVolumeCredit()
  • tr()
  • totalValumeCredits()
  • getTotalAmount()

Hesaplamaları ve Formatı Aşamalara Bölme:

Bu zamana kadar karmaşık parçaları, küçük parçalara ayırıp fonksiyonlar yardımı ile çağırdık. Bu aşamadan sonra, daha çok işlevselliğin değişimi üzerine konuşacağız.

Şimdi gelin tüm methodları, CalculateAmounth() methodu altında topluyalım. Ayrı bir method altına toplamaya Extract Function, method içinde method tanımlamaya Inline Function, bunu C# karşılığına da Local Function denilmektedir.

Main(): CalculateAmount() methodunun çağrıldığı ana methoddur. C# Console Aplication’ın çalıştırıldığı an, default çağrılan static methoddur.

CalculateAmounth(): Tüm methodların toplandığı, global methoddur. Böylece, kod parçalanarak fonksiyonlara dağtılmış olsa da,  tüm işler tek bir yerden yönetlebilmektedir.

PassingData : Ara veri taşıyan bir sınıftır. Kısaca Ana methodda (CalculateAmounth())’a paramtere taşımak amaçlı kullanılan, ara bir sınıftır.

Image Source: https://refactoringguru.cn/images/refactoring/content/smells/long-parameter-list-01-2x.png

Yine aşağıda, “CalculateAmounth()” methodunun işleyişinin 2 faza bölünmesi amaçlanmıştır. 2 faz arasında, ara veri taşıyan bir nesne yaratılmıştır. Bu nesne yukarıda tanımlanan PassingData sınıfıdır.

static void Main(string[] args)
{
        .
        PassingDatapassingData=new PassingData();
        CalculateAmounth(passingData, invoice, courses);
}
public static void CalculateAmounth(PassingData data, Invoice invoice, IDictionary<string, Course> courses)
{
        .
}
Şimdi gelin, kod kalabalığını azaltmak ve okunaklığı arttırmak adına invoice parametresini ortadan kaldıralım. Öncelikle Invoice sınıfına ait CustomerName ==> passingData nesnesine atanır. Ve CalculateAmounth() methodunda : 
static void Main(string[] args)
{
        .
        PassingData passingData=new PassingData();
        passingData.customerName = invoice.customerName;
        CalculateAmounth(passingData, invoice, courses);
}
public static void CalculateAmounth(PassingData data, Invoice invoice, IDictionary<string, Course> courses)
{
        /* var result = $”{invoice.customerName} için Fatura Detayı: \n”; */
        var result=$”{data.customerName} için Fatura Detayı: \n”;
        .
}
Sıra geldi Invoice sınıfına ait Registers property’sinin, passingData nesnesine atanmasına ve Invoice parametresinin tamamen kaldırılmasına. Amaç kod sadeliğidir :
static void Main(string[] args)
{
        .
        PassingData passingData=new PassingData();
        passingData.customerName = invoice.customerName;
        passingData.registers=invoice.registers;
        CalculateAmounth(passingData, invoice, courses);
}
public static void CalculateAmounth(PassingData data, Invoice invoice, IDictionary<string, Course> courses)
{
        var result=$”{data.customerName} için Fatura Detayı: \n”;
        /* foreach (Register reg in invoice.registers) */
        foreach (Register reg in data.registers)
        {
                result+=$”{findCourse(reg).Name}: {tr(getAmounth(reg) /100)} ({reg.student} kişi)\n”;
        }
        decimal totalValumeCredits()
        {
                decimalresultValume=0;
                /* foreach (Register reg in invoice.registers) */
                foreach (Register reg in data.registers)
                {
                        resultValume+=calculateVolumeCredit(reg);
                }
                return resultValume;
        }
        decimal getTotalAmount()
        {
                decimal resultTotalAmounth=0;
                /* foreach (Register reg in invoice.registers) */
                foreach (Register reg in data.registers)
                {
                        resultTotalAmounth+=getAmounth(reg);
                }
                return resultTotalAmounth;
        }
        .
        .
}
Artık eğlence başlıyor :) Şimdi sıra, registers nesnesinin bir kopyasının oluşturulmasına geldi. Amaç işleve alınacak register verilerinin, değerlerinin değiştirilmemesidir. Yani diziye ait tüm elemanların Shallow Copy’si alınacaktır. Amaç orjinal datanın bozulmamasının sağlanmasıdır.
Öncelikle Register sınıfına, aşağıdaki Clone() methodu eklenir:
using System;
public class Register : ICloneable
{
        public string courseID { get; set; }
        public int student { get; set; }
        public object Clone()
        {
                return this.MemberwiseClone();
        }
}
Program.cs‘e aşağıdaki değişiklikler yapılır : Böylece registers dizisi’nin kopyası alınmış olunur. CloneRegister adında Register[] dizisi için, extension bir method yaratılmış olunur.
static class Program
{
        public static Register[] CloneRegister(this Register[] arrayToClone)
        {
                return arrayToClone.Select(item=> (Register)item.Clone()).ToArray();
        }
        static void Main(string[] args)
        {
                .
                PassingData passingData=new PassingData();
                passingData.customerName = invoice.customerName;
                /*passingData.registers=invoice.registers;*/
                passingData.registers = invoice.registers.CloneRegister();
                CalculateAmounth(passingData, courses);
        }
        .
        .
}
1-) Öncelikle gelin Register sınıfına, Course property’si ekleyelim.
using System;
public class Register : ICloneable
{
        publicstring courseID { get; set; }
        publicint student { get; set; }
        publicobject Clone()
        {
                return this.MemberwiseClone();
        }
        public Course course { get; set; }
}
2-) Şimdi bu property’yi CloneRegister Extension‘ında aşağıdaki gibi set edelim : findCorse () methodu çağrılmadan CloneRegister() extensiom’ı ile ilgili course bulunmuş olunur.
public static Register[] CloneRegister(this Register[] arrayToClone)
{
        return arrayToClone.Select(item =>
        {
                Register reg=(Register)item.Clone();
                reg.course=findCourse(reg);
                return reg;
        }).ToArray();
}
3-) findCourse() methodunun CloneRegister() methodu içinde erşilebilmesi için, CalculateAmounth() methodunun dışına alınması gerekmektedir :
public static Course findCourse(Register register)
{
        return courses[register.courseID];
}
4-) Şimdi sıra geldi CalculateAmounth() içinde geçen findCourse() methodunun, refactor edilip kaldırılmasına: Nested methodlardan kurtulup, bu işlemleri sınıfların propertylerine yaptırılır. Böylece kod okunaklığı ve sadelik arttırılır.
public static void CalculateAmounth(PassingData data, IDictionary<string, Course> courses)
{
        var result=$”{data.customerName} için Fatura Detayı: \n”;
        foreach (Register reg in data.registers)
        {
            /* result += $”{findCourse(reg).Name}: {tr(getAmounth(reg) / 100)} ({reg.student} kişi)\n”; */
                result+=$”{reg.course.Name}: {tr(getAmounth(reg) /100)} ({reg.student} kişi)\n”;
        }
        result+=$”Toplam borç { tr(getTotalAmount() /100) }\n”;
        result+=$”Kazancınız { tr(totalValumeCredits()) } \n”;
        Console.WriteLine(result);
        Console.ReadLine();
        decimal calculate VolumeCredit(Register register)
        {
                decimal volumeCredits=0;
                //kazanılan para puan
                volumeCredits+=Math.Max(register.student-15, 0);
                // extra bonus para puan her 5 yazılım öğrencisi için
                decimal fiveStudentGroup=register.student/5;
                /* if (Types.Software == findCourse(register).Type) volumeCredits += Math.Floor(fiveStudentGroup); */
                if (Types.Software == register.course.Type) volumeCredits+=Math.Floor(fiveStudentGroup);
                return volumeCredits;
        }
        string tr(decimal value)
        {
                CultureInfo trFormat=new CultureInfo(“tr-TR”, false);
                trFormat.NumberFormat.CurrencySymbol=”TL”;
                trFormat.NumberFormat.NumberDecimalDigits=2;
                return value.ToString(“C”, trFormat);
        }
        decimal totalValumeCredits()
        {
                decimal resultValume=0;
                foreach (Register reg in data.registers)
                {
                        resultValume+=calculateVolumeCredit(reg);
                }
                return resultValume;
        }
        decimal getTotalAmount()
        {
                decimal resultTotalAmounth=0;
                foreach (Register reg in data.registers)
                {
                         resultTotalAmounth+=getAmounth(reg);
                 }
                return resultTotalAmounth;
        }
        int getAmounth(Register register)
        {
                var resultAmounth=0;
                /* switch (findCourse(register).Type) */
                switch (register.course.Type)
                {
                        case Types.Art:
                        {
                                resultAmounth=3000;
                                if (register.student>15)
                                {
                                        resultAmounth += 1000 * (register.student-10);
                                }
                                break;
                        }
                        case Types.Software:
                        {
                                resultAmounth=30000;
                                if (register.student>10)
                                {
                                        resultAmounth+=10000+500 * (register.student-5);
                                }
                                resultAmounth+=300 * register.student;
                                break;
                        }
                }
                return resultAmounth;
        }
}
Gelin aynı işlemleri amount için de yapalım : Böylece CalculateAmounth() methodunun ayrıca içinden external methodlar çağrılmadan, propertyleri ile çalıştırılabilmesini sağlıyalım.
1-) Şimdi amount property’sini CloneRegister Extension‘ında aşağıdaki gibi set edelim :
public static Register[] CloneRegister(this Register[] arrayToClone)
{
        return arrayToClone.Select(item =>
        {
                Register reg=(Register)item.Clone();
                reg.course=findCourse(reg);
                reg.amount=getAmounth(reg);
                return reg;
        }).ToArray();
}
2-) Register sınıfına amount propertysi eklenir.
using System;
public class Register : ICloneable
{
        publicstring courseID { get; set; }
        publicint student { get; set; }
        publicobject Clone()
        {
                return this.MemberwiseClone();
        }
        public Course course { get; set; }
        public int amount { get; set; }
}
3-) getAmounth () methodunun erşilebilmesi adına, CalculateAmounth() methodunun dışına alınır :
public static int getAmounth(Register register) { … }
4-) Şimdi sırada CalculateAmounth() içinde geçen getAmounth() methodunun, refactor edilip kaldırılmasına:
public static void CalculateAmounth(PassingData data, IDictionary<string, Course> courses)
{
        var result=$”{data.customerName} için Fatura Detayı: \n”;
        foreach (Register reg in data.registers)
        {
                /*result+=$”{reg.course.Name}: {tr(getAmounth(reg) /100)} ({reg.student} kişi)\n”;*/
                result += $”{reg.course.Name}: {tr(reg.amount/100)} ({reg.student} kişi)\n”;
        }
        result+=$”Toplam borç { tr(getTotalAmount() /100) }\n”;
        result+=$”Kazancınız { tr(totalValumeCredits()) } \n”;
        Console.WriteLine(result);
        Console.ReadLine();
        decimal calculateVolumeCredit(Register register)
        {
                decimal volumeCredits=0;
                //kazanılan para puan
                volumeCredits+=Math.Max(register.student-15, 0);
                // extra bonus para puan her 5 yazılım öğrencisi için
                decimal fiveStudentGroup=register.student/5;
                if (Types.Software == register.course.Type) volumeCredits+=Math.Floor(fiveStudentGroup);
                return volumeCredits;
        }
        string tr(decimal value)
        {
                CultureInfo trFormat=new CultureInfo(“tr-TR”, false);
                trFormat.NumberFormat.CurrencySymbol=”TL”;
                trFormat.NumberFormat.NumberDecimalDigits=2;
                return value.ToString(“C”, trFormat);
        }
        decimal totalValumeCredits()
        {
                decimal resultValume=0;
                foreach (Register reg in data.registers)
                {
                        resultValume+=calculateVolumeCredit(reg);
                }
                return resultValume;
        }
        decimal getTotalAmount()
        {
                decimal resultTotalAmounth=0;
                foreach (Register reg in data.registers)
                {
                        /* resultTotalAmounth+=getAmounth(reg);*/
                            resultTotalAmounth += reg.amount;
                 }
                return resultTotalAmounth;
        }
}
Bu değişikliğin ardından tüm Test adımları tekrardan çalıştırılır. Eğer alltaki sonuçların aynısı alınmış ise, herşey yolunda demektir.
volumeCredit propertsi için de gerekli refactoring işlemlerini yapalım : Böylece CalculateAmounth() methodunun ayrıca içinden external methodlar çağrılmadan, propertyleri ile çalıştırılabilmesi sağlanmıştı.
1-) Şimdi volumeCredit property’sini CloneRegisterExtension‘ında aşağıdaki gibi set edelim :
public static Register[] CloneRegister(this Register[] arrayToClone)
{
        return arrayToClone.Select(item =>
        {
                Register reg=(Register)item.Clone();
                reg.course=findCourse(reg);
                reg.amount=getAmounth(reg);
                reg.valumeCredit=calculateVolumeCredit(reg);
                return reg;
        }).ToArray();
}
2-) Register sınıfına valumeCredit propertysi eklenir.
using System;
public class Register : ICloneable
{
        publicstring courseID { get; set; }
        publicint student { get; set; }
        publicobject Clone()
        {
                return this.MemberwiseClone();
        }
        public Course course { get; set; }
        public int amount { get; set; }
        public decimal valumeCredit { get; set; }
}
3-) calculateVolumeCredit() methodunun erşilebilmesi adına, CalculateAmounth() methodunun dışına alınır :
public static decimal calculateVolumeCredit(Register register) { … }
4-) Şimdi sırada CalculateAmounth() içinde geçen calculateVolumeCredit() methodunun, refactor edilip kaldırılmasında:
public static void CalculateAmounth(PassingData data, IDictionary<string, Course> courses)
{
       .
       .
        decimal totalValumeCredits()
        {
                decimal resultValume=0;
                foreach (Register reg in data.registers)
                {
                        /*resultValume+=calculateVolumeCredit(reg);*/
                        resultValume += reg.valumeCredit;
                }
                return resultValume;
        }
        .
        .
}
Şimdi sıra geldi totalValumeCredits() ve getTotalAmount() methodlarının CalculateAmounth() methodundan çıkarmaya :
1-) İlgili methodlar CalculateAmounth() methodu içinden dışarıya alınır. Ve PassingData parametre olarak atanır.
public static decimal totalVolumeCredit(PassingData data)  { … }
public static decimal getTotalAmounth(PassingData data)  { … }
2-) PassinfData sınıfına TotalAmounth ve TotalValumeCredits paramtere olarak eklenir :
using System.Collections.Generic;
public class PassingData
{
        public string customerName { get; set; }
        public Register[] registers { get; set; }
        public decimal TotalAmount { get; set; }
        public decimal TotalValumeCredits { get; set; }
}
3-) Main () methodunda passingData sınıfına TotalAmounth ve TotalValumeCredits parametreleri atanır.
static void Main(string[] args)
{
        .
        .
        PassingData passingData =new PassingData();
        passingData.customerName = invoice.customerName;
        passingData.registers = invoice.registers.CloneRegister();
        passingData.TotalAmount = getTotalAmount(passingData);
        passingData.TotalValumeCredits = totalValumeCredits(passingData);
        CalculateAmounth(passingData, courses);
}
4-) Son olarak CalculateAmounth() methodu aşağıdaki gibi değiştirilir: Böylece Nested Functions silsilesinden kurtulunmuş olunur. Kodlar çok daha sade bir hale gelmedi mi :)
public static void CalculateAmounth(PassingData data, IDictionary<string, Course> courses)
{
        var result =$”{data.customerName} için Fatura Detayı: \n”;
        foreach (Register reg in data.registers)
        {
                result += $”{reg.course.Name}: {tr(reg.amount / 100)} ({reg.student} kişi)\n”;
        }
        result += $”Kazancınız { tr(totalValumeCredits()) } \n”; */
        /* result += $”Toplam borç { tr(getTotalAmount() / 100) }\n”;
        result += $”Kazancınız { tr(totalValumeCredits()) } \n”;*/
        result += $”Toplam borç { tr(data.TotalAmount / 100) }\n”;
        result += $”Kazancınız { tr(data.TotalValumeCredits) } \n”;
        .
        .
}
Refactoring işleminin son adımı olarak, Register oluşturan bir RegisterGenerator sınıfı yapalım . Amaç çeşitli özellikleri üreten bir fabrika. Böylece yeni özellikler geldiği zaman, ya da var olan özelliklerin değiştirilmesi gerektiği zaman, güncellenmesi gereken tek yer ilgili fabrika olacaktır.
1-) RegisterGenerator: Register Fabrika sınıfı oluşturulur ve  ilk property course bu sınıftan üretilir.

CloneRegister methodu course’u, bu yeni Register fabrikasından alacak şekilde değiştirilir:
public static Register[] CloneRegister(thisRegister[] arrayToClone)
{
        return arrayToClone.Select(item =>
        {
                RegisterGenerator regGenerator =new RegisterGenerator(item, findCourse(item));
                Register reg = (Register)item.Clone();
                //reg.course = findCourse(reg);
                reg.course = regGenerator.course;
                reg.amount = getAmounth(reg);
                reg.valumeCredit = calculateVolumeCredit(reg);
                return reg;
        }).ToArray();
}
2-) Kullanılan methodlar RegistorGenerator sınıfı altına toplanır: getAmounth propertysi aşağıdaki gibi eklenir :

getAmounth() methodu aşağıdaki gibi değiştirilebilir: Ama biz zaten bu örnekde, ilgili methoda direk erişilemiyecektir. RegisterGenerator sınıfı üzerinden erişilecektir.
public static int getAmounth(Register register)
{
        return new RegisterGenerator(register, findCourse(register)).getAmounth;
}
CloneRegister() methodu aşağıdaki gibi güncellenir: “amount” property’si, yine RegisterGenerator fabrikasından alınır.
public static Register[] CloneRegister(this Register[] arrayToClone)
{
        return arrayToClone.Select(item =>
        {
                RegisterGenerator regGenerator =new RegisterGenerator(item, findCourse(item));
                Register reg = (Register)item.Clone();
                reg.course = regGenerator.course;
                //reg.amount = getAmounth(reg);
                reg.amount = regGenerator.getAmounth;
                reg.valumeCredit = calculateVolumeCredit(reg);
                return reg;
        }).ToArray();
}
3-) Aynı işlem ValumeCredit değişkeni için de yapılır: RegisterGenerator sınıfınagetValumeCredit” propertysi eklenir.

İstenir ise calculateVolumeCredit() methodu aşağıdaki gibi değiştirilebilir: Biz yine bu örnekde ilgili methoda doğrudan erişmeyeceğiz.

public static decimal calculateVolumeCredit(Register register)
{
        return new RegisterGenerator(register, findCourse(register)).getValumeCredit;
}
CloneRegister() methodu aşağıdaki gibi güncellenir: “getValumeCredit” propertysi de, RegisterGenerator fabrikasından alınır.
public static Register[] CloneRegister(this Register[] arrayToClone)
{
        return arrayToClone.Select(item =>
        {
                RegisterGenerator regGenerator =new RegisterGenerator(item, findCourse(item));
                Register reg = (Register)item.Clone();
                reg.course = regGenerator.course;
                reg.amount = regGenerator.getAmounth;
                /*reg.valumeCredit = calculateVolumeCredit(reg);*/
                reg.valumeCredit = regGenerator.getValumeCredit;
                return reg;
        }).ToArray();
}
Tüm test adımları çalıştırıldığı zaman, en başta olduğu gibi yine aynı çıktının, aşağıdaki gibi alınması gerekmektedir:
Polymorphic Olarak RegisterGenerator Oluşturma : Son adım olarak, ihtiyaca göre farklı eğitimlere göre classları generate edelim. Not: Burada amaç switch case yapısı kullanılmadan, eğitim tipine göre özel sınıf yaratıp ücretin hesaplanmasıdır.
En tepeye alttaki RegesterGenerate oluşturan method tanımlanır:

CloneRegister() methodu aşağıdaki gibi güncellenir:
public static Register[] CloneRegister(this Register[] arrayToClone)
{
        return arrayToClone.Select(item =>
        {
                /*RegisterGenerator regGenerator =new RegisterGenerator(item, findCourse(item));*/
                RegisterGenerator regGenerator = createRegisterGenerator(item, findCourse(item));
                Register reg = (Register)item.Clone();
                reg.course = regGenerator.course;
                reg.amount = regGenerator.getAmounth;
                reg.valumeCredit = regGenerator.getValumeCredit;
                return reg;
        }).ToArray();
}
Şimdi sırada Art ve Software oluşturacak Factoryleri RegisterGenerator’dan yaratmaya geldi: Böylece Art, Software kursları ve daha yeni eklenebilecek kurslar için ayrı ayrı fabrikalar yaratılabilir. Hepsinin kendine özel bussiness planına göre fiyat hesaplanabilir. Tüm Generatorlar “RegisterGenerator“‘dan aşağıdaki gibi türetilmiştir: Hepsinin kendine özel olan “getAmounth propertysi farklıdır.
ArtRegisterGenerator, SoftwareRegisterGenerator :

RegisterGenerator aşağıdaki gibi değiştirilir: Switch Case’e göre hesaplanan getAmounth property’si, artık bir değer döndürmez. Kendisinden türetülen sınıfın tipine göre, farklı bussiness planlara ait sonuçlar ilgili sınıflar tarafından döndürülür.

public class RegisterGenerator
{
        public Register register { get; set; }
        public Course course { get; set; }
        public virtual int getAmounth
        {
                get
                {
                        var result Amounth=0;
                        switch (this.course.Type)
                        {
                                case Types.Art:
                                {
                                    /* resultAmounth = 3000;
                                        if (this.register.student > 15)
                                        {
                                                 resultAmounth += 1000 * (this.register.student – 10);
                                        }
                                        break;*/
                                        throw new Exception(“Subclass Responsibility”);
                               }
                               case Types.Software:
                               {
                                       /* resultAmounth = 30000;
                                       if (this.register.student > 10)
                                       {
                                               resultAmounth += 10000 + 500 * (this.register.student – 5);
                                       }
                                       resultAmounth += 300 * this.register.student;
                                       break; */
                                      throw new Exception(“Subclass Responsibility”);
                               }
                      }
                     return resultAmounth;
                }
        }
        public virtual decimal getValumeCredit
        {
                get
                {
                        decimal volumeCredits=0;
                        //kazanılan para puan
                        volumeCredits+=Math.Max(this.register.student-15, 0);
                        //extra bonus para puan her 5 yazılım öğrencisi için
                        decimal fiveStudentGroup=this.register.student/5;
                        if (Types.Software==this.course.Type) volumeCredits+=Math.Floor(fiveStudentGroup);
                        return volumeCredits;
                }
        }
        public RegisterGenerator(Register_register, Course_course)
        {
                this.register=_register;
                this.course=_course;
        }
}
createRegisterGenerator() methodu, aşağıda görüldüğü gibi gelen Kurs (course) tipine göre, yeni bir Generator oluşturmaktadır. Böylece herbir kurs için, farklı bir generator yapılarak koda devam edilebilir.
public static RegisterGenerator createRegisterGenerator(Register reg, Course course)
{
        //return new RegisterGenerator(reg, course);
        switch (course.Type)
        {
                case Types.Art: return new ArtRegisterGenerator(reg, course);
                case Types.Software: return new SoftwareRegisterGenerator(reg, course);
                default:
                throw new Exception($”Unknown type: { course.Type }”);
        }
}
Benzer değişikliği getValumeCredit için de yapalım. Öncelikle RegisterGenerator aşağıdaki gibi değiştirilir: getValumeCredit’de course tipi Software için, ilgili hesaplama kaldırılır. Bu method artık SoftwareRegisterGenerator sınıfı altında tanımlanır.
public class RegisterGenerator
{
        public Register register { get; set; }
        public Course course { get; set; }
        public virtual int getAmounth
        {
                get
                {
                        throw new Exception(“Subclass Responsibility”);
                }
        }
        public virtual decimal getValumeCredit
        {
                get
                {
                        return Math.Max(this.register.student – 15, 0);
                        /*decimal volumeCredits=0;
                        volumeCredits+=Math.Max(this.register.student-15, 0);
                        decimal fiveStudentGroup=this.register.student/5;
                        if (Types.Software==this.course.Type) volumeCredits+=Math.Floor(fiveStudentGroup);
                        return volumeCredits;*/
                }
        }
        public RegisterGenerator(Register_register, Course_course)
        {
                this.register=_register;
                this.course=_course;
        }
}
SoftwareRegisterGenerator : Aşağıda görüldüğü gibi Software kursu için getValumeCredit özelleştirilmiştir. Ama Art kursu için ayrıca bir getValumeCredit hesaplaması olmadığı için, inherit edilen RegisterGenerator sınıfına ait getValumeCredit property’si kullanılmaktadır.
public class SoftwareRegisterGenerator : RegisterGenerator
{
        public SoftwareRegisterGenerator(Register_register, Course_course) : base(_register, _course){}
        public override int getAmounth
        {
                get
                {
                        var resultAmounth=30000;
                        if (this.register.student>10)
                       {
                               resultAmounth+=10000+500 * (this.register.student-5);
                       }
                       resultAmounth+=300 * this.register.student;
                       return resultAmounth;
                }
        }
        public override decimal getValumeCredit
        {
                get
                {
                        decimal fiveStudentGroup=this.register.student/5;
                        return base.getValumeCredit + Math.Floor(fiveStudentGroup);
                 }
        }        
}
ArtRegisterGenerator : Olduğu gibi bırakılır. getValumeCredit()methodu eklenmez. base class‘daki, yani RegisterGenerator class’ındaki getValumeCredit() methodu kullanılır.
public class ArtRegisterGenerator : RegisterGenerator
{
        public ArtRegisterGenerator(Register_register, Course_course) : base(_register, _course){}
        public override int getAmounth
        {
                get
                {
                        var resultAmounth=3000;
                        if (this.register.student>15)
                        {
                                resultAmounth+=1000* (this.register.student-10);
                        }
                        return resultAmounth;
                 }
        }
}
Uygulama bu hali ile test edildiğinde, aşağıdaki gibi bir sonuç alınır ise, Refactoring işlemi başarı ile sağlanmış demektir.
Uygulamanın son hali aşağıdaki gibidir: Görüldüğü gibi 1. Bölümdeki hali ile oldukça farklıdır :)
Bu makalede Refactoring nedir ne değildir gibi sorulara, canlı bir örnek ile değinmeye çalıştık.Bu örnek sırasında birçok farklı refactoring yöntemi kullanılmıştır. Extract Function, Inline Variable, Move Function ve Polymorphism ile Replace Conditional bunlardan bazılarıdır. Dikkat etti iseniz, herşey küçük adımlar ile parça parça ilerlenilmiştir. Esas amaç, Martin Fowler’ın dediği gibi, hatayı minimuma indirmek, küçük adımlar ile oluşabilecek hataları hızlıca yakalamak en nihayetinde “Küçük Adımlar İle Hızlanmaktır“.  Martin Fowler’ın kitabında bahsettiği bir diğer konu da, kod geliştirirken 2 faklı şapka takmanız gerektiğidir. Kod geliştirirken sadece kod yazın. Sonra kod şapkasını çıkarın ve Refactoring şapkasını takın. Asla ikisini bir anda yapmayın.
Bir başkasının kodlarını refactor etmeniz gerektiğinde, önce kodun ne yapmak istediğini parçalara ayırarak anlamaya çalışın. Bu en önemli kuraldır. Sonra refactoring’e başlayın. Küçük adımlar ile aynı kodU, bu örnekde olduğu gibi bir çok kere refactor edebilirsiniz. Refactor işleminde ritim herşeydir. Küçük adımlar ile ve testle ilerleyin. Bir seferde en iyi kod’a ulaşamaya çalışmayın. Her adımdan sonra, ilgili testleri çalıştırıp sonuçları gözlemleyin. Unutmayın. Refactoring demek, TEST demektir. Genelde firmalarda Refactoring işlemi boşa zaman kaybı olarak görülür. Çünkü Refactoring ile gösterebilecekleri bir değer yoktur. Ne zaman ki o proje üzerinde yeni bir feature eklenmesi istensin ya da değişikliğe gidilsin, işte Refactoring orda kendini gösterir. Legacy ya da Spagetti kodların içine girmek zaman ve yürek ister :)  Refactor edilmiş bir koda, 1 günde yeni bir feature eklenirken, spaggetti koda yeni bir özelliğin 1 ay da eklenemediği bizzat benim tarafımdan görülmüştür.
Refactoring’in bir sonu yoktur. Aynı bir müzisyen gibi, her zaman daha iyisi çalınabilir.
“İyi bir kodun gerçek testi, ne kadar kolay değiştirilebildiği ile alakalıdır!” 
                                                                                                  Martin Fowler…
Son olarak size bir ödev bıraktım. Bu projenin son halinde atıl olan bir kod parçası vardır. Bunu da bulup yorumlarda paylaşırsanız sevinirim.
Geldik bir makalenin daha sonuna yeni bir makalede görüşmek üzere hepinize hoşçakalın. Refactoring ile kalın.
Reference By (Source) : Martin Fowler Refactoring (2019) Second Edition
Herkes Görsün:

Bunlar da hoşunuza gidebilir...

6 Cevaplar

  1. Ali Hakan VURAN dedi ki:

    Merhaba,

    Program sınıftaki getAmounth ve calculateVolumeCredit atıl kodlardır. Bir işlevleri yok. Refactoring sonrası kaldırılabilir.
    Bir yazılımcı için çok önemli olduğunu düşündüğüm refactoring konusunda hazırlamış olduğun makale için de çok teşekkürler.
    Emeğine sağlık.

    • borsoft dedi ki:

      Teşekkürler Ali,
      Evet bu 2 method atıldır. Atıl olan detay birşey daha var. Onu da bulursan, bu makaleye yutmuşun demektir :)

      İyi çalışmalar.

      • Ali Hakan VURAN dedi ki:

        Main de atıl durumdaki string[] args kaldırıyoruz. :-)

      • Ali Hakan VURAN dedi ki:

        GetTotalAmount ve TotalValumeCredits methodlarını tek bir method da birleştirebiliriz.

        public static decimal Total(PassingData data,string fieldName)
        {
        decimal result = 0;
        foreach (Register reg in data.registers)
        {
        result += Convert.ToDecimal(reg.GetType().GetProperty(fieldName).GetValue(reg, null));
        }
        return result;
        }

        • Ali Hakan VURAN dedi ki:

          Ayrıca CalculateAmounth methodunda ikinci parametre IDictionary courses yi kaldırabiliriz.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.