Total Pageviews

Showing posts with label Programming. Show all posts
Showing posts with label Programming. Show all posts

2018/03/05

[Java] Apply Builder Pattern

Before (do not apply builder pattern)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package albert.practice.designPattern.builder;

import java.io.File;
import java.util.List;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class PlainEmailParam {
    @NotNull(message = "請提供收件者 email")
    private List<String> receiverEmails;
 
    @NotEmpty(message = "主旨不可為空")
    private String subject;
 
    @NotEmpty(message = "內文不可為空")
    private String content;
 
    private List<File> attachments;
}


After (apply builder pattern and apply validation in build method)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package albert.practice.designPattern.builder;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import org.apache.commons.validator.routines.EmailValidator;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;

import lombok.Getter;
import lombok.ToString;

@ToString
public class EmailParam {

    @Getter
    @NotNull(message = "請提供收件者 email")
    private List<String> receiverEmails; 

    @Getter
    @NotEmpty(message = "主旨不可為空")
    private String subject;

    @Getter
    @NotEmpty(message = "內文不可為空")
    private String content; 

    @Getter
    private List<File> attachments; 

    private EmailParam(Builder builder) {
        this.receiverEmails = builder.receiverEmails;
        this.subject = builder.subject;
        this.content = builder.content;
        this.attachments = builder.attachments;
    }

    public static class Builder {

        private List<String> receiverEmails; 
        private String subject;
        private String content;
        private List<File> attachments; 

        public Builder receivers(List<String> receiverEmails) {
            this.receiverEmails = receiverEmails;
            return this;
        } 

        public Builder subject(String subject) {
            this.subject = subject;
            return this;
        }

        public Builder content(String content) {
            this.content = content;
            return this;
        }

        public Builder attachments(List<File> attachments) {
            this.attachments = attachments;
            return this;
        }

        public EmailParam build() {
            EmailParam param = new EmailParam(this);
   
            validate(param);
            validateEmails(param);

            return param;
        }

        /**
         * Do email validation.
         * 
         * @param param
         */
        private void validateEmails(EmailParam param) {
            List<String> receivers = param.getReceiverEmails();
            if (receivers != null && receivers.size() > 0) {
                for (String email : receivers) {
                    if (!EmailValidator.getInstance().isValid(email)) {
                        throw new IllegalArgumentException("Email 格式有誤");
                    }
                }
            }
        }

        /**
          * Do parameter validation.
          * 
          * @param obj
         */
         public void validate(Object obj) {
             List<String> errors = new ArrayList<>();

             Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
             Set<ConstraintViolation<Object>> violations = validator.validate(obj);
             for (ConstraintViolation<Object> violation : violations) {
                 errors.add(violation.getMessage());
              }

             String completeErrorMsg = Joiner.on("\n").join(errors);
             if (!Strings.isNullOrEmpty(completeErrorMsg)) {
                 throw new IllegalArgumentException(completeErrorMsg);
             }
          }
  
 }

}



Test Class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package albert.practice.designPattern.builder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import org.apache.commons.validator.routines.EmailValidator;
import org.junit.Test;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class EmailParamTest {
 
 @Test
 public void testPlainEmailParam() {
     PlainEmailParam param = new PlainEmailParam();
     param.setReceiverEmails(Arrays.asList("test1@test.com", "test2@test.com"));
     param.setSubject("test");
     param.setContent("just for testing");
  
     validate(param);
     validateEmails(param);
  
     log.debug(param.toString());
 }
 
 @Test
 public void testEmailParam() {
     List<String> receivers = Arrays.asList("test1@test.com", "test2@test.com");
     EmailParam param
         = new EmailParam.Builder().receivers(receivers).subject("test").content("just for testing")
           .build();
     log.debug(param.toString());
 }
 
 private void validateEmails(PlainEmailParam param) {
     List<String> receivers = param.getReceiverEmails();
     if (receivers != null && receivers.size() > 0) {
         for (String email : receivers) {
             if (!EmailValidator.getInstance().isValid(email)) {
                 throw new IllegalArgumentException("Email 格式有誤");
              }
         }
     }
 }

 private void validate(Object obj) {
     List<String> errors = new ArrayList<>();

     Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
     Set<ConstraintViolation<Object>> violations = validator.validate(obj);
     for (ConstraintViolation<Object> violation : violations) {
         errors.add(violation.getMessage());
     }

     String completeErrorMsg = Joiner.on("\n").join(errors);
     if (!Strings.isNullOrEmpty(completeErrorMsg)) {
         throw new IllegalArgumentException(completeErrorMsg);
     }
 }

}




2016/10/02

