2016年5月15日 星期日

搬家囉

決定搬家到點部落

並且把 Blog 命名為 : 技術債

因為目前最為討厭的事情就是處理技術債

但是該還的總有一天會還

不如就把當初留的債都記錄下來吧

2015年11月22日 星期日

電商大講堂 - 心得

為期 2 天由資策會主辦的講座,

各種辛酸血淚的分享與行銷經驗,

此文無法道盡 8 個講堂。

依自己的印象及紀錄日後能繼續深究的工具及知識,

如果有些實際/實務經驗更能與講師產生共鳴。

講堂1 -- 電商的行銷預算配置


  1. 指名度非常重要,In Taiwan 想到網購就想到 PChome Yahoo...
  2. 搞懂你產品 ( 想要 ? 需要 ? )與客群
  3. 虛擬通路與實體通路不應該是 1 + 1 = 2
  4. 部落客推薦 - 意象傳遞

講堂2 -- 在精準度、價格與搜尋量三難中取得 Google Adwords 最佳解


  1. 在關鍵字的策略避開廣義
  2. 了解客戶情境與需求
  3. 網友使用搜尋的習慣
  4. 出價策略上調整,千萬別用金錢去換跳出率只會把 SEO 越搞越爛
  5. Google Trends 搜尋趨勢
  6. Using Dynamic Search Ads

講堂3 -- 行動流量的實戰經驗


  1. 不要一開始強迫別人來註冊你家會員
  2. 購買步驟少一步是一步
  3. 打字不如選,選不如不用選
  4. 中途放棄購物提供加入收藏功能
  5. 購買後 Hot sales & Also buy
  6. 公司預算夠的話還是建議開發 Native APP for mobile

講堂4 -- 電商平台在 Facebook 廣告投放下的轉換率解析


  1. 50%技術 + 50 %策略才能成就好的紛絲團經營
  2. 粉絲團經營的不是粉絲而是粉絲的朋友 (觸及)
  3. 質重於量
  4. 廣告投射善用 Tools 過濾忠實粉絲 / 消費過對象
  5. Data & Analytics
  6. Facebook Pixel
  7. Website Custom Audiences
  8. Facebook 動態產品廣告
  9. 出價策略之後都會採用 OCPM
  10. Growth Hack (這裡找到 Xdite 分享的一篇初級入門文 )

講堂5 -- 讓電商諸葛亮,做你的跨境軍師


  1. 進入市場的事前功課分析不可少

講堂6 -- 流量的品質與轉換率指標


  1. 終極目標就是將潛在客戶轉換為忠實客戶
  2. 廣告預算的取捨
  3. 流量大 != 成交量大
  4. 收集各種成交 data 客戶群組合
  5. 細水長流的穩健發展才是最真實的

講堂7 -- 創造顧客終身價值的第一步:edm


  1. 主打忠實訂閱客戶
  2. 禁止買名單、濫發
  3. 垃圾郵件的象徵與 ISP 扣分機制
  4. 經營自家的 Mail Server 分數
  5. EDM 發送時段

講堂8 -- 電商平台跨媒體投放轉換率解析


  1. Google Analytics 網站分析資訊網 
  2. 為廣告網址產生自訂廣告活動參數
  3. 別為一時的流量產生過多的情緒
  4. 從數據的分析解讀頁面缺失
  5. 文案數據分析與工程師的配合缺一不可


2015年5月31日 星期日

自動測試與 TDD 實務開發第三梯 - 心得後記

去年因為 Cash & Raymond 有幸參加第二梯,

但是基本功 & 物件導向觀念還太菜的我,一直很難跟上進度。

今年初看到 SkillTree 開出第三梯課程,就趕緊湊人報名。

一樣是連續 3 周 7 個小時拳拳到肉的觀念與實作的課程 By Joey

「寫測試?功能都還沒完成為什麼還要我寫測試?」
「那不是應該 QA 負責的嗎?為什麼要由 Developer 寫?」
「聽起來很棒!但是並不適合我們公司文化。」
「應該不是我的 Bug 吧?我在我電腦 Rnu 過都是沒問題的阿…」
「我猜 Bug 可能是這段 Code 的關係…」

相信上述對話都真實發生過,

上過這個課程後會對這些疑問及觀念有極大的收穫。

Day 1
  • 3A 原則 - Arrange, Act, Assert
  • Unit Test 特性 - FIRST
  • 測試框架 MsTest
  • 3 種驗證方式 - 回傳值/狀態改變/外部互動
  • 物件導向 - 相依/隔離
  • 手刻 Stub
  • Stub/Mock Framework - NSubstitute
  • Internal Testing
  • Code Coverage
Day 2
  • Web Testing - Selenium IDE
  • FluentAutomation
  • MessageHangler  Unit Test
  • Contorller Unit Test
  • Page Object Pattern by FluentAutomation
  • Refactoring
  • TDD
