Refactoring Using CodeMaid

CodeMaid adalah ekstensi open source untuk visual studio yang bisa digunakan untuk melihat tingkat kompleksitas code program kita selain melakukan formating dan mengorganisir kode. Semakin tinggi nilai kompleksitas sebuah fungsi, semakin butuh fungsi itu untuk direfactor. Karena salah satu prinsip clean code adalah satu method harusnya hanya memiliki 1 high level abstraksi.

contoh code dengan kompleksitas yang tinggi seperti di bawah ini.

private Expression<Func<Vehicle, bool>> CreateVehicleFilter(VehicleFilterInput input)
{
Expression<Func<Vehicle, bool>> queryFilter = PredicateHelper.True();

if (!string.IsNullOrEmpty(input.Name))
queryFilter = queryFilter.And(ve => ve.VehicleTranslations.Any(t => t.Language == input.Language) ?
ve.VehicleTranslations.Any(t => t.Language == input.Language && t.TranslatedText.ToLower().Contains(input.Name.ToLower()))
: ve.Name.ToLower().Contains(input.Name.ToLower()));

if (!string.IsNullOrEmpty(input.InternalNumber))
queryFilter = queryFilter.And(ve => ve.InternalNumber.ToLower().Contains(input.InternalNumber.ToLower()));

if (!string.IsNullOrEmpty(input.ChassisNumber))
queryFilter = queryFilter.And(ve => ve.Chassis.ToLower().Contains(input.ChassisNumber.ToLower()));

if (input.VehicleBrandId != null && input.VehicleBrandId != 0)
queryFilter = queryFilter.And(ve => ve.VehicleModel.VehicleBrand.Id == input.VehicleBrandId);

if (input.VehicleModelId != null && input.VehicleModelId != 0)
queryFilter = queryFilter.And(ve => ve.VehicleModel.Id == input.VehicleModelId);

if (input.DateOfEntry != null && input.DateOfEntry != DateTime.MinValue &&
input.DateOfEntryTo != null && input.DateOfEntryTo != DateTime.MinValue)
queryFilter = queryFilter.And(ve => ve.DateEntry >= input.DateOfEntry && ve.DateEntry <= input.DateOfEntryTo); else { if (input.DateOfEntry != null && input.DateOfEntry != DateTime.MinValue) queryFilter = queryFilter.And(ve => ve.DateEntry >= input.DateOfEntry);
if (input.DateOfEntryTo != null && input.DateOfEntryTo != DateTime.MinValue)
queryFilter = queryFilter.And(ve => ve.DateEntry <= input.DateOfEntryTo); } if (input.ReleaseDate != null && input.ReleaseDate != DateTime.MinValue && input.ReleaseDateTo != null && input.ReleaseDateTo != DateTime.MinValue) { queryFilter = queryFilter.And(ve => (ve.DateActualRelease >= input.ReleaseDate && ve.DateActualRelease <= input.ReleaseDateTo) || (ve.DateSupposedRelease >= input.ReleaseDate && ve.DateSupposedRelease <= input.ReleaseDateTo)); } else { if (input.ReleaseDate != null && input.ReleaseDate != DateTime.MinValue) { queryFilter = queryFilter.And(ve => (ve.DateActualRelease >= input.ReleaseDate) || (ve.DateSupposedRelease >= input.ReleaseDate));
}
if (input.ReleaseDateTo != null && input.ReleaseDateTo != DateTime.MinValue)
{
queryFilter = queryFilter.And(ve => (ve.DateActualRelease <= input.ReleaseDateTo) || (ve.DateSupposedRelease <= input.ReleaseDateTo)); } } if (!string.IsNullOrEmpty(input.Status)) queryFilter = queryFilter.And(ve => ve.Status.ToLower().Contains(input.Status.ToLower()));

return queryFilter;
}

Terlihat pada CodeMaid Spade di Visual Studio fungsi tersebut kompleksitasnya sangat tinggi, di atas nilai 10.

code_main_high_complexity

Kita refactor, dengan membaginya ke dalam beberapa fungsi yang lebih kecil seperti ini.

