2016年10月24日 星期一

[上課筆記] Learning How to Learn


時間就這麼多,除了在有限的時間內安排時間學習外,我還想要有效率地學習(學得快又記得久)。

上個月花了四週上完這堂課,把四週的重點寫下來,讓沒時間去上課或者是有興趣的人先快速瞭解一下。以下是我用到的教材,上課前可以先看一下講者去 TED 的快講,不合拍就不用去上課了。書的部分基本上跟上課內容完全一致,買或不買都沒差,我比較喜歡紙本所以就買了。

之前對學習技巧的書都很有興趣,這四週課程有把以前看過的技巧建構成一個系統流程,試著寫一下我的理解,下面的部分項目“學習”換成“工作“也適用。
  1. 制定學習計劃 // 有目標,執行,無論如何就是有進步
  2. 安排時間學習 // 養成習慣,就不容易停滯下來
  3. 營造好的學習環境 // 專心效果才會好
  4. 配合蕃茄鐘專注學習 // 人一天的專注力是有限的,跑一段休息一段,才跑的遠。
  5. 針對學習內容先鳥瞰再詳讀 // 先建構輪廓,有助於拼湊章節間的前因後果。
  6. 做整理 // 讀過就只是讀過,用筆寫能幫助記憶。
  7. 隔一段時間複習 // 一次念多次沒有比過一段時間重唸一次的效果好。
  8. 與人分享學習成果 // 能說給別人聽才代表自己有讀懂了
  9. 回到第一項...
四週筆記如下:

第一週
  • 專注工作一陣子(大腦進入專注模式),然後休息一下子(大腦進入發散模式),讓腦袋消化後學習效率更好。工具:蕃茄鐘。
  • 一天重念多次,不如每隔幾天重念一次。間隔一段時間重複練習,可以將知識深刻地記在大腦的長期記憶中。
  • 遇到難解問題,先放下做些別的事情再回來看,讓大腦有時間消化。
  • 睡眠充足有助於學習。
  • 不拖延的最好作法就是趕快動手做。
  • 邊做邊學效果最好。
  • 運動/放空有助於大腦產生新想法。
  • 有熱情就能堅持。