Day 3
  • BDD by Cucumber
  • Specflow
  • ATDD
  • Auto Generate Document
  • how to PO/PM/SA support you ?
  • CI introduction
講師真的很專業也很熱心,

都能給予一些實務開發上的建議與不同的想法。

也很感謝該單位提供這麼物超所值的課程阿!

最後這堂課程給我感觸最深的一段話︰



友善連結︰

2015年4月20日 星期一

JavaScript 網頁應用程式設計 閱讀後記


函式庫 Spine、Backbone、JavaScriptMVC 這 3 個章節因為沒有使用就跳過沒看了。

如果日後有需要再去翻閱即可。

這本主要是要講解 MVC 架構在 JavaScript 上的實現及觀念。

jQuery Framework 還是非常強大,

書中範例解釋許多 jQuery API 還蠻詳盡的,

以及如何使用這些工具打造基本的 MVC 架構。

循序漸進的方式改良程式碼及解耦合。

  1. 控制器 Controller 基本抽象實現
  2. 改良版 ( 破除 Contoller & View 相依 )

而且讀到訂閱模式跟代理器又跟前幾周學的 Flux 架構有連結到,

  1. 搜尋檢視器內的元件透過參考值實現加快搜尋速度
  2. 改良版 ( 事件、代理器、相關程式架構整頓並使用委派去動態捕捉事件 )
當然這本書出版有段時間了,

裡面舉例的有些 Library 放到現代來看就沒這麼普遍。

  1. 繫結機制 Binding 模型 搭配 jQuery.tmpl.js
但許多觀念我覺的有紮實的學到,

對於日後有助於在撰寫程式碼或思考上都會有所幫助。

2015年3月29日 星期日

FLUX 架構 - 使用原生 JavaScript

隨著 ReactJS 的火紅,

另一項被大家所期待的就是 Flux ,

官方也特此開了一個站點去詳細介紹這個資料流概念,

各位點進去就會看到下面這張圖片。

varying transports between each step of the Flux data flow
擷取自 ReactJS 官方網站

第一次我聽完 小翊 介紹完 Flux 與 ReFlux ( 國外某人把 Flux 簡化後推出的 Lib)

完全是空有概念很難想像實際要寫的時候該如何實作。

今天就是給各位展示如何使用 JavaScript,

然後完全不用任何 Framework 去展現 Flux 架構與精神。

首先,要來定義 Flux 架構。


大家可觀察到扣除 Render 後其實跟官方的圖片其實就差不多一致了。

由於 Render 的工作內容已由 ReactJS 取代掉了,

所以 Flux 資料流中並沒有出現這個角色在。

今天要展示的 JavaScript 需求如下︰


看到這可能一堆人會說「根本是在汙辱我」、「很簡單啊」

舉 jQuery 來說好了,

來個監聽 input keyup 事件就搞定了。

但篇幅就是要來介紹如何不使用 Framework 並且套入 Flux 架構去完成。

而不是用各位熟悉的框架去達到此目的。

先定好下方的 HTML。



一般如果要監聽會經常看到以下這樣寫︰

Dispatcher.js

var Dispatcher = {
    init: function(){
        this.displayBlock = document.getElementById('DisplayBlock');
        this.inputBox = document.getElementById('InputBox');
        //監聽
        this.displayBlock.addEventListener('click', function(){
            //do something...
        });
        this.inputBox.addEventListener('keyup', function(){
            //do something...
        });
    }
};

如果想要把 function 獨立出來可以這樣變化︰

var Dispatcher = {
    init: function(){
        this.displayBlock = document.getElementById('DisplayBlock');
        this.inputBox = document.getElementById('InputBox');
        //監聽
        this.displayBlock.addEventListener('click', this.clcik1);
        this.inputBox.addEventListener('keyup', this.keyup1);
    },
    click1: function(){
        //do something...
    },
    keyup1: function(){
        //do something...
    },
};

如果再 click1 跟 keyup1 中想使用 dispatcher 的變數或方法︰

var Dispatcher = {
    init: function(){
        this.displayBlock = document.getElementById('DisplayBlock');
        this.inputBox = document.getElementById('InputBox');
        //監聽
        this.displayBlock.addEventListener('click', this.clcik1.bind(this));
        this.inputBox.addEventListener('keyup', this.keyup1.bind(this));
    },
    click1: function(){
        //do something...
    },
    keyup1: function(){
        //do something...
    },
};

到目前為止大家應該都能接受,

如果要切的乾淨就再把 function 切出去。

但是重新檢視一下我們的 Flux 視圖,

我們應該要把元素跟事件分離乾淨,

所以需要把事件註冊到統一的分配器,

再針對不同的事件做處理,

這時候我們就可以用到 JavaScript 內建的 handEvent 了。

