Clean Code – Function Part II

Kondisi

Sebuah fungsi bisa jadi memiliki banyak pengecekan kondisi di dalamnya. Kita bisa memastikan bahwa tiap kondisi dilakukan pada low-level class yaitu dengan menggunakan polymorphisme dan inheritance.

public abstract class Employee
        {
            public double BasicSalary;
            public abstract double CalculatePayment();
        }

Dari potongan kode program di atas, semakin banyak tipe customer dan semakin kompleks kalkulasi payment tiap tipe customer, akan semakin panjang kode program pada fungsi CalculatePay() sehingga melanggar prinsip bahwa sebuah fungsi haruslah kecil. Mari kita refactor menjadi beberapa implementasi pada low-level class seperti ini.

Kita bisa menggunakan interface ataupun class abstract. Selanjutnya kita buat 2 konkrit class yaitu class Sales dan Manager, masing-masing mengimplementasikan operasi CalculatePay() secara berbeda.

public class Sales : Employee
       {
           private const double COMMISION_PERCENTAGE = 0.2;

           public override double CalculatePayment()
           {
               return BasicSalary + (COMMISION_PERCENTAGE * BasicSalary);
           }
       }

       public class Manager : Employee
       {

           private const double BONUS_PERCENTAGE = 0.5;

           public override double CalculatePayment()
           {
               return BasicSalary + (BONUS_PERCENTAGE * BasicSalary);
           }
       }

Untuk menentukan class mana yang sesuai, kita bisa menggunakan Factory Pattern. Melalui Factory Pattern sebuah kelas mendelagasikan tanggung jawab pembuatan objectnya.

public class EmployeeFactory
        {
            public Employee MakeEmployee(EmployeeType employeeType)
            {
                switch (employeeType)
                {
                    case EmployeeType.Sales:
                        return new Sales();
                    case EmployeeType.Manager:
                        return new Manager();
                    default:
                        throw new Exception("Incorrect Employee");
                }
            }
        }

Bentuk akhir dari class PayrollService jadinya seperti ini.

public class PayrollService
        {
            private Employee _employee;
            public PayrollService(EmployeeType employeeType)
            {
                _employee = new EmployeeFactory().MakeEmployee(employeeType);
            }
            public double GetEmployeeSalary(double basicSalary)
            {
                _employee.BasicSalary = basicSalary;
                return _employee.CalculatePayment();
            }
        }

 

 

Clean Code – Function Part I

Sebuah fungsi  ideal adalah fungsi yang hanya memiliki 0 argument (cuman ini sulit untuk dihindari). Sebisa mungkin maksimum jumlah argument pada sebuah fungsi tidak lebih dari 3 argument.

employee.CalculateSalary();

Jika argumen sebuah fungsi lebih dari 2 buah, sebaiknya dibungkus dalam sebuah class. Ingat penamaan fungsi harus mengandung kata kerja (Verb) dan keyword.

Circle MakeCircle(double x, double y, double radius)

Argument x dan y lebih baik kita bungkus dalam satu class seperti ini:

Circle MakeCircle(Point center, double radius)

Sebuah fungsi haruslah kecil. Jika kita menulis sebuah fungsi yang sangat panjang kita bisa membaginya menjadi beberapa fungsi yang lebih kecil.  Fungsi yang kecil adalah fungsi yang terdiri dari kurang 150 karakter per barisnya dan  tidak lebih dari 20 baris.

Sebuah fungsi harusnya hanya focus melakukan satu hal saja dan hanya memiliki 1 high level abstraksi seperti contoh di bawah ini, High level abstraksinya adalah CreateTestPage()

public void CreateTestPage()
            {
                IncludeSetups();
                IncludeTestPageContent();
                IncludeTeardowns();
            }

Clean Code – Naming Part II

Lanjutan dari Clean Code – Naming Part I

Mudah dilafalkan

Gunakanlah penamaan yang mudah untuk dilafalkan sehingga memudahkan kita mendiskusikannya dengan teman kerja secara verbal. Contoh penamaan yang tidak baik seperti di bawah ini.

public class DtPrsn
        {
            public int DtKy { get; set; }
            public string FrstNm { get; set; }
            public string LstNm { get; set; }
            public DateTime DtoBrth { get; set; }
        }

Bandingkan jika diubah menjadi seperti ini.