[閱讀筆記] 97 Things Every Programmer Should Know (2/2)


  1. API 設計的黃金準則是:只為你開發的API 編寫測試是不夠的;你必須為使用API 的程式編寫單元測試。你必須親身體驗,一旦你體驗過了,就可以面對每一種設計挑戰
  2. 專心在專案上,尋找聰明的解決方案來盡可能做出貢獻,提升你的技巧,反省你所做的,然後調整你的做法。別工作的像在籠子裡跑的倉鼠,每週工作超過60個小時是不明智的。要用專業的方式去做:準備、實現、觀察、反省與改變
  3. 一份好的bug report 需要包含三件事情
    1. 如何重現bug 以及出現的頻率
    2. 預期原本應該要發生什麼事情
    3. 實際上發生什麼事情
  4. IDE 工具的存在是為了讓開發更容易,並增進程式設計師的生產力。但是有時候妳應該掀開引擎蓋,了解你的IDE 為你做了什麼,最好的方法就是學習command-line tools。之後回過頭來使用IDE 就會更了解他正在為你做什麼,以及如何控制建置的過程。
  5. 評估、目標和承諾是彼此獨立的。但是,目標和承諾是建立在可靠的評估之上。軟體評估的主要目的並非預測專案的成果,而是為了判別專案的目標能否實現,讓專案得以掌控
  6. 評估時程的用意是為了使適當的專案管理與規劃成為可能,允許專案的利害關係人將於實現目標做出承諾
  7. 專案經理常對程式設計師要求的不是一個評估的時程,而是要對管理者內心不確定的目標做出承諾
  8. 一個好的interface 應是容易被正確的使用、難以被誤用。故你必須要事先預想到並避免使用者可能犯的錯誤,並觀察早期釋出的interface 如何被誤用並修改interface ,避免發生同樣錯誤
  9. 雖然在一般遇到的案例裡,使用 if - then - else 比多型更實用些,但絕大多數用 polymorphism 的寫作風格會讓程式碼更精簡、更易讀,且更少脆弱的程式碼。所以在我們的程式碼裡,要是錯過使用 polymorphism 的機會,便會逐漸增加  if - then - else 敘述句的數量。試試 command  pattern 吧
  10. deployment的環境資訊也要保持在版本控制之下。沒有比破壞一個環境設定,卻沒辦法知道哪裡被改變過還糟。環境資訊應該要與程式碼分開控制版本,因為他們改變的頻率與理由接不相同
  11. 如果你的程式碼需要註解來解釋,請考慮把它 refactor 到不再需要註解
  12. 專業程式設計師唯一最重要的特點就是個人的責任感。他們對自己擁有的職業負責,對確認自己的程式碼可以良好的運作負責,對自己工作產出的品質負責,即使當截止日期迫在眉睫,他們還是不放棄自己的原則。當壓力日益增加時,專家要比以往任何時候都更嚴格堅持著原則,他們知道這樣才是正確的
  13. 試圖挽救糟糕程式碼所浪費的時間,會比預想的多更多。一旦遇到怎麼修改都毫無起色的程式碼,應該立刻丟棄。修改糟糕程式碼的最好的方式就是換個做法,將程式碼無情的refactor 、移到別的地方或乾脆把它刪除掉。
  14. SRP(Single Responsibility Principle) 是良好設計的最基本原則,即將那些為了相同的理由而改變的東西聚集在一起,並將那些為了不同的理由而改變的東西獨立出來。
  15. 越接近交付期限,壓力越大,人本能的就開始偷工減料略過測試,所以你要想辦法把測試自動化,執行完就知道測試結果
  16. 你可以把 soak test 設定在假日執行 (如星期五半夜到下週一早上六點),藉此查看是否有 memory leak 的問題
  17. 當一個程式設計師,做個有經驗的技術專家已經不夠了,你還必須能與其他人有效率地一起工作
  18. 程式碼不會說謊,但是它卻會自我矛盾。有時候,錯加上錯就會變成對的,但它會變成更難以修復
  19. 良好的測試就像受測程式碼的文件一樣,它描述了程式碼如何運作。在每一個使用情境中,測試可以
    1. 描述出這些程式碼的執行脈絡、起始點或者必須滿足的前置條件
    2. 說明軟體如何被調用
    3. 描述出預期的結果或是需要驗證的 post condition
  20. 在測試的過程中,過多的程式碼會把閱讀你程式碼的人轉移到無關緊要的瑣事上,盡可能把這些瑣事使用具有意義命名的方法隱藏起來,這樣的方法稱為 extract method,這個 refactor 技巧會是你最好的朋友
  21. 你想要寫出良好的程式碼,並希望能成為一位好的程式設計師,你必須
    1. 在任何情況下,拒絕用 hack 的方式做出那種只是看似能夠運作的程式碼
    2. 你所寫的程式碼是淺顯易懂、易於維護、正確及確認已經解決問題
    3. 考慮到團隊的其他程式設計師,讓其他人可以讀懂你建構的程式碼
    4. check in 程式前,要讓程式碼比當初 check out 時來得更好 (如更好閱讀、更好的結構、更容易測試等)
    5. 持續學習新的語言、方法和技術,在適當的時機就可以派上用場
  22. 盡早挑戰你的客戶,並且要經常挑戰他們。別輕易的用他們的話重述他們告訴你的需求。請記住:客戶所告訴你的,不一定是他們想要的。客戶無法告訴你全部的事實,只是以客戶的方式陳述自己的想法,而非以開發人員的方式