var Dispatcher = {
    init: function(){
        this.displayBlock = document.getElementById('DisplayBlock');
        this.inputBox = document.getElementById('InputBox');
        //監聽
        this.displayBlock.addEventListener('click', this);
        this.inputBox.addEventListener('keyup', this);
    },
    handleEvent: function(e){
        switch(e.type){
            case 'click':
                switch(e.target){
                    case this.displayBlock:
                        this.click1();
                        break;
                }
                break;
            case 'keyup':
                switch(e.target){
                    case this.inputBox:
                        this.keyup1();
                        break;
                }
                break;
        }
    },
    click1: function(){
        //do something...
    },
    keyup1: function(){
        //do something...
    },
};

這樣就完成了呼叫的統一了,

而且也不用寫 bind(this) 就能呼叫到 dispatcher 內的變數與方法。

如果需要反註冊的話可以使用︰

this.displayBlock.removeEventListener('click', this);

為了將資料處理分離,我們需要一個 Store (資料處理器) 來負責處理,

Store.js

function Store() {
    this._data = 0;
}

Store.prototype = {
    getSomething: function (){
        return this._data;
    },
    setSomething: function (val) {
        this._data = val; 
    }
};

Dispatcher.js

var Dispatcher = {
    init: function(){
        this.displayBlock = document.getElementById('DisplayBlock');
        this.inputBox = document.getElementById('InputBox');
        //click 事件在此需求無需關注
        //this.displayBlock.addEventListener('click', this);
        this.inputBox.addEventListener('keyup', this);
        this.store = new Store();
    }
    //略
};

資料分配器 Store 應該避免掉從外部直接改變

可以在呼叫端使用 window.DispatchEvent 發送自訂事件 CustomerEvent ,

並在 Store 內接收自訂事件去做到。

所以在觸發行為時也不會去撰寫 Store.doSomething()

Store.js

function Store() {
    this._data = 0;
}

Store.prototype = {
    init: function(){
        window.addEventListener('store_set',this);
    },
    handleEvent: function (val) {
        switch(e.type){
            case 'store_set':
                this.setSomething(e.detail.val);
                break;
        }
    },
    getSomething: function (){
        return this._data;
    },
    setSomething: function (val) {
        this._data = val; 
    }
};

Dispatcher.js

var Dispatcher = {
    init: function(){
        this.displayBlock = document.getElementById('DisplayBlock');
        this.inputBox = document.getElementById('InputBox');
        this.inputBox.addEventListener('keyup', this);
        this.store = new Store();
        this.store.init();
    },
    handleEvent: function(e){
        switch(e.type){
            /*
            case 'click':
                switch(e.target){
                    case this.displayBlock:
                        this.click1();
                        break;
                }
                break;
            */
            case 'keyup':
                switch(e.target){
                    case this.inputBox:
                        //觸發自訂事件
                        window.dispatchEvent(new CustomEvent('store_set',
                            {'detail':{'val':this.inputBox.value}}
                        ));
                        break;
                }
                break;
        }
    }
    // click1() & keyup1() 這2個假事件可以拿掉無需關注
};

最後來做出 Render 來改變畫面,

透過 Store 發送通知 Render 來繪製。

Render.js

var Render = {
    init: function(element, Store){
        this.element = element;
        this.store = Store;
        window.addEventListener('render_view', this);
    },
    handleEvent: function(e){
        switch(e.type){
            case 'render_view':
                this.element.textContent = this.store.getSomething();
                break;
        }
    }
};

Store.js

function Store() {
    this._data = 0;
}

Store.prototype = {
    init: function(){
        window.addEventListener('store_set',this);
    },
    handleEvent: function(e){
        switch(e.type){
            case 'store_set':
                this.setSomething(e.detail.val);
                break;
        }
    },
    getSomething: function () {
        return this._data; 
    },
    setSomething: function (val) {
        this._data = val;
        window.dispatchEvent(new CustomEvent('render_view'));
    }
};

Dispatcher.js

var Dispatcher = {
    init: function(){
        this.displayBlock = document.getElementById('DisplayBlock');
        this.inputBox = document.getElementById('InputBox');
        this.inputBox.addEventListener('keyup', this);
        this.store = new Store();
        this.store.init();
        Render.init(this.displayBlock, this.store);
    },
    handleEvent: function(e){
        switch(e.type){
            case 'keyup':
                switch(e.target){
                    case this.inputBox:
                        //觸發自訂事件
                        window.dispatchEvent(new CustomEvent('store_set',
                            {'detail':{'val':this.inputBox.value}}
                        ));
                        break;
                }
                break;
        }
    }
};

Dispatcher.init();

所以上述程式碼實現︰

由 Dispatch 註冊 Render,並傳入 Store 與所需的 View 元件,

資料更新完全由 Store 控制,

Render 去渲染 View 元件。

透過大量的 handleEvent 降低邏輯,資料,與介面元件之間的關聯程度。

之後再去研究 Flux 與 ReFlux 應該也比較好上手。

以上程式範例參照 Gasolin 發表的一篇漸進改善程式碼的組織方式的文章加以修改。

友善連結︰