2013年10月17日 星期四

CleanCode 讀書筆記 (四) 邊界

邊界主要是描述使用第三方API時如何想辦法
將API與我們內部的程式分隔開來   減低耦合
裡面有提到可以使用 Adapter  (轉接器)
Adapter是系統 與 API 之間的橋梁
使得API有所更動時  不會影響到系統  只需要在 Adapter上做修改即可

以下兩個範例  廠商在開發初期出版了  FactoryAPI_0
以及過了一陣子出版了新版  FactoryAPI_1
在兩版之間  裡面有一個method做了修正
如果沒有使用Adapter  會使得主程式必須做修正  修正後也可能會衍生出bug
但是使用Adapter的話  由於修改都在一個class內   相對來說比較不會衍生出新問題

廠商舊版FactoryAPI_0


    public class FactoryAPI_0
    {
        public void Make()
        {
            System.Diagnostics.Debug.Write("Make");
        }
    }

    public class FactoryAdapter
    {
        private FactoryAPI_0 ApI;
        public FactoryAdapter(FactoryAPI_0 API)
        {
            this.ApI = API;
        }
        public void Process()
        {
            ApI.Make();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FactoryAPI_0 api = new FactoryAPI_0();
            FactoryAdapter adapter = new FactoryAdapter(api);
            adapter.Process();
            Console.Read();

        }
    }



廠商新版 FactoryAPI_1


    public class FactoryAPI_1
    {
        public void Execute()
        {
            System.Diagnostics.Debug.Write("Make");
        }
    }
    public class FactoryAdapter
    {
        private FactoryAPI_1 ApI;
        public FactoryAdapter(FactoryAPI_1 API)
        {
            this.ApI = API;
        }
        public void Process()
        {
            ApI.Execute();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FactoryAPI_0 api = new FactoryAPI_0();
            FactoryAdapter adapter = new FactoryAdapter(api);
            adapter.Process();
            Console.Read();

        }
    }
由上面兩個範例可以發現    主程式並沒有被更動到
所有的更動都在 class 內部
造成的影響也都在class內   主程式成功的與API 分開

簡潔的程式邊界
當改變來臨時
好的設計能適應變化
不好的設計必須重新改寫或投入大量時間做修正

API 可以用封裝的方式  或者使用adapter
使API 被修正時  我們只需要做及少的修正

2013年10月16日 星期三

CleanCode 讀書筆記 (三) 錯誤處理I

裡面提到了一些例外的處理方式
其中有一點我覺得蠻重要的
不要回傳null  
與其回傳null不如回傳一個special case
這樣呼叫這個class的人就不需要去做 if(xx ==null)的判斷
也不會出現 NullPointerException  以下是範例


    abstract class EmployeeData
    {
        protected string name;
        protected string birthday;
        protected string address;
        public string GetName()
        {
            return name;
        }
    }
    class Ted : EmployeeData
    {
        public Ted()
        {
            name = "Ted";
            birthday = "1009";
            address = "Taipei";
        }
    }
    class NullEmployeeData : EmployeeData
    {
        public NullEmployeeData()
        {
            name = "";
            birthday = "";
            address = "";
        }
    }
    class EmployeeList
    {
        public EmployeeData GetData(string name)
        {
            switch (name)
            {
                case "Ted":
                    return new Ted();
                default:
                    return null;
            }

        }
    }
    class EmplyeeListBetter
    {
        public EmployeeData GetData(string name)
        {
            switch (name)
            {
                case "Ted":
                    return new Ted();
                default:
                    return new NullEmployeeData();
            }

        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            EmployeeData employeedata;
            EmployeeList badEmployeeList = new EmployeeList();

            employeedata = badEmployeeList.GetData("Marry");
            if (employeedata!=null)
                employeedata.GetName();


            EmplyeeListBetter betterEmployeeList = new EmplyeeListBetter();
            employeedata = betterEmployeeList.GetData("Marry");
            employeedata.GetName();
            Console.ReadKey();
        }
    }

上面的範例說明了當 user想要去Get Marry的資料時  系統內並沒有這個資料
badEmployeeList  return 了 null
然而 betterEmployeeList 回傳了  NullEmployeeData這個物件
這樣寫的好處是就算user沒有做  if (employeedata!=null) 這個判斷式
也不需要擔心 NullPointerException 的發生  系統還是可以繼續 run 下去

CleanCode 讀書筆記 (二) 物件及資料結構

在這個章節提到的重點是
結構話化程式碼容易添加新的函式  而不需要變動已有的資料結構
而物件導向得程式碼則是容易添加新的類別而不用變動已有的函式


以下是結構化的程式碼
 
    class SalaryCalculator
    {
        public double Calculate(string person)
        {
            if (person.Equals("boss"))
            {
                return 500 * 22 * 24;
            }
            else if (person.Equals("DayWorker"))
            {
                return 200 * 22 * 24;
            }
            else if (person.Equals("partTime"))
            {
                return 100 * 22 * 24;
            }
            throw new NullReferenceException();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            SalaryCalculator calculator = new SalaryCalculator();
            Console.WriteLine(calculator.Calculate("boss"));
            Console.ReadLine();
        }
    }