private Expression<Func<Vehicle, bool>> CreateVehicleFilter(VehicleFilterInput input)
{
Expression<Func<Vehicle, bool>> queryFilter = PredicateHelper.True();

FilterVehicleByName(input, ref queryFilter);
FilterByDateOfEntry(input, ref queryFilter);
FilterByChassisNumber(input, ref queryFilter);
FilterByInternalNumber(input, ref queryFilter);
FilterByVehicleBrand(input, ref queryFilter);
FilterByVehicleModel(input, ref queryFilter);
FilterByRangeReleaseDate(input, ref queryFilter);
FilterByStatus(input, ref queryFilter);
return queryFilter;

}

CodeMaid akan menunjukan bahwa tingkat kompleksitasnya sekarang jauh lebih sedikit.

code_main_low_complexity

Iklan

Error dialog when Open .cshtml File

Saya mendapat error pagi ini. Ketika melakukan double click pada View (.cshtml) file di Asp.Net MVC project pada Visual Studio, muncul popup dialog dengan pesan error. Setelah googling, ternyata untuk mensolve issue ini caranya sangat mudah.

  1. Tutup Visual Studio
  2. Hapus semua file cache pada folder %LocalAppData%\Microsoft\VisualStudio\14.0\ComponentModelCache
  3. Buka kembali Visual Studio. Harusnya tidak ada lagi error seperti sebelumnya.

Mocking Database Context In Repository Unit Test Using Effort

Seringkali kita ingin membuat unit test di level repository untuk menguji query yang kita buat pada sebuah repository. Karena pada unit testing, prinsipnya adalah melakukan isolasi dari external class ataupun external resource dan eksekusi unit testing harus bisa secepat mungkin, maka kita hindari menggunakan database secara langsung.

Jika menggunakan database secara langsung, sangat berpengaruh pada kecepatan eksekusi unit testing dan masalah data yang harus diinsert dan dihapus ketika selesai digunakan oleh unit testing. Salah satu solusinya adalah menggunakan in-memory-database. In-memory database ini akan dihancurkan oleh Garbage collector ketika sudah tidak digunakan.

Untuk mempermudah kita bisa menggunakan Effort. Instalasi cukup mudah, cukup di install lewat nuget package manager.

Pada initialisasi unit test kita mockup context kita.

Pertama, buat connection untuk Effort.

var connection = DbConnectionFactory.CreateTransient();

Selanjutnya pada class context, kita tambahkan satu constuctor untuk melewatkan connection dari Effort tersebut.

public BookStoreEntities(DbConnection connection) : base(connection, true)
{
}

pada unit test initialization method lewatkan connection Effort tadi ke context dan tambahkan code untuk membuat database jika tidak ada. Ini untuk menghindari munculnya error migrations data nantinya.

_context = new BookStoreEntities(connection);
_context.Database.CreateIfNotExists();

selanjutnya, insert data untuk table table yang akan kita gunakan nantinya.

_context.Categories.Add(new Category { Name = "Programming" });
_context.SaveChanges();
var category = _context.Categories.First();
_context.Books.Add(new Book { Title="Effort In Action", Category = category });
_context.Books.Add(new Book { Title = "C# In Action", Category = category });
_context.SaveChanges();

Kode program selengkapnya seperti ini.

[TestClass]
public class BookRepositoryTest
{
private BookStoreEntities _context;
private IUnitOfWork _unitOfWork;
private IGenericRepository _bookRepository;
[TestInitialize]
public void Setup()
{

var connection = DbConnectionFactory.CreateTransient();
_context = new BookStoreEntities(connection);
_context.Database.CreateIfNotExists();

_context.Categories.Add(new Category { Name = "Programming" });
_context.SaveChanges();
var category = _context.Categories.First();
_context.Books.Add(new Book { Title="Effort In Action", Category = category });
_context.SaveChanges();
_unitOfWork = new EFUnitOfWork(_context);
_bookRepository = _unitOfWork.GetGenericRepository();
}
[TestCleanup]
public void Cleanup()
{
_context = null;
_unitOfWork = null;
_bookRepository = null;
}
[TestMethod]
public void GetAll()
{
var books = _bookRepository.Get().ToList();
Assert.AreNotEqual(0, books.Count);
}
}

[TestMethod]
public void FindByName()
{
var books = _bookRepository.Get(x=>x.Title.Contains("Effor")).ToList();
Assert.AreNotEqual(0, books.Count);
}
[TestMethod]
public void GetById()
{
var book = _bookRepository.GetById(1);
Assert.IsNotNull(book);
}

