2017年12月11日 星期一

如何不寫註解也看得懂程式

當一個人再開發程式的時候,寫的程式碼自己看得懂就好
但多人開發的時候,你的程式碼不只要給自己看,別人也要看
所以有些公司比較要求會有Code Review,來確保持程式碼有一定的水準

怎樣的程式不好閱讀?我覺得是以下兩個最常發生

  • 亂命名
  • 方法超過50行

其實我覺得當一個方法超過50行,裡面又包含上百行if else
這種程式碼看起來就相當累,也是在浪費生命
就如以下範例假設有一個列表的頁面需要輸入很多關鍵字來進行搜尋
所以搜尋頁面會有以下的程式

namespace Example.Controllers
{
    public class OrderController : Controller
    {
        // GET: Order
        public ActionResult Index(OrderArgs args)
        {
            using(var db = new NORTHWNDEntities())
            {
                var query = db.Orders.AsQueryable();

                //搜尋CustomerID
                if (string.IsNullOrEmpty(args.CustomerID))
                    query = query.Where(x => x.CustomerID.Contains(args.CustomerID));

                //搜尋OrderDate
                if (args.OrderStartDate != null && args.OrderEndDate != null)
                    query = query.Where(x => x.OrderDate >= args.OrderStartDate && x.OrderDate <= args.OrderEndDate);

                //搜尋ShipName
                if (string.IsNullOrEmpty(args.ShipName))
                    query = query.Where(x => x.ShipName.Contains(args.ShipName));

                //搜尋ShipAddress
                if (string.IsNullOrEmpty(args.ShipAddress))
                    query = query.Where(x => x.ShipAddress.Contains(args.ShipAddress));

                //搜尋ShipCity
                if (string.IsNullOrEmpty(args.ShipCity))
                    query = query.Where(x => x.ShipCity.Contains(args.ShipCity));

                return View(query.ToList());
            }            
        }
    }
}

這段Code在寫若有關鍵字傳進來,就對這個關鍵字做搜尋
其實這樣有註解、沒有複雜的邏輯也算還滿容易看得懂
那如果每個關鍵字裡面又有很多邏輯要判斷就會變成以下程式碼

namespace Example.Controllers
{
    public class OrderController : Controller
    {
        // GET: Order
        public ActionResult Index(OrderArgs args)
        {
            using(var db = new NORTHWNDEntities())
            {
                var query = db.Orders.AsQueryable();

                //搜尋CustomerID
                if (string.IsNullOrEmpty(args.CustomerID))
                {
                    /*
                    超多邏輯判斷
                    ............
                    ............
                    */
                    query = query.Where(x => x.CustomerID.Contains(args.CustomerID));
                }
                    

                //搜尋OrderDate
                if (args.OrderStartDate != null && args.OrderEndDate != null)
                {
                    /*
                    超多邏輯判斷
                    ............
                    ............
                    */
                    query = query.Where(x => x.OrderDate >= args.OrderStartDate && x.OrderDate <= args.OrderEndDate);
                }
                

                //搜尋ShipName
                if (string.IsNullOrEmpty(args.ShipName))
                {
                    /*
                    超多邏輯判斷
                    ............
                    ............
                    */
                    query = query.Where(x => x.ShipName.Contains(args.ShipName));
                }
                

                //搜尋ShipAddress
                if (string.IsNullOrEmpty(args.ShipAddress))
                {
                    /*
                    超多邏輯判斷
                    ............
                    ............
                    */
                    query = query.Where(x => x.ShipAddress.Contains(args.ShipAddress));
                }
                    

                //搜尋ShipCity
                if (string.IsNullOrEmpty(args.ShipCity))
                {
                    /*
                    超多邏輯判斷
                    ............
                    ............
                    */
                    query = query.Where(x => x.ShipCity.Contains(args.ShipCity));
                }                    

                return View(query.ToList());
            }            
        }
    }
}

是不是開始感覺有點雜亂了,當你這樣寫出來後面要修改你程式的人
又在加一些邏輯上去,一個方法破百行的成就就解開了
解開之後當然要繼續解破千行的成就= =,要如何讓他好閱讀呢
首先我把各種搜尋關鍵字寫成擴展,讓各種關鍵字複雜的邏輯切開,關注點分離
將每個複雜的搜尋都放在各別的擴展方法裡面

namespace Example.Models
{
    public static class OrderExtensions
    {
        public static IQueryable<Order> WhereByCustomerID(this IQueryable<Order> order, string customerID)
        {

            if (string.IsNullOrEmpty(customerID))
                return order;
            /*
            超多邏輯判斷
            ............
            ............
            */
            return order.Where(x => x.CustomerID.Contains(customerID));
        }