2016/09/02

[閱讀筆記] 97 Things Every Programmer Should Know (1/2)


  1. 最新式的電腦,只不過是加速加重了人與人之間一直以來的問題,最終,溝通者要面對的,依然在於如何表達出自己要說的
  2. 技術債 (technical debt) 就像貸款,你可以為了時程決定「先快速做完」,而不是「做好」,你會在短期內得利,但在債務還清之前都必須支付利息。滿足一時的程式碼,會使新增功能與重構變得困難。時間越久,傷害越大。事實上,往往都是當事情糟到你不得不修正時,你才會真的回頭去解決當初的問題,但是這時候程式碼已經變得很難修正。
  3. 記得要儘快償還技術債 (technical debt),否則會因為草率的行為而嚐到惡果。
  4. 我們常認為別人的想法和我們一樣,但事實上不是。心理學將此稱為共識偏誤(false consensus bias)。請直接去問使用者會怎麼操作,你不算是使用者
  5. 使用者傾向於只要有能用的方法就好,他們一旦找到一種可行的方法之後,就會一直用,不論這個方法是多麼迂迴。因此,最好準備一條明顯的道路,而不是給兩三種道路供選擇
  6. 你也會發現使用者所描述的,和他們實際做的往往有段落差。因此最好的方法是直接觀察使用者,花幾個小時觀察,比起花一整天想使用者到底要什麼,反而可以得到更多資訊
  7. coding standard 應該是靈活不死板的,隨著專案演進,需求改變,一些當初看似聰明的想法,在幾個月後已經不見得聰明了
  8. 柏拉圖曾說:風格之美、和諧、優美與節奏取決於簡約。美麗的程式的最低限度是簡單的程式碼。不只職責簡單,與系統的其他部分也要保持簡單的關係。簡單、乾淨、可測試的程式碼,可以提高可讀性、可維護性、開發效率。美,就在簡單中誕生,在簡單之中發現
  9. 在你重構前,你要思考:
    1. 從現有的程式碼與其對應的測試開始,從當前系統的錯誤中學到教訓
    2. 避免全部重寫的誘惑,重寫代表要放棄經年累月的測試
    3. 多次漸進式的修改會優於一次大量的修改
    4. 每次development iteration結束後,要確保現有的測試都必須通過
    5. 程式碼的結構與風格不符合你的喜好,不是重構的正確理由
    6. 新技術的出現不足以成為重構的理由
    7. 每個人都是會犯錯的,重構不保證新的程式碼一定比較好
  10. 童子軍有一條規則:『每次離開營區時,都要讓它比剛到的時候更乾淨』。也就是說,如果你發現環境髒亂,不管是誰造成的,你都要把它打掃乾淨,留給下一批露營者有更好的環境。相同的,你不必在 check in 前讓每個模組盡善盡美,只要讓它比當初 check out 的時候好一點點就可以,如改善了變數名稱、將一個過長的function拆成兩個小的function等。軟體開發團隊要互相幫助,互相清理程式碼。成員門遵循童子軍規則,不只是為了自己,而是因為這對每一個人都好。
  11. Debug 時,回想福爾摩斯的建議:「除去不可能的,剩下的即使不尋常,那也是真相。」
  12. 面對multi-thread 的系統,簡潔的設計非常重要,簡潔代表容易debug
  13. 程式碼的排版很重要,有研究指出,我們花在瀏覽和閱讀程式碼,找出哪裡該修改的時間,比實際修改的時間多更多。所以程式排版有三個優化措施:易於瀏覽、清楚的排版、緊湊的格式
  14. code review 不只是找出並修正程式錯誤,更重要的是分享知識,並建立普遍的coding方針。code review 的重點是在 review 的過程當中去學習、了解這些程式碼
  15. 採用表達力佳(但相對較短)的名稱來命名物件、型別與函式,讓程式碼本身具備如文件的說明能力
  16. 註解應該要說出程式碼所沒有表達、也無法表達的事情。如果程式自己可以說清楚的事情,你就不需要為它加上註解
  17. 外科醫生知道必須要切出傷口才能進行手術,也知道傷口是暫時的,會痊癒的。害怕改變程式碼導致癱瘓,可能正是造成系統落入癱瘓的起點。投入時間 refactor,對於專案的整個生命週期可以帶來數倍的價值,也因為有了處理病態系統的經驗,也成為了解系統應當如何運作的專家
  18. 無論你認為你的程式碼有多麼不可能出現錯誤,你也應該每一次都檢查並處理,即使你現在不做,你也不會省下時間,你未來還是要還債,而且累積越久,要還的債更多,未來會連本帶利要你償還
  19. 如果你忽視一項錯誤,選擇視而不見,假裝沒這件事情,你其實在承擔巨大的風險,你也無法預料這個巨大的風險會在何時向你反撲。
  20. 放著錯誤不管會造成
    1. 脆弱的程式碼且難以debug
    2. 不安全的程式碼
    3. 糟糕的結構
  21. 沒有開發經驗的專案經理會認為程式設計師做的事情很簡單,而沒有管理經驗的程式設計師,也認為專案經理做的事情很簡單
  22. 軟體的構成實體,應當對於擴充保持開放,但對修改保持封閉
  23. DRY ( Don’t Repeat Yourself ) 的原則,是指開發人員要學會便是重複的程式碼,也懂得利用更好的實踐和更適當的抽象化來消除重複,比起只知道用不必要的重複來污染程式的人,可以產出更乾淨的程式碼。DRY 原則,位開發人員提供基本指引,幫助開發人員所產出的程式變得更簡單、更容易維護且品質更好
  24. 假設有個物件,其典型的特色是需要先完成幾件事情才能開始使用,此時就可以使用abstract factory pattern or factory method pattern 來實現。如果某個物件有多種可變的行為,那麼這些行為可以用strategy pattern 來實作並放進物件,不該用一個巨大的 if-else 結構
  25. 當一個class 的containment (封裝)被破壞,常會看到其method 出現大量的if-then-else的結構,這樣的method 很容易崩壞且幾乎無法維護