假設要添加一個SetWorkDays的函式
是蠻容易而且不會變動到已有的結構但是如果要新增一個
NightWorker (夜班) 人員的薪資  就是必要更改現有的函式

    class SalaryCalculator
    {
        private int workHours = 24;
        public void SetWorkHours (int workHours )
        {
            this.workHours = workHours ;
        }
        public double Calculate(string person)
        {
            if (person.Equals("boss"))
            {
                return 500 * 22 * workHours ;
            }
            else if (person.Equals("DayWorker"))
            {
                return 200 * 22 * workHours ;
            }
            else if (person.Equals("partTime"))
            {
                return 100 * 22 * workHours ;
            }
            else if (person.Equals("NightWorker"))
            {
                return 300 * 22 * workHours ;
            }
            throw new NullReferenceException();
        }
    }


接著我們看看  物件導向的程式

    
    interface CalculatorBase
    {        
        public double Calculate();
    }
    class BossSalaryCalculate : CalculatorBase
    {
        private int workHours = 8;
        public override double Calculate()
        {
            return 500 * 22 * workHours;
        }
    }
    class DayWorkerSalaryCalculate : CalculatorBase
    {
        private int workHours = 8;
        public override double Calculate()
        {
            return 200 * 22 * workHours;
        }
    }
    class partTimeSalaryCalculate : CalculatorBase
    {
        private int workHours = 8;
        public override double Calculate()
        {
            return 100 * 22 * workHours;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //SalaryCalculator calculator = new SalaryCalculator();
            CalculatorBase calculator = new BossSalaryCalculate();
            Console.WriteLine(calculator.Calculate());
            Console.ReadLine();
        }
    }


    interface  CalculatorBase
    {             
       public double Calculate();
       public void SetWorkHours(int workHours);
    }
    class BossSalaryCalculate : CalculatorBase
    {
        private int workHours = 8;
        public void SetWorkHours(int workHours)
        {
            this.workHours = workHours;
        }
        public double Calculate()
        {
            return 500 * 22 * workHours;
        }
    }
    class DayWorkerSalaryCalculate : CalculatorBase
    {
        private int workHours = 8;
        public void SetWorkHours(int workHours)
        {
            this.workHours = workHours;
        }
        public double Calculate()
        {
            return 200 * 22 * workHours;
        }
    }
    class partTimeSalaryCalculate : CalculatorBase
    {
        private int workHours = 8;
        public void SetWorkHours(int workHours)
        {
            this.workHours = workHours;
        }
        public double Calculate()
        {
            return 100 * 22 * workHours;
        }
    }
    class NightWorkerSalaryCalculate : CalculatorBase
    {
        private int workHours = 8;
        public void SetWorkHours(int workHours)
        {
            this.workHours = workHours;
        }
        public override double Calculate()
        {
            return 300 * 22 * workHours;
        }
    }
上面的程式使用了interface 將計算的方式分隔開來使我們在新增NightWorker的時候
並不會更改到其他的class  但是當我們需要添加新的函式時反而造成了很大的困擾
必須針對每個class 實做SetWorkHours  當class一多  這樣是很痛苦的

所以必須觀察系統所需要的
若要增加worker的薪資計算的彈性  就要使用物件導向的設計
若要增加worker的函式功能的彈性  就要使用結構化的設計

2013年10月14日 星期一

Android 開發 (二) reflection java 動態載入class的方法

為了妥善利用resource 所以使用動態的方式載入class
以下為android的範例

先create一個interface

package Printer;

public interface MessgaePrinter {
 public String print();
}

接著create一個class
package Printer;

public class HelloWorldPrinter implements MessgaePrinter{

 /**
  * @param args
  */
 
 public static void main(String[] args) {
  // TODO Auto-generated method stub

 }

 @Override
 public String print() {  
  return "Hello World!";
 }

}
接著將 檔案 export 成 jar 檔  export的file會在 project路徑底下
接著使用 cmd 將 java 轉成android可識別的檔案
在資料夾  sdk\build-tools\android-4.2.2 底下打入
dx --dex --output=test.jar printer.jar


接著在要執行的地方打入
public class MainActivity extends Activity {