Jika unit Test kita jalankan hasilnya seperti ini.

unit-test-effort

 

Selamat mencoba!

SOLID Principles-O-Open Closed Principle

“software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

Untuk mengimplement OCP, sebelumnya kode kita harus SRP – lihat pada tulisan SOLID Prinsiples – S-Single Responsibility, sehingga kita dapat mengindentifikasi dan memisahkan behavior yang berubah kedalam berbagai class. Output dari OCP adalah sekumpulan class yang memiliki behavior yang sama tapi dengan implementasi yang berbeda-beda. Perhatikan potongan kode program di bawah ini.

public class DiscountService
{
private const int GOLD = 1;
private const int SILVER = 2;
private double _totalSales;
public DiscountService(double TotalSales)
{
_totalSales = TotalSales;
}

private int _custType;
public int CustType
{
get { return _custType; }
set { _custType = value; }
}

public double GetDiscount(int CustTpe)
{
if (_custType == GOLD)
{
return _totalSales - 100;
}
else if (_custType == SILVER)
{
return _totalSales - 50;
}
return _totalSales;
}
}

Masalah timbul ketika kita akan menambahkan beberapa tipe customer lain, maka akan sangat banyak kondisi “IF” di dalam kode program. Menambahkan kondisi “IF” baru artinya kita mengubah class CustomerService dan itu melanggar prinsip Close for modification.

Lantas bagaimana caranya mengimplementasikan prinsip Open for extension? Yaitu dengan menggunakan inheritance (lewat parent classnya) atau abstraksi (bisa menggunakan abstract class ataupun interface).

public interface ICustomer
{
string Name { get; }
double GetDiscount(double TotalSales);
}

Kemudian kita buat beberapa class konkrit untuk implementasi interface ICustomer di atas.

public class GoldCustomer : ICustomer
{
public string Name
{
get
{
return &quot;Gold&quot;;
}
}

public double GetDiscount(double TotalSales)
{
return TotalSales - 100;
}
}
public class SilverCustomer : ICustomer
{
public string Name
{
get
{
return &quot;Silver&quot;;
}
}

public double GetDiscount(double TotalSales)
{
return TotalSales - 50;
}
}

Kemudian mari kita refactor kode program class DiscountService di atas sehingga jika ingin menambah tipe customer lagi, tidak perlu mengubah implementasi dari class DiscountService.

public class DiscountService
{
private readonly ICustomer _discount;
private double _totalSales;
public DiscountService(double TotalSales, ICustomer discount)
{
_totalSales = TotalSales;
_discount = discount;
}
public virtual double GetDiscount()
{
return _discount.GetDiscount(_totalSales);
}
}

class Program
{
static void Main(string[] args)
{
double totalSales = 1000;
DiscountService discountService;
var listCustomer = new List();
listCustomer.Add(new GoldCustomer());
listCustomer.Add(new SilverCustomer());
foreach (var customer in listCustomer)
{
discountService = new DiscountService(totalSales, customer);
Console.WriteLine(&quot;Customer Type: &quot; + customer.Name);
Console.WriteLine(&quot;Total after discount : &quot; + discountService.GetDiscount());
Console.WriteLine(&quot;----------------------&quot;);
}

Console.ReadKey();

}
}

ocp

Sekarang kita bisa menambahkan 1 jenis customer lagi, tanpa harus mengubah code pada class Discount Service.

listCustomer.Add(new PlatinumCustomer());

ocp-2.png

SOLID Prinsiples – S-Single Responsibility

S.O.L.I.D prinsip adalah kumpulan dari best-practices untuk Object Oriented design. Semua design pattern pada Gang Of Four juga dibangun berdasarkan prinsip ini.

SOLID merupakan  prinsip dasar dalam membangun arsitektur software yang baik. SOLID merupakan singkatan dari 5 prinsip:

  1. S dari SRP (Single Responsibility Principle)
  2. O dari OCP (Open Closed Principle)
  3. L dari LSV (Liskov Substitution Principle)
  4. I dari ISP (Interface segregation principle)
  5. D dari DIP ( Dependency Inversion Principle)

S-Single Responsibility Principle

