<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[hSATAC]]></title>
  <link href="http://blog.hsatac.net/atom.xml" rel="self"/>
  <link href="http://blog.hsatac.net/"/>
  <updated>2014-10-22T11:15:51+08:00</updated>
  <id>http://blog.hsatac.net/</id>
  <author>
    <name><![CDATA[Ash Wu]]></name>
    <email><![CDATA[hsatac@gmail.com]]></email>
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[Docker 1.3 釋出]]></title>
    <link href="http://blog.hsatac.net/2014/10/docker-1-dot-3-release/"/>
    <updated>2014-10-22T11:09:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/10/docker-1-dot-3-release</id>
    <content type="html"><![CDATA[<p><a href="https://blog.docker.com/2014/10/docker-1-3-signed-images-process-injection-security-options-mac-shared-directories/">Docker 1.3</a> 釋出。</p>

<p>最方便的一個地方是多了一個 <code>docker exec</code> 可以 inject 新的 process 到正在執行中的 docker container 中，只要下 <code>docker exec -i -t &lt;container id&gt; /bin/bash</code> 就可以進 shell 了， debug 方便許多，不需要再像之前一樣用各種 hack 例如 <code>lxc-attach</code> 或者 endpoint script 最後下 <code>/bin/bash</code> 之類的 workaround 來解了。</p>

<p>再來就是 boot2docker 在這版也解了 OSX 的 mount volume 的問題，可以正確 mount 到 Mac 本機，一樣可以拋棄之前的第三方 workaround 了。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Duplicate Production Requests for Testing]]></title>
    <link href="http://blog.hsatac.net/2014/10/duplicate-production-requests-for-testing/"/>
    <updated>2014-10-21T00:09:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/10/duplicate-production-requests-for-testing</id>
    <content type="html"><![CDATA[<p>當我們上新的架構或者調整資料庫參數等等的時候，總希望能夠用 production 環境來測試才比較準確，不過這麼重大的改變不能隨便拿正式環境和使用者當白老鼠；就算使用 production 資料庫的 dump, 也無法重複真正使用者的行為。</p>

<p>一般常用的方式是偷偷把 production 環境的流量複製一份，導到測試機器/環境去，觀察看看行為是否符合預期。</p>

<p>相較於以往看到較為複雜的 solution, 這邊介紹一個用 Go 開發的工具 &ndash; <a href="https://github.com/buger/gor">gor</a></p>

<!--more-->


<h2>簡介</h2>

<p>gor 已經發展了一年多了，進展的非常快。現在是一個簡單又非常好用的工具。可以監聽你指定的 port, 把流量轉到其他機器、或者 dump 成檔案，之後可再從這個檔案 replay、如果有統計分析的需求，也可以直接 redirect 進 elasticsearch 中再進行處理。</p>

<h2>Usage</h2>

<p>這邊列幾個基本常用的用途：</p>

<ul>
<li>把 request dump 到檔案</li>
</ul>


<p><code>sudo ./gor --input-raw :80 --output-file requests.gor</code></p>

<ul>
<li>從檔案 replay 流量 (限定 GET)</li>
</ul>


<p><code>sudo ./gor --input-file requests.gor --output-http http://10.2.7.202 --output-http-method GET</code></p>

<ul>
<li>直接複製流量丟到測試機</li>
</ul>


<p><code>sudo ./gor --input-raw :80 --output-http http://10.2.7.202 --output-http-method GET</code></p>

<p>注意：這個動作會比較吃 CPU, 官方文件是建議開兩個 process 一個錄流量，一個丟到 output.</p>

<p>除此之外還有很多功能，例如各種 regex, filter, 插入 header, http basic auth, 丟到多個機器&hellip;等等。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[使用 Docker 建置 QA 環境]]></title>
    <link href="http://blog.hsatac.net/2014/10/build-qa-environment-with-docker/"/>
    <updated>2014-10-18T16:58:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/10/build-qa-environment-with-docker</id>
    <content type="html"><![CDATA[<p>這篇是延續上一篇<a href="http://blog.hsatac.net/2014/06/working-with-docker-and-jenkins/">《整合 Jenkins 和 Docker》</a>，使用 Docker 來建置 QA 環境的想法。</p>

<p>在上一篇有提到，原本的設計是當 Github 收到 Pull Request 時，就讓 Jenkins 來跑測試，如果測試通過，就直接用原本建置出來的 docker image 建立起一個 QA 環境，這樣就可以直接透過連到 <code>&lt;ticket-number&gt;.qa.domain.internal</code> 的方式來驗收，確定無誤後再按下 merge 按鈕，然後 trigger 自動關閉此 QA 環境。</p>

<p>不過後來因為這個情境不適合我們的 workflow, 所以最後沒有這樣實做。而是寫成 rake task 來運用。</p>

<!-- more -->


<p>具體的方式是，把整個 docker 中的 app 目錄 mount 出來，然後 nginx 透過 subdomain  來決定 app root 和 proxy_pass backend. 由於可能會有許多 qa 環境同時存在，要弄轉 port 還挺麻煩的，所以這邊都使用 unix domain socket.</p>

<h2>Nginx</h2>

<p>先來看一下 nginx 這邊的設定，其實很單純，簡化過後大概就長這樣：</p>

<div><script src='https://gist.github.com/fba5950eec5e90385015.js?file=nginx.conf'></script>
<noscript><pre><code>server {
        listen       80;
        server_name  &quot;~^(?&lt;sub&gt;.+)\.qa\.domain\.internal$&quot; &quot;~^(.+)\.(?&lt;sub&gt;.+)\.qa-cc\.domain\.internal$&quot;;
        root /var/www/qa/$sub/appname/public;
        charset utf-8;
        try_files $uri/index.html $uri.html $uri @app;
        location @app {
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                proxy_pass http://unix:/var/www/qa/$sub/appname/puma.sock;
                error_page 502 /qa_404.html; # Does not exist.
        }
        location ~* ^\/(images|javascripts)\/.*\.(ico|css|js|gif|jpe?g|png)(\?[0-9]+)?$  {
                expires max;
                break;
        }
}</code></pre></noscript></div>


<p>這樣透過 <code>newfeature.qa.domain.internal</code> 就可以連到從 docker 裡面 mount 出來到 <code>/var/www/newfeature</code> 這個目錄的靜態檔案和 unix domain socket.</p>

<h2>Makefile</h2>

<p>Makefile 是用來 build docker image, 這部分做的事情大致上是切到一個 workspace 目錄，此目錄是我 app 的 git repo, 然後切到我要建置的 QA branch 後開始 build docker image.</p>

<div><script src='https://gist.github.com/fba5950eec5e90385015.js?file=Makefile'></script>
<noscript><pre><code>all: base gem test qa
base:
    docker build --rm --no-cache -t myapp/base base
gem: fetch_workspace
    rm -f gem/*
    cp workspace/myapp/Gemfile* gem/
test:
    cp -R gem test/
    docker build --rm --no-cache -t myapp/test test
    rm -rf test/gem
qa:
    cp -R gem qa/
    docker build --rm --no-cache -t myapp/qa qa
    rm -rf qa/gem
clean_workspace:
    cd workspace/myapp;\
    git clean -f;\
    git reset --hard HEAD
fetch_workspace: clean_workspace
    cd workspace/myapp;\
    git fetch;\
    git checkout master;\
    git pull
qa_branch:
ifdef QA_BRANCH
    $(info ******* Start building branch ${QA_BRANCH} for name ${QA_DOMAIN} ********)
    # Check out the branch
    cd workspace/myapp;\
    git clean -f;\
    git reset --hard HEAD;\
    git fetch;\
    git checkout ${QA_BRANCH};\
    git pull
    # Copy required files
    cp -r qa/config/* workspace/myapp/config/
    # use sed to replace some config in config/*.yml here.
    cp qa/*.sh workspace/myapp/
    echo &quot;FROM myapp/qa&quot; &gt; workspace/myapp/Dockerfile
    # Store branch and domain name into files
    echo &quot;${QA_DOMAIN}&quot; &gt; workspace/myapp/qa_domain
    echo &quot;${QA_BRANCH}&quot; &gt; workspace/myapp/qa_branch
    # Build
    docker build --rm -t myapp_qa/${QA_DOMAIN} workspace/myapp/
    # Clean
    cd workspace/myapp;\
    git clean -f;\
    git reset --hard HEAD;\
    git checkout master
else
    $(info ******* Please specify QA_BRANCH ********)
endif
.PHONY: all base test qa qa_branch gem</code></pre></noscript></div>


<p>gem 的部分再上一篇有提過，預先安裝 gem 是為了加速整個建置的過程，這邊會丟到 crontab 每天晚上自動執行更新。</p>

<p>執行 <code>make qa_branch QA_BRANCH=master QA_DOMAIN=master</code> 即可建置出特定 branch 的 docker image.</p>

<p>可以看到我直接把 branch name 和 domain name 寫到 app 資料夾中，這樣就不用資料庫來紀錄什麼 domain name 對應什麼 branch 了。</p>

<p>這邊需要注意由於 domain name 用在三個地方：</p>

<ol>
<li>domain</li>
<li>路徑</li>
<li>docker image name</li>
</ol>


<p>比較麻煩的是 domain name 只能有 <code>-</code> 不能有 <code>_</code>, 而 docker image name 則剛好相反，不能有 <code>-</code> 只能有 <code>_</code> 所以在輸入名稱的時候要特別注意。由於我外面是包 messaging bot 來下指令，那邊有做檢查，所以這邊就沒有另外再做檢查。</p>

<h2>Rakefile</h2>

<p>Rakefile 是拿來啟動 / 關閉 QA 環境用的。</p>

<div><script src='https://gist.github.com/fba5950eec5e90385015.js?file=Rakefile'></script>
<noscript><pre><code>require &#39;yaml&#39;
require &#39;docker&#39;
require &#39;pty&#39;
require &#39;slack-notifier&#39;

Docker.url = &quot;tcp://127.0.0.1:4243&quot;

QA_DIR = &quot;/var/www/qa&quot;

QA_URL = &quot;qa.domain.internal&quot;

QA_IMAGE_REGEX = /myapp_qa\/(\w+):latest/

ROOT_PATH = File.dirname(__FILE__)

desc &quot;List running containers&quot;
task :running do
    puts `docker ps`
end

desc &quot;Clean stopped containers&quot;
task :remove_stopped_containers do
    puts &quot;----- Cleaning stopped containers -----&quot;
    puts `#{ROOT_PATH}/bin/remove_stopped_containers.sh`
end

namespace :qa do
    desc &quot;List all running QA containers&quot;
    task :list do
        running_qa_domains = qa_running_containers.map {|c| c.info[&quot;Image&quot;].match(QA_IMAGE_REGEX)[1]}
        puts &quot;----- Current QA containers -----&quot;
        running_qa_domains.each do |domain|
            puts &quot;http://#{domain}.#{QA_URL} (#{`cat #{QA_DIR}/#{domain}/myapp/qa_branch`.strip})&quot;
        end
    end

    desc &quot;Build and start a QA container&quot;
    task :start, [:branch_name, :domain_name] do |t, args|
        branch_name = args[:branch_name]
        domain_name = args[:domain_name]
        hipchat_notify(&quot;Start to build QA container *myapp_qa/#{domain_name}* for :branch: *#{branch_name}*&quot;)
        puts &quot;----- Building QA container myapp_qa/#{domain_name} for branch #{branch_name}&quot;
        pipe_exec &quot;cd #{ROOT_PATH} &amp;&amp; make qa_branch QA_BRANCH=#{branch_name} QA_DOMAIN=#{domain_name}&quot;
        puts &quot;----- Starting QA container myapp_qa/#{domain_name} for branch #{branch_name}&quot;
        puts `sudo rm -rf #{QA_DIR}/#{domain_name}` # Remove workspace if it existed
        pipe_exec &quot;docker run -d -i -t -v #{QA_DIR}/#{domain_name}:/opt/run:rw --privileged myapp_qa/#{domain_name}&quot;
        puts &quot;----- Running and initializing fake data and indexes... -----&quot;
        
        status_file = &quot;#{QA_DIR}/#{domain_name}/myapp/status&quot;
        status = &quot;init&quot;
        loop do # Loop until ready.
            if File.exists?(status_file)
                current_status = File.read(status_file).strip
                break if current_status == &quot;ready&quot;
                if status != current_status
                    puts &quot;#### Status changed from #{status} to #{current_status} ####&quot;
                    status = current_status
                end
            end
            sleep 5
        end 
        puts &quot;=====&gt; http://#{domain_name}.#{QA_URL} &lt;=====&quot;
        hipchat_notify(&quot;QA environment for :branch: *#{branch_name}* is ready at http://#{domain_name}.#{QA_URL}&quot;)
    end

    desc &quot;Stop QA container&quot;
    task :stop, [:domain_name] do |t, args|
    domain_name = args[:domain_name]
        container = nil
        Docker::Container.all.each do |c| 
            container = c if c.info[&quot;Image&quot;] == &quot;myapp_qa/#{domain_name}:latest&quot;
        end
        if container
            puts &quot;----- Stopping QA container #{domain_name} -----&quot;
            puts container.stop
            puts &quot;----- Deleting QA container #{domain_name} -----&quot;
            puts container.delete
            puts &quot;----- Deleting QA workspace #{domain_name} -----&quot;
            puts `sudo rm -rf #{QA_DIR}/#{domain_name}`
            puts &quot;----- Deleting QA image myapp_qa/#{domain_name} -----&quot;
            puts `docker rmi myapp_qa/#{domain_name}`
            puts &quot;----- Done. -----&quot;
        else
            puts &quot;There&#39;s no running QA container of domain #{domain_name}&quot;
        end
    end

    desc &quot;Clean stopped container and remove unused workspaces.&quot;
    task :clean do
        Rake::Task[&quot;remove_stopped_containers&quot;].invoke

        running_qa_domains = qa_running_containers.map {|c| c.info[&quot;Image&quot;].match(QA_IMAGE_REGEX)[1]}
        puts &quot;----- Deleting unused QA images -----&quot;
        qa_images.map do |image|
            unless running_qa_domains.include?(image.info[&quot;RepoTags&quot;].first.match(QA_IMAGE_REGEX)[1])
                puts &quot;Image #{image.info[&quot;RepoTags&quot;].first} removed.&quot;
                image.remove 
            end
        end
        puts &quot;----- Cleaning stopped QA workspaces -----&quot;
        Dir.glob(&quot;#{QA_DIR}/*&quot;).each do |f|
            unless running_qa_domains.include?(File.basename(f))
                puts `sudo rm -rf #{f}` 
            end
        end
            
    end
end

######## Helpers ########
#
def pipe_exec(command)
  begin
    PTY.spawn(command) do |stdin, stdout, pid|
      begin
        stdin.each { |line| print line }
      rescue Errno::EIO
      end
    end
  rescue PTY::ChildExited
    puts &quot;The child process exited!&quot;
  end
end

def qa_running_containers
    Docker::Container.all.select {|c| c.info[&quot;Image&quot;] =~ QA_IMAGE_REGEX }
end

def qa_images
    Docker::Image.all.select { |c| c.info[&quot;RepoTags&quot;].first =~ QA_IMAGE_REGEX }
end

def hipchat_notify(msg)
    config = YAML.load_file(&#39;config/config.yml&#39;)
    return if config[&#39;slack_token&#39;] == nil || config[&#39;slack_token&#39;] == &quot;&quot;
    notifier = Slack::Notifier.new &quot;kkbox&quot;, config[&#39;slack_token&#39;], channel: &#39;#myapp&#39;, username: &#39;Docker&#39;
    notifier.ping msg, icon_emoji: &quot;:myapp:&quot;
end
</code></pre></noscript></div>


<p>這邊值得一提的是，由於 container build 好，到整個 service run 起來其實還有一段時間差，是用來啟動 service, initial db 等等動作&hellip;所以在 app 目錄下會寫一個 <code>status</code> file 來判斷現在 app initial 到什麼階段，等他變成 <code>ready</code> 後才判斷為建置完成。</p>

<h2>start_rails.sh</h2>

<p>這是 QA docker image run 起來後會執行的 script, 比較重要的有兩個部分，一個是上面提的把目前階段寫入 <code>status</code> 檔案，讓外面知道現在進行到什麼步驟；另一個則是使用 <a href="http://www.dest-unreach.org/socat/">socat</a> 把 port 轉為 unix domain socket 再 mount 出去，就可以讓外面的 nginx 跟 docker 內部的 web services 溝通，而不需要處理 docker 的 port 了。</p>

<p>把 8080 port 轉到 <code>/opt/run/myapp/go.sock</code>：</p>

<p><code>socat UNIX-LISTEN:/opt/run/myapp/go.sock,reuseaddr,fork TCP:localhost:8080</code></p>

<div><script src='https://gist.github.com/fba5950eec5e90385015.js?file=start_rails.sh'></script>
<noscript><pre><code>echo &quot;dev:fake&quot; &gt; /opt/run/myapp/status
cd /opt/run/myapp &amp;&amp; rvm all do bundle exec rake dev:build
cd /opt/run/myapp &amp;&amp; rvm all do bundle exec rake dev:load
cd /opt/run/myapp &amp;&amp; rvm all do unicorn_rails -c config/unicorn.rb -D

echo &quot;building golang&quot; &gt; /opt/run/myapp/status
if [ -d &quot;/opt/run/myapp/go&quot; ]; then
    #cd /opt/run/myapp/go &amp;&amp; gom install # This takes some time.
    cd /opt/run/myapp/go &amp;&amp; ./make build
    cd /opt/run/myapp/go &amp;&amp; ./kktix_go start -d
    socat UNIX-LISTEN:/opt/run/myapp/go.sock,reuseaddr,fork TCP:localhost:8080 &amp;
fi

echo &quot;sidekiq&quot; &gt; /opt/run/myapp/status
cd /opt/run/myapp &amp;&amp; rvm all do bundle exec sidekiq -c 5 -d -L log/sidekiq.log &amp;
cd /opt/run/myapp &amp;&amp; rvm all do ruby -rsidekiq/api -e &#39;loop do;count = Sidekiq::Stats.new.enqueued;puts &quot;Remaining jobs: #{count}&quot;;if count == 0;`echo &quot;ready&quot; &gt; /opt/run/kktix/status`;break;end;sleep 5;end&#39;

if [ -e &quot;/opt/run/myapp/go.sock&quot; ]; then
    chmod 777 /opt/run/myapp/go.sock
fi

cd /opt/run/myapp &amp;&amp; /bin/bash
</code></pre></noscript></div>


<h2>Conclusion</h2>

<p>這篇的 scripts 有點繁雜，不過概念其實很簡單，只是細節上有不少需要注意的地方。</p>

<p>使用 docker 來建置 QA branch 可以方便快速的建出乾淨的環境，相較以往要處理非常多資料庫 / 轉 port / services isolation 等等問題，實在是輕鬆太多了。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[整合 Jenkins 和 Docker]]></title>
    <link href="http://blog.hsatac.net/2014/06/working-with-docker-and-jenkins/"/>
    <updated>2014-06-29T22:51:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/06/working-with-docker-and-jenkins</id>
    <content type="html"><![CDATA[<p>這篇將會記述一些我自己整合 Jenkins CI 和 Docker 的思路、想法、要點以及備忘。不會有 step by step 的教學，若有此類需求請參考最後附錄。</p>

<h2>Why Docker?</h2>

<p>Jenkins 跑的好好的，為什麼要摻 Docker 呢？原本我們 Rails Rspec 跑的其實也不錯，但受限於 database 以及 elasticsearch, redis 等 services，無法同時跑多個 worker, 再加上未來若有平行化測試以及多個專案 / 不同 db 版本等等的需求，引入 docker 可以完美解決這些問題。</p>

<h2>Concept</h2>

<p>使用 Docker 的好處就是原本的 shell script 幾乎都不用改即可繼續使用，引入的門檻降到極低。</p>

<p>基本概念是建立一個可以跑 Rails app 起來的環境，然後把整個 CI 的 workspace 丟進去跑測試，其他的步驟都一模一樣。</p>

<p>在建立環境這邊基本上有兩個選擇，一種是全部包成一個 image, 就用這個 container 來跑測試。另一種是每個需要的 service 都是一個各自的 container, 彼此之間透過 <a href="https://docs.docker.com/userguide/dockerlinks/">Docker Container Linking</a> 來通訊，例如 postgresql 自己一個、elasticsearch 自己一個、rails 自己一個這樣。</p>

<p>不過由於跑測試都是用過即丟，這次我直接採用最簡單的包一大包的策略來進行，減少複雜度。</p>

<!--more-->


<p>我會選擇自己 Build docker 來跑測試主要是還想運用在其他地方，包括 trigger 不同的瀏覽器跑 feature tests 而不需重新 Build docker image 等等，如果沒有特殊需求的話也可以參考看看 Jenkins 的 <a href="https://wiki.jenkins-ci.org/display/JENKINS/Docker+Plugin">Docker Plugin</a> 基本概念是直接把 Jenkins slave 用 docker 跑起來。可以評估看看自己是否合用。</p>

<h3>Base Image</h3>

<p>我的設計是先建立一個 base image 例如給他 tag 叫 <code>project/base</code> 裡面先預裝好了所有環境包括 pg, elasticsearch, redis, rvm, ruby 等等。</p>

<p>舉例來說可能長這樣：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
<span class='line-number'>58</span>
<span class='line-number'>59</span>
<span class='line-number'>60</span>
<span class='line-number'>61</span>
<span class='line-number'>62</span>
<span class='line-number'>63</span>
<span class='line-number'>64</span>
<span class='line-number'>65</span>
<span class='line-number'>66</span>
<span class='line-number'>67</span>
<span class='line-number'>68</span>
<span class='line-number'>69</span>
<span class='line-number'>70</span>
<span class='line-number'>71</span>
<span class='line-number'>72</span>
<span class='line-number'>73</span>
<span class='line-number'>74</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>FROM ubuntu:12.04
</span><span class='line'>MAINTAINER hSATAC
</span><span class='line'>
</span><span class='line'># We use bash
</span><span class='line'>RUN rm /bin/sh && ln -s /bin/bash /bin/sh
</span><span class='line'>
</span><span class='line'># We don't like apt-get warnings.
</span><span class='line'>ENV DEBIAN_FRONTEND noninteractive
</span><span class='line'>
</span><span class='line'># Add the PostgreSQL PGP key to verify their Debian packages.
</span><span class='line'># It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc
</span><span class='line'>RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
</span><span class='line'>
</span><span class='line'># Add PostgreSQL's repository. It contains the most recent stable release
</span><span class='line'>#     of PostgreSQL, ``9.3``.
</span><span class='line'>RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" &gt; /etc/apt/sources.list.d/pgdg.list
</span><span class='line'>
</span><span class='line'># Update the Ubuntu and PostgreSQL repository indexes
</span><span class='line'>RUN apt-get update
</span><span class='line'>
</span><span class='line'># === Locale ===
</span><span class='line'>RUN locale-gen  en_US.UTF-8
</span><span class='line'>ENV LANG en_US.UTF-8
</span><span class='line'>ENV LANGUAGE en_US.UTF-8
</span><span class='line'>ENV LC_ALL en_US.UTF-8
</span><span class='line'>
</span><span class='line'># === Requirements ===
</span><span class='line'>RUN apt-get -y -q install nodejs libpq-dev wget git curl imagemagick vim postfix
</span><span class='line'>
</span><span class='line'>RUN cd /tmp &&\
</span><span class='line'>  wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.0/wkhtmltox-linux-amd64_0.12.0-03c001d.tar.xz &&\
</span><span class='line'>  tar Jxvf wkhtmltox-linux-amd64_0.12.0-03c001d.tar.xz &&\
</span><span class='line'>  cd wkhtmltox &&\
</span><span class='line'>  install bin/wkhtmltoimage /usr/bin/wkhtmltoimage
</span><span class='line'>
</span><span class='line'># === Redis ===
</span><span class='line'>RUN apt-get -y -q install redis-server
</span><span class='line'>
</span><span class='line'># === Elasticsearch ===
</span><span class='line'>RUN apt-get install openjdk-7-jre-headless -y -q
</span><span class='line'>RUN wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.1.0.deb
</span><span class='line'>RUN dpkg -i elasticsearch-1.1.0.deb
</span><span class='line'>
</span><span class='line'># === RVM ===
</span><span class='line'>RUN curl -L https://get.rvm.io | bash -s stable
</span><span class='line'>ENV PATH /usr/local/rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
</span><span class='line'>RUN /bin/bash -l -c rvm requirements
</span><span class='line'>RUN source /usr/local/rvm/scripts/rvm && rvm install ruby-2.1.2
</span><span class='line'>RUN rvm all do gem install bundler
</span><span class='line'>
</span><span class='line'># === Postgresql ===
</span><span class='line'>RUN apt-get -y -q install python-software-properties software-properties-common
</span><span class='line'>RUN apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
</span><span class='line'>
</span><span class='line'># Update template1 to enable UTF-8 and hstore
</span><span class='line'>USER postgres
</span><span class='line'>RUN    /etc/init.d/postgresql start &&\
</span><span class='line'>    psql -c "update pg_database set datistemplate=false where datname='template1';" &&\
</span><span class='line'>    psql -c 'drop database Template1;' &&\
</span><span class='line'>    psql -c "create database template1 with owner=postgres encoding='UTF-8' lc_collate='en_US.utf8' lc_ctype='en_US.utf8' template template0;" &&\
</span><span class='line'>    psql -c 'CREATE EXTENSION hstore;' -d template1
</span><span class='line'>
</span><span class='line'># Create a PostgreSQL role and db
</span><span class='line'>RUN    /etc/init.d/postgresql start &&\
</span><span class='line'>    psql --command "CREATE ROLE jenkins LOGIN PASSWORD 'jenkins' SUPERUSER INHERIT CREATEDB NOCREATEROLE NOREPLICATION;" &&\
</span><span class='line'>    createdb -O jenkins jenkins_test &&\
</span><span class='line'>    createdb -O jenkins jenkins_production
</span><span class='line'>
</span><span class='line'>
</span><span class='line'># Adjust PostgreSQL configuration
</span><span class='line'>RUN echo "local all  all  md5" &gt; /etc/postgresql/9.3/main/pg_hba.conf
</span><span class='line'>
</span><span class='line'># And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf``
</span><span class='line'>RUN echo "listen_addresses='*'" &gt;&gt; /etc/postgresql/9.3/main/postgresql.conf</span></code></pre></td></tr></table></div></figure>


<h3>Test Image</h3>

<p>接著用 <code>project/base</code> build 一個 <code>project/test</code>，這個 image 會安裝一些「只有測試會用到」的套件，順便把 Gemfile 複製進去安裝一下 Gem, 這樣到時在跑測試的時候就可以省略 <code>bundle install</code> 的時間了。因為我的 base image 還有打算拿來做其他用途，所以這邊是這樣設計。</p>

<p>每天凌晨三點左右用 crontab 重新 Build 一次這個 <code>project/test</code> 的 image 以更新 gems. 當然這邊牽涉到一些如何同步你專案中的 Gemfile 不過這都是簡單的 script 可以解決的問題，這邊不贅述。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>FROM project/base
</span><span class='line'>
</span><span class='line'># Switch back to root
</span><span class='line'>USER root
</span><span class='line'>
</span><span class='line'># Set ENV
</span><span class='line'>ENV RAILS_ENV test
</span><span class='line'>
</span><span class='line'># Preinstall gem
</span><span class='line'>ADD gem /opt/project_gem # 這個 gem 目錄裡面有 Gemfile 和 Gemfile.lock
</span><span class='line'>WORKDIR /opt/project_gem
</span><span class='line'>RUN rvm all do bundle install
</span><span class='line'>
</span><span class='line'># Install Firefox & Xvfb
</span><span class='line'>RUN apt-get -y install firefox xvfb # 跑 selenium test 用的
</span><span class='line'>
</span><span class='line'># Onbuild witch back to root
</span><span class='line'>ONBUILD USER root
</span><span class='line'>
</span><span class='line'># Mound Rails directory
</span><span class='line'>ONBUILD ADD . /opt/project
</span><span class='line'>ONBUILD WORKDIR /opt/project
</span><span class='line'>
</span><span class='line'># Bundle install
</span><span class='line'>ONBUILD RUN rvm all do bundle install
</span><span class='line'>
</span><span class='line'># Go
</span><span class='line'>ONBUILD ADD start_services.sh /opt/project/start_services.sh
</span><span class='line'>ONBUILD RUN chmod +x /opt/project/start_services.sh
</span><span class='line'>ONBUILD ADD run_tests.sh /opt/project/run_tests.sh
</span><span class='line'>ONBUILD RUN chmod +x /opt/project/run_tests.sh
</span><span class='line'>ONBUILD CMD /opt/project/start_services.sh && /opt/project/run_tests.sh</span></code></pre></td></tr></table></div></figure>


<p>注意這邊最後一段用到了上一篇文章 <a href="http://blog.hsatac.net/2014/06/docker-basics/">Docker Basics</a> 中所講到的 ONBUILD, 使用這個功能我們就可以很輕鬆的 build 出真正用來測試的 Image.</p>

<p>這幾行實際做的動作是複製兩個 scripts 分別名叫 <code>start_services.sh</code> 和 <code>run_tests.sh</code> 並且 <code>CMD</code> 預設執行這兩個檔案。</p>

<p>但是這兩個檔案現在實際不存在，我會透過 jenkins 的 build scripts 來寫這兩個檔案，其實也就是原本在 build scripts 的內容移到這兩個檔案中了。之所以不把這兩個檔案存在某處再複製過來，就是想保留原本在 Jenkins configure 可以調整 Build Script 的機制，多留一點彈性。</p>

<p>為什麼要這麼麻煩使用 ONBUILD + CMD ，而不是直接 RUN 然後最後直接看 Image 有沒有建置成功就好？除了上述想多留一點彈性的原因外，還有用 Build 這樣 Build 的過程勢必會跑這兩支 script, 而我有可能 Build 完以後不跑這兩支 script, 而是做一些其他的動作例如 <code>/bin/bash</code> 進去 debug 等等，當然可以透過改寫這兩支 script 的內容來使 Build 過程不跑測試，但增添了複雜度。使用這個機制我覺得是最有彈性的。</p>

<h3>Jenkins Build Script</h3>

<p>Jenkins build script 這邊改動的幅度不大，原本的流程大概是：</p>

<ol>
<li><p>改好相關 application.yml, database.yml 等等 local 設定檔並塞進去。</p></li>
<li><p>跑 db:reset 等等重置環境</p></li>
<li><p>跑測試</p></li>
</ol>


<p>基本步驟還是一樣，第一步可以完全不用變，後面就得修改一下，例如：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># Start services in docker
</span><span class='line'>echo "
</span><span class='line'>Xvfb :99 -screen 0 1366x768x24 -ac 2&gt;/dev/null &gt;/dev/null &
</span><span class='line'>/etc/init.d/postgresql start &&\
</span><span class='line'>/etc/init.d/redis-server start &&\
</span><span class='line'>/etc/init.d/elasticsearch start
</span><span class='line'>" &gt; start_services.sh
</span><span class='line'>
</span><span class='line'># Run tests
</span><span class='line'>echo "
</span><span class='line'>rvm all do bundle exec rake db:migrate &&\
</span><span class='line'>DISPLAY=:99 rvm all do bundle exec rspec spec --format=documentation
</span><span class='line'>" &gt; run_tests.sh
</span><span class='line'>
</span><span class='line'>echo "FROM project/test" &gt; Dockerfile
</span><span class='line'>docker build --rm -t project/$BUILD_NUMBER .
</span><span class='line'>docker run --rm project/$BUILD_NUMBER</span></code></pre></td></tr></table></div></figure>


<p>一開始用 echo 寫入兩個檔案，內容大致就是開啟 service 並且開始跑測試，值得注意的是我們在 Jenkins workspace 裡寫了一個新的 Dockerfile, 裡面只有一行內容 <code>FROM project/test</code> 配合之前的 <code>ONBUILD</code> 就可以建置出這個 image. 之所以不直接用 <code>&lt;</code> 的方式把內容丟到 <code>docker build</code> 指令，是因為 <code>ADD</code> 需要 context, 也就是 Jenkins workspace, 所以必須要寫實體的檔案出來。</p>

<p>Image tag 直接取用 Jenkins 的環境變數 <code>$BUILD_NUMBER</code> 因此像第 300 個 build 他的 image 就會叫 <code>project/300</code> 清楚明瞭。</p>

<p>Build 和 Run 都使用 <code>--rm</code> 來確保跑完以後就刪除，節省系統空間。當然如果有保留的需求，例如這個跑完以後自動 trigger 一個專門測 IE 的 selenium test target 的話這邊是可以不用刪除的，看個人需求。</p>

<p>Build 完以後也可以同時跑好幾個 containers，利用一個 image 可以跑很多 containers 的特性，例如把 spec 目錄分成幾區，同時開始跑測試，這樣平行處理可以節省時間。</p>

<p>這邊有一個問題就是 <code>docker run</code> 理論上要回傳 command 的 exit code 不過這部分常常出問題，<a href="https://github.com/dotcloud/docker/issues/6259">時好時壞</a> 所以這邊我決定自己來處理。</p>

<p>想法很簡單，直接把 <code>docker run</code> 的 output 拿來檢查，有偵測到爆炸的話就寫一個檔案，最後來檢查檔案，如果沒過就手動爆炸。等這個 bug 修復穩定之後，就可以不要使用這個 workaround 了。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>docker run --rm project/$BUILD_NUMBER | perl -pe '/Failed examples:/ && `echo "fail" &gt; docker-tests-failed`'
</span><span class='line'>docker rmi project/$BUILD_NUMBER
</span><span class='line'>
</span><span class='line'>if [ ! -f docker-tests-failed ]; then
</span><span class='line'>  echo -e "No docker-tests-failed file. Apparently tests passed."
</span><span class='line'>else
</span><span class='line'>  echo -e "docker-tests-failed file found, so build failed."
</span><span class='line'>  rm docker-tests-failed
</span><span class='line'>  exit 1
</span><span class='line'>fi</span></code></pre></td></tr></table></div></figure>


<p>如果你沒有遇到這個問題，或者你是使用 <a href="https://github.com/sj26/rspec_junit_formatter">rspec_junit_formatter</a> 之類的套件產生 JUnit 檔案的話並加掛 Post-build action 的話，這個動作會讀 JUnit 檔案的內容來改變 Build result 因此也不需要這個 workaround.</p>

<h2>其他整合</h2>

<ul>
<li><p>Jenkins 有一個 plugin <a href="https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin">Github Pull Request Builder</a> 可以讓 Jenkins 像 travis-ci 那類 service 在 Github 有人發 PR 時自動抓回來 Build。</p></li>
<li><p>Hipchat plugin 可以整合到公司通訊軟體。</p></li>
<li><p><a href="https://github.com/sj26/rspec_junit_formatter">rspec_junit_formatter</a> 可以把 rspec 的結果產生成 JUnit 的 xml 給 Jenkins 讀取。</p></li>
<li><p>Test Coverage 的部分我們則是使用 <a href="https://github.com/colszowka/simplecov">SimpleCov</a> 可以搭配 <a href="https://github.com/fguillen/simplecov-rcov">SimpleCov Rcov Formatter</a> 產生 Jenkins 可讀的報表。</p></li>
</ul>


<p>要使用以上這兩個套件，必須在測試跑完以後使用 <code>docker cp &lt;file&gt; .</code> 指令把報表複製回 workspace 讓 Jenkins 讀取。</p>

<h2>參考連結</h2>

<p>這邊列出一些不錯的連結：</p>

<ul>
<li><a href="http://www.tech-d.net/2014/02/06/docker-quicktip-3-onbuild/">Docker quicktip #3 – ONBUILD</a></li>
<li><a href="http://www.powpark.com/blog/programming/2014/01/29/integrating-docker-with-jenkins-for-ruby-on-rails-app">Integrating Docker with Jenkins for continuous deployment of a Ruby on Rails application</a></li>
<li><a href="http://www.activestate.com/blog/2014/01/using-docker-run-ruby-rspec-ci-jenkins">Using Docker To Run Ruby Rspec CI In Jenkins</a></li>
<li><a href="http://blog.gemnasium.com/post/66356385701/your-dockerfile-for-rails">Your Dockerfile for Rails</a></li>
<li><a href="http://ngauthier.com/2013/10/using-docker-to-parallelize-rails-tests.html">Using Docker to Parallelize Rails Tests</a></li>
<li><a href="https://zapier.com/engineering/continuous-integration-jenkins-docker-github/">How to Set Up TravisCI-like Continuous Integration with Docker and Jenkins</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Docker Basics]]></title>
    <link href="http://blog.hsatac.net/2014/06/docker-basics/"/>
    <updated>2014-06-29T10:52:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/06/docker-basics</id>
    <content type="html"><![CDATA[<p>隨著 AWS Elastic Beanstalk <a href="http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_docker.html">支援 Docker</a>, Google Computer Engine 也宣布<a href="https://developers.google.com/compute/docs/containers/container_vms">支援 Docker</a>，以及 Google 最近發表的一些 container 工具例如 <a href="https://github.com/google/cadvisor">cAdvisor</a> 這套分析 container 資源和效能的軟體，也同時支援 Google 自己的 <a href="https://github.com/google/lmctfy">lmctfy</a> 和 Docker 來看，Docker 真的是越來越普及了。這個月 Docker 更<a href="http://blog.docker.com/2014/06/its-here-docker-1-0/">釋出了 1.0 版</a> 標誌著 Docker 已經 production-ready 了。</p>

<p>關於 Docker 的概念這邊不多加著墨，可以直接上 <a href="http://www.docker.com/">Docker 官網</a> 閱讀。第一次接觸的朋友可以花十分鐘玩一下 <a href="http://www.docker.com/tryit/">Try Docker</a>。這邊先簡單筆記一些名詞解說和常用的指令。</p>

<!--more-->


<h2>Containers and Images</h2>

<p>使用 Docker 時很常見到 container 和 image 這兩個名詞。</p>

<p>Image 是做好的磁碟檔案，可以透過四種方式取得，一種是 <code>docker pull</code> 拉下遠端檔案、另一種是 <code>docker build</code> 從 <code>Dockerfile</code> 開始建置、第三種是 <code>docker commit</code> 從某個 container commit 成 image, 第四種則是 <code>docker import</code> 匯入。</p>

<p>Container 則是 <code>docker run</code> 某個 image 時產生的，可以透過 <code>docker ps</code> 查看正在運行中的 containers, 一個 image 可以同時運行好幾個 containers. <code>docker ps -a</code> 可以查看所有包含停止運行的 containers. 當 container 停止運行後，磁碟中的檔案會存在該 container 中，但記憶體中的資料都會消失。在 Docker 0.12 版本中使用 cgroup freeze 機制，加入了 <code>docker pause</code> 和 <code>docker unpause</code> 指令，可以 suspend 和 resume 指定的 container.</p>

<p>會注意到每個 image 和 container 都有一個 hash id, 當你每 commit 一次上去時其實就是透過 aufs 疊了一層檔案上去。</p>

<h2>常用指令</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>docker ps
</span><span class='line'># 列出運行中的 container
</span><span class='line'>
</span><span class='line'>docker ps -a
</span><span class='line'># 列出所有 container
</span><span class='line'>
</span><span class='line'>docker images
</span><span class='line'># 列出 image
</span><span class='line'>
</span><span class='line'>docker rm &lt;container id&gt;
</span><span class='line'># 刪除 continer
</span><span class='line'>
</span><span class='line'>docker rmi &lt;image id&gt;
</span><span class='line'># 刪除 image
</span><span class='line'>
</span><span class='line'>docker build .
</span><span class='line'># Build ./Dockerfile 的 image
</span><span class='line'>
</span><span class='line'>docker build --rm . 
</span><span class='line'># Build 但是刪除 intermediate layer, 也就是不會保留中間步驟產生的 container
</span><span class='line'>
</span><span class='line'>docker build --no-cache . 
</span><span class='line'># 不使用 cache, 會從頭重新 build
</span><span class='line'>
</span><span class='line'>docker build -t kktix/base . 
</span><span class='line'># build 完以後給他一個 tag kktix/base
</span><span class='line'>
</span><span class='line'>docker build --rm --no-cache -t base . 
</span><span class='line'># 組合技
</span><span class='line'>
</span><span class='line'>docker run base 
</span><span class='line'># 跑 base image，會產生一個 container
</span><span class='line'>
</span><span class='line'>docker run base /bin/ping www.google.com 
</span><span class='line'># 跑 base image 並指定指令
</span><span class='line'>
</span><span class='line'>docker run -d base /bin/ping www.google.com 
</span><span class='line'># 用 daemon 模式跑
</span><span class='line'>
</span><span class='line'>docker run -i -t base /bin/bash 
</span><span class='line'># (i)nterative (t)ty 跑 bash 就等於是進去他的 shell
</span><span class='line'>
</span><span class='line'>docker run --rm base /bin/ping www.google.com 
</span><span class='line'># 跑完以後自動把這個 container 砍掉，注意 --rm 和 -d 無法同時下
</span><span class='line'>
</span><span class='line'>docker run -v /host/folder:/docker/folder base
</span><span class='line'># 把 Host 的目錄 mount 到 docker container 的目錄
</span><span class='line'>
</span><span class='line'>docker run -d -i -t base /bin/bash 
</span><span class='line'># 組合技，這樣可以用 docker attach 回去 shell
</span><span class='line'>
</span><span class='line'>docker attach &lt;container id&gt; 
</span><span class='line'># attach 回某個 container
</span><span class='line'># 如果跑的時候不是給 -d -i -t /bin/bash 的話是不能下指令的
</span><span class='line'># ctrl + c 會跳出。
</span><span class='line'># 但如果是 -d -i -t /bin/bash ctrl + c 會 stop 整個 container.
</span><span class='line'># 在此情況下不想停止 container 只想跳出請用 ctrl + p, ctrl + q</span></code></pre></td></tr></table></div></figure>


<h2>Dockerfile</h2>

<p>Dockerfile 是用來建置 Docker image 的檔案，簡介可以直接參考<a href="http://docs.docker.com/reference/builder/">官方文件</a>，這邊筆記一些容易搞錯的部分。</p>

<h3>ENTRYPOINT, CMD and RUN</h3>

<ul>
<li><p><code>RUN</code> 是最基本的，就單純是在 build 的時候跑某個指令。</p></li>
<li><p><code>CMD</code> 則是 <code>docker run &lt;image&gt; &lt;command&gt;</code> 時，如果沒有指定 command 時會跑的指令。</p></li>
<li><p><code>ENTRYPOINT</code> 則是設定 <code>docker run &lt;image&gt; &lt;command&gt;</code> 時，用來接 command 的指令。預設的 <code>ENTRYPOINT</code> 是 <code>/bin/sh -c</code> ，例如我們把 <code>ENTRYPOINT</code> 改成 <code>/usr/bin/redis-cli</code> 這樣當我們跑 <code>docker run redis monitor</code> 時，他實際執行的指令就會是 <code>/usr/bin/redis-cli monitor</code>。</p></li>
</ul>


<h3>ADD vs COPY</h3>

<p><code>ADD</code> 會把你指定的檔案或目錄複製到 docker image 中，需要注意的是他不能用 <code>../</code> 指定到當前目錄(context) 之外，並且如果你是用 <code>docker build - &lt; somefile</code> 這樣的方式也沒辦法使用，因為沒有 context。</p>

<p><code>ADD</code> 和 <code>COPY</code> 基本上是一樣的，唯一的差別在於當使用 <code>ADD</code> 時，如果檔案是認得的壓縮檔(gzip, bzip2 or xz) 他會自動幫你解壓縮，而 COPY 則不會。</p>

<h3>ONBUILD</h3>

<p><code>ONBUILD</code> 是一個很好用的功能，他不會發生在你這個 Dockerfile 的建置過程中，而是「用此 Dockerfile 建置出來的 IMAGE 來建置」的過程才會觸發。</p>

<p>舉例你用現在的 Dockerfile 建立了一個 image 叫做 base, 當有另一個 Dockerfile 使用 <code>FROM base</code> 來建置 image 時才會跑 <code>ONBUILD</code> 這行。這是一個有點 tricky 但又很實用的功能。</p>

<h2>小結</h2>

<p>Docker 真的是把 linux container 的操作難度降低非常多，也加上了很多實用的功能以及安全性的限制，他可以運用的範圍非常廣，接下來預計寫幾篇我們實際運用 docker 的例子和筆記。</p>

<h2>補充</h2>

<p>如果你是在 Mac 上使用 docker, 其實他底下是透過 boot2docker 連到一個 vm 裡面去跑 docker, 但是 Mac 常常蓋上電腦就走，這時 vm suspend 後開機 resume 時間都會跑掉，很容易造成靈異現象。這時要進去 boot2docker 的 vm 裡面調整時間。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ boot2docker ssh # 密碼 tcuser
</span><span class='line'>$ sudo /usr/local/bin/ntpclient -s -h pool.ntp.org</span></code></pre></td></tr></table></div></figure>


<p>可以考慮把這段寫成 crontab 省功夫。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[在 Virtualbox 中安裝 OSX Mavericks 10.9]]></title>
    <link href="http://blog.hsatac.net/2014/06/install-osx-mavericks-on-virtualbox/"/>
    <updated>2014-06-29T09:58:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/06/install-osx-mavericks-on-virtualbox</id>
    <content type="html"><![CDATA[<p>筆記一下在 Virtualbox 中安裝 OSX 10.9.3 的方式，裝一個乾淨的 Mac vm image 作為測試環境或者 CI 編譯用的 VM 來說相當實用。</p>

<p>以下說明略過 Virtualbox 安裝以及使用的部分，請自行參考 Virtualbox 說明文件。以下步驟在 Virtualbox 4.3.12 測試成功。</p>

<ol>
<li><p>從 AppStore 下載 OSX 安裝檔，不需要進行安裝。</p></li>
<li><p><code>$ gem install iesd</code></p></li>
<li><p><code>$ iesd -i /Applications/Install\ OS\ X\ Mavericks.app -o Mavericks.dmg -t BaseSyste</code>
這個步驟做完在家目錄會多一個 Mavericks.dmg 檔案</p></li>
<li><p>建立一個新的 Virtualbox 虛擬機器，選 OSX Mavericks 建立好以後先不要開機，進入設定 > 系統 > 晶片組選 PIIX3 然後確定 EFI 有打勾。</p></li>
<li><p><code>$ VBoxManage modifyvm &lt;vmname&gt; –cpuidset 1 000206a7 02100800 1fbae3bf bfebfbff</code>
vmname 請帶入你設定的虛擬機器名稱 (可使用指令 VBoxManage list vms 查看)</p></li>
<li><p>啟動虛擬機，會要求啟動磁碟，選剛剛的 Mavericks.dmg 經過一段開機時間後就會進入安裝畫面，依照正常程序安裝。</p></li>
</ol>


<p>Ref: <a href="http://www.robertsetiadi.net/install-os-x-virtualbox/">How to install OS X on VirtualBox</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Puppet Hiera Encrypted YAML]]></title>
    <link href="http://blog.hsatac.net/2014/06/puppet-hiera-encrypted-yml/"/>
    <updated>2014-06-01T02:04:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/06/puppet-hiera-encrypted-yml</id>
    <content type="html"><![CDATA[<p>在 Puppet 中一些設定大多都是透過 hiera 來取得。 Hiera 最常見，也是預設的 backend 是 YAML 檔。不過有些機敏資訊諸如 API key 或密碼等等，總是不想直接毫無保護的存進 git repository 中，以往我的作法是使用 <a href="https://github.com/crayfishx/hiera-gpg">Hiera-gpg</a> 來加密 YAML 檔，不過一來 gpg 頗為麻煩，二來他是整個檔案都加密，但有時我們只需要修改一些無傷大雅的設定，比如 cluster number 之類的，為此再抽一個加密的檔案專門放這些重要資訊，又破壞了原本的設計。</p>

<p>這次改用了 <a href="https://github.com/TomPoulton/hiera-eyaml">Hiera-eyaml</a> 這個 plugin，他有許多特色和功能，最大的特點當然是他支援片段加密，也就是一個 YAML 檔，只有需要加密的地方再加密即可。我們的檔案可能就會長成這樣：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>---
</span><span class='line'>account: hSATAC
</span><span class='line'>
</span><span class='line'>password: &gt;
</span><span class='line'>    ENC[PKCS7,Y22exl+OvjDe+drmik2XEeD3VQtl1uZJXFFF2NnrMXDWx0csyqLB/2NOWefv
</span><span class='line'>    NBTZfOlPvMlAesyr4bUY4I5XeVbVk38XKxeriH69EFAD4CahIZlC8lkE/uDh
</span><span class='line'>    jJGQfh052eonkungHIcuGKY/5sEbbZl/qufjAtp/ufor15VBJtsXt17tXP4y
</span><span class='line'>    l5ZP119Fwq8xiREGOL0lVvFYJz2hZc1ppPCNG5lwuLnTekXN/OazNYpf4CMd
</span><span class='line'>    /HjZFXwcXRtTlzewJLc+/gox2IfByQRhsI/AgogRfYQKocZgFb/DOZoXR7wm
</span><span class='line'>    IZGeunzwhqfmEtGiqpvJJQ5wVRdzJVpTnANBA5qxeA==]</span></code></pre></td></tr></table></div></figure>


<!--more-->


<p>這樣不用特別破壞原本的目錄結構，要改一般設定時也不需要麻煩的加解密，非常方便。使用公鑰即可加密，所以可以把 public key 放在 repo 中，私鑰給相關權限人等即可。</p>

<p>使用上非常容易，基本上只要在需要用到的地方(例如你開發的電腦，以及 puppet master)透過 gem 安裝 <code>hiera-eyaml</code> 即可。</p>

<p>不過實際上我在配合 <a href="http://apt.puppetlabs.com/">apt.puppetlabs</a> 提供的 apt 套件時卻發生了問題，追了一下才發現因為 apt.puppetlabs 提供的 puppet 是直接安裝到 <code>/usr/lib/ruby/vendor_ruby/</code> 下面，所以直接用了一點暴力的方式來解決這個問題：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="c">#!/bin/bash</span>
</span><span class='line'><span class="nv">gems</span><span class="o">=(</span> <span class="s2">&quot;hiera-eyaml-2.0.2&quot;</span> <span class="s2">&quot;trollop-2.0&quot;</span> <span class="s2">&quot;highline-1.6.21&quot;</span> <span class="o">)</span>
</span><span class='line'>
</span><span class='line'><span class="k">for </span>gem in <span class="s2">&quot;${gems[@]}&quot;</span>
</span><span class='line'><span class="k">do</span>
</span><span class='line'><span class="k">    </span>wget http://rubygems.org/downloads/<span class="nv">$gem</span>.gem --quiet
</span><span class='line'>    gem unpack <span class="nv">$gem</span>.gem
</span><span class='line'>    sudo cp -r <span class="nv">$gem</span>/lib/* /usr/lib/ruby/vendor_ruby/
</span><span class='line'>    rm -rf <span class="nv">$gem</span>*
</span><span class='line'><span class="k">done</span>
</span></code></pre></td></tr></table></div></figure>


<p>自己手動把 gem 抓下來以後 unpack 也一起塞進 vendor_ruby 中就搞定了。如果你正常使用 gem 安裝 puppet 的話，應該直接安裝 hiera-eyaml 就可以了。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Vagrant Provider and Provisioner]]></title>
    <link href="http://blog.hsatac.net/2014/06/vagrant-provider-and-privisioner/"/>
    <updated>2014-06-01T01:34:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/06/vagrant-provider-and-privisioner</id>
    <content type="html"><![CDATA[<p>在寫 Vagrantfile 的時候如果需要用不同的 provider，例如用 virtualbox 和 aws 兩個 provider，但是 provisioner 的內容非常類似，這時候想省掉重複的部分，可以利用 Vagrantfile 本身就是 Ruby 的特性，直接定義一個共用的 lambda，不同的地方再另外設定：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'>  <span class="c1"># Puppet config</span>
</span><span class='line'>  <span class="n">puppet_block</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="k">do</span> <span class="o">|</span><span class="n">puppet</span><span class="o">|</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">options</span> <span class="o">=</span> <span class="s2">&quot;--parser future --verbose --debug&quot;</span> <span class="c1"># For debug only</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">manifests_path</span> <span class="o">=</span> <span class="s2">&quot;manifests&quot;</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">manifest_file</span>  <span class="o">=</span> <span class="s2">&quot;vagrant.pp&quot;</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">module_path</span>    <span class="o">=</span> <span class="s2">&quot;modules&quot;</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">hiera_config_path</span> <span class="o">=</span> <span class="s2">&quot;hiera.yaml&quot;</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provider</span> <span class="s2">&quot;virtualbox&quot;</span> <span class="k">do</span> <span class="o">|</span><span class="n">vb</span><span class="p">,</span> <span class="n">override</span><span class="o">|</span>
</span><span class='line'>    <span class="n">override</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provision</span> <span class="s2">&quot;puppet&quot;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">puppet_block</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provider</span> <span class="ss">:aws</span> <span class="k">do</span> <span class="o">|</span><span class="n">aws</span><span class="p">,</span> <span class="n">override</span><span class="o">|</span>
</span><span class='line'>    <span class="n">override</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provision</span> <span class="s2">&quot;puppet&quot;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">puppet_block</span>
</span><span class='line'>  <span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<p></p>

<!--more-->


<p>provisioner 其實是可以多個的，就算是相同類型也一樣，並且他會依照順序執行，例如：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'>  <span class="c1"># Upgrade Puppet from 2.7 to 3.x</span>
</span><span class='line'>  <span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provision</span> <span class="ss">:shell</span><span class="p">,</span> <span class="ss">:path</span> <span class="o">=&gt;</span> <span class="s2">&quot;scripts/upgrade_puppet.sh&quot;</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1"># Install hiera-eyaml for Puppet</span>
</span><span class='line'>  <span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provision</span> <span class="ss">:shell</span><span class="p">,</span> <span class="ss">:path</span> <span class="o">=&gt;</span> <span class="s2">&quot;scripts/install_eyaml.sh&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>以上會依序執行兩個 script。萬一需要 override 掉之前的某個 provisioner 怎麼辦呢？</p>

<p>按照<a href="https://docs.vagrantup.com/v2/provisioning/basic_usage.html">官方文件</a>其實是可以指定一個 id 來 override 掉，但是這個功能好像一直沒有實現&hellip;當我踩到這個雷的時候發現正好在一天前發佈的 Vagrant 1.6.1 把這個 bug 修掉了&hellip;可以想見我心中的喜悅。</p>

<p>於是在需要 override 的場合就可以這樣寫：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'>  <span class="c1"># Puppet config</span>
</span><span class='line'>  <span class="n">puppet_block</span> <span class="o">=</span> <span class="nb">lambda</span> <span class="k">do</span> <span class="o">|</span><span class="n">puppet</span><span class="o">|</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">options</span> <span class="o">=</span> <span class="s2">&quot;--parser future --verbose --debug&quot;</span> <span class="c1"># For debug only</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">manifests_path</span> <span class="o">=</span> <span class="s2">&quot;manifests&quot;</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">manifest_file</span>  <span class="o">=</span> <span class="s2">&quot;vagrant.pp&quot;</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">module_path</span>    <span class="o">=</span> <span class="s2">&quot;modules&quot;</span>
</span><span class='line'>    <span class="n">puppet</span><span class="o">.</span><span class="n">hiera_config_path</span> <span class="o">=</span> <span class="s2">&quot;hiera.yaml&quot;</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provider</span> <span class="s2">&quot;virtualbox&quot;</span> <span class="k">do</span> <span class="o">|</span><span class="n">vb</span><span class="p">,</span> <span class="n">override</span><span class="o">|</span>
</span><span class='line'>    <span class="n">override</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provision</span> <span class="s2">&quot;puppet&quot;</span><span class="p">,</span> <span class="ss">:id</span> <span class="o">=&gt;</span> <span class="s2">&quot;puppet&quot;</span> <span class="p">,</span> <span class="o">&amp;</span><span class="n">puppet_block</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">define</span> <span class="s2">&quot;staging&quot;</span> <span class="k">do</span> <span class="o">|</span><span class="n">staging</span><span class="o">|</span>
</span><span class='line'>    <span class="n">staging</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">hostname</span> <span class="o">=</span> <span class="s2">&quot;staging&quot;</span>
</span><span class='line'>    <span class="n">staging</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">provision</span> <span class="s2">&quot;puppet&quot;</span><span class="p">,</span> <span class="ss">:id</span> <span class="o">=&gt;</span> <span class="s2">&quot;puppet&quot;</span><span class="p">,</span> <span class="ss">:preserve_order</span> <span class="o">=&gt;</span> <span class="kp">true</span> <span class="k">do</span> <span class="o">|</span><span class="n">puppet</span><span class="o">|</span>
</span><span class='line'>      <span class="n">puppet_block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">puppet</span><span class="p">)</span>
</span><span class='line'>      <span class="n">puppet</span><span class="o">.</span><span class="n">facter</span> <span class="o">=</span> <span class="p">{</span>
</span><span class='line'>        <span class="s1">&#39;environment&#39;</span> <span class="o">=&gt;</span> <span class="s2">&quot;staging&quot;</span><span class="p">,</span>
</span><span class='line'>      <span class="p">}</span>
</span><span class='line'>    <span class="k">end</span>
</span><span class='line'>  <span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>preserve_order</code> 可以維持原本被 override 掉的 provisioner 順序，沒有指定的話會加到最後面，變成最後執行。如果你的 provisioner 有順序相依性請務必注意這點。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Puppet Development Tools]]></title>
    <link href="http://blog.hsatac.net/2014/04/puppet-development-tools/"/>
    <updated>2014-04-20T13:36:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/04/puppet-development-tools</id>
    <content type="html"><![CDATA[<p>好一陣子沒寫 Puppet, 最近回來研究發現多了不少好用的工具，可以有效加速開發速度。</p>

<h2>Vagrant</h2>

<p>現在 <a href="http://www.vagrantup.com/">Vagrant</a> provisioner 直接提供了 <a href="http://docs.vagrantup.com/v2/provisioning/puppet_apply.html">Puppet 選項</a>，可以幫你 sync hiera, manifests, modules 進去直接 run，也可以帶入 custom options 或是 facter，在開發 modules 的時候可以不用管其他東西，專心 focus 在 pp 本身。</p>

<p>只要在 Vagrantfile 裡面加入這樣的設定即可：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>  # Puppet config
</span><span class='line'>  config.vm.provider "virtualbox" do |vb, override|
</span><span class='line'>    override.vm.provision "puppet" do |puppet|
</span><span class='line'>      puppet.options = "--parser future --verbose --debug" # For debug only
</span><span class='line'>      puppet.manifests_path = "manifests"
</span><span class='line'>      puppet.manifest_file  = "vagrant.pp"
</span><span class='line'>      puppet.module_path    = "modules"
</span><span class='line'>      puppet.hiera_config_path = "hiera.yaml"
</span><span class='line'>    end
</span><span class='line'>  end</span></code></pre></td></tr></table></div></figure>


<p>不過有兩個小地方需要注意：</p>

<!-- more -->


<h3>Puppet 版本</h3>

<p>Vagrant 附的 Puppet 版本比較舊，是 2.7x。如果有新版本的需求，可以使用這隻 script 來升級 Puppet：</p>

<script src="https://gist.github.com/hSATAC/11106132.js"></script>




<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>  # Upgrade Puppet from 2.7 to 3.x
</span><span class='line'>  config.vm.provision :shell, :path =&gt; "scripts/upgrade_puppet.sh"</span></code></pre></td></tr></table></div></figure>


<h3>Hiera 目錄</h3>

<p>通常 hiera 不會只有一隻檔案，但 Vagrant 只會幫你掛上 modules 和 manifests 資料夾。這時就需要把 hiera 的目錄丟到 manifests 下面，並且在 hiera 設定 <code>:datadir: "%{settings::manifestdir}/hieradata"</code> 直接去吃 manifests 的路徑即可。</p>

<hr />

<h2>Librarian-Puppet</h2>

<p><a href="http://librarian-puppet.com/">Librarian-Puppet</a> 是一個管理 puppet modules 的工具，基本上跟 bundler 的概念一樣。編寫 Puppetfile 以後，使用指令 <code>librarian-puppet install</code> 來安裝到 <code>modules</code> 目錄下。這樣就不用處理 <code>puppet module install</code> 和指定版本，以及安裝自己 private modules 的問題了。反正使用 librarian-puppet 他會幫你管好一個 <code>modules</code> 目錄。</p>

<p>Puppetfile 支援幾種指定方式，都非常實用：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>modulefile</span></code></pre></td></tr></table></div></figure>


<p>只要直接下 modulefile 他就會去吃你 modulefile 裡面的 dependencies。這在開發 puppet modules 的時候會用到。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mod "puppetlabs/stdlib"</span></code></pre></td></tr></table></div></figure>


<p>指定 puppet forge 的 package name。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mod "puppetlabs/apt",
</span><span class='line'>  :git =&gt; "git://github.com/puppetlabs/puppetlabs-apt.git",
</span><span class='line'>  :ref =&gt; '0.0.3'</span></code></pre></td></tr></table></div></figure>


<p>指定某 repo 的 ref。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mod "puppetlabs/apt",
</span><span class='line'>  :git =&gt; "git://github.com/fake/puppet-modules.git",
</span><span class='line'>  :path =&gt; "modules/apt"</span></code></pre></td></tr></table></div></figure>


<p>指定 repo 下的 path。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mod "puppetlabs/apt", :path =&gt; "modules/apt"</span></code></pre></td></tr></table></div></figure>


<p>指定 local path，可以用在 private modules。</p>

<hr />

<h2>Puppet Skeleton</h2>

<p>以上這兩個工具搭配起來，開發 Puppet 就變得很容易了：把需要的 community modules 定義在 Puppetfile 裡面，private modules 放在 local, 一樣用 Puppetfile 掛起來安裝，再透過 Vagrant 指定 manifest path, file, hiera 的設定，直接 <code>$ vagrant up</code> 就可以反覆測試 puppet 了。</p>

<p>我有做了一個 <a href="https://github.com/hSATAC/puppet-skeleton">puppet-skeleton</a> 的專案，這是我自己開發 Puppet 的專案架構跟 workspace。</p>

<h3>Rake Tasks</h3>

<p>這邊是一些我自己開發常用的 rake tasks，基本上就是省 keystroke&hellip;</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>rake -T                                  <span class="c"># List all tasks.</span>
</span><span class='line'><span class="nv">$ </span>rake -D                                  <span class="c"># List all tasks with descriptions.</span>
</span><span class='line'><span class="nv">$ </span>rake module:lint                         <span class="c"># Puppet lint.</span>
</span><span class='line'><span class="nv">$ </span>rake module:reinstall                    <span class="c"># Clean and reinstall modules.</span>
</span><span class='line'><span class="nv">$ </span>rake module:sync                         <span class="c"># Sync private modules.</span>
</span><span class='line'><span class="nv">$ </span>rake syntax                              <span class="c"># Syntax check Puppet manifests and templates</span>
</span><span class='line'><span class="nv">$ </span>rake syntax:hiera                        <span class="c"># Syntax check Hiera config files</span>
</span><span class='line'><span class="nv">$ </span>rake syntax:manifests                    <span class="c"># Syntax check Puppet manifests</span>
</span><span class='line'><span class="nv">$ </span>rake syntax:templates                    <span class="c"># Syntax check Puppet templates</span>
</span><span class='line'><span class="nv">$ </span>rake vagrant:provision<span class="o">[</span>name,provisioner<span class="o">]</span> <span class="c"># Provision vagrant VM.</span>
</span><span class='line'><span class="nv">$ </span>rake vagrant:rebuild<span class="o">[</span>name<span class="o">]</span>               <span class="c"># Rebuild vagrant VM.</span>
</span></code></pre></td></tr></table></div></figure>


<h3>目錄架構</h3>

<p>跟上面講的差不多，除了特別把 <code>role</code> 跟 <code>profile</code> 兩個 modules 從 <code>private</code> modules 裡面抽出來到頂層。</p>

<p>關於 <code>role</code> 以及 <code>profile</code> 可以看我之前的文章 <a href="http://blog.hsatac.net/2014/04/roles-and-profiles-pattern-in-puppet/">Roles and Profiles Pattern in Puppet</a>。</p>

<p>也算是提供一個這個 pattern 的範例。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>.
</span><span class='line'>├── Gemfile             <span class="c"># Required rubygems, use bundler to install.</span>
</span><span class='line'>├── Puppetfile          <span class="c"># Required puppet modules, use librarian-puppet to install.</span>
</span><span class='line'>├── README.md
</span><span class='line'>├── Rakefile            <span class="c"># Some predefined tasks, to speed up development.</span>
</span><span class='line'>├── Vagrantfile         <span class="c"># Vagrant configuration.</span>
</span><span class='line'>├── hiera.yaml          <span class="c"># Puppet hiera config, only define hierarchy and datadir in this file.</span>
</span><span class='line'>├── docs                <span class="c"># Some documents</span>
</span><span class='line'>├── manifests
</span><span class='line'>│   ├── hieradata         <span class="c"># The actual heirdata stored in this folder.</span>
</span><span class='line'>│   ├── site.pp           <span class="c"># Node definition for production.</span>
</span><span class='line'>│   └── vagrant.pp        <span class="c"># Node definition for local development.</span>
</span><span class='line'>├── private             <span class="c"># Private modules, will be sync into `modules` folder by `librarian-puppet`.</span>
</span><span class='line'>│   ├── common
</span><span class='line'>│   └── users
</span><span class='line'>├── profile             <span class="c"># Profile, abstraction of &quot;Technology stack&quot;</span>
</span><span class='line'>│   ├── files
</span><span class='line'>│   └── manifests
</span><span class='line'>├── role                <span class="c"># Role, abstraction of &quot;What does this server do?&quot;</span>
</span><span class='line'>│   └── manifests
</span><span class='line'>├── spec                <span class="c"># Put test files</span>
</span><span class='line'>└── scripts
</span><span class='line'>    └── upgrade_puppet.sh <span class="c"># Script of upgrading puppet to version 3 on Ubuntu</span>
</span></code></pre></td></tr></table></div></figure>


<h3>在 EC2 上測試</h3>

<p>只要稍微設定一下 Vagrantfile，就可以利用 <a href="https://github.com/mitchellh/vagrant-aws">Vagrant-AWS</a> 直接 deploy 到 AWS EC2 上面測試。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Roles and Profiles Pattern in Puppet]]></title>
    <link href="http://blog.hsatac.net/2014/04/roles-and-profiles-pattern-in-puppet/"/>
    <updated>2014-04-12T14:04:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/04/roles-and-profiles-pattern-in-puppet</id>
    <content type="html"><![CDATA[<p>一開始寫 Puppet 就是 node definition 直接寫寫寫&hellip;後來就會開始把重複的 resource, file 等等拆成 modules&hellip;。不過當機器越來越多，發現還是有許多重複的地方，例如有好多台 web server, 但是他們有些又有些許的不同&hellip;。</p>

<!-- more -->


<h2>Original Style</h2>

<p>先看看傳統的寫法：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>node web {
</span><span class='line'>  include users
</span><span class='line'>  include nginx
</span><span class='line'>  include rails
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>node worker {
</span><span class='line'>  include users
</span><span class='line'>  include rails
</span><span class='line'>  include redis
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>node db {
</span><span class='line'>  include users
</span><span class='line'>  include mysql
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>node web-qa {
</span><span class='line'>  include users
</span><span class='line'>  include nginx
</span><span class='line'>  include rails
</span><span class='line'>  include mysql
</span><span class='line'>  include redis
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>四個看起來還好，但是當機器越來越多的時候，就會感到難以維護了。</p>

<h2>Roles and Profiles Pattern</h2>

<blockquote><p>All problems in computer science can be solved by another level of indirection.</p></blockquote>

<p>所有電腦科學領域的問題都可以用抽象化來解決(除了抽象太多層以外)，以上的問題我們可以用現在常見的 &ldquo;Roles and Profiles Pattern&rdquo; 來做抽象。</p>

<h2>Role</h2>

<p>Role 很好理解，顧名思義就是「扮演的角色」，以上面的例子我們就有 web, db, worker，web 可能又分成 production 環境和 QA 環境。</p>

<p>這邊舉的例子可能太技術取向了，可以想想平常團隊在溝通時，在稱呼某台機器的時候都是怎麼稱呼的：「那台壓影片的」、「那台 QA 環境」&hellip;就很清楚明瞭了。</p>

<p>用這個思路來整理 node 會變成這樣：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>node web {
</span><span class='line'>  include role::web::production
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>node worker {
</span><span class='line'>  include role::worker
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>node db {
</span><span class='line'>  include role::db
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>node web-qa {
</span><span class='line'>  include role::web::qa
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>Role 本身大概會長這樣：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>class role { 
</span><span class='line'>  include profile::base
</span><span class='line'>}
</span><span class='line'> 
</span><span class='line'>class role::web inherits role { 
</span><span class='line'>  include profile::nginx
</span><span class='line'>}
</span><span class='line'> 
</span><span class='line'>class role::web::production inherits role::web { 
</span><span class='line'>  include profile::nginx::production
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>class role::web::qa inherits role::web { 
</span><span class='line'>  include profile::nginx::qa
</span><span class='line'>  include profile::db
</span><span class='line'>  include profile::worker
</span><span class='line'>}
</span><span class='line'> 
</span><span class='line'>class role::db inherits role { 
</span><span class='line'>  include profile::mysql
</span><span class='line'>}
</span><span class='line'> 
</span><span class='line'>class role::worker inherits role { 
</span><span class='line'>  include profile::worker
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<h2>Profile</h2>

<p>Profile 則是用來抽象化「一組服務、設定」的。看上面 Role 的部份應該有點感覺了：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>class profile::base {
</span><span class='line'>  include users
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>class profile::web {
</span><span class='line'>  include nginx
</span><span class='line'>  include rails
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>class profile::web::production inherits profile::web {
</span><span class='line'>  ::nginx::file { 'production.conf':
</span><span class='line'>      content =&gt; ...,
</span><span class='line'>  }
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>class profile::web::qa inherits profile::web {
</span><span class='line'>  ::nginx::file { 'qa.conf':
</span><span class='line'>      content =&gt; ...,
</span><span class='line'>  }
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>class profile::db inherits { 
</span><span class='line'>  include profile::mysql
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>class profile::worker {}
</span><span class='line'>  include rails
</span><span class='line'>  include redis
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>這樣就可以把重複的程式碼減少，同時又保留彈性。</p>

<h2>Tips</h2>

<ul>
<li>一個 node 只 include <em>一個</em> role。如果這兩個 role 很像，但又有些微不同，那就是一個新 role。</li>
<li>一個 role include 一個或多個 profile，而且 <em>只能 include profile</em> 。</li>
</ul>


<h2>Further Reading</h2>

<p>更多詳細細節、優缺點以及不同的設計方式可以參考以下的幾篇連結：</p>

<ul>
<li><a href="http://www.craigdunn.org/2012/05/239/">Designing Puppet – Roles and Profiles</a></li>
<li><a href="http://www.slideshare.net/PuppetLabs/roles-talk">Designing Puppet: Roles/Profiles Pattern</a></li>
<li><a href="http://garylarizza.com/blog/2014/02/17/puppet-workflow-part-2/">Building a Functional Puppet Workflow Part 2: Roles and Profiles</a></li>
<li><a href="http://sysadvent.blogspot.tw/2012/12/day-13-configuration-management-as-legos.html">Configuration Management as Legos</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Rails i18n Relative Key in Controller Before Action]]></title>
    <link href="http://blog.hsatac.net/2014/04/rails-i18n-relative-key-in-controller-before-action/"/>
    <updated>2014-04-12T04:42:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/04/rails-i18n-relative-key-in-controller-before-action</id>
    <content type="html"><![CDATA[<p>在 Rails i18n 裡面可以用 relative path 像 <code>t('.key')</code> 這樣的 shortcut，不過這個 shortcut 吃得是 <code>"#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }"</code>。</p>

<p>今天碰到 controller 的 before action 裡面用 relative path 結果 locale yml 編到 before action 的 key，但真正會去 lookup 的是 <code>action_name</code> 而不是 before action 的 method name 所以就爆 missing 了。</p>

<p>萬一這個 before_action 之後多加幾個其他的 action 的話很容易就沒改到 locale yml 而爆錯誤，為了保險起見決定把他們都改成 absolute key path. 不過 controller 檔案非常多，所以要寫一隻程式把所有 before action 裡面有用到 relative path 的 t 撈出來。</p>

<p>一開始的想法是用 regex 速解，不過因為 controller 裡面的 t 還滿多的，然後我又很難判斷 before action method 的 scope，於是念頭一轉就直接改用 <a href="https://github.com/whitequark/parser">Ruby Parser</a> 來做。</p>

<p>直接把 controller 檔案都讀進來，拿 AST 來抓 before actions，再去檢查這些 before action 是否有 call 到 relative keypath 的 t。用起來還滿方便的，效率也不差，一下子就寫完了。</p>

<p>程式碼放在 <a href="https://github.com/hSATAC/parse-relative-key">Github</a>，不過這麼特定目的東西應該是無法重用，放著以後有需要的時候回來看一下。</p>

<p>最後就是&hellip;i18n 沒事還是不要用 relative key 比較好&hellip;散在 controller, helper, service 裡面到時要搬移 code 或者作 refactor 的時候就麻煩了。動態語言重構不像靜態語言這麼便利啊&hellip;。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[AWS OpsWorks Overview]]></title>
    <link href="http://blog.hsatac.net/2014/03/aws-opsworks-overview/"/>
    <updated>2014-03-30T04:38:00+08:00</updated>
    <id>http://blog.hsatac.net/2014/03/aws-opsworks-overview</id>
    <content type="html"><![CDATA[<p>前陣子 survey 了一下 <a href="https://aws.amazon.com/opsworks/">AWS OpsWorks</a> 做了一點筆記：</p>

<script async class="speakerdeck-embed" data-id="72d076a09a160131f1ab7a4f652a1694" data-ratio="1.33333333333333" src="http://blog.hsatac.net//speakerdeck.com/assets/embed.js"></script>


<!-- more -->


<p>如果對 Chef / Puppet 的概念還不熟悉的話可以參考我之前的投影片</p>

<script async class="speakerdeck-embed" data-id="2ea36f409ebd01301b2f469a61e096c5" data-ratio="1.33333333333333" src="http://blog.hsatac.net//speakerdeck.com/assets/embed.js"></script>


<p>結論來說，我覺得 OpsWorks 已經完全取代掉 CloudFormation 了。提供非常多實用的功能和架構，community 也有起來，resource 挺多的。如果沒有什麼歷史包袱，可以直接採用。就算不寫 chef 用他提供的內建 cookbook 也可以做到很多事。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Functional Reactive Programming]]></title>
    <link href="http://blog.hsatac.net/2013/12/functional-reactive-programming/"/>
    <updated>2013-12-07T20:49:00+08:00</updated>
    <id>http://blog.hsatac.net/2013/12/functional-reactive-programming</id>
    <content type="html"><![CDATA[<h3>Functional Reactive Programming</h3>

<p>第一次聽到 <a href="http://en.wikipedia.org/wiki/Functional_reactive_programming">Functional Reactive Programming(FRP)</a> 是在今年新加坡的 RedDotRubyConf 2013 的最後一個 Session: <a href="https://github.com/steveklabnik/frappuccino">Functional Reactive Programming in Ruby</a>。那時一直無法理解這個主題，直到大約九月時碰到了 <a href="https://github.com/ReactiveCocoa/ReactiveCocoa">ReactiveCocoa</a> 並開始在公司專案中使用，才對 FRP 開始有些感覺。</p>

<p>Functional Reactive Programming 就是 Functional Programming + Reactive Programming。 Functional Programming 大家應該比較熟悉，那什麼是 Reactive Programming 呢？最常看到的比喻就是像試算表一樣，你可以定義 <code>C1 = A1 + B1</code> ，之後只要你更改了 <code>A1</code> 或 <code>B1</code> 的值， <code>C1</code> 就會跟著改變。</p>

<p>光看這樣實在還是無法理解到底 FRP 是什麼，以及可以帶來什麼好處。這就是我今年六月剛聽到這個名詞時的感受。直到我開始使用 ReactiveCocoa(RAC)。</p>

<!-- more -->


<h3>ReactiveCocoa</h3>

<p>ReactiveCocoa 的概念是從 .NET 的 Reactive Extensions 來的，在 Cocoa Framework 上實作了這個 paradigm，使用 ReactiveCocoa 可以讓我們減少大量複雜的程式碼。</p>

<p>幾個 FRP 中比較重要的名詞：<code>Streams</code>, <code>Sequences</code>, <code>Signals</code>,  <code>Subscriptions</code>。</p>

<p><code>Stream</code> 就像是一個水管，裡面會一直有東西跑出來。</p>

<p><code>Sequences</code> 是一種以拉為主的 <code>Stream</code>，常用在把 <code>NSArray</code>, <code>NSDictionary</code> 轉成 <code>RACSequence</code> 來接上高階函數操作 <code>map</code>, <code>filter</code>, <code>fold</code>, <code>reduce</code> 等。</p>

<p><code>Signal</code> 是一種以推為主的 <code>Stream</code>，有三種類型：<code>next</code>, <code>error</code>, <code>completion</code> 分別表達有新的值、錯誤以及結束。</p>

<p><code>Subscription</code> 則是誰要來等待/處理這些 <code>Signal</code>。</p>

<p>基本概念大概是這樣，不過有什麼應用場景呢？官網給的範例其實都滿簡單的，例如處理表單驗證，或是等到兩個 requests 都完成後才做下一步動作等等&hellip;。其實無處不可用，用下去幾乎都能看到立即的好處，甚至也有看到把 delegate protocol 都用 FRP 來寫的。不過我覺得這反而有點難讀了。</p>

<p>我們專案目前最常用的情境是：</p>

<ol>
<li><p>處理 Model 跟 View 之間的 binding, 值有變化的時候不用再一直去通知 View 更新。</p></li>
<li><p>Request 回來後把資料轉成我們要的樣子。光是這樣就已經很好用了。</p></li>
</ol>


<p>不過 View 在處理深入一點的話可能要看看 MVVM 跟 <a href="https://github.com/ReactiveCocoa/ReactiveViewModel">ReactiveViewModel</a> 這樣感覺也比較好寫測試&hellip;不過可能要等之後的新專案再來研究看看。</p>

<p>然後順便提一下 <a href="https://github.com/jspahrsummers/libextobjc">Extended Objective-C (libextobjc)</a> 這個東西&hellip;因為 ReactiveCocoa 依賴 libextobjc 所以一定會裝到，除了很常見的 <code>@weakify</code>, <code>@strongify</code> 以外 libextobjc 還有不少好東西可以用，例如 <code>Concrete protocols</code>，<code>EXTNil</code> 等等，可以參考看看。</p>

<h3>Reference</h3>

<ul>
<li><a href="https://github.com/ReactiveCocoa/ReactiveCocoa">ReactiveCocoa</a></li>
<li><a href="https://github.com/ReactiveCocoa/ReactiveViewModel">ReactiveViewModel</a></li>
<li><a href="http://nshipster.com/reactivecocoa/">Mattt Thompson 介紹 RAC</a></li>
<li><a href="http://codeblog.shape.dk/blog/categories/reactivecocoa/">codeblog.share.dk 有幾篇不錯的文章</a></li>
<li><a href="https://leanpub.com/iosfrp">Ash Furrow 寫的書 Functional Reactive Programming on iOS</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Go Development Toolbox]]></title>
    <link href="http://blog.hsatac.net/2013/11/golang-development-toolbox/"/>
    <updated>2013-11-02T13:24:00+08:00</updated>
    <id>http://blog.hsatac.net/2013/11/golang-development-toolbox</id>
    <content type="html"><![CDATA[<p>這次去北京參加 RubyConfChina 2013 的來回飛機上，寫完了一個練習用的小專案：<a href="https://github.com/hSATAC/gosnake">gosnake</a>，很明顯就是用 Go 寫的貪食蛇。會挑貪食蛇來練習，是因為之前在 iOS Dev Bootcamp 參加 <a href="https://twitter.com/zonble">zonble</a> 的 workshop，題目就是寫一個貪食蛇，覺得這個題目拿來練習真的是挺不錯的。</p>

<p>先來看看動起來的樣子：</p>

<script type="text/javascript" src="http://asciinema.org/a/6115.js" id="asciicast-6115" async></script>


<p>程式本身很簡單，沒什麼好說的，倒是想紀錄一些開發上使用到的工具。</p>

<!-- more -->


<h3>開發環境</h3>

<p>首先我們都知道 Go 有所謂的 <code>GOPATH</code>，src, pkg 等等東西都會安裝在這裡。不過每個專案都有自己的套件相依性，再加上如果東西一直裝，這個目錄會很大一包。所以一般建議會在開發專案時，把 <code>GOPATH</code> 設定到專案目錄底下，以免互相污染。<a href="http://twitter.com/c9s">c9s</a> 有寫了一個 script <a href="https://github.com/c9s/goenv">goenv</a> 來簡化這個步驟，我使用的則是我 <a href="https://github.com/hSATAC/goenv">fork 的版本</a>。</p>

<p>使用的方式很簡單，要開發這個專案的時候，切到專案目錄下 <code>source goenv</code> 即可。你的專案目錄下會建立一個 <code>go</code> 目錄，並且 <code>GOPATH</code> 會被指向此處。</p>

<h3>套件管理</h3>

<p><a href="https://github.com/c9s/goenv">goenv</a> 其實已經可以解決大部分的問題，如果把 <code>go</code> 目錄也直接 commit 進去的話其實就可以解決 reproducible build 的問題。不過還是希望能有類似 <code>bundler</code> 這樣的工具。</p>

<p>試了兩套 <a href="https://github.com/mattn/gom">gom</a> 跟 <a href="https://github.com/kr/godep">godep</a>。我自己是比較喜歡 gom 的 API 設計，而且他的 star 數也比較多。但是在 <code>gom gen gomfile</code> 自動掃描生成 <code>Gomfile</code> 這邊一直出現問題，會掃到很多不相關的東西。相對的同樣功能的 <code>godep save</code> 就沒什麼問題。並且也支援 <code>godep save -copy</code> 直接把整個 dependencies tree 複製到專案目錄下，讓你用 <code>GOPATH</code> 的方式使用，目前使用起來 <a href="https://github.com/kr/godep">godep</a> 是一個挺不錯的選擇。</p>

<p><a href="https://github.com/kr/godep">godep</a> 會把你指定的每個 package 裝到 tmp 目錄下，使用 <code>godep path</code> 就可以看到。</p>

<p>用 <code>godep</code> 指令包 <code>go</code> 指令的話就會從這些地方載入套件，有點像是 bundler 的感覺。<code>godep go build</code>, <code>godep go test</code>。</p>

<p>目前我還是 goenv 和 godeps 並行，看看將來的發展怎麼樣。也希望官方能儘快解決這個問題，不然現在第三方的 Go package management tool 以一個禮拜一套的速度在推出啊&hellip;。</p>

<p>更詳細的 <a href="https://github.com/kr/godep">godep</a> 教學，可以參考這篇文章： <a href="http://www.goinggo.net/2013/10/manage-dependencies-with-godep.html">Manage Dependencies With GODEP</a></p>

<h3>測試套件</h3>

<p>可能是 Ruby 寫慣了，總覺得 Go 內建的測試語法不太親民。我在這個專案使用了 <a href="https://github.com/stretchr/testify">testify</a> 的 <code>assert</code> 套件，可以寫出這樣的語法：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'>  <span class="c1">// assert equality</span>
</span><span class='line'>  <span class="nx">assert</span><span class="p">.</span><span class="nx">Equal</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="mi">123</span><span class="p">,</span> <span class="mi">123</span><span class="p">,</span> <span class="s">&quot;they should be equal&quot;</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1">// assert inequality</span>
</span><span class='line'>  <span class="nx">assert</span><span class="p">.</span><span class="nx">NotEqual</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="mi">123</span><span class="p">,</span> <span class="mi">456</span><span class="p">,</span> <span class="s">&quot;they should not be equal&quot;</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1">// assert for nil (good for errors)</span>
</span><span class='line'>  <span class="nx">assert</span><span class="p">.</span><span class="nx">Nil</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">object</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>  <span class="c1">// assert for not nil (good when you expect something)</span>
</span><span class='line'>  <span class="k">if</span> <span class="nx">assert</span><span class="p">.</span><span class="nx">NotNil</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">object</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1">// now we know that object isn&#39;t nil, we are safe to make</span>
</span><span class='line'>    <span class="c1">// further assertions without causing any errors</span>
</span><span class='line'>    <span class="nx">assert</span><span class="p">.</span><span class="nx">Equal</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="s">&quot;Something&quot;</span><span class="p">,</span> <span class="nx">object</span><span class="p">.</span><span class="nx">Value</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p><a href="https://github.com/stretchr/testify">testify</a> 除了 <code>assert</code> 之外，也提供了 <code>http</code>, <code>mock</code>, <code>suite</code> 可使用，算是滿全面的測試工具。相類似的套件還有 <a href="http://labix.org/gocheck">gocheck</a> 這個也滿受歡迎的。</p>

<p>另外兩個我覺得不錯的測試工具是 <a href="https://github.com/smartystreets/goconvey">Goconvey</a> 以及 <a href="https://github.com/remogatto/prettytest">PrettyTest</a>。</p>

<p><a href="https://github.com/smartystreets/goconvey">GoConvey</a> 可算一套完整的 BDD/TDD 測試框架，使用了自己的語法，離原生的 <code>testing</code> 又更遙遠了，帶有 WebUI 以及漂亮的 terminal output 可以很清楚產出測試報表。</p>

<p><a href="https://github.com/remogatto/prettytest">PrettyTest</a> 則是用自己的 assert 來產出清晰的 terminal output，也可以搭配上面提到的 <a href="http://labix.org/gocheck">gocheck</a> 使用。</p>

<p>還有另外非常多的工具可以參考這篇：<a href="http://nathany.com/go-testing-toolbox/">Go Testing Toolbox</a></p>

<h3>其他工具</h3>

<p><a href="https://github.com/davecgh/go-spew">go-spew</a> 噴東西相當好用，什麼都可以噴，可以看一下他的 sample output 超威：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>(main.Foo) {
</span><span class='line'> unexportedField: (*main.Bar)(0xf84002e210)({
</span><span class='line'>  flag: (main.Flag) flagTwo,
</span><span class='line'>  data: (uintptr) &lt;nil&gt;
</span><span class='line'> }),
</span><span class='line'> ExportedField: (map[interface {}]interface {}) {
</span><span class='line'>  (string) "one": (bool) true
</span><span class='line'> }
</span><span class='line'>}
</span><span class='line'>([]uint8) {
</span><span class='line'> 00000000  11 12 13 14 15 16 17 18  19 1a 1b 1c 1d 1e 1f 20  |............... |
</span><span class='line'> 00000010  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30  |!"#$%&'()*+,-./0|
</span><span class='line'> 00000020  31 32                                             |12|
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p><a href="https://github.com/nsf/termbox-go">termbox-go</a> 則是寫 CUI 程式的好幫手，非常容易使用。之前那個用 terminal 看股票的 top <a href="https://github.com/michaeldv/mop">mop</a> 也是用這個寫的。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Golang The Fun Part - Struct And Interfacce]]></title>
    <link href="http://blog.hsatac.net/2013/09/golang-the-fun-part-struct-and-interfacce/"/>
    <updated>2013-09-17T22:03:00+08:00</updated>
    <id>http://blog.hsatac.net/2013/09/golang-the-fun-part-struct-and-interfacce</id>
    <content type="html"><![CDATA[<p>之前說要寫一篇 Go 簡介…不過網路上 Go 的資料已經很豐富，把一些我喜歡 Go 的點記錄下來好了。</p>

<p>Go 是物件導向的語言嗎？是，也不是。</p>

<p>他沒有類別，也沒有繼承。我們來用實例來看看 Go 如何實現物件導向的特性。</p>

<h3>Struct</h3>

<p>有接觸過 c-like 語言的人應該都對 <code>struct</code> 不陌生，我們可以定義一組結構，裡面包含各種資料型態的變數。</p>

<p>舉例來說我們可以定義一個叫 <code>Human</code> 的 <code>struct</code>：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">type</span> <span class="nx">Human</span> <span class="kd">struct</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">name</span> <span class="kt">string</span>
</span><span class='line'>  <span class="nx">age</span> <span class="kt">int</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>然後我們就可以這樣來使用：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="nx">person</span> <span class="o">:=</span> <span class="nx">Human</span><span class="p">{</span><span class="s">&quot;Ash&quot;</span><span class="p">,</span> <span class="mi">18</span><span class="p">}</span>
</span><span class='line'><span class="c1">//或者</span>
</span><span class='line'><span class="nx">person</span> <span class="o">:=</span> <span class="nx">Human</span><span class="p">{</span><span class="nx">name</span><span class="p">:</span><span class="s">&quot;Ash&quot;</span><span class="p">,</span> <span class="nx">age</span><span class="p">:</span><span class="mi">18</span><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">person</span><span class="p">.</span><span class="nx">name</span>
</span><span class='line'><span class="nx">person</span><span class="p">.</span><span class="nx">age</span>
</span></code></pre></td></tr></table></div></figure>




<!--more-->


<p>不一樣的地方是，我們可以給這個 <code>struct</code> 定義方法：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">func</span> <span class="p">(</span><span class="nx">human</span> <span class="o">*</span><span class="nx">Human</span><span class="p">)</span><span class="nx">Eat</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;Eating&quot;</span><span class="p">)</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">person</span><span class="p">.</span><span class="nx">Eat</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>


<p>我們可以定義另一個 <code>struct</code> 來「繼承」<code>Human</code> 的屬性和方法，例如我們定義一個 <code>F2E</code>：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">type</span> <span class="nx">F2E</span> <span class="kd">struct</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">Human</span>
</span><span class='line'>  <span class="nx">cssLevel</span> <span class="kt">int</span>
</span><span class='line'>  <span class="nx">javascriptLevel</span> <span class="kt">int</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>然後我們就可以這樣使用 <code>F2E</code>：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="nx">aar0n</span> <span class="o">:=</span> <span class="nx">F2E</span><span class="p">{</span><span class="nx">Human</span><span class="p">{</span><span class="nx">name</span><span class="p">:</span><span class="s">&quot;aar0n&quot;</span><span class="p">,</span> <span class="nx">age</span><span class="p">:</span><span class="mi">35</span><span class="p">},</span> <span class="mi">80</span><span class="p">,</span> <span class="mi">90</span><span class="p">}</span>
</span><span class='line'><span class="nx">aar0n</span><span class="p">.</span><span class="nx">Eat</span><span class="p">()</span>
</span><span class='line'><span class="c1">// 當然也可以 access Human 的屬性</span>
</span><span class='line'><span class="nx">aar0n</span><span class="p">.</span><span class="nx">name</span>
</span><span class='line'><span class="c1">// 或者</span>
</span><span class='line'><span class="nx">aar0n</span><span class="p">.</span><span class="nx">Human</span><span class="p">.</span><span class="nx">name</span>
</span></code></pre></td></tr></table></div></figure>


<p>我們也可以讓 <code>F2E</code> override <code>Human</code> 的屬性跟方法：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">type</span> <span class="nx">F2E</span> <span class="kd">struct</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">Human</span>
</span><span class='line'>  <span class="nx">cssLevel</span> <span class="kt">int</span>
</span><span class='line'>  <span class="nx">javascriptLevel</span> <span class="kt">int</span>
</span><span class='line'>  <span class="nx">name</span> <span class="kt">int</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">func</span> <span class="p">(</span><span class="nx">f2e</span> <span class="o">*</span><span class="nx">F2E</span><span class="p">)</span><span class="nx">Eat</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;F2E does not eat!&quot;</span><span class="p">)</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="c1">// 還是可以 access Human 的屬性跟方法</span>
</span><span class='line'><span class="nx">aar0n</span><span class="p">.</span><span class="nx">Human</span><span class="p">.</span><span class="nx">name</span>
</span><span class='line'><span class="nx">aar0n</span><span class="p">.</span><span class="nx">Human</span><span class="p">.</span><span class="nx">Eat</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>


<h4>Type 其他技巧</h4>

<p><code>type</code> 基本上是一個 alias 資料型態的關鍵字，不只可以使用在 <code>struct</code> 上。例如我們可以定義一個 Value Object 叫 <code>Money</code>：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">type</span> <span class="nx">Money</span> <span class="kt">int</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Money 也可以有方法</span>
</span><span class='line'><span class="kd">func</span> <span class="p">(</span><span class="nx">money</span> <span class="nx">Money</span><span class="p">)</span><span class="nx">Disappear</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;Magic!&quot;</span><span class="p">)</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">money</span> <span class="o">:=</span> <span class="nx">Money</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
</span><span class='line'><span class="nx">money</span><span class="p">.</span><span class="nx">Disappear</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>


<h3>Interface</h3>

<p>Go 另外一個很棒的設計是 <code>interface</code> 來實現多型。基本上 <code>interface</code> 的概念是，假設你會作某些事，我就把你當這個對象。</p>

<p>例如我們定義一個 <code>interface</code> 叫 <code>RD</code>，條件是要會 <code>Coding</code>：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">type</span> <span class="nx">RD</span> <span class="kd">interface</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">Coding</span><span class="p">()</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>然後我們幫剛剛的 <code>F2E</code> 加一個 <code>Coding()</code> 的方法，他就滿足了 <code>RD</code> 這個 <code>interface</code>：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">func</span> <span class="p">(</span><span class="nx">f2e</span> <span class="o">*</span><span class="nx">F2E</span><span class="p">)</span><span class="nx">Coding</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;I write cool css and javascript!&quot;</span><span class="p">)</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// RD(會 Coding) 可以工作</span>
</span><span class='line'><span class="kd">func</span> <span class="nx">work</span><span class="p">(</span><span class="nx">rd</span> <span class="nx">RD</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">rd</span><span class="p">.</span><span class="nx">Coding</span><span class="p">()</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="nx">work</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">aar0n</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>所以我們可以再從 <code>Human</code> 繼承出一個 <code>Backend</code> 出來，一樣實作 <code>Coding()</code> 方法，他也就符合了 <code>RD</code> 這個 <code>interface</code>，一樣可以丟去工作。</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">type</span> <span class="nx">Backend</span> <span class="kd">struct</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">Human</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'><span class="kd">func</span> <span class="p">(</span><span class="nx">backend</span> <span class="o">*</span><span class="nx">Backend</span><span class="p">)</span><span class="nx">Coding</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;I write Rails applications!&quot;</span><span class="p">)</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 凡是 RD(會 Coding) 就給我去工作</span>
</span><span class='line'><span class="nx">ilake</span> <span class="o">:=</span> <span class="nx">Backend</span><span class="p">{</span><span class="nx">Human</span><span class="p">{</span><span class="s">&quot;ilake&quot;</span><span class="p">,</span> <span class="mi">30</span><span class="p">}}</span>
</span><span class='line'><span class="nx">work</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">aar0n</span><span class="p">)</span>
</span><span class='line'><span class="nx">work</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">ilake</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>更有趣的地方是，<code>interface</code> 也可以組合(繼承)。例如我們再定義一個 <code>interface</code> 叫 <code>Designer</code> 條件是會 <code>Design()</code>：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="kd">type</span> <span class="nx">Designer</span> <span class="kd">interface</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">Design</span><span class="p">()</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>那我們就可以稱「又會 Design 又會 Coding」的人叫全端工程師(FullStack)：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="c1">// 同時滿足 RD 跟 Designer 兩個 interface</span>
</span><span class='line'><span class="kd">type</span> <span class="nx">FullStack</span> <span class="kd">interface</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">RD</span>
</span><span class='line'>  <span class="nx">Designer</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 既是全端工程師，又會唱歌跳舞，那你肯定是 CTO 了</span>
</span><span class='line'><span class="kd">type</span> <span class="nx">CTO</span> <span class="kd">interface</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">FullStack</span>
</span><span class='line'>  <span class="nx">Dance</span><span class="p">()</span>
</span><span class='line'>  <span class="nx">Sing</span><span class="p">()</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<h4>Interface 其他技巧</h4>

<p>在 Go 裡面，所有的資料型態都滿足「空的 interface」 <code>interface{}</code>。所以如果我們真的有一個 <code>slice</code> 或方法，裡面要塞可能是任何型態的變數，我們就可以使用「空 interface」：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='go'><span class='line'><span class="c1">// 隨便你傳</span>
</span><span class='line'><span class="kd">func</span> <span class="nx">DoSomething</span><span class="p">(</span><span class="nx">obj</span> <span class="kd">interface</span><span class="p">{})</span> <span class="p">{</span>
</span><span class='line'>  <span class="c1">//...</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 隨便你塞</span>
</span><span class='line'><span class="nx">ary</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kd">interface</span><span class="p">{},</span> <span class="mi">2</span><span class="p">)</span>
</span><span class='line'><span class="nx">ary</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="p">=</span> <span class="mi">1</span>
</span><span class='line'><span class="nx">ary</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">=</span> <span class="s">&quot;string&quot;</span>
</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[我的 Golang Vim 配置]]></title>
    <link href="http://blog.hsatac.net/2013/08/my-vimrc-for-golang/"/>
    <updated>2013-08-31T19:09:00+08:00</updated>
    <id>http://blog.hsatac.net/2013/08/my-vimrc-for-golang</id>
    <content type="html"><![CDATA[<p>去年九月發過一篇<a href="http://blog.hsatac.net/2012/09/golang-ides/">開發 Golang 的 IDEs</a>，不過現在我基本上都使用 vim 開發了， update 一下我現在的配置。所有資料都可以在我的 <a href="https://github.com/hSATAC/vimrc">vimrc</a> 找到。</p>

<h3>插件</h3>

<p>和之前一樣最主要還是靠 golang 官方 plugin 以及 <a href="https://github.com/nsf/gocode">gocode</a> 這兩個，多加了一個 <a href="https://github.com/jstemmer/gotags">gotags</a> 取代 ctags，這個超好用的。</p>

<p>由於 golang 官方 plugin 和 gocode 的 plugin 都沒有抽出單獨的 repo，不方便 vundle 或 pathogen 使用，所以我之前就有自己抽出方便安裝的 repo：</p>

<ul>
<li><a href="https://github.com/golangtw/go.vim">Golang 官方 vim plugin</a></li>
<li><a href="https://github.com/golangtw/gocode.vim">Gocode vim plugin</a></li>
</ul>


<p>如果搭配 <a href="https://github.com/ervandew/supertab">supertab</a> 可以設 <code>let g:SuperTabDefaultCompletionType = "context"</code> 來 trigger gocode 自動補完。</p>

<p><img src="http://blog.hsatac.net/images/vimrc_golang/gocode.png" alt="gocode" /></p>

<!--more-->


<p><a href="https://github.com/jstemmer/gotags">gotags</a> 的部份則是要搭配 <a href="http://majutsushi.github.com/tagbar/">tagbar</a> 來使用，抓的非常準(感謝 go 天生內建語法樹 parser)，而且安排的順序就完全是建議的順序，按這個順序組織你的程式碼就對了。</p>

<p><img src="http://blog.hsatac.net/images/vimrc_golang/gotags.png" alt="gotags" /></p>

<p><a href="https://github.com/bling/vim-airline">vim-airline</a> 現在也內建 <a href="http://majutsushi.github.com/tagbar/">tagbar</a> 支援了，所以可以直接在狀態列看到現在在程式的什麼區塊。</p>

<h3>設定與巨集</h3>

<p><code>go fmt</code> 實在是一個非常優秀的設計，不用再為了 style 的瑣事吵半天。在存檔的時候順手執行 <code>go fmt</code> 吧！安裝過官方插件的話，只要加上 <code>au FileType go au BufWritePre &lt;buffer&gt; Fmt</code> 即可。</p>

<p>由於 golang 的哲學是，不需要的程式碼就不要，所以沒用到的變數或 import package 都會被當成 error 處理。導致常常改一改就要回到檔案最上方處理 import。多利用 <code>:Import &lt;package&gt;</code> 跟 <code>:Drop &lt;package&gt;</code> 兩個命令可以簡化這個步驟。</p>

<p>測試的部份，原來我就有使用 <a href="https://github.com/jgdavey/tslime.vim">tslime</a> 這個插件，就多 bind 一個指令 <code>au FileType go map &lt;leader&gt;t :Tmux go test&lt;CR&gt;</code> 把 <code>go test</code> 指令送到 tmux 其他 window。目前這樣就能滿足我的需求。</p>

<p><img src="http://blog.hsatac.net/images/vimrc_golang/gotest.png" alt="gotest" /></p>

<h3>參考資料</h3>

<p>還有更多 plugin 可以參考這篇：<a href="http://0value.com/my-Go-centric-Vim-setup">My (Go-centric) Vim Setup</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[The Art of Readable Code 讀書筆記]]></title>
    <link href="http://blog.hsatac.net/2013/08/the-art-of-readable-code/"/>
    <updated>2013-08-07T22:07:00+08:00</updated>
    <id>http://blog.hsatac.net/2013/08/the-art-of-readable-code</id>
    <content type="html"><![CDATA[<p>這次公司的讀書分享會我被指定報告這本 <a href="http://shop.oreilly.com/product/9780596802301.do">The Art of Readable Code</a>。</p>

<p><img src="http://blog.hsatac.net/images/art_of_readable_code/cover.jpg" alt="The Art of Readable Code Cover" /></p>

<p>這本書我以前就看過英文本，這次借這個機會重新複習整理了一下，又有新的收穫。把一些我覺得比較重要的點筆記下來，太基礎或可能用不太到的這邊就省略了。很推薦各位翻一下這本，是一本很值得一讀的小書。</p>

<!--more-->


<h2>摘要</h2>

<p>可讀性就是易於理解(最短時間理解)。</p>

<p>把寫程式從「會動就好」(寫給機器讀)，提升到「表明自己的意圖」(寫給人讀)的層次。</p>

<p>試著思考，閱讀這段程式的人會用怎樣的脈絡來理解你的程式碼。</p>

<h2>Part 1. 表層改善</h2>

<h3>富含資訊的名稱</h3>

<ul>
<li>選擇詞彙</li>
</ul>


<p><code>FetchPage</code> 比 <code>GetPage</code> 要好，表達出從網路拉資料的行為。</p>

<p>可以使用比 <code>Stop</code> 更清楚的名稱，例如不能復原的用 <code>Kill</code>，能復原的用 <code>Pause</code>, <code>Resume</code>。</p>

<ul>
<li><p>避免使用 tmp, ret, i, j, k (除非真的是要交換變數)</p></li>
<li><p>優先使用具體名稱而非抽象名稱</p></li>
</ul>


<p><code>ServerCanStart</code> 抽象</p>

<p><code>ServerCanListenOnPort</code> 具體</p>

<ul>
<li>在名稱中加入額外資訊</li>
</ul>


<p><code>start</code> => <code>start_ms</code></p>

<p><code>size</code> => <code>size_mb</code></p>

<ul>
<li>加入其他重要屬性</li>
</ul>


<p><code>password</code> => <code>plaintext_password</code></p>

<p><code>comment</code> => <code>unescaped_comment</code></p>

<p>較小範圍適合較短變數名稱</p>

<h3>不被誤解的名稱</h3>

<p><code>Filter()</code> 是包含還是排除？ <code>Select()</code>, <code>Exclude()</code> 更清楚</p>

<p><code>start, stop</code> 有沒有包含？ <code>first, last</code> 清楚表明有包含</p>

<ul>
<li>符合使用者的預期</li>
</ul>


<p><code>get*()</code> 開頭預期是輕量 getter，不要做耗時運算。</p>

<p><code>size()</code> 預期輕量，要計算可改為 <code>computeSize()</code></p>

<h3>美學</h3>

<ul>
<li><p>排版</p></li>
<li><p>有意義的順序</p></li>
</ul>


<p><code>first_name, last_name, email</code></p>

<p><code>first_name, email,...last_name</code></p>

<ul>
<li><p>風格一致性</p></li>
<li><p>區分程式碼段落</p></li>
</ul>


<h3>註解</h3>

<ul>
<li><p>註解自己的想法</p></li>
<li><p>註解程式碼缺陷</p></li>
<li><p>註解常數 (常數的設定通常都有其原因和意義)</p></li>
</ul>


<p><code>NUM_THREADS</code> 可能是根據 CPU 核心數推算出來。</p>

<ul>
<li>為讀者設想(可能需要額外思考)</li>
</ul>


<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='obj-c'><span class='line'><span class="n">NSSet</span> <span class="o">*</span><span class="n">selectedAdvisorIDs</span>  <span class="o">=</span> <span class="n">_filterVC</span><span class="p">.</span><span class="n">selectedAdvisors</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">for</span> <span class="p">(</span><span class="n">MBAdvisor</span> <span class="o">*</span><span class="n">advisor</span> <span class="k">in</span> <span class="p">[</span><span class="n">self</span> <span class="n">currentGroup</span><span class="p">].</span><span class="n">advisors</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">([</span><span class="n">selectedAdvisorIDs</span> <span class="nl">containsObject:</span><span class="n">advisor</span><span class="p">.</span><span class="n">ID</span><span class="p">])</span> <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_filteredAdvisors</span> <span class="nl">addObject:</span><span class="n">advisor</span><span class="p">];</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 為什麼不直接從 selectedAdvisorIDs 迴圈作處理？</span>
</span><span class='line'><span class="k">for</span> <span class="p">(</span><span class="n">NSNumber</span> <span class="o">*</span><span class="n">advisorID</span> <span class="k">in</span> <span class="n">selectedAdvisorIDs</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="p">[</span><span class="n">_filteredAdvisors</span> <span class="nl">addObject:</span><span class="p">[</span><span class="n">self</span> <span class="nl">findAdvisorByID:</span><span class="n">advisorID</span><span class="p">]];</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 為了維持原本 Advisor 的順序</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>註明可能的陷阱</li>
</ul>


<h3>讓註解精確與簡潔</h3>

<ul>
<li>精確描述函數行為</li>
</ul>


<p><code>傳回檔案行數</code> 可能有很多狀況，改為 <code>計算檔案中 \n 個數</code> 更為精確。</p>

<ul>
<li><p>使用代表性的輸入輸出範例 (rdoc)</p></li>
<li><p>函數參數名稱註解 (named parameters)</p></li>
</ul>


<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="n">Connect</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//=&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="n">Connect</span><span class="p">(</span><span class="n">timeout_ms</span> <span class="o">=</span> <span class="mi">10</span><span class="p">,</span> <span class="n">use_encryption</span> <span class="o">=</span> <span class="nb">false</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="c1">//=&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="n">Connect</span><span class="p">(</span><span class="cm">/* timeout_ms = */</span> <span class="mi">10</span><span class="p">,</span> <span class="cm">/* use_encryption = */</span> <span class="nb">false</span><span class="p">);</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>使用訊息密集的詞彙</li>
</ul>


<p><code>cache</code>, <code>singleton</code></p>

<h2>Part 2. 簡化迴圈與邏輯</h2>

<h3>提高控制流程與可讀性</h3>

<ul>
<li>if/else 區塊順序

<ol>
<li> 先肯定而非否定的情況</li>
<li> 先簡單的情況</li>
<li> 先<em>有趣</em>或明顯的情況</li>
</ol>
</li>
</ul>


<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">url</span><span class="p">.</span><span class="n">HasQueryParameter</span><span class="p">(</span><span class="s">&quot;expand_all&quot;</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>     <span class="n">response</span><span class="p">.</span><span class="n">Render</span><span class="p">(</span><span class="n">items</span><span class="p">);</span>
</span><span class='line'>     <span class="p">...</span>
</span><span class='line'><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>     <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">items</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>         <span class="n">items</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Expand</span><span class="p">();</span>
</span><span class='line'>     <span class="p">}</span>
</span><span class='line'>     <span class="p">...</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// 看到 expand_all 會一直想著 expand_all =&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="n">url</span><span class="p">.</span><span class="n">HasQueryParameter</span><span class="p">(</span><span class="s">&quot;expand_all&quot;</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>     <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">items</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>         <span class="n">items</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Expand</span><span class="p">();</span>
</span><span class='line'>     <span class="p">}</span>
</span><span class='line'>     <span class="p">...</span>
</span><span class='line'><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>     <span class="n">response</span><span class="p">.</span><span class="n">Render</span><span class="p">(</span><span class="n">items</span><span class="p">);</span>
</span><span class='line'>     <span class="p">...</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li><p>盡早返回 (return)</p></li>
<li><p>消除迴圈中的巢狀結構 (continue)</p></li>
</ul>