2016/02/02

[閱讀筆記] 易讀程式之美 (Part 2)


  1. C++的創造者Bjarne Stroustrup說,在我的個人經驗裡,do / while 敘述經常是錯誤與誤解的來源,我寧願把條件式列在最前面,總而言之,我會盡量避免使用do / while 敘述
  2. 修改程式碼時要以全新的角度審視,退一步以整體的角度考慮程式碼
  3. 盡量消除loop中的巢狀結構,因為巢狀結構可讀性較差
  4. 程式碼的表示式越大,就越難理解。所以,要將巨大表示式分解為更容易消化的大小
  5. 寫法很酷的程式碼,經常會對後續使用程式的人造成困擾
  6. 對邏輯表示式使用De Morgan’s Law,有時能將boolean 表示式以更清晰的方式表達,比較簡單的記法是,把not分配到各項,再反轉and / or。如,if( !(a && !b)) 改寫成 if (!a || b),if ( !(file_exists && !is_protected)) 改寫成 if (( !file_exists || is_protected))
  7. 濫用變數會造成三個問題:變數越多越難同時記住所有變數;變數存活的範圍越大,就必須記得越久;變數越常改變,越難記得目前的數值
  8. 工程就是將大問題分解成小問題在將小問題的解答組合成原本大問題的解答在程式碼應用這個原則能讓程式更強固也更易於閱讀
  9. 抽離程式碼為獨立函數,這是程式設計師每天都在做的工作
  10. 函式庫提供的介面要夠簡潔,不要太多參數,也不要有太多的設定程序,用起來不要有額外的負擔。讓程式碼看起來更佳優雅,也更簡單與強而有力
  11. 程式碼應該組織為一次只做一件事
  12. 如果程式碼很難閱讀,試著列出執行的工作,某些工作可以輕易的抽離為函數或類別,其他部分可以成為函數內邏輯的段落。分離工作細節的形式並不重要,重要的是彼此分咧,真正困難的部份在於列出所有函數執行的小工作
  13. 愛因斯坦曾說:除非能解釋讓祖母了解,不然就不算真正了解
  14. 可讀性最高的程式碼就是完全沒有程式碼
  15. 程式設計師常會低估實作功能需要的功夫,會很理想的估計實作粗糙原形所需的時間,忽略後續維護、文件以及對程式碼增加的額外負擔
  16. 程式設計師一般會高估專案正正需要的核心功能,造成許多功能來不及完成,或祇是增加應用程式的複雜度
  17. 消除不必要的需求、簡化問題非常重要,重新思考需求,在仍然能達到目的的情況下,改為解決簡化過的問題
  18. 避免過度設計
  19. 當任何系統成長時,維持系統所需的複雜度會以更快的速度成長
  20. 盡量維持程式碼小而美
  21. 測試應該易於閱讀,以便其他人可以放心地修改或其加入其他測試。測試程式碼與非測試用程式碼一樣重要。
  22. 依據一般設計原則,應該「對使用者隱藏不重要的細節,凸顯重要的細節」
  23. 錯誤訊息應該盡量清楚地提供有幫助的資訊,有時候,「自行手工打造」印出所需的訊息,會是最好的方式
  24. 測試都時候,應該挑選能完整執行程式碼所需之最簡單的集合作為輸入值。優先使用簡單、明確但能達到測試效果的輸入值
  25. 一般來說,如果在設計程式的時候體認到,「嗯,之後這段會很難測試」,這就是個停止考慮這種設計的好理由
  26. 100行容易讀的程式,比起50行艱澀難懂的程式碼要來得好