        public static IQueryable<Order> WhereByOrderDate(this IQueryable<Order> order, DateTime? StartDate, DateTime? EndDate)
        {

            if (StartDate == null || EndDate == null)
                return order;
            /*
            超多邏輯判斷
            ............
            ............
            */
            return order.Where(x => x.OrderDate >= StartDate && x.OrderDate <= EndDate);
        }

        public static IQueryable<Order> WhereByShipName(this IQueryable<Order> order, string ShipName)
        {

            if (string.IsNullOrEmpty(ShipName))
                return order;
            /*
            超多邏輯判斷
            ............
            ............
            */
            return order.Where(x => x.ShipName.Contains(ShipName));
        }

        public static IQueryable<Order> WhereByShipAddress(this IQueryable<Order> order, string ShipAddress)
        {

            if (string.IsNullOrEmpty(ShipAddress))
                return order;
            /*
            超多邏輯判斷
            ............
            ............
            */
            return order.Where(x => x.ShipAddress.Contains(ShipAddress));
        }

        public static IQueryable<Order> WhereByShipCity(this IQueryable<Order> order, string ShipCity)
        {

            if (string.IsNullOrEmpty(ShipCity))
                return order;
            /*
            超多邏輯判斷
            ............
            ............
            */
            return order.Where(x => x.ShipCity.Contains(ShipCity));
        }
    }
}

所以Controller的程式碼變得很有感覺,就算不寫註解您們看得懂嗎?

namespace Example.Controllers
{
    public class OrderController : Controller
    {
        // GET: Order
        public ActionResult Index(OrderArgs args)
        {
            using(var db = new NORTHWNDEntities())
            {
                var query = db.Orders.AsQueryable()
                    .WhereByCustomerID(args.CustomerID)
                    .WhereByOrderDate(args.OrderStartDate, args.OrderEndDate)
                    .WhereByShipName(args.ShipName)
                    .WhereByShipAddress(args.ShipAddress)
                    .WhereByShipCity(args.ShipCity);
                return View(query.ToList());
            }            
        }
    }
}

參考網址:
http://www.cnblogs.com/leotsai/p/how-to-write-beautiful-query-code.html

2017年12月6日 星期三

Entity Framework IEnumerable與IQueryable差異比較

若您有在使用Entity Framework的話,你去面試很多面試官都會問
IEnumerable與IQueryable的差別,我已經被問過N次了

IEnumerable:

取出當前符合條件的所有資料後,在記憶體中進行後續的資料篩選

IQueryable:

會保存所有條件限制於Query Expression中,直到資料真正被列舉成具體資料的當下,才將最終的Query Expression透過Query Provider轉換為實際執行的SQL語法,從DB取出符合條件資料。

講白話一點就是,你下同樣的條件式

var result = Table.AsEnumerable().Where(x=> x.ID == 1).ToList();
var result = Table.AsQueryable().Where(x=> x.ID == 1).ToList();

上面兩行取出來的結果是一樣的,但是取的過程完全不一樣

IEnumerable會在您ToList的時候先取出Table的所有資料存到記憶體中
再去從記憶體中的資料下條件式(where)取出
也就是你Table有10萬筆資料,他會先把10萬筆存到記憶體中,再去撈ID=1得到的結果
所以你Table有上百萬筆,千萬別用IEnumerable來取資料您會GG

IQueryable會在您ToList的時候,他會包含WHERE條件式送給SQL SERVER取要得資料

適用時機

IEnumerable: 處理記憶體內的資料 ex. List / Array
IQueryable: 處理遠端來源的資料 ex. Database / Service


參考網址:
https://dotblogs.com.tw/wasichris/archive/2015/03/04/150633.aspx



2017年12月5日 星期二

C# 委派 delegate

C# 的委派好像都很少用到,不過最近看同事寫一個驗證資料的東西
好像不錯用...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static StringBuilder result = new StringBuilder();
        private delegate void Verification<T>(T data);
        static void Main(string[] args)
        {            
            string data = "xxxx";//要驗證的資料

            Verification<string> verif;
            verif = CheckName;
            verif += CheckEmail;
            verif += CheckAddress;
            verif(data);

            Console.WriteLine(result.ToString());
            Console.ReadLine();
        }

        private static void CheckName(string data)
        {
            result.AppendLine("驗證姓名");
        }
        private static void CheckEmail(string data)
        {
            result.AppendLine("驗證電子信箱");
        }
        private static void CheckAddress(string data)
        {
            result.AppendLine("驗證地址");
        }
    }
}


其實丟進去都是同一個物件
要從這一個物件判斷很多驗證,使用委派就挺不錯的!!