<h3>分解巨大表示式</h3>

<ul>
<li>解釋性變數</li>
</ul>


<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">&#39;:&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="o">==</span> <span class="s">&quot;root&quot;</span><span class="p">:</span>
</span><span class='line'>
</span><span class='line'><span class="c">#=&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="n">username</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">&#39;:&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span><span class='line'><span class="k">if</span> <span class="n">username</span> <span class="o">==</span> <span class="s">&quot;root&quot;</span><span class="p">:</span>
</span></code></pre></td></tr></table></div></figure>


<h3>變數與可讀性</h3>

<ul>
<li>消除變數</li>
</ul>


<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">now</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span><span class='line'><span class="n">root_message</span><span class="o">.</span><span class="n">last_view_time</span> <span class="o">=</span> <span class="n">now</span>
</span><span class='line'>
</span><span class='line'><span class="c"># =&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="n">root_message</span><span class="o">.</span><span class="n">last_view_time</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>縮減變數範圍</li>
</ul>


<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='obj-c'><span class='line'><span class="n">UIButton</span> <span class="o">*</span><span class="n">sideMenuButton</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIButton</span> <span class="nl">buttonWithType:</span><span class="n">UIButtonTypeCustom</span><span class="p">];</span>
</span><span class='line'><span class="n">sideMenuButton</span><span class="p">.</span><span class="n">bounds</span> <span class="o">=</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">20</span><span class="p">);</span>
</span><span class='line'><span class="p">[</span><span class="n">sideMenuButton</span> <span class="nl">setImage:</span><span class="p">[</span><span class="n">UIImage</span> <span class="nl">imageNamed:</span><span class="s">@&quot;sidemenu_icon.png&quot;</span><span class="p">]</span> <span class="nl">forState:</span><span class="n">UIControlStateNormal</span><span class="p">];</span>
</span><span class='line'><span class="p">[</span><span class="n">sideMenuButton</span> <span class="nl">addTarget:</span><span class="n">self</span> <span class="nl">action:</span><span class="k">@selector</span><span class="p">(</span><span class="n">toggleRightPanelAction</span><span class="p">)</span> <span class="nl">forControlEvents:</span><span class="n">UIControlEventTouchUpInside</span><span class="p">];</span>
</span><span class='line'><span class="n">self</span><span class="p">.</span><span class="n">navigationItem</span><span class="p">.</span><span class="n">rightBarButtonItem</span> <span class="o">=</span> <span class="p">[[</span><span class="n">UIBarButtonItem</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithCustomView:</span><span class="n">sideMenuButton</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// =&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// scoped temp variables. last line will be returned.</span>
</span><span class='line'><span class="n">self</span><span class="p">.</span><span class="n">navigationItem</span><span class="p">.</span><span class="n">rightBarButtonItem</span> <span class="o">=</span> <span class="p">({</span>
</span><span class='line'>  <span class="n">UIButton</span> <span class="o">*</span><span class="n">sideMenuButton</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIButton</span> <span class="nl">buttonWithType:</span><span class="n">UIButtonTypeCustom</span><span class="p">];</span>
</span><span class='line'>  <span class="n">sideMenuButton</span><span class="p">.</span><span class="n">bounds</span> <span class="o">=</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">20</span><span class="p">);</span>
</span><span class='line'>  <span class="p">[</span><span class="n">sideMenuButton</span> <span class="nl">setImage:</span><span class="p">[</span><span class="n">UIImage</span> <span class="nl">imageNamed:</span><span class="s">@&quot;sidemenu_icon.png&quot;</span><span class="p">]</span> <span class="nl">forState:</span><span class="n">UIControlStateNormal</span><span class="p">];</span>
</span><span class='line'>  <span class="p">[</span><span class="n">sideMenuButton</span> <span class="nl">addTarget:</span><span class="n">self</span> <span class="nl">action:</span><span class="k">@selector</span><span class="p">(</span><span class="n">toggleRightPanelAction</span><span class="p">)</span> <span class="nl">forControlEvents:</span><span class="n">UIControlEventTouchUpInside</span><span class="p">];</span>
</span><span class='line'>  <span class="p">[[</span><span class="n">UIBarButtonItem</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithCustomView:</span><span class="n">sideMenuButton</span><span class="p">];</span>
</span><span class='line'><span class="p">});</span>
</span></code></pre></td></tr></table></div></figure>


<ul>
<li>減少變數改變</li>
</ul>


<h2>Part 3. 重新組織程式碼</h2>

<h3>抽離不相關子問題</h3>

<ul>
<li>避免過猶不及</li>
</ul>


<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">user_info</span> <span class="o">=</span> <span class="p">{</span> <span class="s">&quot;username&quot;</span><span class="p">:</span> <span class="s">&quot;...&quot;</span><span class="p">,</span> <span class="s">&quot;password&quot;</span><span class="p">:</span> <span class="s">&quot;...&quot;</span> <span class="p">}</span>
</span><span class='line'><span class="n">user_str</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">user_info</span><span class="p">)</span>
</span><span class='line'><span class="n">cipher</span> <span class="o">=</span> <span class="n">Cipher</span><span class="p">(</span><span class="s">&quot;aes_128_cbc&quot;</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="n">PRIVATE_KEY</span><span class="p">,</span> <span class="n">init_vector</span><span class="o">=</span><span class="n">INIT_VECTOR</span><span class="p">,</span> <span class="n">op</span><span class="o">=</span><span class="n">ENCODE</span><span class="p">)</span>
</span><span class='line'><span class="n">encrypted_bytes</span> <span class="o">=</span> <span class="n">cipher</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">user_str</span><span class="p">)</span>
</span><span class='line'><span class="n">encrypted_bytes</span> <span class="o">+=</span> <span class="n">cipher</span><span class="o">.</span><span class="n">final</span><span class="p">()</span> <span class="c"># flush out the current 128 bit block</span>
</span><span class='line'><span class="n">url</span> <span class="o">=</span> <span class="s">&quot;http://example.com/?user_info=&quot;</span> <span class="o">+</span> <span class="n">base64</span><span class="o">.</span><span class="n">urlsafe_b64encode</span><span class="p">(</span><span class="n">encrypted_bytes</span><span class="p">)</span>
</span><span class='line'><span class="err">…</span>
</span><span class='line'>
</span><span class='line'><span class="c">#=&gt;</span>
</span><span class='line'>
</span><span class='line'><span class="k">def</span> <span class="nf">url_safe_encrypt</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
</span><span class='line'>  <span class="n">obj_str</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
</span><span class='line'>  <span class="n">cipher</span> <span class="o">=</span> <span class="n">Cipher</span><span class="p">(</span><span class="s">&quot;aes_128_cbc&quot;</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="n">PRIVATE_KEY</span><span class="p">,</span> <span class="n">init_vector</span><span class="o">=</span><span class="n">INIT_VECTOR</span><span class="p">,</span> <span class="n">op</span><span class="o">=</span><span class="n">ENCODE</span><span class="p">)</span>
</span><span class='line'>  <span class="n">encrypted_bytes</span> <span class="o">=</span> <span class="n">cipher</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">obj_str</span><span class="p">)</span>
</span><span class='line'>  <span class="n">encrypted_bytes</span> <span class="o">+=</span> <span class="n">cipher</span><span class="o">.</span><span class="n">final</span><span class="p">()</span> <span class="c"># flush out the current 128 bit block</span>
</span><span class='line'>  <span class="k">return</span> <span class="n">base64</span><span class="o">.</span><span class="n">urlsafe_b64encode</span><span class="p">(</span><span class="n">encrypted_bytes</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="n">user_info</span> <span class="o">=</span> <span class="p">{</span> <span class="s">&quot;username&quot;</span><span class="p">:</span> <span class="s">&quot;...&quot;</span><span class="p">,</span> <span class="s">&quot;password&quot;</span><span class="p">:</span> <span class="s">&quot;...&quot;</span> <span class="p">}</span>
</span><span class='line'><span class="n">url</span> <span class="o">=</span> <span class="s">&quot;http://example.com/?user_info=&quot;</span> <span class="o">+</span> <span class="n">url_safe_encrypt</span><span class="p">(</span><span class="n">user_info</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="c">#=&gt; this went too far…</span>
</span><span class='line'>
</span><span class='line'><span class="n">user_info</span> <span class="o">=</span> <span class="p">{</span> <span class="s">&quot;username&quot;</span><span class="p">:</span> <span class="s">&quot;...&quot;</span><span class="p">,</span> <span class="s">&quot;password&quot;</span><span class="p">:</span> <span class="s">&quot;...&quot;</span> <span class="p">}</span>
</span><span class='line'><span class="n">url</span> <span class="o">=</span> <span class="s">&quot;http://example.com/?user_info=&quot;</span> <span class="o">+</span> <span class="n">url_safe_encrypt_obj</span><span class="p">(</span><span class="n">user_info</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="k">def</span> <span class="nf">url_safe_encrypt_obj</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
</span><span class='line'>  <span class="n">obj_str</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
</span><span class='line'>  <span class="k">return</span> <span class="n">url_safe_encrypt_str</span><span class="p">(</span><span class="n">obj_str</span><span class="p">)</span>
</span><span class='line'><span class="k">def</span> <span class="nf">url_safe_encrypt_str</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span><span class='line'>  <span class="n">encrypted_bytes</span> <span class="o">=</span> <span class="n">encrypt</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span><span class='line'>  <span class="k">return</span> <span class="n">base64</span><span class="o">.</span><span class="n">urlsafe_b64encode</span><span class="p">(</span><span class="n">encrypted_bytes</span><span class="p">)</span>
</span><span class='line'><span class="k">def</span> <span class="nf">encrypt</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span><span class='line'>  <span class="n">cipher</span> <span class="o">=</span> <span class="n">make_cipher</span><span class="p">()</span>
</span><span class='line'>  <span class="n">encrypted_bytes</span> <span class="o">=</span> <span class="n">cipher</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span><span class='line'>  <span class="n">encrypted_bytes</span> <span class="o">+=</span> <span class="n">cipher</span><span class="o">.</span><span class="n">final</span><span class="p">()</span> <span class="c"># flush out any remaining bytes</span>
</span><span class='line'>  <span class="k">return</span> <span class="n">encrypted_bytes</span>
</span><span class='line'><span class="k">def</span> <span class="nf">make_cipher</span><span class="p">():</span>
</span><span class='line'>  <span class="k">return</span> <span class="n">Cipher</span><span class="p">(</span><span class="s">&quot;aes_128_cbc&quot;</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="n">PRIVATE_KEY</span><span class="p">,</span> <span class="n">init_vector</span><span class="o">=</span><span class="n">INIT_VECTOR</span><span class="p">,</span> <span class="n">op</span><span class="o">=</span><span class="n">ENCODE</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<h3>撰寫較少程式碼</h3>

<ul>
<li><p>可讀性最高的程式碼就是完全沒有程式碼</p></li>
<li><p>不開發那些功能 &ndash; 不會需要</p></li>
<li><p>詢問與分解需求</p></li>
<li><p>熟悉你的函式庫</p></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[用 Commandline 發 github pull request]]></title>
    <link href="http://blog.hsatac.net/2013/07/github-pull-request-from-commandline/"/>
    <updated>2013-07-16T12:40:00+08:00</updated>
    <id>http://blog.hsatac.net/2013/07/github-pull-request-from-commandline</id>
    <content type="html"><![CDATA[<p>現在團隊使用 github 來作 code hosting, 利用 pull request 機制來做 code review。比以往自己架 gitosis 和 redmine 的方式更方便好用。</p>

<p>不過 programmer 天性懶惰，日子一久對於要開 github 網頁用滑鼠選 branch 發 pull request 的操作感到厭倦，能自動化的東西就懶得自己按按鈕啦！</p>

<p>使用 <a href="https://github.com/github/hub">hub</a> 就可以用 commandline 進行各種 github 的操作。</p>

<!--more-->


<p>用 <code>homebrew</code> 或 <code>gem</code> 都可以進行安裝。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>brew install hub
</span><span class='line'>gem install hub</span></code></pre></td></tr></table></div></figure>


<p>我們 team 開發流程是 feature branch 開發完畢後 push 到專案 remote 發 pull request，所以我在 <code>.bash_profile</code> 加了下面這個 function：</p>

<div><script src='https://gist.github.com/5591270.js'></script>
<noscript><pre><code></code></pre></noscript></div>


<blockquote><p>從 team 的 (現在目錄所在 branch) 發 pull request 到 team 的 (develop) branch</p></blockquote>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Xray-rails 與 tmux, vim 整合]]></title>
    <link href="http://blog.hsatac.net/2013/07/xray-rails-tmux-vim-integration/"/>
    <updated>2013-07-09T22:00:00+08:00</updated>
    <id>http://blog.hsatac.net/2013/07/xray-rails-tmux-vim-integration</id>
    <content type="html"><![CDATA[<p>剛好又開新專案了，來介紹一下 <a href="https://github.com/brentd/xray-rails">Xray-rails</a> 與 tmux, vim 的整合。</p>

<p><a href="https://github.com/brentd/xray-rails">Xray-rails</a> 是一層 rack middleware，會 inject 你的 view 和 javascript 檔案，只要在開發模式按快速鍵 <code>⌘ + ⇧ + x</code> 就會開啟一層 overlay，讓你很清楚的看出現在的畫面由哪些 view, partial, controller 生成，更方便的是只要一點畫面，即可在編輯器中開啟該檔案，大大降低 trace 程式碼的時間。</p>

<p><img src="https://dl.dropboxusercontent.com/u/156655/xray-screenshot.png" alt="Xray-rails" /></p>

<!--more-->


<p><a href="https://github.com/brentd/xray-rails">Xray-rails</a> 預設的編輯器是 <a href="http://www.sublimetext.com/2">Sublime Text 2</a> (<code>/usr/local/bin/subl</code>)。可以透過 overlay 右下角的設定圖示、或者自己新增 <code>~/.xrayconfig</code> 檔案來設定你使用的編輯器。</p>

<p>我平常使用 <a href="https://github.com/aziz/tmuxinator">Tmuxinator</a> 來管理我的專案和 tmux, 每個專案有自己的 tmux session，讓我可以快速在不同專案的開發環境之間切換。</p>

<p>我的 <code>~/.xrayconfig</code> 也改成透過 tmux 傳送指令給我的 vim，範例設定檔如下：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>:editor: "/usr/local/bin/tmux send -t openapply:editor ESCAPE :tabe $file ENTER"</span></code></pre></td></tr></table></div></figure>


<p><code>openapply</code> 是我的專案 tmux session 名稱，而 <code>editor</code> 是該 session 的 window 名稱，專門用來開啟 vim 編輯檔案。</p>

<p>但問題來了，我每一個專案都有自己獨立的 tmux session，這樣每次切換專案的時候我都要修改 <code>~/.xrayconfig</code> 實在很不方便，所以希望能在每一個專案底下放自己的 <code>.xrayconfig</code>。</p>

<p class='info info' data-title='Update 2013/08/14'>這個 Pull request 已被 upstream 收下，可以直接使用官方 git repo。</p>


<p>這個功能已經<a href="https://github.com/brentd/xray-rails/issues/21">提案給原作者同意</a>，也送了 <a href="https://github.com/brentd/xray-rails/pull/23">pull request</a>，不過還沒被 merge 回主幹，如果現在有需要這個功能的朋友可以暫時先使用我修改的 fork。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>  gem 'xray-rails', :git =&gt; 'https://github.com/hSATAC/xray-rails.git',
</span><span class='line'>                    :branch =&gt; 'feature/project_specific_config'</span></code></pre></td></tr></table></div></figure>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[RSpec-Given 與 RSpec-Spies]]></title>
    <link href="http://blog.hsatac.net/2013/06/rspec-given-rspec-spies/"/>
    <updated>2013-06-30T17:53:00+08:00</updated>
    <id>http://blog.hsatac.net/2013/06/rspec-given-rspec-spies</id>
    <content type="html"><![CDATA[<h2>RSpec/Given</h2>

<p><a href="https://github.com/jimweirich/rspec-given">rspec-given</a> 其實是這次去新加坡 <a href="http://www.reddotrubyconf.com/">RedDotRubyConf</a> 聽 rake 的作者 <a href="https://twitter.com/jimweirich">jimweirich</a> 介紹的。</p>

<p>乍看之下只是一個 syntax sugar，但實際用起來非常有幫助，可以有效的協助你寫出乾淨漂亮的測試。</p>

<p><a href="https://github.com/jimweirich/rspec-given">rspec-given</a> 提供了 <code>Given</code>, <code>Then</code>, <code>When</code> 三個關鍵字以及其他一些額外的功能。 <code>Given</code> 類似原本的 <code>let</code>，而 <code>it</code> 則拆成 <code>Then</code> 和 <code>When</code>。</p>

<p>原本用 <code>it</code> 來寫測試，一個 <code>it</code> 裡面容易越寫愈多，越寫越肥，而且執行的程式碼和 assertion 混在一起，不容易閱讀。</p>

<p>用 <code>Given</code> 定義需要的東西、 <code>When</code> 寫實際執行的程式碼、 <code>Then</code> 放 assertion，這樣可以很方便、清楚的組織你的測試程式碼。</p>

<!--more-->


<p>此外還有 <code>And</code> 可以搭配 <code>Then</code> 使用，以及一個比較特別的 <code>Invariant</code>：當每次 <code>Then</code> 被執行到的時候都會跑這個 assertion。</p>

<h2>RSpec-Spies</h2>

<p>我們現在把測試分很明顯的三個區塊 <code>Given</code>, <code>Then</code>, <code>When</code> 以後，就會碰到一個問題叫 <code>should_receive</code>。</p>

<p>以往 <code>should_receive</code> 這件事是跟在 mock method 一起做的，這語句本身就同時有 <code>Then</code> 和 <code>When</code> 的涵義在。而且整段測試會變成前面有 assertion, 中間一段執行程式碼，後面又是 assertion ，使的整個閱讀性大大降低。</p>

<p>並且，我們一般人思考的順序是「我做了什麼事」 → 「得到什麼結果」。而 <code>should_receive</code> 是要寫在真正執行的程式碼前面的，跟我們思考的順序恰好相反，容易混淆。所以我們需要有一個語法能把 mock 跟 assertion(should_receive) 這兩件事分開。</p>

<p>這時候就可以使用 <a href="https://github.com/technicalpickles/rspec-spies">rspec-spies</a>。</p>

<p>這樣我們就可以把 <code>have_receieved</code> 當成一般的 matcher 搬到 <code>Then</code> 區塊，整段測試就很清楚明瞭。</p>

<p>更好的是這個語法在 RSpec 2.14 就會內建支援，所以現在先使用這個 gem ，等 RSpec 2.14 正式 release 以後再拿掉即可無縫銜接。</p>

<h2>延伸閱讀</h2>

<ul>
<li><a href="https://github.com/jimweirich/rspec-given/wiki/Tutorial">RSpec/Given Tutorial</a></li>
<li><a href="http://xunitpatterns.com/Test%20Spy.html">Test Spy Pattern</a></li>
</ul>

]]></content>
  </entry>
  
</feed>