 @SuppressLint("NewApi")
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  DexClassLoader cl = new DexClassLoader("FilePath",
    "FilePath", null, getClassLoader());
  try {
   Class dynamiclib = cl.loadClass("Printer.HelloWorldPrinter");
   MessgaePrinter lib = (MessgaePrinter)dynamiclib.newInstance();
   Log.d("Ted",lib.print());
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (InstantiationException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

2013年10月6日 星期日

CleanCode 讀書筆記 (一) 有意義的命名

最近正在讀 Clean Code 第二章剛讀完  就順便來整理一下讀後心得
有意義的命名  目的就是在讓人讀完程式碼之後就知道在幹嘛
舉例來說
C#
        
        public List<int[]> GetList(List<int[]> LJ)
        {
            List<int[]> ListK = new List<int[]>();
            foreach (int[] i in LJ)
            {
                if (i[0] == 1)
                {
                    ListK.Add(i);
                }
            }
            return ListK;
        }
看完上面的範例之後其實完全不知道程式在幹嘛
讓我們在來看看下面的範例
          
        public List<int[]> GetOpenTVList(List<int[]> TVList)
        {
            List<int[]> TV_OnList = new List<int[]>();
            foreach (int[] TV in TVList)
            {
                if (TV[State] == State_TV_On)
                {
                    TV_OnList .Add(TV);
                }
            }
            return TV_OnList;
        }
上下兩段程式的執行結果其實是一模一樣的
但是我相信下面的code看起來比較知道在幹嘛......吧(至少我是這樣覺得~_~)


這個function的工作就是從所有的電視中找出目前是開著的電視


關於變數的命名盡量使用有意義的命名 例如 ListJ vs TVList

使用可發音的變數  LJ vs TVList
使用不可發音的變數  會讓讀code的人去猜測他的用處  這是非常不好的情況

關於數字盡量使用variable去代替  例如  1 vs State_TV_On
當程式變得龐大時  搜尋 1 和 State_TV_On 的結果是完全不同的

在介面實作部分
舉例來說許多人習慣這樣寫
 
    abstract class ICellPhoneFactory
    {
    }
    class CellPhoneFactory
    {
    }
這樣寫其實並不好  我們並不希望使用者知道我們給他的interface
所以與其這樣寫不如
 
    abstract class CellPhoneFactory
    {
    }
    class OEMCellPhoneFactory
    {
    }
使用者只需知道這是一個CellPhoneFactory

關於類別的命名  應該要清楚  例如  Customer vs Data

關於方法的命名  要取出資料就應該有 Get   例如  GetCustomerData
                              要設定資料就應該有 Set    例如  SetCustomerId
                              要判斷資料就應該有 Is      例如  IsMyCustomer

書中有特別強調  命名的時候不要裝可愛 不要發揮幽默感  請正常的命名
沒有人會知道  IsMmmmmonsterKill 這個function是幹嘛用的 XDD
請用正常的命名方式  IsKillManyPeople

在方法得命名時不要出現兩種命名方式  例如  GetData 和  AccessData
請統一用同一種方式命名

在套用某些pattern或演算法時  盡量使用那些術語  讓讀程式的人能一眼看出這是在做甚麼
例如  CustomerFactory  閱讀的人一眼就知道這是個factory

結論  :
寫程式的命名就像是在寫作文一般
最好的programmer  就是讓使用者一眼就看出你現在想幹嘛
使用者根本不需要去看  function裡面實踐甚麼
只需要看主架構就知道程式的功能
好好的學習命名    這也是一門學問

2013年10月5日 星期六

Android 開發 (一) Singleton

在Android 使用 Singleton的時候要特別小心
由於Android 的 App 在 離開的時候並不會把static 的class destory掉
所以當 App 重新開啟時  可能會造成某些問題
例如
當App重開時  由於Activity已經更換了但是假設有某些item是base在舊的Activity
就會出現error


當App重開時 在App重開時將新的Activity做更換可以避免掉這個問題
或者是使用下面的方式也可以避免掉一些錯誤

public class Dialog extends Application{

       private static Dialog instance;

       public static Dialog GetInstance()

       {

                  return instance;

       }

       public final void onCreate(){

              super.onCreate();

              instance = this;

       }

}



設計模式 (七) Observable Pattern

觀察者模式
 當某個狀態改變時就通知所有訂閱者
 目的是要讓互動的物件做鬆綁 兩個物件可以作互動 
但彼此之間並不太清楚彼此細節
下面的範例是 當有個人Post一篇文章
 所有他的定閱者都會收到通知 然後都可以做他們相對應的動作

        public interface Notifier
        {
             void Notify();            
        }
        public class Paul : Notifier
        {
            public void Notify()
            {
                Console.WriteLine("Paul 立刻去看文章");
            }
        }
        public class Ted : Notifier
        {
            public void Notify()
            {
                Console.WriteLine("Ted 把文章加入書籤");
            }
        }
        public class Tina : Notifier
        {
            public void Notify()
            {
                Console.WriteLine("Tina 不理會");
            }
        }
        public class ArticlePoster
        {
            private List<Notifier> PersonList = new List<Notifier>();
            public void Add(Notifier person)
            {
                PersonList.Add(person);
            }
            public void PostArticle()
            {
                foreach (Notifier person in PersonList)
                {
                    person.Notify();
                }
            }
        }
        static void Main(string[] args)
        {
            ArticlePoster poster = new ArticlePoster();
            poster.Add(new Paul());
            poster.Add(new Tina());
            poster.Add(new Ted());
            poster.PostArticle();
            Console.ReadKey();
        }