2016/01/02

[閱讀筆記] 易讀程式之美 (Part 1)


  1. 程式碼應該易於理解
  2. 撰寫程式時,應該將讀者理解所需的時間降到最短
  3. 雖然減少程式碼數量是個很好的目標,但縮短理解時間更加重要
  4. 實務上,讓程式碼易於了解,往往也會產生良好的架構且易於測試
  5. 無論是重新命名變數、函數或類別,基本原則大同小異。名稱可視為簡短的註解,即使空間有限,透過選擇好的名稱就能包含許多資訊
  6. retval 這類名稱不包含任何有意的資訊,只能代表return value,應該使用能夠說明便數值意義的名稱
  7. tmp 這樣的名稱,只適用於變數生命週期很短,且作為暫存用途的變數
  8. 變數名稱不宜太長,名稱愈長就愈不容易記住,也會佔用更多畫面空間,甚至造成額外的自動換行
  9. 反覆思考名稱,自問別人會怎麼解釋這個名稱
  10. 對於邊界的極值而言,最清楚的方式是在名稱前面加上max_或min_
  11. 選擇 boolean 變數或回傳 boolean 值的函數名稱時,必須確保能清楚表示true / false的意義
  12. 一般來說,加上is / has / can / should 可以讓boolean value更加明確
  13. 避免在名稱中使用否定描述,如disableSSL,改用useSSL會比較容易閱讀且簡短
  14. 最好的名稱是最不容易被誤用的名稱,閱讀程式碼的人能夠清楚了解撰寫程式的人的意圖。不幸的是,許多英文單字在程式碼都會有兩種以上的解釋,如filter、length以及limit
  15. 在決定名稱前,要從反向思考,想像可能造成的誤解,最好的名稱能夠盡量避免誤會
  16. 對於定義數值的上下限,max與min是很有幫助的prefix;對於必區間,first與last也是十分合適的prefix;對於半開放區間,begin與end是符合使用慣例的好選擇
  17. 注意使用者對特定單字的預期,如使用者會認為get與size是lightweight accessor
  18. 良好的程式碼也應該賞心悅目,其應該包含以下三原則:排版一致,讓讀者習慣固定的模式;調整類似程式碼有相似的外觀;組織相關程式碼成為段落
  19. 賞心悅目的程式碼會比較容易處理,寫程式大多數時間都花在閱讀程式上,能預快瀏覽程式碼,就愈容易使用程式碼
  20. 一致化的風格比「正確」的風格重要
  21. 程式註解的目的是協助使用者了解程式碼作者的思想
  22. 不要註解那些能很快從程式碼知道的事實
  23. 好程式 > 壞程式 + 好註解
  24. 程式設計師常用的標記有,TODO:作者還沒處理的部份;FIXME:已知的問題:HACK:承認程式解決方法不夠優雅;XXX:危險!重要問題
  25. 程式註解要有高資訊空間比
  26. 使用包含大量資訊的詞彙讓註解更加簡潔
  27. 在使用if-else判斷式時,先處理肯定條件;先處理簡單的情況,必較能在畫面中同時呈現if與else區塊,在閱讀上很有幫助;先處理比較有趣或明顯的情況
  28. 縮短其他人理解程式所需的時間,比減少程式碼行數來得重要
  29. 只有在最簡單的情況下使用三元運算子,複雜的情況還是盡量使用if / else
  30. 避免使用do / while loop