“A class should have only one reason to change.” – Robert C Martin

Sebuah class seharusnya diubah itu satu alasan aja. Terlalu banyak tanggung jawab yang dibebankan pada sebuah class sehingga membuat satu perubahan kode program bisa jadi menimbulkan banyak efek kepada yang lain. Fokus pada satu tanggung jawab memudahkan maintenance code.

public class Customer
{
public void Add()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@&quot;c:\Error.txt&quot;, ex.ToString());
}
}
public void Delete()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@&quot;c:\Error.txt&quot;, ex.ToString());
}
}
}

Pada class Customer method Add dan Deleta harusnya hanya menghandle satu high level abstraksi. Di dalam class tersebut juga terdapat duplikasi kode program penulisan log.
Penulisan log harusnya ditangani oleh class lain dalam hal ini class FileLogger. Sehingga jika ada perubahan pada proses penulisan log seperti path file yang diubah atau bahkan penulisan log dipindahkan ke database, kita bisa fokus hanya pada class FileLogger saja. Ingat! SRP bukan berarti satu class hanya boleh memiliki satu method saja, tapi hanya butuh satu alasan untuk memodifikasi class tersebut.

public class Customer
    {

        FileLogger _logger = new FileLogger();
        public void Add()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                _logger.WriteLog(ex.Message);
            }
        }
        public void Delete()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                _logger.WriteLog(ex.Message);
            }
        }
    }

    public class FileLogger
    {
        public void WriteLog(string message)
        {
            System.IO.File.WriteAllText(@&quot;c:\Error.txt&quot;, message);

        }

Apa manfaat dari SRP?
1. Reuse, lebih mudah untuk mendaya-guna kode program
2. Clarity, kode yang kita menjadi sangat jelas karena ditulis hanya focus pada satu hal
3. Naming, tentu dengan hanya berfokus pada satu hal, penamaan class jadi lebih gampang.
4. Readability, karena berkaitan dengan Clarity dan Naming, maka menjadi sangat membantu dalam pembacaan kode program
5. Reduced Coupling, menurunkan tingkat kebergantungan antar class. Semakin tinggi kebergantungan, semakin susah untuk dimodifikasi

Custom Configuration Handler

File configurasi baik itu app.config maupun web.config adalah file xml yang berisi configurasi external dari aplikasi yang kita buat. Seringkali kita melakukan pemanggilan nilai pada file configurasi seperti ini.

var baseUrl = ConfigurationManager.AppSettings[&quot;BaseUrl&quot;].ToString();

Semakin kompleks aplikasi, terkadang membuat bertambahnya nilai yang perlu di store di configurasi. Supaya configurasi kita mudah di maintenance jika ada perubahan key pada configurasi dan menghindari kesalahan pengetikan pada key, kita bisa membuat class untuk menghandle issue tersebut.

public class BLLConfiguration : ConfigurationSection
{
public string BaseUrl { get; set; }
public static BLLConfiguration GetConfig()
{
var section = ConfigurationManager.GetSection(&quot;Astral/BusinessLogic&quot;);
if (section == null)
throw new ConfigurationErrorsException(
ErrorMessages.ConfigurationErrorsException);

return (BLLConfiguration)ConfigurationManager.GetSection(&quot;Astral/BusinessLogic&quot;);
}

public void LoadConfigValues(XmlNode node)
{
var attributeCollection = node.Attributes;
if (attributeCollection[&quot;BaseUrl&quot;] != null)
BaseUrl = attributeCollection[&quot;BaseUrl&quot;].Value;
}
}

public class BLLConfigurationHandler : IConfigurationSectionHandler
{
public Object Create(Object parent, Object configContext, XmlNode node)
{
var config = new BLLConfiguration();
config.LoadConfigValues(node);
return config;
}
}

Pada web config/appconfig daftarkan section tersebut. Pada type, formatnya adalah {Namespace.ClassName},{AssemblyName}

custom_config.png

Kemudian, assign value pada confurasi dibuat dalam satu section berdasarkan pada nama sectionGroup dan section-nya.

custom_config_1.png

Pemanggilan value dari configurasi jadinya seperti ini.

var baseUrl = BLL.BLLConfiguration.GetConfig().BaseUrl;

Mocking Unit Test Object Menggunakan NSubtitute

NSubtitute

http://nsubstitute.github.io/

Nsubtitute adalah salah satu mocking library untuk .NET yang memiliki configurasi yang sederhana sehingga mudah digunakan. NSubtitute di design untuk testing dengan pendekatan Arrange-Act-Assert (AAA), sehingga kita hanya butuh untuk mengatur bagaimana harusnya dia bekerja dan kemudian membuat pemanggilan Assertion.

Kita bisa menginstall NSubtitute lewat Packet Manager Console pada Visual Studio.

install-nsubtitute.png

Sebagai contoh kita akan membuat abstraksi operasi pada Calculator menggunakan interface.

public interface ICalculator
{
   int Add(int number1, int number2);
}

Kemudian kita mempunyai sebuah class yang menyediakan service untuk menampilkan hasil kalkulasi tersebut. Prinsip dasar unit test adalah melakukan test pada unit terkecil dari kode program kita dan mengisolasi extenal class yang ada.

Karena kita akan membuat unit test untuk class CalculatorService, sehingga external class berupa class implentasi dari ICalculator kita buatkan mockup nya saja.

public class CalculatorService
   {
       private ICalculator _calculator;
       public CalculatorService(ICalculator calculator)
       {
           _calculator = calculator;
       }
       public int Add(int number1, int number2)
       {
           if (number2 == 0)
           {
               throw new Exception("Dived by Zero Exception, please input number2 != 0");
           }
           var result = _calculator.Add(number1, number2);
           return result;
       }
   }

Setiap class Unit Test haruslah ditandai dengan attribute [TestClass] dan Unit Test Method dengan attribute [TestMethod].

[TestClass]
public class CalculatorServiceTest
{
    [TestMethod]
    public void Add_ReturnValidResult()
    {
    }
}

Jika kita menginginkan suatu proses tertentu yang harus dilakukan sebelum unit test method dijalankan seperti proses inisialisasi data, kita bisa membuat method dengan attribute [TestInitialize] dan sebalikanya jika kita menginginkan sebuah proses yang dieksekusi setiap sebuah unit test method di jalankan kita bisa membuta method dengan attribute [TestCleanup].

[TestInitialize]
public void Setup()
{

}
[TestCleanup]
public void Cleanup()
{
}

Jika Kita melakukan test terhadap expection yang mungkin ada, tambahkan attribute [ExpectedException].

[TestMethod]
[ExpectedException(typeof(Exception))]
public void Add_WhenNumber2IsZeroReturnException()
{
}

Untuk melakukan mockup object, cukup dengan menulis kode seperti ini. Usahakan implementasi yang kita buat selalu berawal dari abstraksi sehingga memudahkan menulis kode mockup objectnya.
csharp _calculator = Substitute.For();

Selanjutnya kita akan memockup method Add pada ICalculator seperti ini.
csharp _calculator.Add(1, 2).Returns(3);

Bentuk class unit test selengkapnya sepert ini.

[TestClass]
    public class CalculatorServiceTest
    {
        private ICalculator _calculator;
        private CalculatorService _calculatorService;

        [TestInitialize]
        public void Setup()
        {
            _calculator = Substitute.For<ICalculator>();
            _calculatorService = new CalculatorService(_calculator);
        }
        [TestCleanup]
        public void Cleanup()
        {
            _calculator = null;
            _calculatorService = null;
        }

        [TestMethod]
        public void Add_ReturnValidResult()
        {
            var expectedResult = 3;
            ///arrange
            _calculator.Add(1, 2).Returns(expectedResult);
            ///act
            var actualResult = _calculatorService.Add(1, 2);
            ///assert
            Assert.AreEqual(expectedResult, actualResult);
        }
        [TestMethod]
        [ExpectedException(typeof(Exception))]
        public void Add_WhenNumber2IsZeroReturnException()
        {
            try
            {
                _calculatorService.Add(1, 0);
                Assert.Fail();
            }
            catch(Exception ex)
            {
                Assert.IsInstanceOfType(ex, typeof(Exception));
                Assert.AreEqual("Dived by Zero Exception, please input number2 != 0", ex.Message);
                throw ex;

            }
           
        }
    }

 

Mari kita jalankan class unit test kita.

pass-unit-test