第二週
  • chunk 像是建構知識的磚塊,基礎打穩學的知識才牢靠(記得久、內化成常識)
  • 建立 chunk 的三步驟:專注 > 理解基本概念 > 多練習。
  • 不要過早進入細節,先知道輪廓再進入細節(ex:看書前先看大綱)。
  • 回想的效果大於反覆閱讀。複習前不看課本想想自己還記得多少,然後再重新看一次課本。
  • 畫線不等於真正懂了,要做重點筆記、測驗。

    第三週
    • 持續有規律地學習。
    • 熟能生巧。
    • 養成“好”的習慣(ex:固定時間就去把功課做完、下班前整理好隔天要做的項目)。
    • 注意力放在過程而非結果,不要去想還要多久才能做完,在規定的時間內專注做就對了,這可以減少心理上的壓力。
    • 如何對付拖延症:
      • The cue > 了解自己何時開始拖延的訊號 > 移除這些訊號 > 營造不分心的環境
      • The routine > 制定計畫營造自己不分心的策略
      • The reward > 達成後獎賞自己
      • The belief > 相信自己可以做到 + 找夥伴彼此互相督促
    • 制定每週計畫,列出達成計畫需要完成的項目。
    • 每日待做清單,寫下來就不會一直想,完成了就畫線,這種回饋機制有助於建立成就感。
    • 用影像圖片加強記憶。
    • 用自己熟悉的事物做關聯(ex:要記1999,可以與1999末日預言關聯)。
    • 記憶宮殿:人的大腦對於空間的記憶力特別好,所以可以利用自己的熟悉環境作為場景加入要記憶的事物。

    第四週
    • 多運動,運動也會產生新的神經元,能幫助大腦學習。
    • 用比喻的方式來學習新概念。
    • 主動學習比被動學習的效果好(自己想學而非別人逼你)。
    • 改變對考試的態度,不是懼怕到而是興奮(驗證自己到底學了多少),有助於提升考試表現。
    • 考試時先看難題對其有個印象,然後馬上切換去做簡單的題目,此時雖然專注在簡單的問題,但大腦已經在背景處理難題。

    2016年10月16日 星期日

    JCCONF 2016 筆記


    會場有提供幾個黑板讓大家回答問題,還蠻有趣的。

    • IDE:以 Eclipse 和 Intellij 為大宗,Intellij 還不是寫成 Android Studio。
    • 第二語言:以 Python 最多,Groovy 也不少。
    • Server:什麼都有看到但就是沒有 JBoss Server。
    這次參加心得是
    1) 一個團隊就是要重頭包到尾 // 不分開發測試DB維運
    2) 自動化要做的好,才有時間精力從頭包到尾
    3) Spring Boot 看來能快速開發後台,直接包好 Web Server 一個指令執行,不再走傳統部屬至 Server 的流程 // 我想要繼續走後台的話要來學一下
    4) 覺得現場人頭廠商徵才的方式蠻鳥的,覺得最好的方法就是好好把內部的 API 開源出來,派開發者出來演講,想用的人就會上去貢獻程式碼,這些人就是最有潛力的人才。 // 我絕對沒說是東森
    5) 新竹台北通勤好累,來回大概就4小時去了,回來安全帽還被幹走。 // 幹
    6) 回來想說隨便寫,沒想到大概花了又4小時才整理出來。 // 一場鬥陣都還沒開始玩
    7) 沒帶筆電去是對的,沒事做只能認真寫筆記。

    下面是準備回公司分享的投影片, 礙於內部分享不寫太多字太深入,所以一個議題都只有一張投影片,眼睛掃過去有興趣的話大會有提供 hackpad 可以看 ,會後也應該會放上影片,到時我也會更新至這份投影片中。

    2016年6月27日 星期一

    用程式碼介紹 RxJava

    前言

    RxJava 也強迫自己用兩個月了,除了自己的專案外,工作的專案也拿來改寫原本很難看懂的邏輯。這篇建議就當作一篇引導文或是心得文看看,有興趣繼續研究的朋友,網路上已經有很多中/英文資源,不怕沒地方學只怕你不學。

    安裝

    要導入 RxJava 很簡單,gradle 下一行即可,沒用 gradle 也只要抓一個 rxjava.jar 檔案,再引入專案就可以了。
    compile 'io.reactivex:rxjava:x.y.z'

    不用綁定 JVM 版本,不限定是 Android 專案還是純 Java 專案,只要一個 jar 檔案就能使用 RxJava。

    什麼是 RxJava

    直接舉幾個例子,來說明什麼是 RxJava,又為什麼我要用 RxJava,看了程式碼應該比用文字描述來的有感覺。這些例子也是我在專案中反覆用到的程式碼來改寫的。

    Android 中需要更新 GUI 的耗時工作

    取代 AsyncTask 在 Android 上呼叫 耗時工作,呼叫前更新 GUI,呼叫完再更新 GUI。

    對於耗時工作回傳的結果,在 RxJava 中還能做進一步處理,就像工廠的流水線,從頭到尾能看到做哪些處理。想想如果要用第一個耗時工作的結果,去呼叫第二的耗時工作,在 RxJava 就是單純再多一個站點(下面程式碼中的 map()),如果沒有 RxJava 這段程式碼要怎麼寫?寫出來的程式碼會不會很難看?
    Observable.just("input")
        .doOnSubscribe(new Action0()
        {
            @Override
            public void call()
            {
                // 在觸發耗時工作前更新 GUI
                // 比如顯示進度條
            }
        })
        .subscribeOn(Schedulers.newThread()) // 設定耗時工作在新的 Thread 上執行
        .map(new Func1<String, String>()
        {
            @Override
            public String call(String input) 
            {
                // 執行耗時工作
                // 比如是一個 Web API 呼叫
                return new WebApi(input);
           }
        })
        .map(new Func1<String, User>()
        {
            @Override
            public User call(String input) 
            {
                // 處理耗時工作回傳的結果
                // 比如 Web API 回傳的 Json 字串轉成 POJO
                return new Gson().fromJson(input, User.class);
           }
        })
        .observeOn(AndroidSchedulers.mainThread()) // 設定非同步工作完成後回到 Android GUI Thread 上更新 GUI
        .subscribe(new Observer<User>() 
        {
            @Override
            public void onNext(User user) 
            {
                // 更新 GUI
                // 比如在 TextView 上顯示 User 的名稱
            }
    
            @Override
            public void onCompleted()
            {
               // 如果沒有發生例外
               // 比如關掉進度條
            }
    
            @Override
            public void onError(Throwable error)
            {
                // 如果發生例外
                // 比如關掉進度條後跳出警告視窗
            }
        }); 

    移除會造成 Callback Hell 的程式風格

    延續上個範例提到的,如果要用第一個耗時工作的結果(用帳號密碼呼叫 Web API 得到 token),去呼叫第二的耗時工作(用 token 呼叫得到使用者的訊息清單),最後印出訊息清單。

    在 Java 中考慮到呼叫 Web API 是一個耗時工作,所以大部分設計的呼叫參數常會多帶一個 Handler(Observer Pattern),被呼叫端做完耗時工再後,再利用 Handler 讓呼叫端知道該做什麼,程式碼大致會長成這樣:
    webApi.getToken("user", "password", new DefaultHandler()
    {
        // 第一個 Web API 呼叫成功
        @Override
        public void onSuccess(String response)
        {
            webApi.getMessageList(response, new DefaultHandler()
            {   
                // 第二個 Web API 呼叫成功
                @Override
                public void onSuccess(List<String> response)
                {
                    // 呼叫成功
                    for (String message : response)
                    {
                        // 印出每個訊息
                    }
                }
    
                // 第二個 Web API 呼叫失敗
                @Override
                public void onFailure(Exception exception)
                {
                    // 印出失敗訊息
                }
            });
        }
    
        // 第一個 Web API 呼叫失敗
        @Override
        public void onFailure(Exception exception)
        {
            // 印出失敗訊息
        }
    });

    有沒有看到程式碼一直往內凹,這個就是 Callback Hell,雖然程式碼還是可以動,但就是不夠簡單不夠整齊。如果今天採用 RxJava 來設計,RxJava 本來就是 Observer Pattern,前一個方法呼叫完成,才會進入下一個方法,程式碼大致會長成這樣:
    Observable.just(webApi.getToken("user", "password"))
        .flatMap(new Func1<String, Observable<String>>()
        {
            // 第一個 Web API 呼叫成功
            @Override
            public Observable<String> call(String token)
            {
                try
                {
                    // 第二個 Web API 呼叫成功
                    return Observable.from(webApi.getMessageList(token));
                }
                catch (Exception e)
                {
                    // 第二個 Web API 呼叫失敗
                    // 將錯誤丟出來
                    throw Exceptions.propagate(e);
                }
            }
        })
        .subscribe(new Observer<String>() 
        {
            @Override
            public void onNext(String message) 
            {
                // 印出每個訊息
            }
    
            @Override
            public void onCompleted()
            {
                // 如果沒有發生例外
            }
    
            @Override
            public void onError(Throwable error)
            {
                // 第一個 Web API 呼叫失敗
                // 第二個 Web API 呼叫失敗
            }
        });

    當要串的 Web API 越多的時候,帶 Handler 產生的巢狀架構,會讓程式碼越來越複雜不容易看懂,而用 RxJava 寫法的程式碼只會變長。

    執行固定次數的動作

    有時想要執行某個動作10次,一般都是要寫個 for 迴圈,現在不用羨慕 Ruby/Python 有很簡單的寫法,現在我們也有了。
    Observable.range(0, 10).subscribe(new Action1<Integer>() // 0, 1, 2, 3...9
    {
        @Override
        public void call(Integer item)
        {
            // 執行動作
            System.out.println(string);
        }
    });

    幹,好像也沒有比 for 迴圈少打幾個字,這個就必須要你有學好 lamda 了。
    Observable.range(0, 10).subscribe(item -> System.out.println(item)); // 0, 1, 2, 3...9

    資料集合處理

    寫後台寫邏輯常常會需要處理資料集合,今天如果我們有個字串集合,要做以下處理(依序):
    • 只取出有帶 "boy" 的字串
    • 取出字串中的數字
    • 將數字轉成英文(1變成 "one")
    • 只取最前面兩個符合條件的英文
    廢話不多說,直接上程式碼最有感覺。
    public class HelloRxJava
    {
        public static void main(String[] args)
        {
            List<String> list = new ArrayList<String>(Arrays.asList("boy_4", "boy_1", "girl_1", "boy_2", "boy_3", "girl_2"));
    
    
            // 傳統寫法:
            int take = 2;
            int i = 1;
            for (String string : list)
            {
                if (isBoy(string))
                {
                    if (take < i)
                    {
                        return; // Jump!
                    }
    
                    System.out.println(numberToEnglishNumber(getNumber(string)));
                    i++;
                }
            }
    
    
            // RxJava 沒有 lamda 的寫法:
            Observable.from(list)
                .filter(new Func1<String, Boolean>()
                {
                    @Override
                    public Boolean call(String string) 
                    {
                        return isBoy(string);
                    }
                })
                .map(new Func1<String, String>()
                {
                    @Override
                    public String call(String string) 
                    {
                        return getNumber(string);
                    }
                })
                .map(new Func1<String, String>()
                {
                    @Override
                    public String call(String string) 
                    {
                        return numberToEnglishNumber(string);
                    }
                })
                .take(2)
                .subscribe(new Action1<String>()
                {
                    @Override
                    public void call(String string) 
                    {
                        System.out.println(string);
                    }
                });
    
    
            // RxJava 有 lamda 的寫法:
            Observable.from(list)
                .filter(string -> isBoy(string) == true)
                .map(string -> getNumber(string))
                .map(string -> numberToEnglishNumber(string))
                .take(2)
                .subscribe(string -> System.out.println(string));
        }
    
    
    
    
        /**
         * 將阿拉伯數字轉為英文字
         */
        private static String numberToEnglishNumber(String string)
        {
            if (string.equals("1")) return "one";
    
            if (string.equals("2")) return "two";
    
            if (string.equals("3")) return "three";
    
            return "oops!";
        }
    
        /**
         * 取得字串裡的數字
         */
        private static String getNumber(String string)
        {
            return string.substring(string.indexOf("_") + 1);
        }
    
        /**
         * 字串是否包含 boy
         */
        private static boolean isBoy(String string)
        {
            return string.startsWith("boy");
        }
    }

    為求範例簡潔,一些邏輯都抽取成 private 方法。可以發現傳統寫法雖然不長,但有巢狀 if 的關係,需要花一點時間才能了解程式碼的意圖,而用 RxJava 的寫法,基本上根本就是把剛剛條列的動作依序執行,而用 lamda 的方式寫的程式碼更是很容易就能看出程式碼的意圖。

    小結

    本文說明了:
    • 無論是單純的 Java 環境,還是在 Android 上,只要一個 jar 就能開始用 RxJava。
    • RxJava 雖然讓程式碼變長了,但讓程式碼的意圖變得比較好理解。
    • 如果你會用 lamda,你的程式碼就不會那麼長。
    • RxJava 提供的多種運算子,讓我們很容易就能做出"取出最前面兩個元素"、"Retry 10次"之類的邏輯。
    我有把我學習過程中寫的範例碼都放上 github,有興趣的人可以拉下來跑,基本上都是一些釐清觀念的 Code。另外推薦這篇 RxJava 文檔中文版,除了介紹 RxJava 的概念外,也針對各種運算子有很詳細的說明。

    Related Posts Plugin for WordPress, Blogger...