public class Person
        {
            public int Id { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime DateOfBirth { get; set; }
        }

Mudah dicari

Jika kode program yang kita tulis sudah sangat banyak, tentulah akan semakin memakan waktu untuk mencari variable tertentu yang ingin kita modifikasi nilainya.

if (person.Gender == 0)

Jika kita menulisnya secara hardcode seperti di potongan kode di atas, maka pada saat kita ingin memodifikasi nilai Gender untuk Female menjadi 1 dan kondisi tersebut sudah dipanggil diberbagai tempat  sehingga akan sangat berisiko jika kita melakukan replace 0 menjadi 1. Karena bisa jadi beberapa enum menggunakan nilai 0 juga untuk membernya. Buatlah dalam bentuk enum atau konstanta.

if (person.Gender == (int)Gender.Female)

Penamaan Class and Method

Penamaan class haruslah mengandung kata benda (noun) seperti Customer, Account, hindari penamaan seperti Calculate, Processor. Sedangkan penamaan method harusnya mengandung kata kerja (verb)  dan Keyword seperti SaveCustomer, DeletePage, PostPayment.

Semakin pendek penamaan kita, selama itu jelas dan tidak membingungkan adalah semakin bagus. Tapi jangan sampai menimbulkan keambiguan. Contoh, Address adalah contoh yang tepat penamaan sebuah object (instance dari class) tapi bisa tidak begitu bagus sebagai nama class jika kita ingin membedakannya dengan MAC Address, Port Address. Nah, disini kita bisa memilih penamaan  menjadi PostalAddress.

Clean Code – Naming Part I

Programming is the art of telling another human what one wants the computer to do.

— Donald Knuth

Clean code diperkenalkan oleh Rober C. Martin dalam bukunya:  Clean Code: A Handbook of Agile Software Craftsmanship.  Clean code adalah bagaimana kode program yang kita tulis mudah dimengerti dan mudah dimodifikasi. Jika kita menulis dengan Clean Code akan memudahkan diri kita dan juga teman kerja kita di kemudian hari untuk melakukan perbaikan bug maupun modifikasi kode program.

Penamaan (Naming)

Jelas

Kita melakukan penamaan dihampir seluruh code program, entah itu menamakan variablel, fungsi, argument ataupun class. Penamaan yang benar menguras banyak waktu, tapi membuat waktu pembacaan kode program lebih efisien di kemudian hari.  Semakin banyak komentar  yang kita butuhkan untuk menjelaskan maksud penamaan kita, semakin tidak bagus penamaan kita tersebut.

int d; //elapsed time in days

Variable d pada senarai program di atas tidak mengungkapkan sesuatu yang begitu jelas bagi kita tanpa membaca komentar. Alangkah baiknya jika kita memberi nama yang lebih jelas seperti ini:

int elapsedTimeInDays;

Penamaan yang baik juga membantu kita dalam memahami dan memodifikasi kode program. Perhatikan senarai program dibawah ini.

public List GetThem(List Persons)
{
   var p = new List();
   foreach (var person in Persons)
   {
      if (person.Gender == 0)
        p.Add(person);
    }
    return p;
}

Senarai  di atas bisa kita refaktor menjadi kode program yang lebih mudah dimengerti dengan menjawab pertanyaan di bawah ini.

  1. Apa maksud dari fungsi ini?
  2. Nilai 0 pada kondisi Gender mengacu pada apa
public List GetFemaleInPersons(List Persons)
        {
            var femaleInPersons = new List();
            foreach (var person in Persons)
            {
                if (person.Gender == (int)Gender.Female)
                    females.Add(person);
            }
            return femaleInPersons;
        }

Seringkali kita memberikan penamaan argument (parameter yang dilewatkan pada sebuah fungsi) yang bisa membuat pengguna fungsinya harus membaca keseluruhan kode program untuk mengetahui argument sumber dan argument tujuan pada proses duplikasi larik.

public static void CopyChar(char[] a1, char[] a2)
        {
            for (int i = 0; i < a1.Length; i++)
            {
                a2[i] = a1[i];
            }
        }

Tentu lebih baik jika penamaan argument a1 diganti menjadi source dan a2 diganti menjadi destination.

public static void CopyChar(char[] source, char[] destination)
        {
            for (int i = 0; i < source.Length; i++)
            {
                destination[i] = source[i];
            }
        }

SiteMap dan User Role pada ASP.Net MVC

Pada ASP.Net MVC kita bisa menyembunyikan menu/node tertentu pada sitemap beradasarkan user role. Tambahkan roles pada existing sitemapnode kita seperti di bawah ini. Jika kita multiple role gunakan “,” sebagai pemisah.

<mvcSiteMapNode title="Vehicle Index" key="VehicleIndex" controller="Vehicle" action="Index" visibility="false" roles="Admin, Accountant"/>

kemudian buatlah Customer provider untuk mengoverride visibility dari sitemapnode tersebut.

public class SiteMapVisibilityCustomProvider : MvcSiteMapProvider.SiteMapNodeVisibilityProviderBase
    {
        private bool IsInRole(ISiteMapNode node)
        {
            if (HttpContext.Current == null)
               return true;
            if (HttpContext.Current.User == null)
                return true;

            if(node.Roles.Count == 0)
                return true;
            var IsInRole = false;
            foreach(var role in node.Roles)
            {
                IsInRole = HttpContext.Current.User.IsInRole(role);
                if (IsInRole)
                    break;

            }
            return IsInRole;
        }
        public override bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
        {
            if (node == null)
            {
                return true;
            }
            
            // Is a visibility attribute specified?
            object visibilityValue = null;
            node.Attributes.TryGetValue("visibility", out visibilityValue);
            if (visibilityValue != null)
            {
                bool nodeVisible;
                string visibility = visibilityValue.ToString();

                if (bool.TryParse(visibility, out nodeVisible))
                {
                    return nodeVisible;
                }
            }
            else
            {
                //check user role
                return IsInRole(node);
            }
            return true;
        }
    }

Pada web config set default visibility provider ke class tersebut, value = {className}, {assemblyName}

<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="Astral.MVC.Helpers.SiteMapVisibilityCustomProvider, Astral.MVC"/>    

Sekarang, bagaimana agar mencegah user mengakses secara langsung alamat url dari page tersebut? kita buat custom authorization attribute yang akan melewatkan key dari sitemapnode dan medirect url ke halaman tertentu jika role user tidak memiliki akses.

[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class SiteMapAuthorizeAttribute : AuthorizeAttribute
{
  public string Key { get; set; }

  public override void OnAuthorization(AuthorizationContext filterContext)
  {
    base.OnAuthorization(filterContext);
    ISiteMapNode siteMapNode = null;
    var siteMap = MvcSiteMapProvider.SiteMaps.Current;
    if (siteMap == null)
      return;
    siteMapNode = siteMap.FindSiteMapNodeFromKey(Key);
    if (siteMapNode == null)
      return;
    if (siteMapNode.Roles.Count == 0)
      return;

    var IsInRole = false;
    foreach (var role in siteMapNode.Roles)
    {
      IsInRole = filterContext.HttpContext.User.IsInRole(role);
      if (IsInRole)
        break;
     }
     if (!IsInRole)
     {
        HandleUnauthorizedRequest(filterContext);
     }

}

//Called when access is denied
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{

//User isn't logged in
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
  filterContext.Result = new RedirectToRouteResult(
   new RouteValueDictionary(new { controller = "Account", action = "Login" })
  );
}
//User is logged in but has no access
else
{
  filterContext.Result = new RedirectToRouteResult(
  new RouteValueDictionary(new { controller = "Home", action = "Index" })
  );
}
}

Cara penggunaan attribute ini di controller action seperti ini.

[SiteMapAuthorize(Key ="VehicleIndex")]
public ActionResult Index()
{
}

Atau dengan cara lebih praktis, dimanage pada BaseController seperti ini

private void CheckAuthorization(ActionExecutingContext filterContext, string currentPageUrl)
      {
          ISiteMapNode siteMapNode = null;
          var siteMap = MvcSiteMapProvider.SiteMaps.Current;
          if (siteMap == null)
              return;
          siteMapNode = siteMap.FindSiteMapNode(currentPageUrl);
          if (siteMapNode == null)
              return;
          if (siteMapNode.Roles.Count == 0)
              return;

          var IsInRole = false;
          foreach (var role in siteMapNode.Roles)
          {
              IsInRole = filterContext.HttpContext.User.IsInRole(role);
              if (IsInRole)
                  break;
          }
          if (!IsInRole)
          {
              filterContext.Result = new RedirectToRouteResult(
                       new RouteValueDictionary(new { controller = "User", action = "Unauthorized" })
               );
          }
      }

private string GetPageUrl(ActionExecutingContext filterContext)
        {
            string controllerName = filterContext.Controller.GetType().Name;
            controllerName = controllerName.Substring(0, controllerName.Length - 10);

            string actionName = filterContext.ActionDescriptor.ActionName;
            return string.Format("/{0}/{1}", controllerName, actionName);

        }

override OnActionExecuting pada BaseController seperti ini.

protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            var currentPageUrl = GetPageUrl(filterContext);
             CheckAuthorization(filterContext, currentPageUrl);
        }