<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[CapsLock, Studio 大寫鎖定工作室]]></title><description><![CDATA[一個技術文、廢文、工作記事的地方]]></description><link>https://blog.capslock.tw/</link><image><url>https://blog.capslock.tw/favicon.png</url><title>CapsLock, Studio 大寫鎖定工作室</title><link>https://blog.capslock.tw/</link></image><generator>Ghost 2.37</generator><lastBuildDate>Fri, 08 Aug 2025 01:05:55 GMT</lastBuildDate><atom:link href="https://blog.capslock.tw/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[微軟 Teams 真的很難用，難以忍受 / 細數 Teams 問題]]></title><description><![CDATA[<!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2023/08/0.png" class="kg-image"><figcaption>さあ、お前の Bug を数えろ！/ 來細數你的 Bug 吧</figcaption></figure><!--kg-card-end: image--><p>Bug 太多，bug 以外的易用性問題也很多</p><h1 id="-">開發相關問題</h1><h3 id="--1">開發得有付費帳號</h3><ul><li> 一定得要付費帳號才可以著手寫 POC，沒錯，就只是簡單的 POC 都得花錢，如果公司限制開發人員進行測試，那你沒辦法從 0 開始簡單建個可以用的測試環境，因為免費的 Teams 只有 Chat 功能</li></ul><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://blog.capslock.tw/content/images/2023/08/Screenshot-2023-08-28-at-11.27.52-AM.png" class="kg-image"></figure><!--kg-card-end: image--><h3 id="--2">開發流程說明混亂</h3><ul><li>以 TypeScript 開發 bot 為例，<a href="https://learn.microsoft.com/en-us/microsoftteams/platform/sbs-gs-notificationbot?tabs=vscode">微軟官網文件 - 如何用 JavaScript 建立 app</a> 要你裝一堆 VSCode plugin，然後從 plugin 裡 New app (在你登入 VSCode</li></ul>]]></description><link>https://blog.capslock.tw/microsoft-teams-issues/</link><guid isPermaLink="false">64ec1058cabaf60001088c03</guid><category><![CDATA[general]]></category><category><![CDATA[slack]]></category><category><![CDATA[microsoft teams]]></category><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Mon, 28 Aug 2023 04:34:18 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2023/08/87e41231572d50ae2335d5a19975ec2d-1.jpeg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2023/08/0.png" class="kg-image" alt="微軟 Teams 真的很難用，難以忍受 / 細數 Teams 問題"><figcaption>さあ、お前の Bug を数えろ！/ 來細數你的 Bug 吧</figcaption></figure><!--kg-card-end: image--><img src="https://blog.capslock.tw/content/images/2023/08/87e41231572d50ae2335d5a19975ec2d-1.jpeg" alt="微軟 Teams 真的很難用，難以忍受 / 細數 Teams 問題"><p>Bug 太多，bug 以外的易用性問題也很多</p><h1 id="-">開發相關問題</h1><h3 id="--1">開發得有付費帳號</h3><ul><li> 一定得要付費帳號才可以著手寫 POC，沒錯，就只是簡單的 POC 都得花錢，如果公司限制開發人員進行測試，那你沒辦法從 0 開始簡單建個可以用的測試環境，因為免費的 Teams 只有 Chat 功能</li></ul><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://blog.capslock.tw/content/images/2023/08/Screenshot-2023-08-28-at-11.27.52-AM.png" class="kg-image" alt="微軟 Teams 真的很難用，難以忍受 / 細數 Teams 問題"></figure><!--kg-card-end: image--><h3 id="--2">開發流程說明混亂</h3><ul><li>以 TypeScript 開發 bot 為例，<a href="https://learn.microsoft.com/en-us/microsoftteams/platform/sbs-gs-notificationbot?tabs=vscode">微軟官網文件 - 如何用 JavaScript 建立 app</a> 要你裝一堆 VSCode plugin，然後從 plugin 裡 New app (在你登入 VSCode plugin 的時候還會先遇到一次帳號權限問題，因為需要申請 Developer account，但他不會告訴你，只會說沒權限)</li><li><a href="https://learn.microsoft.com/en-us/microsoftteams/platform/sbs-teams-conversation-bot?tutorial-step=2">微軟官方文件 - 如何註冊 Bot</a> 教你從 Azure Portal 登入註冊並取得 credentials，而 github 範例引導你到 <a href="https://learn.microsoft.com/en-us/azure/bot-service/abs-quickstart?view=azure-bot-service-4.0&amp;tabs=userassigned">位於 Azure AI Bot Service 底下的</a> register 流程，雖然一樣都是 Azure Portal，但是 AI Bot Service 他有自己叫做 Bot Service 的區塊，與列在 Marketplace 的 Azure Bot 非常容易搞混，而實際上，有個 <a href="https://dev.teams.microsoft.com">https://dev.teams.microsoft.com</a> 可以用，流程更短更清楚，但在 Teams bot 相關的開發文件都沒有直接連到 <a href="https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/teams-developer-portal">Teams developer portal 的文件</a></li></ul><h1 id="bug-s-">Bug(s)</h1><ul><li>有機會多翻轉幾次螢幕方向後，landscape 模式的側邊 menu 出現在 portrait 模式，看不到完整訊息</li><li>暗黑模式不知道在什麼情況下變成白的，過一陣子又自己突然好了</li><li>Mac desktop 上開會中的視窗右上角時常按不到，像有個 mask 卡在上面的樣子</li><li>Mac 上的 multi task 切換轉不到 Teams，不管是從 Teams 點 URL 開啟 browser 轉不過去，從其他 app 點 Teams window 轉不過來</li><li>Mac 上的開會視窗有時候會錯位，格線跟人的位置搭不上</li><li>iOS app 裡，Group chat (chat) 的時間顯示有時候會維持 Today 不變，就算今天沒有新訊息一看也會以為有新訊息沒跳通知，實際上實際上是昨天看過的舊訊息</li><li>在 Channel 用 code blocks 貼上 code，indent 會莫名全消失 (space，tab 沒問題)</li><li>如果在 Group chat (chat) 裡面試著用 quote 當代用品把 indent 變成不可明喻的東西，貼 code 本來是 4 spaces 會變成 3 然後中間插個看不到的 invisible character (就算不是拿來當 code blocks 帶用品，貼上任何內容都不應該有有原本貼上預期外的內容)</li><li><strong><s>傳過的訊息，就算 remove 收回，還是可以在 Notifications 裡面看到收回前的訊息</s> (2023/09/04 發現已經修好了)</strong></li><li>...(肯定還會有新的，所以這邊定期有遇到會更新)</li></ul><h1 id="uiux-">UIUX 易用性問題</h1><ul><li>Windows desktop / Mac desktop 都一樣，開 teams 內 tab 的內容只能開一個，沒辦法多工，像是 word 跟 excel 沒辦法同時開，很蠢</li><li>各裝置間訊息同步非常慢，回到工作狀態之後會很長一段時間沒反應，但通常這時候都很急著要回訊息，或是根本要自己重開才會更新 (其實算 Bug?)</li><li>Group chat (chat) 不支援 multi line code blocks，跟 channel 行為不一樣，但很多時候明明就需要 1:1 討論程式</li><li>在 Desktop 上不管是 chat or channel 的編輯文字功能都很難切換，如果從別的地方 copy 內容貼過去是大標題或是夾了背景色或文字有顏色，想切回一般字體大小或顏色就辦不到</li><li>Channel 的 Thread 只要有新的回覆就會被拉在最底，也沒辦法自己選訊息排序方式，時常以為沒有新訊息要看，但實際上是因為新的 Thread 被舊的 Thread 蓋過順序</li><li>Meeting 邀請進來的 Guest 如果是以 Channel 為單位 book 的會議，會沒辦法留言，但是 Chat 可以，也沒辦法，也沒辦法細節調整權限 (邏輯不一致，要自己試過才知道)</li><li>上傳的檔案在不同地方有不同預設邏輯，個人上傳的會到 onedrive，Group chat 裡的會到 sharepoint (person)，Channel 會到 sharepoint (channel)......所以說為什麼會冒出個 onedrive</li><li>檔案命名不能有 slash (/)，又不是 Windows 系統，why not</li><li>各個裝置都一樣就算有 book 行事曆都不會會前通知，當然就沒辦法設定幾分鐘前通知，非常蠢</li><li>在 iOS mobile 上訂行事曆，時間下面的欄位會被自動彈出的 Date picker 擋住，我猜應該只有工程師知道要滑動才可以收起來，上面也沒有留個 done 可以收輸入，偶而用的時候都會被嚇一跳</li><li>在 Desktop 想要更新 meeting 的話，高機率開啟 Dialog 後 Skeleton loading 需要等超過一分鐘才會讀完，同樣的，按下更新之後 Loading progress 也會等很久才完成同步</li><li>Book meeting 之後， calendar 會跳回現在的時間點，令用戶感到混亂 (舉例來說: 找到未來兩週後的 meeting 並更改時間，因為更新完/或是按下 close 離開更新之後會回到現在的時間點，但通常會需要一並檢視同時段的會議內容，甚至會不知道之前更動改的時間對不對)</li><li>private channel 沒辦法訂行事曆，這什麼神奇到爆炸的邏輯，難道 private channel 就不用開會嗎？</li><li>沒有辦法像 Google calendar 一樣一樣分享自己的行事曆給別人，所以得親自問對方有沒有空</li><li>...(相信也還會有新的，也會定期遇到就更新)</li></ul><p>…實在太多了，拜託至少把 code blocks 在 Chat 跟 Channel 的行為統一，這產品明明好幾年了卻像是大學生做出來的一樣問題一堆，微軟的資源有比 slack 少嗎？</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2023/08/w700d1q75cms.jpg" class="kg-image" alt="微軟 Teams 真的很難用，難以忍受 / 細數 Teams 問題"><figcaption>今更数えきれるか / 事到如今還數得完嗎！</figcaption></figure><!--kg-card-end: image--><p>ref: <a href="https://zh.moegirl.org.cn/zh-tw/%E6%9D%A5%EF%BC%8C%E7%BB%86%E6%95%B0%E4%BD%A0%E7%9A%84%E7%BD%AA%E6%81%B6%E5%90%A7">https://zh.moegirl.org.cn/zh-tw/来，细数你的罪恶吧</a></p>]]></content:encoded></item><item><title><![CDATA[Safari mobile (iOS) 遇到的天坑筆記 - 包含可以直接放棄的部分]]></title><description><![CDATA[<!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2022/09/1593148838775-1.jpg" class="kg-image"><figcaption>現在放棄就可以下班了喔 ❤️</figcaption></figure><!--kg-card-end: image--><h1 id="pull-to-refresh-ios-15-">pull-to-refresh (iOS 15+)</h1><p>沒有辦法透過 <code>onscroll</code> 或是任何方法知道 user 正在 pull-to-refresh，沒錯‌‌，沒有任何方法 (ref from: <a href="https://developer.apple.com/documentation/webkitjs/windoweventhandlers/">https://developer.apple.com/documentation/webkitjs</a>/)</p><p>解決方法：當 <code>scrollY</code> 在 0 的時候， <code>touchmove</code> 是負數當作開始嘗試 pull-to-refresh (正在 overscroll) 的狀態</p><!--kg-card-begin: code--><figure class="kg-card kg-code-card"><pre><code class="language-javascript">let startTouchY

function onTouchStart(event) {
  // We're going to try to guess whether the user is trying to pull-to-refresh</code></pre></figure>]]></description><link>https://blog.capslock.tw/safari-mobile-ios-issues/</link><guid isPermaLink="false">63025200cabaf60001088805</guid><category><![CDATA[general]]></category><category><![CDATA[javascript]]></category><category><![CDATA[iOS]]></category><category><![CDATA[web]]></category><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Wed, 14 Sep 2022 11:44:26 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2022/09/1593148838775-2.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2022/09/1593148838775-1.jpg" class="kg-image" alt="Safari mobile (iOS) 遇到的天坑筆記 - 包含可以直接放棄的部分"><figcaption>現在放棄就可以下班了喔 ❤️</figcaption></figure><!--kg-card-end: image--><h1 id="pull-to-refresh-ios-15-">pull-to-refresh (iOS 15+)</h1><img src="https://blog.capslock.tw/content/images/2022/09/1593148838775-2.jpg" alt="Safari mobile (iOS) 遇到的天坑筆記 - 包含可以直接放棄的部分"><p>沒有辦法透過 <code>onscroll</code> 或是任何方法知道 user 正在 pull-to-refresh，沒錯‌‌，沒有任何方法 (ref from: <a href="https://developer.apple.com/documentation/webkitjs/windoweventhandlers/">https://developer.apple.com/documentation/webkitjs</a>/)</p><p>解決方法：當 <code>scrollY</code> 在 0 的時候， <code>touchmove</code> 是負數當作開始嘗試 pull-to-refresh (正在 overscroll) 的狀態</p><!--kg-card-begin: code--><figure class="kg-card kg-code-card"><pre><code class="language-javascript">let startTouchY

function onTouchStart(event) {
  // We're going to try to guess whether the user is trying to pull-to-refresh only.
  if (event.touches.length &gt; 1 || window.scrollY !== 0) {
    return
  }
  const [touch] = event.touches

  startTouchY = touch.clientY
}

function onTouchMove(event) {
  if (event.touches.length &gt; 1 || !this.startTouchY) {
    return
  }
  const [touch] = event.touches

  const offsetY = this.startTouchY - touch.clientY
  // Pulling to refresh, offsetY smaller than 0 means scrolling down.
  if (offsetY &lt; 0) {
    // ...Do anything you want when user pulling to refresh.
  }
}

function onTouchEnd(event) {
  startTouchY = null
}</code></pre><figcaption>Sample code for detecting pull-to-refresh.</figcaption></figure><!--kg-card-end: code--><h1 id="navigation-bar-address-bar-ios-15-">Navigation bar/Address bar 縮放 (iOS 15+)</h1><p>同樣的也沒有任何 event 可以知道 user 目前的操作使 navigation bar 變小還是變大，如果需要做出 full screen 效果，唯一的方式是透過 <code>resize</code> event 通知需要重新計算高度，而高度則需要借助 CSS variable 調整 (作法參考來自 <a href="https://zenn.dev/tak_dcxi/articles/2ac77656aa94c2cd40bf">https://zenn.dev/tak_dcxi/articles/2ac77656aa94c2cd40bf</a>)</p><!--kg-card-begin: code--><figure class="kg-card kg-code-card"><pre><code class="language-javascript">let vw
let vh

function setFillHeight() {
  if (vw === window.innerWidth &amp;&amp; vh === window.innerHeight) {
    // 畫面尺寸沒有更動，所以不做任何事
    return
  }

  // 畫面尺寸有更動的時候重新計算高度
  vw = window.innerWidth
  vh = window.innerHeight

  const vhUnit = window.innerHeight * 0.01
  document.documentElement.style.setProperty('--vh', `${vhUnit}px`)
}

window.addEventListener('resize', setFillHeight)
// 記得手動初始化
setFillHeight()

// 沒有用到的時候也要記得回收 listener
window.removeEventListener('resize', setFillHeight)</code></pre><figcaption><a href="https://zenn.dev/tak_dcxi/articles/2ac77656aa94c2cd40bf">https://zenn.dev/tak_dcxi/articles/2ac77656aa94c2cd40bf</a></figcaption></figure><!--kg-card-end: code--><p>另外也可以透過 <code>safe-area-inset-bottom</code> 來預留 address bar 的空間</p><!--kg-card-begin: bookmark--><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://lukechannings.com/blog/2021-06-09-does-safari-15-fix-the-vh-bug/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Does Safari 15 finally fix viewport height?</div><div class="kg-bookmark-description">Luke Channings’s blog</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://lukechannings.com/static/favicon-196-b2407d8734ff19dfa8a6241f4bb87f32.png" alt="Safari mobile (iOS) 遇到的天坑筆記 - 包含可以直接放棄的部分"></div></div><div class="kg-bookmark-thumbnail"><img src="https://lukechannings.com/blog-image-2021-06-09.jpeg" alt="Safari mobile (iOS) 遇到的天坑筆記 - 包含可以直接放棄的部分"></div></a><figcaption>更完整的實作可以參考 Luke 的筆記</figcaption></figure><!--kg-card-end: bookmark--><p>有些實作元素像是 drag&amp;drop 的操作因為上下移動會造成 navigation bar (address bar) 變動，目前<strong>沒有</strong>相對應的處理方法 (像是 disable address bar 縮放的任何手段)</p><p>↓ 範例：navigator resize 造成的 dragging 中斷</p><p><a href="https://user-images.githubusercontent.com/1218900/160944477-cae01126-b2bd-4555-a6f9-5d864637f1a9.mov">https://user-images.githubusercontent.com/1218900/160944477-cae01126-b2bd-4555-a6f9-5d864637f1a9.mov</a></p><h1 id="swipe-to-back">swipe-to-back</h1><p>直接電死系列第三彈，透過手勢回到前/後一頁的功能沒有 event 可以知道開始手勢/觸發，所以當有 swipe 手勢偵測實作的時候，建議可以留左右 edge 的空間，避免手勢重疊 (大約 50px 的空間，如果有官方文件清楚定義 swipe-to-back 的空間 pixel 請告知小弟，非常感謝)</p><p>以上是避免手勢重複的部分，而如果想要 disable swipe-to-back 手勢的話，可以參考 <a href="https://pqina.nl/blog/blocking-navigation-gestures-on-ios-13-4/">https://pqina.nl/blog/blocking-navigation-gestures-on-ios-13-4/</a></p><p>基本上就是在想要 disable 手勢的元素上接收 <code>touchstart</code> event</p><!--kg-card-begin: code--><figure class="kg-card kg-code-card"><pre><code class="language-javascript">&lt;div style="height:100px"&gt;Our element that prevents navigation&lt;/div&gt;

&lt;script&gt;
    const element = document.querySelector('div');

    element.addEventListener('touchstart', (e) =&gt; {
        // prevent swipe to navigate gesture
        e.preventDefault();
    });
&lt;/script&gt;</code></pre><figcaption><a href="https://pqina.nl/blog/blocking-navigation-gestures-on-ios-13-4/">https://pqina.nl/blog/blocking-navigation-gestures-on-ios-13-4/</a></figcaption></figure><!--kg-card-end: code--><h1 id="wrap-it-up">Wrap it up</h1><p>以上特別是 navigator (address bar) 花了小弟特別多時間，最令人難過的是沒有解決方法...雖然我也是比較喜歡 iPhone 帶給我的使用體驗，但開發起來真的只能芭比Ｑ了</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2022/09/miroku_bosatsu_zou.png" class="kg-image" alt="Safari mobile (iOS) 遇到的天坑筆記 - 包含可以直接放棄的部分"><figcaption>Credit: <a href="https://www.irasutoya.com">https://www.irasutoya.com</a></figcaption></figure><!--kg-card-end: image-->]]></content:encoded></item><item><title><![CDATA[2022 婚宴攻略祕技 - 工程師好讀版]]></title><description><![CDATA[2022 年最新婚宴相關注意事項、攻略，包含小工具與心法。]]></description><link>https://blog.capslock.tw/2022-wedding-guildes/</link><guid isPermaLink="false">631e026ccabaf60001088849</guid><category><![CDATA[general]]></category><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Mon, 12 Sep 2022 05:54:35 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2022/09/305867312_489622232624075_2077811342605757732_n.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.capslock.tw/content/images/2022/09/305867312_489622232624075_2077811342605757732_n.jpg" alt="2022 婚宴攻略祕技 - 工程師好讀版"><p>受各方親朋好友的眷顧，小弟在 9/3 完婚了，真的非常感謝 🙇‍♂️ 這篇文章是想要將在婚禮籌辦期間做的小工具做個推廣、文章留存，但止於實在有點短且無聊，所以一並將老婆的心血結晶分享上來，希望可以幫助到網路上萍水相逢的各位。</p><blockquote>先來一波無情工商 - 婚禮入席小助手 (<a href="https://github.com/Calvin-Huang/take-a-seat-guide-assistant">https://github.com/Calvin-Huang/take-a-seat-guide-assistant</a>)</blockquote><!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2022/09/blog.jpg" class="kg-image" alt="2022 婚宴攻略祕技 - 工程師好讀版"><figcaption>(快速業配) 婚禮入席小助手嘗試用最簡單有效的方式協助排除婚禮入座時，招待需要拿著座位圖 + 名單手忙腳亂的問題。</figcaption></figure><!--kg-card-end: image--><!--kg-card-begin: hr--><hr><!--kg-card-end: hr--><p></p><p>結婚 - 人生的大事，撇掉最前面的單膝下跪並且得到雙方家長的同意這些前置努力外，其需要開始費神耗時的時光集中在開始籌備婚禮後 (感謝老婆 200% 的罩，愛你 3000 次)。</p><p>首先，奉上一份整理詳盡的 <a href="https://medium.com/%E5%B7%A5%E7%A8%8B%E5%B8%AB%E5%B0%8F%E5%A4%AB%E5%A6%BB/wedding-excel-ecef8b0bdcd6">👉 婚禮總表 👈</a> (感謝前人的整理) 其中的結婚流程 tab 極為重要，可以有效的檢視需要執行的項目是否被排進行程，確認哪些需要聯絡、告知的事項還有哪些。</p><p>其次，時間有限與聯絡窗口、執行人可能會有不同調的情況，為了溝通的效率，這邊提供一個聯絡事項用的樣本，各位可以利用這份樣本來填上內容，確保雙方在同一條線上。</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2022/09/ezgif.com-gif-maker.jpeg" class="kg-image" alt="2022 婚宴攻略祕技 - 工程師好讀版"><figcaption>https://www.appvizer.com/magazine/operations/project-management/the-5-ws-in-business</figcaption></figure><!--kg-card-end: image--><h2 id="-">要聯絡誰、為了什麼事 (清楚的講出是什麼事情)、什麼時候要發生、在哪裡發生</h2><p>上述雖然與原版的 5Ws 管理法相比少了一個為什麼，但在目前要執行的項目裡他幾乎不在考量內</p><p>切記「<strong>一定要」</strong>問清楚長輩所想的事情跟你們想的事情是否在同一件事上，而且「<strong>一定要</strong>」充分瞭解長輩心中所描繪的儀式的內容步驟有哪些。</p><!--kg-card-begin: html-->舉個切身發生過的例子，我們的婚禮流程並不完整傳統，但有做奉茶的儀式，而因為我們並不清楚儀式本身他需要在什麼時間發生，也不了解儀式的過程會有哪些，只知道飯店會協助準備奉茶，便被「奉茶很簡單，很短，只需要準備回禮就好」給帶過，因此在奉茶的當下因為認不得長輩稱謂顯得失禮 (其餘記事請見 <a href="#奉茶">奉茶</a> 的段落)。<!--kg-card-end: html--><h3 id="--1">「傳統」現今充滿彈性，中間非常多模糊地帶，需要充分釐清</h3><p></p><h1 id="--2">注意事項</h1><h2 id="--3">預約場地、彩排、場佈</h2><p>以下以一般喝喜酒的流程作為預設，一般需要往前抓 1 年保險時間預約，在與飯店方交涉時建議主動詢問對新人來說想要達成的效果相關的事項，畢竟飯店方在接洽各式客人的需求各不相同，要他們主動提出有些強人所難。</p><h3 id="--4">可以詢問飯店方的問題集</h3><ul><li>場地佈置是否可以更動顏色</li><li>送客背板客製化修改程度</li><li>什麼時候可以開始準備會場 / 如果邀約婚佈廠商可以進駐的時間</li><li>是否會有主持人，何時可以確認主持人的時間 (如果飯店方說可以提供主持人，需要再三確認所謂提供主持人的條件是什麼，我們直到婚宴要開始的前一週才知道有桌數限制，而我們剛好壓線卻沒有達成條件，老婆一度接近崩潰)</li><li>設備有哪些可用：投影機是 VGA? HDMI? 能不能帶自己的裝置接音響? 音源線是 3.5mm? 需要準備隨身碟?</li><li>彩排什麼時候進行，婚宴進行的步驟流程有哪些</li></ul><h2 id="--5">婚禮廠商相關 / 工作人員</h2><ul><li>外縣市的車馬費、住宿費</li><li>婚宴當天的飲食偏好，餐盒送達/用膳的時間</li><li>可以主動提出自己的攝影規劃、當日行程規劃</li><li>(以我們邀請來的互動遊戲 - 拍拍貼為例) 是否需要協助準備設備放置區，路線引導，協調婚宴現場影音設備規格，是否需要人手幫忙桌卡擺設</li><li>鮮花保存，協調婚宴場地進出：送貨路線、接洽人員</li></ul><p>這邊順便工商、感謝一下協助我們婚禮協辦的各家廠商們 ，不論是新秘、婚攝、婚錄、拍貼機/遊戲、捧花都非常專業：溝通順暢，效果、成品極佳，我們非常滿意 &amp; 感謝各位廠商的協助。</p><ul><li>新秘： <a href="https://www.facebook.com/artingsmakeup0302?__cft__[0]=AZUP66nhsHDKrdpcCrUlhCim8Kw9jaxPOrRyIr5brcEgqbDt1IsmbOF4wmGsPMRB8ri09gwgrA2KBTytRZ2tlBkQMhbtqLBhkkHJr0fzAfF5tlYLrfIBTvz3jpTsn2U8VP42VOxf_I--1XSCvx-JXYk8yU0tyBqBPyVVi2BAjdq_22XtfwBl5SjApqPcwSLse_k&amp;__tn__=-]K-R">Arting話化妝．新娘秘書.個人彩妝.整體造型</a></li><li>婚攝： <a href="https://www.facebook.com/catcherinthelight/?__cft__[0]=AZUP66nhsHDKrdpcCrUlhCim8Kw9jaxPOrRyIr5brcEgqbDt1IsmbOF4wmGsPMRB8ri09gwgrA2KBTytRZ2tlBkQMhbtqLBhkkHJr0fzAfF5tlYLrfIBTvz3jpTsn2U8VP42VOxf_I--1XSCvx-JXYk8yU0tyBqBPyVVi2BAjdq_22XtfwBl5SjApqPcwSLse_k&amp;__tn__=kK-R">時隧影像－Time Tunnel Photography</a></li><li>婚錄： <a href="https://www.facebook.com/daji0210?__cft__[0]=AZUP66nhsHDKrdpcCrUlhCim8Kw9jaxPOrRyIr5brcEgqbDt1IsmbOF4wmGsPMRB8ri09gwgrA2KBTytRZ2tlBkQMhbtqLBhkkHJr0fzAfF5tlYLrfIBTvz3jpTsn2U8VP42VOxf_I--1XSCvx-JXYk8yU0tyBqBPyVVi2BAjdq_22XtfwBl5SjApqPcwSLse_k&amp;__tn__=-]K-R">Daji Film studio【大吉映像】</a> </li><li>拍貼機＆遊戲： <a href="https://www.facebook.com/linein.cc/?__cft__[0]=AZUP66nhsHDKrdpcCrUlhCim8Kw9jaxPOrRyIr5brcEgqbDt1IsmbOF4wmGsPMRB8ri09gwgrA2KBTytRZ2tlBkQMhbtqLBhkkHJr0fzAfF5tlYLrfIBTvz3jpTsn2U8VP42VOxf_I--1XSCvx-JXYk8yU0tyBqBPyVVi2BAjdq_22XtfwBl5SjApqPcwSLse_k&amp;__tn__=kK-R">拍拍印 LineIn 你的專屬拍貼系統</a></li><li>捧花： <a href="https://www.facebook.com/flora.e2222100?__cft__[0]=AZUP66nhsHDKrdpcCrUlhCim8Kw9jaxPOrRyIr5brcEgqbDt1IsmbOF4wmGsPMRB8ri09gwgrA2KBTytRZ2tlBkQMhbtqLBhkkHJr0fzAfF5tlYLrfIBTvz3jpTsn2U8VP42VOxf_I--1XSCvx-JXYk8yU0tyBqBPyVVi2BAjdq_22XtfwBl5SjApqPcwSLse_k&amp;__tn__=-]K-R">說給花聽花坊</a> (花鮮美到像凍結在最完美時間的假花)</li></ul><h2 id="--6">傳統儀式相關 - 禮車</h2><ul><li>需要跟禮車司機先行確認路線</li><li>留意婚宴場地是否可以預留禮車的停車位</li><li>禮車數量通常為雙數，2, 6...</li><li>車彩通常租車公司會提供，或者是婚紗店會有，當然如果沒有的話可以以便宜的價格在婚禮用品店/百貨批發購買 (感謝老婆到台北火車站後站批發百貨商圈購買 <a href="https://goo.gl/maps/QUKqp5yq6LqiWZAAA">https://goo.gl/maps/QUKqp5yq6LqiWZAAA</a>)</li></ul><!--kg-card-begin: html--><h2 id="奉茶">傳統儀式相關 - 奉茶</h2><!--kg-card-end: html--><ul><li>確認參與的長輩人數，準備相符合的謝禮</li><li>奉茶的過程會需要作媒人向新娘介紹各位長輩是誰 (我們則是我)，因此事前功課需要先做好臉/稱謂的調查</li><li>戴上長輩送的結婚金飾</li></ul><h2 id="--7">其餘事項</h2><ul><li>列出必帶品清單，出門前勾起確認</li><li>準備重要品用的小袋子數個，可以用來帶著印章、金飾等等以供快速取用，收納</li><li>帶著腳跟保護貼、眼藥水、小面紙以備不時之需</li><li>[✅ <strong>非常重要</strong>] 事前記下重要聯絡人的手機號碼，避免手機沒電、網路不穩時通訊軟體不可用時找不到人的情況</li><li>建立備援聯絡人的機制，當分身乏術的時候有人可以補位</li></ul><!--kg-card-begin: hr--><hr><!--kg-card-end: hr--><p></p><p>以上是提供給各位參考的秘技，希望我們的經驗可以對到來此尋求幫助的各位起到作用，祝一切順利 🎉</p>]]></content:encoded></item><item><title><![CDATA[Server Side Rendering 常見認證問題 - Unexpected shared state between different users]]></title><description><![CDATA[理應是不同的 session 卻發生資料錯置、state 共用的問題是前端工程利用 Nextjs 或是 Nuxtjs 實作 Server Side Rendering，特別是想要在 Render 前先準備好資料時常誤觸的禁區。(Nextjs SSR  不同 user 看到自己以外的資料)]]></description><link>https://blog.capslock.tw/unexpected-shared-state-between-different-users/</link><guid isPermaLink="false">5f744918b5c9e40001a47343</guid><category><![CDATA[javascript]]></category><category><![CDATA[nextjs]]></category><category><![CDATA[nuxtjs]]></category><category><![CDATA[SSR]]></category><category><![CDATA[reactjs]]></category><category><![CDATA[vuejs]]></category><category><![CDATA[general]]></category><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Fri, 02 Oct 2020 03:29:36 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2020/10/kougi_woman.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.capslock.tw/content/images/2020/10/kougi_woman.png" alt="Server Side Rendering 常見認證問題 - Unexpected shared state between different users"><p>歷經一番苦心處理 User Login Flow，配合 UX Designer 將需求實作出來，同時完成了整合 <a href="https://nextjs.org/">Nextjs</a> 的複雜設定完美的解決 SEO 需求與提升初始畫面速度、通過 QA 層層打磨，你非常確信這次上線的系統不會有什麼節外生枝的問題在心中放了 120 顆心決定好好放鬆一下近期來的疲憊。</p>
<!--kg-card-end: markdown--><p>「為什麼我會突然什麼事情都沒做就被登出？」</p><p>「嗯？使用者資訊顯示的人不是我啊？」</p><p>收到 PM 回報的 issue，你乾瞪著眼重複確認 code 的 credential 認證沒問題，axios 很聰明的將 JWT 在 interceptor 夾上 Header 送出，看似萬無一失卻又無法抵擋來自 end user 的怒火。</p><!--kg-card-begin: hr--><hr><!--kg-card-end: hr--><!--kg-card-begin: markdown--><p>理應是不同的 session 卻發生資料錯置、state 共用的問題是前端工程利用 <a href="https://nextjs.org/">Nextjs</a> 或是 <a href="https://nuxtjs.org/">Nuxtjs</a> 實作 Server Side Rendering，特別是想要在 Render 前先準備好資料時常誤觸的禁區。</p>
<!--kg-card-end: markdown--><p>我們試著來拆解上面情境一般來說的實作步驟 ↓</p><ol><li>使用者登入成功，將 API 回傳回來的 JWT 存在 Browser 的 cache (可能是 Cookie 或是 LocalStorage 等等)</li><li>axios 實作 interceptor，不管有 Credentail 或是沒有都會在每次 request 前夾入 Header 免得每次需要 Authentication 的 API call 都得自己夾上 Credential</li></ol><!--kg-card-begin: markdown--><script src="https://gist.github.com/Calvin-Huang/c7600e191be7c8bccb43000ec3694724.js"></script><!--kg-card-end: markdown--><p>這段簡單的實作在 Browser 端完全沒有問題，但如果想要更進一步，為了讓 Render 的時候不會閃一下 (因為一開始 intial 的時候沒有 user 資料)，選擇在 Server Side 先執行 API call，我們稍微改一下上面的範例。</p><!--kg-card-begin: markdown--><script src="https://gist.github.com/Calvin-Huang/97cdbd53457fc7afa7d6b1b6e5d68e87.js"></script><!--kg-card-end: markdown--><p>單純地將 API call 往上拉到 Server Side 完成又沒有調整太多實作邏輯，看起來萬無一失且有效地達成目標，<strong>但自從將這個改動部署上去之後就時不時收到有用戶會看到其他人名字的 Bug 回報。</strong></p><p>「當開始使用 Server Side 準備資料，事情就會變得遠比只在 Browser 執行複雜」</p><p>為什麼會發生隨機的 User 狀態錯置問題呢？因為 Server 與 Browser 的記憶體本位不同。</p><!--kg-card-begin: markdown--><br><!--kg-card-end: markdown--><!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2020/10/image.png" class="kg-image" alt="Server Side Rendering 常見認證問題 - Unexpected shared state between different users"><figcaption>從 Browser fetch user data 都是分別獨立在 Client 端</figcaption></figure><!--kg-card-end: image--><!--kg-card-begin: markdown--><br><!--kg-card-end: markdown--><!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.capslock.tw/content/images/2020/10/image-1.png" class="kg-image" alt="Server Side Rendering 常見認證問題 - Unexpected shared state between different users"><figcaption>使用 Nextjs getServerSideProps 則是在集中在 Server Side 統一處理 (廢話)</figcaption></figure><!--kg-card-end: image--><!--kg-card-begin: markdown--><br><!--kg-card-end: markdown--><p>詳細一點拆分步驟來看 ↓</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://blog.capslock.tw/content/images/2020/10/Nextjs-ServerSide-vs-Browser-Get-User-Data-From-Server---Bug-Case-Explaination---loop.gif" class="kg-image" alt="Server Side Rendering 常見認證問題 - Unexpected shared state between different users"></figure><!--kg-card-end: image--><p>問題就出在 Singleton at Server Side</p><!--kg-card-begin: markdown--><pre><code>Home.getServerSideProps = async ({ req }) =&gt; {
  // Set shared cookie for 
  Cookies.shared = Cookies.new(req.headers.cookie)

  const { data } = await axios.get('https://mock.bugfree.app/api/me')
  return {
    props: {
      user: data,
    },
  }
}

-----

axios.interceptors.request.use(config =&gt; {
  config.headers = {
    authorization: `Bearer ${Cookies.shared.get('user-token')}`,
    ...config.headers,
  }

  return config
})
</code></pre>
<!--kg-card-end: markdown--><p>當 Request 進到 getServerSideProps，我們取代/準備了 <code>Cookies.shared</code> 到用的期間只要運氣夠好，axios 都有機會用到不同 Request 所對應的 <code>Cookies.shared</code>。</p><p>知道了問題點，調整的方式其實就非常簡單，每一次的 axios call 都一定需要重新夾 Credential 送出 ↓</p><!--kg-card-begin: markdown--><pre><code>Home.getServerSideProps = async ({ req }) =&gt; {
  // Set shared cookie for 
  const userToken = Cookies.new(req.headers.cookie).get('user-token')

  const { data } = await axios.get(
    'https://mock.bugfree.app/api/me',
    {
      headers: {
        Authroization: `Bearer ${userToken}`
      }
    },
  )
  return {
    props: {
      user: data,
    },
  }
}
</code></pre>
<!--kg-card-end: markdown--><p>上述的以 axios 當做例子所提到的誤區，需要擴充到包含狀態的所有實作，當然這包含了 redux。</p><!--kg-card-begin: markdown--><p>next-redux-wrapper 就是以每個 request 當作單位來準備 redux store，確保 Server Side 不會混淆</p>
<pre><code>// https://github.com/kirill-konshin/next-redux-wrapper/blob/master/packages/wrapper/src/index.tsx#L47
...
if (getIsServer()) {
    const c = context as any;
    let req;
    if (c.req) req = c.req;
    if (c.ctx &amp;&amp; c.ctx.req) req = c.ctx.req;
    if (req) {
        // ATTENTION! THIS IS INTERNAL, DO NOT ACCESS DIRECTLY ANYWHERE ELSE
        // @see https://github.com/kirill-konshin/next-redux-wrapper/pull/196#issuecomment-611673546
        if (!req.__nextReduxWrapperStore) req.__nextReduxWrapperStore = createStore();
        return req.__nextReduxWrapperStore;
    }
    return createStore();
}
...
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>對比常見的在 Client 不會有問題但在 Server Side 會有機率出錯的問題寫法</p>
<pre><code>let store

export cosnt withRedux = () =&gt; {
  store = createStore()
}

export store
</code></pre>
<!--kg-card-end: markdown--><p>到此，我們提出了一個解決方法，卻提升了實作上的冗贅，一開始的 axios interceoptor 在使用時一率都將自動夾上 Credential，如果要沿用這樣的設計，不論如何 axios module 都會在 Nextjs 的 Lifecycle 外，沒辦法達成每一個 Request 都分離的需求。</p><!--kg-card-begin: markdown--><p>相對 Nextjs 來說，Nuxtjs 的解決方案提供的相對齊全，使用整合的 <code>$axios</code> 就不必擔心 by request 的問題。</p>
<pre><code>// https://axios.nuxtjs.org/usage
async asyncData({ $axios }) {
  const ip = await $axios.$get('http://icanhazip.com')
  return { ip }
}

-----

methods: {
  async fetchSomething() {
    const ip = await this.$axios.$get('http://icanhazip.com')
    this.ip = ip
  }
}
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><p>現階段而言，對 React 的設計原則來說，不應該學 Vue 將 <code>axios</code> instance 夾在 props 裡面傳遞，所以在不考慮任何 side effect 解決方案 (<a href="https://redux-observable.js.org/">redux-observable</a>、<a href="https://redux-saga.js.org/">redux-saga</a>) 的整合方案來說，最簡單的處理方式如下 ↓</p>
<script src="https://gist.github.com/Calvin-Huang/dbc5e56df1fc0f2963161fcc1ea8907c.js"></script>
<p><strong>⚠️ 但需要非常小心使用，只有 Client Side 才能用 default，Server Side 得每次都手動賦予 Credential。</strong></p>
<!--kg-card-end: markdown--><!--kg-card-begin: hr--><hr><!--kg-card-end: hr--><p>總結：當需要 Server Side Rendering 特別是處理 state 相關的處理的時候，都需要非常小心避免使用 Singleton。</p><!--kg-card-begin: markdown--><p>另外，上面所提的解決方案只有 axios 的簡單解法，如果是需要整合 <a href="https://redux-observable.js.org/">redux-observable</a>、<a href="https://redux-saga.js.org/">redux-saga</a> 的話，這又是一段艱辛的路程了...</p>
<!--kg-card-end: markdown--><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://blog.capslock.tw/content/images/2020/10/image-2.png" class="kg-image" alt="Server Side Rendering 常見認證問題 - Unexpected shared state between different users"></figure><!--kg-card-end: image-->]]></content:encoded></item><item><title><![CDATA[最近的狀況更新]]></title><description><![CDATA[<p>各位路過的大家好，我是Michael，CapsLock, Studio的編輯及負責人之一，最近一段時間沒有更新blog了，特地來更新一下。</p><h1 id="-bat-">開始接受BAT捐款</h1><p>因為最近伺服器的負擔加重，所以開始尋求贊助(雖然讀者好像不是很多就是) ，如果願意支持的歡迎透過 <code>Brave Browser</code> 進行BAT捐款</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://blog.capslock.tw/content/images/2019/11/---2019-11-19---12.07.24.png" class="kg-image"></figure><!--kg-card-end: image--><h1 id="-coinpayments-">接受coinpayments捐款</h1><p>如果有follow的大概會知道這段時間開通的telegram服務 <code>牧羊犬</code> (@group_captcha_bot)</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://blog.capslock.tw/content/images/2019/11/---2019-11-19---12.14.05.png" class="kg-image"></figure><!--kg-card-end: image--><p>牧羊犬是一個可以防止廣告機器人的anti-spam bot，有興趣的話歡迎隨意使用，同時開通的到現在也開始接受捐獻，有興趣的話歡迎打賞</p><h1 id="-">其他服務開通</h1><p>另外也開通了一些金融相關的服務，有興趣的路過的歡迎來信詢問</p>]]></description><link>https://blog.capslock.tw/recent-updates/</link><guid isPermaLink="false">5dd3690ba0edba00014e4d98</guid><category><![CDATA[general]]></category><dc:creator><![CDATA[Michael.K]]></dc:creator><pubDate>Tue, 19 Nov 2019 04:18:16 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2019/11/shutterstock_1078432223-compressor.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.capslock.tw/content/images/2019/11/shutterstock_1078432223-compressor.jpg" alt="最近的狀況更新"><p>各位路過的大家好，我是Michael，CapsLock, Studio的編輯及負責人之一，最近一段時間沒有更新blog了，特地來更新一下。</p><h1 id="-bat-">開始接受BAT捐款</h1><p>因為最近伺服器的負擔加重，所以開始尋求贊助(雖然讀者好像不是很多就是) ，如果願意支持的歡迎透過 <code>Brave Browser</code> 進行BAT捐款</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://blog.capslock.tw/content/images/2019/11/---2019-11-19---12.07.24.png" class="kg-image" alt="最近的狀況更新"></figure><!--kg-card-end: image--><h1 id="-coinpayments-">接受coinpayments捐款</h1><p>如果有follow的大概會知道這段時間開通的telegram服務 <code>牧羊犬</code> (@group_captcha_bot)</p><!--kg-card-begin: image--><figure class="kg-card kg-image-card"><img src="https://blog.capslock.tw/content/images/2019/11/---2019-11-19---12.14.05.png" class="kg-image" alt="最近的狀況更新"></figure><!--kg-card-end: image--><p>牧羊犬是一個可以防止廣告機器人的anti-spam bot，有興趣的話歡迎隨意使用，同時開通的到現在也開始接受捐獻，有興趣的話歡迎打賞</p><h1 id="-">其他服務開通</h1><p>另外也開通了一些金融相關的服務，有興趣的路過的歡迎來信詢問</p>]]></content:encoded></item><item><title><![CDATA[Cloud Native Forum Taiwan]]></title><description><![CDATA[CNF2018 - 簡單打造GKE上的世界級節點]]></description><link>https://blog.capslock.tw/cloud-native-forum-taiwan/</link><guid isPermaLink="false">5cdc22990ce6ce000142369d</guid><category><![CDATA[CNF2018]]></category><dc:creator><![CDATA[Michael.K]]></dc:creator><pubDate>Sun, 30 Dec 2018 17:00:56 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h2 id>首先</h2>
<p>自從githubuniverse沒上後，今年很難得想說算了，所以沒有去投任何的CFP(Call For Paper)。</p>
<p>一方面也是沒有什麼新鮮題目而感到十分苦惱，但就在十月多的時候...</p>
<p><code>Cloud Native Forum</code> 突然就被telegram群上面的推薦去講了<br>
<img src="https://blog.capslock.tw/content/images/2018/12/f29e83b0390a4ebb98b2245a4efc0e2c.jpg" alt="f29e83b0390a4ebb98b2245a4efc0e2c"></p>
<h2 id>關於題目</h2>
<p>當初思考題目的時候其實沒有想很久，畢竟就台灣的市場來說，導入<strong>容器化</strong>的公司真的比想像中的少很多，尤其接觸尖端網頁技術無關的領域更容易體會到這件事情(譬如: 半導體產業)，所以不管說什麼好像都滿好選題的...</p>
<p>所以選了一個比較少討論到的題目: 多叢集節點</p>
<p>當初發想原因主要也是因為YouTube事件的關係吧<br>
<a href="https://udn.com/news/story/6812/3426081">https://udn.com/news/story/6812/3426081</a></p>
<p>所以這個題目應該會滿有趣的，就直接上了... 前後只花了10分鐘決定</p>
<h2 id>驚訝</h2>
<p>這次真的算是蠻意外的，畢竟Kubernetes的專場是算第一次講，也不曉得自己準備的好不好，<s>花個一小時擬的description也莫名其妙的上了</s>。主辦單位也滿慷慨的，在這之前講了一些比較大型的conference: SITCON、COSCUP、DevFest，甚至自己辦的: 夢森林、小樹屋... <s>加起來都沒有比inwinSTACK大方啊</s></p>
<p><img src="https://blog.capslock.tw/content/images/2018/12/IMG_0420.jpg" alt="IMG_0420"><br>
(超讓人受驚的講師贈品)</p>
<p>其實松菸文創也不是第一次來了，之前有經歷過面試以及eToro的講座，因緣際會的來到這裡至少也三次了，<s>但是我完全找不到路</s>。</p>
<p><s>絕對不是真的老了</s></p>
<h2 id>感想</h2>
<p>這次算準備的十分倉促吧，因為隔週又有<code>好想工作室</code>的邀約，要早上八點多遠赴台南講課... 各種蠟燭多頭燒，準備的不是非常充足，應該能再好一些。</p>
<p>期待下次有機會可以再去</p>
<table>
<thead>
<tr>
<th><img src="https://blog.capslock.tw/content/images/2018/12/IMG_0011.jpg" alt="IMG_0011"></th>
<th><img src="https://blog.capslock.tw/content/images/2018/12/IMG_0005.jpg" alt="IMG_0005"></th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="https://blog.capslock.tw/content/images/2018/12/IMG_0007.jpg" alt="IMG_0007"></td>
<td><img src="https://blog.capslock.tw/content/images/2018/12/IMG_0010.jpg" alt="IMG_0010"></td>
</tr>
</tbody>
</table>
<p>順便附上這次的簡報以及專案</p>
<iframe src="//slides.com/michael34435/2018_cloud_native/embed" width="576" height="420" scrolling="no" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
<p><strong>GitHub</strong><br>
<a href="https://github.com/michael34435/kubefctl">https://github.com/michael34435/kubefctl</a></p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[遲來的DevFest2017...]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><s>其實我原本2017的12月就應該發了，絕對不是因為去國外玩的太爽，而且中間還卡別的專案</s></p>
<p>BTW, 2017年的唯一一講就選擇在台大進行!<br>
<img src="https://devfest.gdg-taipei.org/images/backgrounds/location_3.jpg" alt></p>
<p>DevFest主要是由Google相關的周邊社群所主辦，主要社群包含 <code>GCUPUG.tw</code>、<code>GDG Taipei</code>、<code>Android Study Group</code>、<code>Women TechMakers Group</code>，主題主要環繞在Google上或是相關專案及經驗。</p>
<p>然後很有幸的，這次CFP也有上，主要主題是用GCP Vision打造圖片搜尋，先把相關簡報附上</p>
<iframe src="//slides.com/michael34435/image_recognition/embed" width="576" height="420" scrolling="no" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
<p><s>原則上是公司內部的實驗專案，而且一直都在pending的實驗專案就是了</s></p>
<p>其中有簡單比較一些<code>現成</code>的服務上，不同服務的平台的效益為何，根據不同的公司背景也會有不同的效果出現，其中一些眉眉角角都詳盡的紀錄在簡報中</p>
<p><img src="https://blog.capslock.tw/content/images/2018/08/IMG_5220.JPG" alt="IMG_5220"><br>
<img src="https://blog.capslock.tw/content/images/2018/08/IMG_5223-1.JPG" alt="IMG_5223-1"><br>
<img src="https://blog.capslock.tw/content/images/2018/08/IMG_5218-1.JPG" alt="IMG_5218-1"><br>
<img src="https://blog.capslock.tw/content/images/2018/08/IMG_5219.JPG" alt="IMG_5219"><br>
<img src="https://blog.capslock.tw/content/images/2018/08/IMG_5221.JPG" alt="IMG_5221"></p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></description><link>https://blog.capslock.tw/devfest2017/</link><guid isPermaLink="false">5cdc22990ce6ce0001423696</guid><category><![CDATA[general]]></category><category><![CDATA[DevFest]]></category><dc:creator><![CDATA[Michael.K]]></dc:creator><pubDate>Wed, 22 Aug 2018 05:04:48 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2018/01/devfest.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><img src="https://blog.capslock.tw/content/images/2018/01/devfest.png" alt="遲來的DevFest2017..."><p><s>其實我原本2017的12月就應該發了，絕對不是因為去國外玩的太爽，而且中間還卡別的專案</s></p>
<p>BTW, 2017年的唯一一講就選擇在台大進行!<br>
<img src="https://devfest.gdg-taipei.org/images/backgrounds/location_3.jpg" alt="遲來的DevFest2017..."></p>
<p>DevFest主要是由Google相關的周邊社群所主辦，主要社群包含 <code>GCUPUG.tw</code>、<code>GDG Taipei</code>、<code>Android Study Group</code>、<code>Women TechMakers Group</code>，主題主要環繞在Google上或是相關專案及經驗。</p>
<p>然後很有幸的，這次CFP也有上，主要主題是用GCP Vision打造圖片搜尋，先把相關簡報附上</p>
<iframe src="//slides.com/michael34435/image_recognition/embed" width="576" height="420" scrolling="no" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
<p><s>原則上是公司內部的實驗專案，而且一直都在pending的實驗專案就是了</s></p>
<p>其中有簡單比較一些<code>現成</code>的服務上，不同服務的平台的效益為何，根據不同的公司背景也會有不同的效果出現，其中一些眉眉角角都詳盡的紀錄在簡報中</p>
<p><img src="https://blog.capslock.tw/content/images/2018/08/IMG_5220.JPG" alt="遲來的DevFest2017..."><br>
<img src="https://blog.capslock.tw/content/images/2018/08/IMG_5223-1.JPG" alt="遲來的DevFest2017..."><br>
<img src="https://blog.capslock.tw/content/images/2018/08/IMG_5218-1.JPG" alt="遲來的DevFest2017..."><br>
<img src="https://blog.capslock.tw/content/images/2018/08/IMG_5219.JPG" alt="遲來的DevFest2017..."><br>
<img src="https://blog.capslock.tw/content/images/2018/08/IMG_5221.JPG" alt="遲來的DevFest2017..."></p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[流星轟落開發/隕石開發(英文: Meteo fall)]]></title><description><![CDATA[原文連結：http://eiki.hatenablog.jp/entry/meteo_fall 今天來為大家介紹最能代表日本軟體業界的開發方式吧。 其名為：流星轟落開發 (≒隕石驅動開發/隕石開發) 英文: Meteo fall。]]></description><link>https://blog.capslock.tw/meteo-fall/</link><guid isPermaLink="false">5cdc22990ce6ce000142369c</guid><category><![CDATA[general]]></category><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Fri, 01 Jun 2018 10:51:05 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2020/10/Agile2.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.capslock.tw/content/images/2020/10/Agile2.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"><p>原文連結：<a href="http://eiki.hatenablog.jp/entry/meteo_fall">http://eiki.hatenablog.jp/entry/meteo_fall</a></p>
<p>今天來為大家介紹最能代表日本軟體業界的開發方式吧。</p>
<p>其名為：<span style="font-size: 150%;"><strong>流星轟落開發/隕石開發(英文: Meteo fall)</strong></span> <u><a href="#explination-1">*1</a></u></p>
<br>
<br>
<h1 id>第一節</h1>
<p>通常以 <a href="https://zh.wikipedia.org/zh-tw/%E7%80%91%E5%B8%83%E6%A8%A1%E5%9E%8B">瀑布模型</a> 開發方式進行的專案會是像下面的方式。</p>
<p><img src="https://blog.capslock.tw/content/images/2018/06/Waterfall.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<p>那麼 <strong>流星轟落開發</strong> 則會變成這種形式。<br>
<img src="https://blog.capslock.tw/content/images/2018/06/Meteofall.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<p><span style="font-size: 150%;"><strong>然後變成這樣。</strong></span><br>
<img src="https://blog.capslock.tw/content/images/2018/06/Meteofall2.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<p>接著下面是敏捷開發的生命週期。<br>
<img src="https://blog.capslock.tw/content/images/2018/06/Agile.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<p>但在神的面前一樣婉如螻蟻。<br>
<img src="https://blog.capslock.tw/content/images/2018/06/Agile2.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<p>神之聲，毀滅萬物。<br>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiki_okuma/20180521/20180521165302.jpg" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<p>而人們則傾注全力從廢墟中重造。<br>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiki_okuma/20180529/20180529225612.jpg" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<p>這就是<span style="font-size: 150%;"><strong>流星轟落開發</strong></span> (≒隕石驅動開發)。</p>
<br>
<br>
<h1 id>第二節</h1>
<p>所有的工作排程都由<span style="font-size: 150%;"><strong>天界</strong></span>的意識來做決定。這同時也可以被稱為<span style="font-size: 150%;"><strong>默示錄</strong></span>。<br>
<img src="https://blog.capslock.tw/content/images/2018/06/Schedule.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<p>對於軟體開發來說，Feedback 是重要的一環。<br>
<img src="https://blog.capslock.tw/content/images/2018/06/Feedback.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<p>但對神來說 Feedback <span style="font-size: 150%;"><strong>就像耳邊風一樣。</strong></span><br>
<img src="https://blog.capslock.tw/content/images/2018/06/GodFeedback.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<p>只不過，<span style="font-size: 150%;"><strong>向神奉上祈求</strong></span>是被允許的，雖然<strong>很少能傳達到神到耳中</strong>。<br>
<img src="https://blog.capslock.tw/content/images/2018/06/Pray.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<p>神明大人有各式各樣的姿態。<br>
從外部顯身，<br>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiki_okuma/20180525/20180525122510.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<p>或是棲息在內部。<br>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiki_okuma/20180525/20180525122521.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<p>甚至，根本還沒見到，或是沒辦法見到的某某某也說不定。<u><a href="#explination-2">*2</a></u><br>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiki_okuma/20180530/20180530181807.jpg" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<p>讓軟體開發效率激增的<strong>銀彈</strong>雖然不存在，但<strong>相反的存在</strong>是有的。在梵教裡面的話來說，就像是帝釋天的箭矢一樣吧。<br>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiki_okuma/20180525/20180525122856.jpg" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<br>
<br>
<h1 id>第三節</h1>
<p>即使如此，當秩序是建立在神只有一人的情況下，這都<span style="font-size: 150%;"><strong>算還不錯了</strong></span>。</p>
<p>問題就出在<span style="font-size: 150%;"><strong>神存在兩人以上</strong></span>的時候。<br>
這些神祇們會經常性的下達相反的指示，經常性的爭執。<br>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiki_okuma/20180525/20180525115016.jpg" alt="流星轟落開發/隕石開發(英文: Meteo fall)"></p>
<p>而這就是吾輩的<span style="font-size: 150%;"><strong>諸神黃昏《Ragnarok》</strong></span>。<br>
<span style="font-size: 80%; color: #999999;">然後受害最深的，終究還是我們「人民」</span></p>
<br>
<br>
<p>另外，原本只有一個神所建築起來的秩序，當<span style="font-size: 150%;"><strong>新的神</strong></span>降臨的時候，<br>
古神所構築起來的一切<span style="font-size: 150%;"><strong>全部都會被推翻</strong></span>。<br>
<img src="https://blog.capslock.tw/content/images/2018/06/Ragnarok.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"><br>
而這就是<span style="font-size: 150%;"><strong>聖戰《吉哈德》</strong></span>。<u><a href="#explination-3">*3</a></u></p>
<br>
<br>
<p>但因為神的不同，可能會有依舊持有絕對的力量，卻<span style="font-size: 150%;"><strong>存在感極為稀博的神</strong></span>存在。</p>
<p><img src="https://blog.capslock.tw/content/images/2018/06/God4.png" alt="流星轟落開發/隕石開發(英文: Meteo fall)"><br>
需要的時候卻找不到的神，恐怕就是邪神了。<u><a href="#explination-4">*4</a></u></p>
<br>
<br>
<p>然後我們用血與汗堆砌而成的結果，會在吾輩所未知的地方<strong>盛大的發表</strong>。<br>
<img src="https://cdn-ak.f.st-hatena.com/images/fotolife/e/eiki_okuma/20180525/20180525123200.jpg" alt="流星轟落開發/隕石開發(英文: Meteo fall)"><br>
<strong>然後在此彼方，將會誕生新的規格</strong>。</p>
<br>
<br>
<h1 id>結論</h1>
<p>這次我們介紹了非常自然的存在於日本軟體業界裡，<s>最為災厄性的</s>流星轟落開發給各位。</p>
<br>
<p><span style="font-size: 150%;"><strong>順帶一提，對抗這種情況的解法並不存在。</strong></span><br>
願汝，不，願吾輩能早日在某天找到對抗手段。</p>
<br>
<br>
<p>本篇文章純為虛構。與一切實際存在的人物、團體無關。</p>
<br>
<br>
<p><u id="explination-1">*1</u>: 命名由來。へっぽこ氏（<a href="https://twitter.com/heppoko">@heppoko</a>）<br>
<u id="explination-2">*2</u>: 簡單來說就是指擁有版權者或是著作者。這種模式更為糟糕。<br>
<u id="explination-3">*3</u>: 最常發生在比預定的 Release 時間晚一年的軟體專案裡。<br>
<u id="explination-4">*4</u>: 出處為馬太福音７章７～１２節：『你們祈求，就給你們；尋找，就尋見；叩門，就給你們開門。因為凡祈求的，就得著；尋找的，就尋見；叩門的，就給他開門。』</p>
<p>翻譯授權：EIKI (id:eiki_okuma)<br>
<a href="https://twitter.com/eiki_okuma/status/1002390661448982530">https://twitter.com/eiki_okuma/status/1002390661448982530</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[與 TypeScript 共舞 🐉 - 跟我想的不一樣Ｒ之真實世界篇]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>俗話說 OOP 學得好，Coding 沒煩惱。<s>然後 Swift 有 POP，Ruby 有 mixin，還暫且不說 FP ㄏ</s></p>
<h1 id="fromtheancentthedragon">From the ancent......the Dragon 🐲......</h1>
<p>讓我們從講古開始切入，其實 TypeScript 已經從古早時候就一直存在 (<s>好吧說長不長說短不短的 5 年</s>)，只是近期內開始受到歡迎，吸引更多的開發者選擇使用 TypeScript，並且有穩定的商業應用表現 (有興趣的各位可以聽聽看 SoftwareEnginneringDaily 訪問 slack 使用 TypeScript 的經驗分享 - <a href="https://softwareengineeringdaily.com/2017/08/11/typescript-at-slack-with-felix-rieseberg/">傳送門</a>)。</p>
<h1 id="comparewithflow">Compare with Flow!?</h1>
<p>Facebook 毋庸置疑的贏了一場漂亮的戰爭，不管是 React 也好 babel 也行，</p>]]></description><link>https://blog.capslock.tw/typescript-shen-qi-de-fan-xing-generic/</link><guid isPermaLink="false">5cdc22990ce6ce0001423693</guid><category><![CDATA[javascript]]></category><category><![CDATA[general]]></category><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Thu, 12 Oct 2017 15:04:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>俗話說 OOP 學得好，Coding 沒煩惱。<s>然後 Swift 有 POP，Ruby 有 mixin，還暫且不說 FP ㄏ</s></p>
<h1 id="fromtheancentthedragon">From the ancent......the Dragon 🐲......</h1>
<p>讓我們從講古開始切入，其實 TypeScript 已經從古早時候就一直存在 (<s>好吧說長不長說短不短的 5 年</s>)，只是近期內開始受到歡迎，吸引更多的開發者選擇使用 TypeScript，並且有穩定的商業應用表現 (有興趣的各位可以聽聽看 SoftwareEnginneringDaily 訪問 slack 使用 TypeScript 的經驗分享 - <a href="https://softwareengineeringdaily.com/2017/08/11/typescript-at-slack-with-felix-rieseberg/">傳送門</a>)。</p>
<h1 id="comparewithflow">Compare with Flow!?</h1>
<p>Facebook 毋庸置疑的贏了一場漂亮的戰爭，不管是 React 也好 babel 也行，</p>
<pre><code>interface IntlExtension {
  intl: InjectedIntl
}

function mountWithIntl&lt;C extends IntlExtension&gt;(node: React.ReactElement&lt;C&gt;) {
  const intlProvider = new IntlProvider({ locale: 'zh-TW', messages }, {})
  const { intl } = intlProvider.getChildContext()

  return mount(
    React.cloneElement(node, { intl }) as React.ReactElement&lt;C&gt;,
    {
      context: { intl },
      childContextTypes: { intl: intlShape }
    }
  )
}
</code></pre>
<p><a href="https://softwareengineeringdaily.com/?s=typescript">https://softwareengineeringdaily.com/?s=typescript</a></p>
<pre><code>export interface TableProps&lt;Data extends BasicDataDefine&gt; {
  classNamePrefix?: string
  wrapperClassName?: string
  className?: string
  loading?: boolean
  loader?: JSX.Element
  rowSelection?: RowSelectionOption&lt;Data&gt;
  pagination?: PaginationOption
  dataSource: Data[]
  columns: BasicColumnDefine[]
  children?: undefined

  // #TODO: filter or sorting function
  onChange?: (currentPage: number, pageSize: number) =&gt; void
}

interface DefaultProps {
  classNamePrefix: string
  wrapperClassName: string
  className: string
  loading: boolean
  loader: JSX.Element
}

type PropsWithDefault = TableProps &amp; DefaultProps
</code></pre>
<p>TS2315: Type 'PropsWithDefault' is not generic.</p>
<h1 id="deconstructor">deconstructor</h1>
<pre><code>this.selectedRecords = dataSource.filter(data =&gt;
  (data.key ? false : selectedKeys.includes(data.key))
)

TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.
  
this.selectedRecords = dataSource.filter(data =&gt;
  (data.key ? false : selectedKeys.includes(data.key as string))
)
</code></pre>
<pre><code>const foo?: string = undefined
console.log(foo as string)
</code></pre>
<h1 id="overload">Overload!?</h1>
<p>我們期望的 Overload <s>in Swift</s></p>
<pre><code class="language-swift">class User {
  func register(name: String, gender: Gender, age: Int) {
    ...
  }
  func register(facebookID: String, gender: Gender) {
    ...
  }
  func register(referURL: URL) {
    ...
  }
}
</code></pre>
<p>Protocol 版本</p>
<pre><code class="language-swift">protocol User {
  func register(name: String, gender: Gender, age: Int)
  func register(facebookID: String, gender: Gender)
  func register(referURL: URL)
}
</code></pre>
<p>哦哦哦！？ TypeScript 有型態，所以把前面的邏輯慣例應該可以套用在 TypeScript 吧？</p>
<pre><code>class User {
  register(name: string, gender: Gender, age: number): void {
    ...
  }
  register(facebookID: string, gender: Gender): void {
    ...
  }
  register(referURL: URL): void {
    ...
  }
}
</code></pre>
<p>......淦，怎麼只有最後一個 method 被叫到！！看了一下官方的文件...好吧，手動處理囉！</p>
<pre><code>class User {
  register(name: string, gender: Gender, age: number): void;
  register(facebookID: string, gender: Gender): void;
  register(referURL: URL): void;
  register(nameOrFacebookIDOrReferURL: URL | string, gender?: Gender, age?: number): void {
    ......
  }
}
</code></pre>
<p>雖然這樣寫真心非常醜，不過至少可以算是一種往前踏了一個閃身的改進了吧（？）<br>
<img src="https://media.giphy.com/media/3ohc1cffXa1nl4vyHC/giphy.gif" alt="typescript-overload-for-classes"></p>
<p>嗯，那這樣訂個 interface 如果要有 overload 大概也知道要怎麼做了。</p>
<pre><code>interface User {
  register(name: string, gender: Gender, age: number): void;
  register(facebookID: string, gender: Gender): void;
  register(referURL: URL): void;
  register(nameOrFacebookIDOrReferURL: URL | string, gender?: Gender, age?: number): void;
}
</code></pre>
<p><img src="https://media.giphy.com/media/xULW8laeFh0Wq5GpCE/giphy.gif" alt="overload-for-interface"><br>
WTF!? 怎麼有 4 個 method，做 Overload 處理的只有三個啊？最後一個實作細節怎麼被暴露出來了...<br>
其實在 interface 宣告層想要有 Overload 效果的話，只要宣告需要的 method，而實際上會被呼叫到的 method 在 implement interface 的時候寫上就沒問題了。</p>
<pre><code>interface User {
  register(name: string, gender: Gender, age: number);
  register(facebookID: string, gender: Gender);
  register(referURL: URL);
}
const user: User {
  ...
  register(nameOrFacebookIDOrReferURL: URL | string, gender?: Gender, age?: number) {
    ......
  }
}
// Or
class Member implements User {
  ...
  register(nameOrFacebookIDOrReferURL: URL | string, gender?: Gender, age?: number) {
    ......
  }
}
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[jQuery ♥️ Redux - Adapting old school jQuery with Redux]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><a style="display: inline-block;text-align: center;" href="https://slides.com/calvinpeak/jq-with-redux/"><img style="margin: 0 auto;" title="Flying-Spagehtti-Monster" src="https://s3.amazonaws.com/media-p.slid.es/uploads/127485/images/4093424/Fsm.jpg"><br>
<span>本篇文章 slide 請見此</span><br>
</a></p>
<h1 id="inthebeginning">In The Beginning</h1>
<p>相信有很多開發者第一次接觸 Redux 就是從 React 了解過來，或是從 Redux 當作進入點，開始學習 React，如此一來的結果會是很容易忽略了 Redux 官方文件上一開頭就寫的 <code>You can use Redux together with React, or with any other view library.</code>。而基於之前得到的任務要求：「你要如何向只會 React，但不知道 Redux，甚至只會 jQuery 的開發者介紹 Redux？」的原因，所以這次從 jQuery + Redux 這有趣的組合出發，來重新檢視 Redux 是什麼。</p>
<h1 id="spaghetticode">Spaghetti</h1>]]></description><link>https://blog.capslock.tw/jquery-with-redux/</link><guid isPermaLink="false">5cdc22990ce6ce0001423692</guid><category><![CDATA[general]]></category><category><![CDATA[javascript]]></category><category><![CDATA[redux]]></category><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Mon, 02 Oct 2017 10:08:05 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2020/10/Fsm.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.capslock.tw/content/images/2020/10/Fsm.jpg" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"><p><a style="display: inline-block;text-align: center;" href="https://slides.com/calvinpeak/jq-with-redux/"><img style="margin: 0 auto;" title="Flying-Spagehtti-Monster" src="https://s3.amazonaws.com/media-p.slid.es/uploads/127485/images/4093424/Fsm.jpg" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"><br>
<span>本篇文章 slide 請見此</span><br>
</a></p>
<h1 id="inthebeginning">In The Beginning</h1>
<p>相信有很多開發者第一次接觸 Redux 就是從 React 了解過來，或是從 Redux 當作進入點，開始學習 React，如此一來的結果會是很容易忽略了 Redux 官方文件上一開頭就寫的 <code>You can use Redux together with React, or with any other view library.</code>。而基於之前得到的任務要求：「你要如何向只會 React，但不知道 Redux，甚至只會 jQuery 的開發者介紹 Redux？」的原因，所以這次從 jQuery + Redux 這有趣的組合出發，來重新檢視 Redux 是什麼。</p>
<h1 id="spaghetticode">Spaghetti Code</h1>
<p><a style="display: inline-block;text-align: center;" href="https://zh.wikipedia.org/zh-tw/%E9%A3%9E%E8%A1%8C%E9%9D%A2%E6%9D%A1%E6%80%AA%E7%89%A9"><img style="margin: 0 auto;" title="Flying-Spagehtti-Monster" src="https://blog.capslock.tw/content/images/2017/09/Screen-Shot-2017-09-09-at-1.39.01-PM.png" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"><br>
<span>國外很有趣的飛行義大利麵怪物(Flying Spaghetti Monster)</span><br>
</a></p>
<blockquote>
<p>Spaghetti code is a pejorative phrase for source code that has a complex and tangled control structure, especially one using many GOTO statements, exceptions, threads, or other &quot;unstructured&quot; branching constructs. It is named such because program flow is conceptually like a bowl of spaghetti, i.e. twisted and tangled.</p>
</blockquote>
<p>對於 jQuery，一直以來我對他的印象就是跟義大利麵一樣裹成一團(相信應該也很多人跟我有一樣的印象），而利用 Redux 來將麵團解開就是這篇文章的最終目標。</p>
<h1 id="startfromsimplejqueryimplementation">Start from simple jQuery implementation</h1>
<p>使用 GitHub API 搭配自己的 Bookmark API 來實作簡易的 Bookshelf 功能。</p>
<h4 id="fetchupdateui">Fetch 資料並且 Update UI</h4>
<p>從 GitHub API 取得 repo 列表時，同時從 local server 拿到 Bookmark 列表，並且將原本不存在的 <code>is_saved</code> 屬性加回於 repo list。</p>
<script src="https://gist.github.com/Calvin-Huang/15499bc0392644768fdbd183bcfb088f.js"></script>
<h4 id="togglebookmarkrequestfailed">Toggle Bookmark 而且如果 request failed 恢復狀態</h4>
<p>Toggle bookmark icon 之後會送出請求，新增/刪除 存在於 local server 的 Bookmark，當 request failed 將狀態改回去並且顯示 message。</p>
<script src="https://gist.github.com/Calvin-Huang/9710bc29e3186afd5100a779d5c0f46f.js"></script>
<p><a href="https://github.com/Calvin-Huang/jq-with-redux/tree/release/1.0.0">Template based GitHub bookshelf implemented by jQuery - release 1.0.0</a></p>
<h3 id="nottoobadhuh">Not too bad, huh?</h3>
<p>但，有很多地方藏著可怕的潛在問題。像是：<strong>重複的程式碼、操作分散、意料外的修改所造成的邏輯 Bug</strong><br>
<img style="margin-bottom: 10px;" title="The Hidden Evil -1" src="/content/images/2017/09/Screen-Shot-2017-09-09-at-3.07.22-PM.png" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"><em>修改 <code>is_save </code> 屬性的地方幾乎一樣但在不一樣 callback 做了兩次，<code>page </code> 要是在別的地方被改到，整個 fetching process 就會噴掉而且不知道為什麼。</em></p>
<h3 id="unittest">Unit Test</h3>
<p><img style="margin-bottom: 10px;" title="Horrible Movie Style" src="/content/images/2017/09/FSM-loop.gif" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"><em>基本上不可能做測試，如果正在看這篇文章的你覺得可以的話，還請教學相長一下</em>🙏</p>
<h1 id="itstimetoredux">IT'S TIME TO REDUX</h1>
<h3 id="whatsredux">What's Redux?</h3>
<blockquote>
<p>Redux is a predictable state container for JavaScript apps. It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. (From redux official site.)</p>
</blockquote>
<h3 id="reduxmvc">那 Redux 與 MVC 的差異在哪裡？</h3>
<p>傳統 MVC 在 Controller 內處理 Model 再把資料交給 View ，但當 Model 一多 MVC 的複雜度就會急劇增加，而 Redux 維持著單一個 State 來源試圖減少因為複雜度所造成的問題。<br>
<a style="width: 100%; display: inline-block;text-align: center;" href="https://www.youtube.com/watch?v=nYkdrAPrdcw"><img style="margin: 0 auto;" title="F8 Flux" src="http://iweave.com/assets/blog/mvc_v_flux.png" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"><span>F8 2014 議程影片 (<strike>順帶一提</strike>，Redux 是由 Flux 所派生出來的)</span><br>
</a></p>
<p>當然還有其他 Pattern 像是 MVVM、MVP、VIPER 等等不錯的解決方案...而且老實說我從一開始接觸 Redux 的時候對於 Holding State 這件事相當反感，因為當時我最熟悉的 MVVM 就是透過 <code>ViewModel &lt;=&gt; View</code> 的 Data Binding 來消除 State，減少 Side Effect，但實際上 Redux Flow 相較於 Two Way Data Binding 所帶來的不可預期性，Redux 更為簡單並好測試。</p>
<h3 id="reduxflow">REDUX FLOW</h3>
<p><a style="width: 100%; display: inline-block;text-align: center;" href="https://www.theodo.fr/blog/2016/03/getting-started-with-react-redux-and-immutable-a-test-driven-tutorial-part-1/"><img style="margin: 0 auto;" title="Redux Flow" src="http://www.theodo.fr/uploads/blog//2016/03/ui_workflow.png" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"><span>Getting Started with React, Redux and Immutable: a Test-Driven Tutorial (Part 1)</span><br>
</a></p>
<p>Redux 維持 State，並且只有單一方向的透過 dispatch actions 去更新 State，如同官方文件上所提到的 <code>Redux is a predictable state container for JavaScript apps.</code>，它非常的好理解與預期，甚至因為這一點，有很棒的開發工具能協助我們有效的的提升效率。<br>
<a style="width: 100%; display: inline-block;text-align: center;" href="https://github.com/gaearon/redux-devtools"><img style="margin: 0 auto;max-width: 500px" title="Redux Devtool" src="https://camo.githubusercontent.com/a0d66cf145fe35cbe5fb341494b04f277d5d85dd/687474703a2f2f692e696d6775722e636f6d2f4a34476557304d2e676966" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"><span>Redux Devtool 能檢視現在的 state、dispatched action、甚至返回已經執行過的步驟</span><br>
</a></p>
<h1 id="reduxjqueryapp">使用 Redux 重構 jQuery App</h1>
<p>加入 lastAction reducer 後透過 subscribe store 來取得最後執行的 action，對應 action 觸發其他 action。</p>
<script src="https://gist.github.com/Calvin-Huang/c9df65314deedd3145a4641c7099aff8.js"></script>
<p>使用 Redux 重構之後，現在我們解決了重複程式碼與迴避無可預期狀態改變的情形。<br>
<video autoplay loop src="https://media.giphy.com/media/l378apkwSbrYbLT8Y/giphy-hd.mp4" style="width: 100%;"></video></p>
<p>但，截至目前為止，code 變多了，它依舊無法被測試。 <img style="margin: 0; display: inline; width: 100px; vertical-align: middle;" src="https://s3.amazonaws.com/media-p.slid.es/uploads/127485/images/4096691/AreYouKiddingMe.png" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux"></p>
<h1>SPLIT APP INTO MODULES<small style="color: darkgray">, and wrap it up as ESModule for jest</small></h1>
將 App 拆分正依照功能區分的模組
<script src="https://gist.github.com/Calvin-Huang/319be6649604a3a99252e88167bd6715.js"></script>
<p>並且修改 subscribe，把異步動作註冊為不影響 action 的 middleware，同時 middleware 為了徹底解耦合利用 Curry 的方式做 DI。</p>
<script src="https://gist.github.com/Calvin-Huang/202e234039698b1ff0376343eaf1a515.js"></script>
<p>最後留下的進入點就變得非常乾淨</p>
<script src="https://gist.github.com/Calvin-Huang/00c5d8bd59602173fb61548d774e2590.js"></script>
<p>並且可以依照模組進行測試，大功告成！</p>
<script src="https://gist.github.com/Calvin-Huang/a0e42e579e43e5fde37f211b51ac367b.js"></script>
<h1 id="bonusreduxthunk">Bonus: redux-thunk</h1>
<p>使用自己註冊一個 middleware 實作的方式是一個方法，但到底還是不夠精簡，在這個 Bonus section 裡面我另外又實作了一個使用 redux-thunk 時做了相同的功能，各位可以在 <a href="https://github.com/Calvin-Huang/jq-with-redux/tree/release/3.0.0">GitHub - release 3.0.0</a> 找到 code，在這裡就不增加篇幅解釋了。</p>
<p>另外在 redux community 裡面處理 asynchronous action 的優秀 middleware 很多，關於其他 middleware，我整理了另一篇文章，希望可以幫助到各位做選擇 <a href="https://blog.capslock.tw/2017/09/06/rxjs-awesome/">redux-observable/Redux-Saga</a>。</p>
<h1 id="wrapitup">Wrap it up</h1>
<p>這篇文章的專案裡面我們使用到了 JsRender 來做 template，所以實際上也可以再加上同系列的 JsViews + JsObservable 去實作 MVVM pattern，就兩者相對的比較來說，雖然以這個小專案來說 MVVM 實作程式碼肯定比較精簡，但redux/flux pattern 的 one way data flow 更好預期，不論是開發或是協作上都很有幫助。</p>
<p>另外就是 jq 本身並不像 React 一樣是 one way data flow，所以在當多人協作時難說會有人就打破了 one way data flow 造成管理上的混亂，所以既然都 redux 了，<strong>Why not React? Lol</strong></p>
<p>最後希望各位可以找到最適合自己/團隊的開發方式/技術，來跟飛天義大利麵怪物 Say good bye! 祝各位平安喜樂、身體健康，我們下次再會。</p>
<img style="margin: 0 auto;" title="F8 Flux" src="http://pixelartmaker.com/art/c51f7a3b2fc7cc5.png" alt="jQuery ♥️ Redux - Adapting old school jQuery with Redux">
<h2 id="references">References</h2>
<ul>
<li><a href="https://github.com/Calvin-Huang/jq-with-redux/tree/release/1.0.0">Pure jQuery implementation - release 1.0.0</a></li>
<li><a href="https://github.com/Calvin-Huang/jq-with-redux/tree/release/2.0.2">Redux middleware implementation - release 2.0.2</a></li>
<li><a href="https://github.com/Calvin-Huang/jq-with-redux/tree/release/3.0.0">redux-thunk implementation - release 3.0.0</a></li>
<li><a href="https://jq-with-redux.herokuapp.com">Online site in Heroku</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[從 Build Fake Data 開始的 TDD 開發流程 - 有效的減少除錯與開發時間]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h2 id="ntdd">講了 N 次的 <a href="http://c2.com/cgi/wiki?TestDrivenDevelopment">TDD</a></h2>
<p>總地來說**「TDD 能有效減少開發時間 &amp; 降低 Bug 發生頻率。」**</p>
<p>在這邊簡單舉一些 TDD 的優點 ↓</p>
<ul>
<li>先整理過的 Test Target 能幫助減少 Over Design 與未確認規格。</li>
<li>已經完成的功能可以透過 Test 來確保重構或是其他變更時舊有部分可以正常執行。</li>
<li>構思 Test 的過程中可以列舉出 Code 可能遇到的所有問題可能性，確保思考的完整度。</li>
<li>可以被抽離測試的功能意味著程式碼的耦合度低，重用性高，架構方向沒有問題。</li>
<li>減少開發途中手動觀測執行結果的等待時間。</li>
</ul>
<p>畢竟這篇文章並沒有要盡全力解說 TDD，有興趣的朋友們可以參考、關於 TDD 的一些介紹文章：<a href="https://dotblogs.com.tw/hatelove/archive/2013/01/11/learning-tdd-in-30-days-catalog-and-reference.aspx">30天快速上手TDD</a>、<a href="http://teddy-chen-tw.blogspot.tw/2014/09/bddtdd.html">關於BDD/TDD的三大誤解</a></p>
<p>而其實這篇文章的起頭是因為「重構」而且沒有寫 Unit Test 所埋下的隱憂開花結果引起過錯！<del>所以有</del></p>]]></description><link>https://blog.capslock.tw/cong-build-fake-data-kai-shi-de-tdd-kai-fa-liu-cheng-you-xiao-de-jian-shao-chu-cuo-yu-kai-fa-shi-jian/</link><guid isPermaLink="false">5cdc22990ce6ce000142367e</guid><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Wed, 13 Sep 2017 15:17:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h2 id="ntdd">講了 N 次的 <a href="http://c2.com/cgi/wiki?TestDrivenDevelopment">TDD</a></h2>
<p>總地來說**「TDD 能有效減少開發時間 &amp; 降低 Bug 發生頻率。」**</p>
<p>在這邊簡單舉一些 TDD 的優點 ↓</p>
<ul>
<li>先整理過的 Test Target 能幫助減少 Over Design 與未確認規格。</li>
<li>已經完成的功能可以透過 Test 來確保重構或是其他變更時舊有部分可以正常執行。</li>
<li>構思 Test 的過程中可以列舉出 Code 可能遇到的所有問題可能性，確保思考的完整度。</li>
<li>可以被抽離測試的功能意味著程式碼的耦合度低，重用性高，架構方向沒有問題。</li>
<li>減少開發途中手動觀測執行結果的等待時間。</li>
</ul>
<p>畢竟這篇文章並沒有要盡全力解說 TDD，有興趣的朋友們可以參考、關於 TDD 的一些介紹文章：<a href="https://dotblogs.com.tw/hatelove/archive/2013/01/11/learning-tdd-in-30-days-catalog-and-reference.aspx">30天快速上手TDD</a>、<a href="http://teddy-chen-tw.blogspot.tw/2014/09/bddtdd.html">關於BDD/TDD的三大誤解</a></p>
<p>而其實這篇文章的起頭是因為「重構」而且沒有寫 Unit Test 所埋下的隱憂開花結果引起過錯！<del>所以有 Test 很重要！有 Test 超重要！<del><br>
<img src="https://blog.capslock.tw/content/images/2016/09/twice-because-important.jpg" alt></del></del></p>
<h3 id>凡事都有個起因，而事情是這樣的</h3>
<p>最近公司在將 APP 舊的 UI 翻新，同時開發新的功能，而因為新功能內容跟舊的功能內容完全相同，所以透過人工檢查就容易因為以往的經驗來推測結果，而忽視了問題發生的可能性，既然是因為體感與輕忽所引起，那麼開發者本身也無可避免，像是我自己就踩了幾次自己所留下來的雷。<br>
舉個之前犯錯的經驗來當範例。</p>
<h3 id>自爆雷案例：不知道到底有沒有換的圖片</h3>
<p>經過重新設計之後，我們 <a href="http://www.pipimy.com.tw/">pipimy</a> 的廣告格式改為在 Web 上常見，大家所熟識的 Carousel<br>
<img src="https://blog.capslock.tw/content/images/2016/09/carousel.gif" alt="pipimy-ad-carousel"><br>
開發為基本功能，確定可以滑動之後，經過 Code Review 之後交給其他同事測也沒有問題，沒想到上版本之後才發現在<strong>切換類別時需要重新取得廣告內容的功能缺了</strong>！</p>
<h3 id>明明缺了功能，卻到底為什麼大家都沒有發現！？</h3>
<p>「嗯？不對啊...怎麼記得好像沒有測到這個問題？」一邊在腦子找尋蛛絲馬跡，一面碎碎念打開 Charles 比較舊版 App 跟新版 App 的差異才發現原來問題出在「在更新成新版前切換不同類別 <strong>API 所提供的 image url 完全沒有變動</strong>」因為在不同的類別下都是同樣的廣告，所以在更新後切換類別廣告沒有動也是很正常的。</p>
<p>其實對於這個案例來說最嚴謹的測試流程應該是得</p>
<ol>
<li>設定廣告內容為 1 個</li>
<li>下拉更新 &amp; 測試點擊 &amp; 檢查廣告內容是否正確</li>
<li>設定廣告內容為 2 個</li>
<li>下拉更新 &amp; 測試點擊 &amp; 檢查廣告內容是否正確</li>
<li>設定廣告內容為 N 個 ( N 為適當次數以假設複數內容皆無問題 )</li>
<li>下拉更新 &amp; 測試點擊 &amp; 檢查廣告內容是否正確</li>
<li>切換不同類別檢查廣告內容是否變更，回到 1 步驟</li>
</ol>
<p>不過光是重複同樣動作設定廣告內容這件事對於測試人員來說就是一件非常浪費時間的事情了，再說身為 iOS 開發人員，我並沒有後台設定管理權限沒辦法自己有事沒事就去隨便更動內容，一需要測試的時候就去麻煩後端設定光是往返不計中斷工作的時間成本就很高，跑 Test 的方式無庸置疑的能節省掉這些時間上的浪費，可以更專注於其他工作上。</p>
<p>在這邊必須先提一下我們目前公司內現在專案 Coverage 不到 10%，既有的 Code 也說實在不少，補上那些 Test 需要心力，加上開發新功能的時程考量下，就會重複考慮是否真的需要補 Test 跟 寫新功能跑 TDD，但實際上當然是可以越早開始越好。</p>
<p><strong>所以可以的話還是建議新 project 就開始跑 TDD，但不建議程式新手一頭栽入，你會發現試圖去了解 Unit Test 是什麼、如何寫的時間效益反而拉慢專案進程。</strong></p>
<h3 id="uitestchcarouselview">UI Test 小試身手 ( 以 <a href="https://github.com/Calvin-Huang/CHCarouselView">CHCarouselView</a> 做範例 )</h3>
<p>測試更換 CarouselView 的內容</p>
<script src="https://gist.github.com/Calvin-Huang/1364c05d08af1191fa5376ba81a4c4ed.js"></script>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[redux-observable/Redux-Saga]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h1 id="reduxsideeffects">關於 redux side effects</h1>
<p>在開發前端 SPA 的時候，我們有一大半的時間再處理異步動作，更具體來說的話就是網路請求(ajax)。</p>
<p>在一般的使用情境下的流程大概會是這樣的<br>
<img src="https://blog.capslock.tw/content/images/2017/09/network-request-flow.png" alt="network request flow"></p>
<p>處理從請求開始到請求結束的流程在 redux only 下，我們可以透過 store.subscribe 去監聽 actions，然後進一步的調用異步方法，並在異步方法結束的時候 dispatch 相對應的 action。</p>
<script src="https://gist.github.com/Calvin-Huang/ba1a310b3b780f527904f0b5d5a5e0a1.js"></script>
<ul>
<li><em>lastAction 需要自己在 reducer 內實作，可以參考到 <a href="https://github.com/reactjs/redux/issues/580">https://github.com/reactjs/redux/issues/580</a></em></li>
</ul>
<p>但如果註冊為 middleware 而不是 subscribe store 的話，不但可以取得 store/action 甚至進而調整輸出的 action。</p>
<script src="https://gist.github.com/Calvin-Huang/87534c0df8fb25cd6a3e493bd85e6ecd.js"></script>
<p>到這邊，其實光是用來舉例的單單一個</p>]]></description><link>https://blog.capslock.tw/rxjs-awesome/</link><guid isPermaLink="false">5cdc22990ce6ce0001423691</guid><category><![CDATA[general]]></category><category><![CDATA[javascript]]></category><category><![CDATA[redux]]></category><dc:creator><![CDATA[Calvin Huang]]></dc:creator><pubDate>Wed, 06 Sep 2017 03:28:57 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2020/10/network-request-flow.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h1 id="reduxsideeffects">關於 redux side effects</h1>
<img src="https://blog.capslock.tw/content/images/2020/10/network-request-flow.png" alt="redux-observable/Redux-Saga"><p>在開發前端 SPA 的時候，我們有一大半的時間再處理異步動作，更具體來說的話就是網路請求(ajax)。</p>
<p>在一般的使用情境下的流程大概會是這樣的<br>
<img src="https://blog.capslock.tw/content/images/2017/09/network-request-flow.png" alt="redux-observable/Redux-Saga"></p>
<p>處理從請求開始到請求結束的流程在 redux only 下，我們可以透過 store.subscribe 去監聽 actions，然後進一步的調用異步方法，並在異步方法結束的時候 dispatch 相對應的 action。</p>
<script src="https://gist.github.com/Calvin-Huang/ba1a310b3b780f527904f0b5d5a5e0a1.js"></script>
<ul>
<li><em>lastAction 需要自己在 reducer 內實作，可以參考到 <a href="https://github.com/reactjs/redux/issues/580">https://github.com/reactjs/redux/issues/580</a></em></li>
</ul>
<p>但如果註冊為 middleware 而不是 subscribe store 的話，不但可以取得 store/action 甚至進而調整輸出的 action。</p>
<script src="https://gist.github.com/Calvin-Huang/87534c0df8fb25cd6a3e493bd85e6ecd.js"></script>
<p>到這邊，其實光是用來舉例的單單一個 action 在不管是 subscribe store 或是 middleware 裡面的實作，從冗長的 switch 判斷就知道這樣的解決方式是一個不好的方案。</p>
<h2 id="reduxthunk"><a href="https://github.com/gaearon/redux-thunk">redux-thunk</a></h2>
<p>redux-thunk 是 redux 異步處理 middleware 的開拓者，他非常簡單易懂 <s>(不過其實在接觸thunk初期的時候我根本不知道 <code>dispatch</code> 從哪來，有興趣的人可以看一下 <a href="https://github.com/gaearon/redux-thunk/blob/master/src/index.js">redux-thunk source code</a>，極短)</s>。</p>
<script src="https://gist.github.com/Calvin-Huang/512f5386e988e23e614204319f20fa97.js"></script>
<p>但在更為複雜的架構下就顯得較為無力招架，所以開發者們從最原初的<br>
thunk 出發，開始尋求其他的解決方案。</p>
<p>處理 side effects 的其他 middleware 中較為亮眼的則有 <a href="https://redux-observable.js.org/">redux-observable</a>，與 <a href="https://redux-saga.js.org/">redux-sage</a>，基本上兩者處理 side effects 都非常優秀，並且相似，而這篇文章則是想要整理些比較做個筆記以及提供給各位做選擇上的參考。</p>
<p>那麼先從簡單的介紹開始切入核心：</p>
<p><a href="https://redux-observable.js.org/" style="min-width: 100%; overflow: auto; display: flex; align-items: center;"><img style="max-width: 80px;margin-bottom: 10px;" src="https://redux-observable.js.org/logo/logo-small.gif" alt="redux-observable/Redux-Saga"> <img style="max-width: 300px;margin-bottom: 10px;" src="https://redux-observable.js.org/logo/logo-text-small.png" alt="redux-observable/Redux-Saga"></a><br>
以 RxJS 為主，核心概念為將 action 視為 stream，透過 observe action 並且轉換為另一個 action 來處理異步請求。</p>
<p><code>Actions in, actions out</code>，在這邊附上從<a href="https://redux-observable.js.org/docs/basics/Epics.html">官方文件來的簡單範例</a>。</p>
<script src="https://gist.github.com/Calvin-Huang/d9a51a180d417653ab25e673294baba4.js"></script>
<div style="text-align: center">~~Rx 系列老話一句，什麼東西都 Observable。~~</div>
<img style="max-width: 300px; margin: 10px auto;" title="every thing is sequence" src="https://s3.amazonaws.com/media-p.slid.es/uploads/127485/images/4066746/Screen_Shot_2017-08-23_at_8.17.44_AM.png" alt="redux-observable/Redux-Saga">
<p>而對 RxJS 並不熟悉的各位，Rx 系列的文件非常齊全，推薦可以透過 <a href="http://rxmarbles.com/">RxMarbles</a> 的視覺化 Operator 來學習。</p>
<p>Netflix 工程師 Jay Phelps 的 talk 也非常有幫助，並且非常風趣 (<s>Jay: <a href="https://youtu.be/AslncyG8whg?t=20m5s">open source的第一步，就是要做一個好 Logo</a></s>)。<br>
<a href="https://www.youtube.com/watch?v=AslncyG8whg"><img src="http://img.youtube.com/vi/AslncyG8whg/0.jpg" alt="redux-observable/Redux-Saga"></a></p>
<div style="text-align: center">~~到這邊 Rx 的缺點：需要的入門門檻有點高就悄悄的冒出來了~~</div>
<p><a href="https://redux-saga.js.org/" style="display: flex; align-items: center;"><img style="max-width: 300px;margin-bottom: 10px;" src="https://redux-saga.js.org/logo/0800/Redux-Saga-Logo-Landscape.png" alt="redux-observable/Redux-Saga"></a><br>
以 ES6 generator 為核心主軸，利用 yield 和 redux saga 所提供的一些 helpers 來處理 async actions。</p>
<p>我們可以從<a href="https://redux-saga.js.org/docs/basics/UsingSagaHelpers.html">官方的範例</a>裡看到 generator 的運用，而學習 redux-saga 基本上就等同於學習 ES6 generator 的過程。</p>
<script src="https://gist.github.com/Calvin-Huang/26234eb7202c795c3c6335d0cf0487ba.js"></script>
<blockquote>
<p>By doing so, these asynchronous flows look like your standard synchronous JavaScript code. (kind of like async/await, but generators have a few more awesome features we need)</p>
</blockquote>
<div style="text-align: center; margin-bottom: 40px;">~~不是我偏心，不對 redux-saga 提更多介紹，因為他的本體幾乎就是 generator。~~</div>
<h1 id="comparesidebyside">Compare side by side</h1>
<img style="max-width: 300px; margin: 10px auto;" title="every thing is sequence" src="http://image.itmedia.co.jp/nl/articles/1509/28/mach_150928moe03.jpg" alt="redux-observable/Redux-Saga">
<p>因為 redux-sage 或是 redux-observabl 其實兩者的差異並沒有到天差地遠的大，那就跟選用顯卡一樣各取所需，接下來就依照一些使用情境來讓各位能更清楚的了解兩者的差異。</p>
<h3 id="launchup">Launch Up</h3>
<p><mark>saga</mark> - Watcher + Worker</p>
<script src="https://gist.github.com/Calvin-Huang/53173f7835227f044dbbb163975d1b4f.js"></script>
<p><mark>observable</mark> - Type &amp; Observable</p>
<script src="https://gist.github.com/Calvin-Huang/eee0a624c9f81d12bb09cb236c0f555a.js"></script>
<h3 id="cancelable">Cancelable</h3>
<p><mark>saga</mark></p>
<script src="https://gist.github.com/Calvin-Huang/e87d42292e0a0688556ccfcdef45a22d.js"></script>
<p><mark>observable</mark></p>
<script src="https://gist.github.com/Calvin-Huang/909d1673e83b40bccbf72f39d1313fad.js"></script>
<h3 id="throttling">Throttling</h3>
<p><mark>saga</mark></p>
<script src="https://gist.github.com/Calvin-Huang/2facd70892688c1e19931ebac7254e0a.js"></script>
<p><mark>observable</mark></p>
<script src="https://gist.github.com/Calvin-Huang/74231679fb19e60dbdce01b7dd2d4f79.js"></script>
<h3 id="debouncing">Debouncing</h3>
<p><mark>saga</mark></p>
<script src="https://gist.github.com/Calvin-Huang/698cbb954d714a41c1726d0cee1be629.js"></script>
<p><mark>observable</mark></p>
<script src="https://gist.github.com/Calvin-Huang/5323cc0ce20339a67ac58851b37f0354.js"></script>
<h3 id="retrying">Retrying</h3>
<p><mark>saga</mark></p>
<script src="https://gist.github.com/Calvin-Huang/a0e0883c338cae75cd300d16a2ceb930.js"></script>
<p><mark>observable</mark></p>
<script src="https://gist.github.com/Calvin-Huang/df5f4cecc2e98343c48a7d317ab69891.js"></script>
<h1 id="conclusion">Conclusion</h1>
<div style="width: 100%; overflow: auto;"><table>
  <thead>
    <tr>
      <th></th>
      <th style="width: 40%;">redux-observable</th>
      <th style="width: 40%;">Redux-Saga</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        Learning Path
      </td>
      <td>
        <ul>
          <li>redux</li>
          <li>Functional Programming</li>
          <li>RxJS (ReactiveX)</li>
          <li>redux-observable</li>
        </ul>
      </td>
      <td>
        <ul>
          <li>redux</li>
          <li>ES6 Generator</li>
          <li>Redux-Saga</li>
        </ul>
      </td>
    </tr>
    <tr>
      <td>
        Style
      </td>
      <td>Declarative</td>
      <td>Imperative</td>
    </tr>
    <tr>
      <td>
        Pros/Cons
      </td>
      <td>
        <ul>
          <li>需要學習新的概念，例如 FP & ReactiveX，入門門檻高。</li>
          <li>FP 的優點 Declarative 讓程式更可預測，易懂、好閱讀，同時也可以是缺點<strike>(chaining 像哈味一樣，有人喜歡有人不喜歡)</strike>。</li>
          <li>習慣(<strike>中毒</strike>) Observable Pattern 之後可以將概念運用在各個地方，不單是 redux-observable，可以像是 Animation - <br><a href="http://slides.com/davidkhourshid/getting-reactive-with-css#/">[推薦閱讀，神一般的 CSS animation] <strong>Getting Reactive with CSS</strong> - David Khourshid</a></li> 
        </ul>
      </td>
      <td>
        <ul>
          <li>實作 Generator 對熟悉 ES6 的開發者來說上手Redux-Saga 時間很短，只需要了解 Saga 提供的 helpers 與抽象概念 Effects 就行了。(但也有一說是不需要學習 Generator ，除非你是 lib or framework devloper - <a href="https://derickbailey.com/2017/04/19/with-es7-and-beyond-do-you-need-to-know-es6-generators/">With ES7 And Beyond, Do You Need To Know ES6 Generators?</a>。</li>
          <li>Imperative Programming 也跟 Declarative 一樣既是優點也是缺點，他是每一位開發者從小到大(?)熟悉的開發模式，但實作上相較 FP 來說更不可預期，需要時間理解，會有 state 變多所帶來的 Side Effects，<strike>但神人就是可以寫得很好</strike>。</li>
        </ul>
      </td>
    </tr>
  </tbody>
</table></div>
Thanks for the authorization from Chao wei Chiu. [Side by side comparsion](https://hackmd.io/p/H1xLHUQ8e#/) is an awesome presentation. (Here is the [transcript](https://hackmd.io/s/H1xLHUQ8e#side-by-side-comparison))
<p><strong>另外有其他更棒的 side by side comparison 例子歡迎大家討論提供 😉</strong></p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[開發之各種心酸血淚史以及各種通靈篇]]></title><description><![CDATA[去你的twilio。]]></description><link>https://blog.capslock.tw/fuck-you-twilio/</link><guid isPermaLink="false">5cdc22990ce6ce000142368f</guid><category><![CDATA[general]]></category><category><![CDATA[twilio]]></category><dc:creator><![CDATA[Michael.K]]></dc:creator><pubDate>Wed, 03 May 2017 09:59:57 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>話說在前頭，<s>這是一篇廢文</s>。<br>
純粹是做祖國的專案的murmur(好啦其實是供應商是偉大的祖國而已)<br>
因為本次難得的文主要都是當成廢文札記打的，所以麻煩就照著廢文的標準看吧</p>
<hr>
<h2 id>故事是這樣的</h2>
<p>某天同班同學B：「我朋友這裡有人想做一個專案blablablabla...」，對於<s>錢</s>誠信當仁不讓的我就理所當然的接了<br>
<img src="https://blog.capslock.tw/content/images/2017/04/-----2017-04-27---11.48.45.png" alt></p>
<h2 id>各種鬼打牆開始</h2>
<p>專案走走停停大概快一個月終於完成了，好不容易剩下介接的部份，由於前幾次的介接經驗基本上都是自己家的服務，那「國外的服務來試看看好了～」大概這樣智障的感覺。</p>
<h3 id>故事的起源</h3>
<p>想想當初為什麼要串<code>twilio</code>呢？</p>
<ol>
<li>廣告很大</li>
<li>米國人很喜歡用</li>
<li>還可以接電話，Voice Service做的還不錯</li>
<li>支援本地號碼</li>
</ol>
<p>寫起來大概會長的像是這樣<br>
<img src="https://blog.capslock.tw/content/images/2017/05/-----2017-05-03---4.27.21.png" alt></p>
<p>然後收個簡訊...<br>
<img src="https://blog.capslock.tw/content/images/2017/05/IMG_3119.PNG" alt></p>
<p>嗯？是不是哪裡怪怪的？</p>
<p>翻了一下文件，原來是要經過Messaging Service才有本地電話啊！<br>
<a href="https://www.twilio.com/docs/api/rest/messaging-services-and-copilot-overview">https://www.twilio.com/docs/api/rest/messaging-services-and-copilot-overview</a></p>
<p>於是又稍微改了一下code，變成像是這樣<br>
<img src="https://blog.capslock.tw/content/images/2017/05/-----2017-05-03---4.32.56.png" alt></p>
<p>雖然醜了一點，不過看起來還算可以？<br>
<img src="https://blog.capslock.tw/content/images/2017/05/IMG_3120.PNG" alt></p>
<h3 id>吃我的簡訊小王子</h3>
<p>然後回去之後到摩斯繼續趕工，再試了一次整個臉都綠掉了<br>
<img src="https://blog.capslock.tw/content/images/2017/05/IMG_3121.PNG" alt></p>
<p>試完之後整個傻掉了，因為不知道為什麼會這樣，於是又送了幾封信到twilio那裡，過了N(N&gt;=2)終於收到twilio的回信<br>
<img src="https://blog.capslock.tw/content/images/2017/05/-----2017-05-03---4.50.26.png" alt></p>
<p>那個我說... 他是不是有講跟沒講一樣...  所以我又送了一封信過去問(為了確保我英文太爛的問題還特別多解釋了一下，然後我又是兩天之後才收到回覆)<br>
<img src="https://blog.capslock.tw/content/images/2017/05/-----2017-05-03---4.52.55.png" alt></p>
<p>嗯？ 所以3/24號送到澳門的門號顯示香港的應該是我的幻覺啊～ 看來得找一天去看眼科了</p>
<p>不過更慘的不是這個...</p>
<h3 id>無限的收不到</h3>
<p>進到最後的UAT階段，不該發生的的終於發生惹...<br>
<img src="https://blog.capslock.tw/content/images/2017/05/IMG_3122.PNG" alt></p>
<p>對，簡訊各種收不到(死</p>
<p>因為處理速度實在是太慢了，所以只好再送一張ticket過去問，過了一天又收到罐頭回覆<br>
<img src="https://blog.capslock.tw/content/images/2017/05/-----2017-05-03---5.18.59.png" alt></p>
<p>稍微補充了一下敘述，然後又過了一天才收到新的罐頭回覆<br>
<img src="https://blog.capslock.tw/content/images/2017/05/-----2017-05-03---5.20.51.png" alt></p>
<p>嗯？所以解ticket的速度是一天一次是SOP嗎？還是我太勤勞半夜等他們上班回覆後馬上回信是白癡呢？(這裡來個數學小教室：1+1+1=3，這個過程又處理了三天)</p>
<p>因為拖的時間實在是太久了，只是查原因就過了12天，然後一點實際上的進度都看不太到<br>
<img src="https://blog.capslock.tw/content/images/2017/05/IMG_3123.PNG" alt></p>
<p>然後過了一天後我只收到了這樣的回覆<br>
<img src="https://blog.capslock.tw/content/images/2017/05/-----2017-05-03---5.32.13.png" alt></p>
<p>除了傻眼之外想不到更好的形容詞了... 前前後後經過了兩個禮拜，結果完全沒有進度，technical customer service engineer也來來回回好幾次</p>
<h2 id="twilio">再見雞掰Twilio</h2>
<p>忍耐兩個禮拜終於受不了去退錢了，好在退錢還蠻乾脆的，只是兩個禮拜已經用掉快100塊的簡訊錢...</p>
<p><img src="https://blog.capslock.tw/content/images/2017/05/-----2017-05-03---5.38.29.png" alt></p>
<p>收到退款的瞬間有種解脫的感覺... 只是退錢完之後我就永遠找不到我的ticket了...</p>
<p>下次是不是該把這種廣告打很大的供應商先列黑名單好呢....</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[GPA(Grade Point Average) 衡量你的專案品質]]></title><description><![CDATA[簡單介紹如何衡量專案的程式品質以及codeclimate怎麼算出來專案得分。]]></description><link>https://blog.capslock.tw/how-gpa-be-calculated/</link><guid isPermaLink="false">5cdc22990ce6ce000142368a</guid><category><![CDATA[general]]></category><category><![CDATA[code quality]]></category><dc:creator><![CDATA[Michael.K]]></dc:creator><pubDate>Tue, 27 Dec 2016 10:56:55 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>最近在處理維運及品質控管的工作，一邊研究各式各樣的code review的工具。</p>
<p>舉凡如<code>codeclimate</code>、<code>codebeat</code>、<code>scrutinizer</code>之類的自動測試化工具，會依據一些品質指標進行分數評比，再將最終的分數進行以rank A~F的GPA評分來衡量最後的情況。</p>
<h4 id="gpagradepointaverage">何謂GPA呢(Grade Point Average)?</h4>
<p>GPA是美國一種衡量學生的分數指標，通常來說分為四點分數(4-point)，也就是最高就是4分(A rank)，但是這個指標會依據不同的地區有不同的評量標準。</p>
<p>以最常見的四分(4-point)來看的話，學生可以分成下面的等級</p>
<table>
  <thead>
    <th>Rank</th>
    <th>Score</th>
  </thead>
  <tbody>
    <tr>
      <td>A</td>
      <td>4.0</td>
    </tr>
    <tr>
      <td>B</td>
      <td>3.0</td>
    </tr>
    <tr>
      <td>C</td>
      <td>2.0</td>
    </tr>
    <tr>
      <td>D</td>
      <td>1.0</td>
    </tr>
    <tr>
      <td>F</td>
      <td>0</td>
    </tr>
  </tbody>
</table>
<p>以小明來說好了:<br>
某天小明得了<code>兩個A</code>、<code>一個B</code>和<code>一個C</code>，那麼小明的分數就是<code>(4+4+3+2)/4=3.25</code>，整體平均看起來還不錯。</p>
<p>當然，GPA不全然只有單純的四分得點，可能依據不同的環境會有不同的計分方式，像是比較常見的4.5分得點(4.5-point)以及五分得點(5-point)。</p>
<h4 id>程式碼評斷的標準</h4>
<p>程式碼不會像是純粹的得分計算，就跟學生對於不同科目來說，每個科目的重要性也不相同。<br>
以程式碼來說，程式的主要衡量指標為<code>行數</code>，不然一個只有10行的程式跟100行的程式直接比較差距很容易過於樂觀。</p>
<h4 id>那計算的標準呢？</h4>
<p>品質的計算方式不外乎下面幾個指標</p>
<ul>
<li>待處理的區塊(todo code block)</li>
<li>重複(duplication)</li>
<li>複雜度(mess)</li>
<li>風格(coding style)</li>
</ul>
<p>依據不同指標會有不同程度的評比，以<code>codeclimate</code>來說，他的計算方式為累計得點，換算如下</p>
<table>
  <thead>
    <th>score</th>
    <th>Rank</th>
  </thead>
  <tbody>
    <tr>
      <td>0 - 2M</td>
      <td>A</td>
    </tr>
    <tr>
      <td>>2M - 4M</td>
      <td>B</td>
    </tr>
    <tr>
      <td>>4M - 8M</td>
      <td>C</td>
    </tr>
    <tr>
      <td>>8M - 16M</td>
      <td>D</td>
    </tr>
    <tr>
      <td>>16M</td>
      <td>F</td>
    </tr>
  </tbody>
</table>
<h4 id>如何計算</h4>
<p>先前有提到評斷的標準為行數，以一個例子來說好了。<br>
目前專案的狀態像是下面的表</p>
<table>
  <thead>
    <th>file</th>
    <th>point</th>
    <th>loc</th>
  </thead>
  <tbody>
    <tr>
      <td>Foo.php</td>
      <td>3000000</td>
      <td>100</td>
    </tr>
    <tr>
      <td>Bar.php</td>
      <td>1500000</td>
      <td>50</td>
    </tr>
    <tr>
      <td>Foo1.php</td>
      <td>4100000</td>
      <td>200</td>
    </tr>
  </tbody>
</table>
<p><code>file</code>是目前這個專案底下所有的檔案，<code>point</code>是累計得點，而<code>loc</code>是該檔案的總共行數。</p>
<p>根據上一個標題我們可以得到:<br>
Foo.php為Rank B(3.0)<br>
Bar.php為Rank A(4.0)<br>
Foo1.php為Rank C(2.0)</p>
<p>那我們的分數可以依據下面的公式得出<br>
GPA = (3*100 + 50 * 4 + 200 * 2) / (100 + 50 + 200) = 2.57</p>
<p>最後得到的<code>2.57</code>就是你這個專案的得分了喔！</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[[廢廢心得文]談程式碼靜態分析開發心得]]></title><description><![CDATA[Capslock-Studio/FilterClass 的開發心得以及筆記 by Michael.K]]></description><link>https://blog.capslock.tw/tips-in-static-code-analysis/</link><guid isPermaLink="false">5cdc22990ce6ce0001423689</guid><category><![CDATA[PHP]]></category><category><![CDATA[Note]]></category><dc:creator><![CDATA[Michael.K]]></dc:creator><pubDate>Wed, 26 Oct 2016 17:19:00 GMT</pubDate><media:content url="https://blog.capslock.tw/content/images/2016/10/php_logo.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><img src="https://blog.capslock.tw/content/images/2016/10/php_logo.png" alt="[廢廢心得文]談程式碼靜態分析開發心得"><p>為了確保產出良好的品質，我們大多都要模擬不同的情境下程式可能會發生的狀況，傳統不走TDD的話都要回去一行一行的翻程式碼，此舉需要花費的時間相當漫長，而且並不是很有效。</p>
<p>甚至寫到一半要去看covergae report有踩到哪些地方，這樣下次寫的時候才可以儘量不重複寫(尤其多人開發非常容易踩到)<br>
<img src="https://blog.capslock.tw/content/images/2016/10/-----2016-10-23---5-11-12.png" alt="[廢廢心得文]談程式碼靜態分析開發心得"></p>
<p>如果是小範圍的程式還好，但是total lines成長到上千或是上萬行就很頭疼了...<br>
<img src="https://blog.capslock.tw/content/images/2016/10/-----2016-10-23---5-20-44.png" alt="[廢廢心得文]談程式碼靜態分析開發心得"></p>
<p>對於這樣漫無目的的找無疑是相當大的成本，常常找一找就一整天過去了，所以就在想說有什麼更有效的方式可以處理這個問題，而目前也沒有很有效的方式去判斷哪些函式(function)哪些有用到哪些沒用到的，基本上都是靠漫無目的的人為判斷。</p>
<p>但這時候又要問了：<strong><code>難道沒有什麼更有效的方法嗎？</code></strong></p>
<h3 id>簡單來說，那時候的初步構想是這樣的</h3>
<p><img src="https://blog.capslock.tw/content/images/2016/10/Untitled-Diagram.png" alt="[廢廢心得文]談程式碼靜態分析開發心得"></p>
<p>那時候的需求很簡單，就是我要知道哪些class有用到哪些模組。</p>
<p>那作法呢，首先必須要有兩個封裝的<strong>ClassA</strong>及<strong>ClassB</strong>，當ClassB需要ClassA的時候總是會用到像是<code>use Foo\ClassA</code>或是<code>new ClassB</code>之類的關鍵字做引入動作</p>
<script src="https://gist.github.com/michael34435/0f13f38aca3f06f73f7c60b0627bff6c.js"></script>
<p>用上面的關鍵字做regex(正則)的全文比對來找哪些有用到，這樣就可以判斷是不是有使用啦～</p>
<h3 id>但是我想的實在太美好了...</h3>
<h4 id="function">如果我想知道有什麼function有用到什麼沒用到</h4>
<p>還是得去擲筊，因為我這樣只能知道引入什麼套件啊</p>
<h4 id>那除此之外還有其他作用嗎？</h4>
<p>不好意思沒有了</p>
<h4 id>所以只好硬著頭皮繼續改</h4>
<p>一個龐大的專案經過多人維護，因為PHP「奔放」的syntax，其coding style及用的邏輯還有規則基本上都是「不可考」的狀態，像是這樣</p>
<script src="https://gist.github.com/michael34435/9499dcb95a25a17f864988ae80f23ced.js"></script>
<p>用傳統的正則拉勢必會拉到<s>獵奇</s>不該出現的東西，所以正則不是一個好辦法。</p>
<p>到後面用上了這個東西<br>
<a href="http://php.net/manual/en/function.token-get-all.php">http://php.net/manual/en/function.token-get-all.php</a></p>
<p>這個function是早期PHP4的時候因為大量與HTML混搭，所以PHP的核心模組就把PHP Interpreter的解析用的模組另外再放出來使用，它可以很準確的判斷現在這個區塊(block)的是什麼型態。</p>
<p>像是上面的程式解析出來就會長的像是這樣子</p>
<pre><code>array(11) {
  [0] =&gt;
  array(3) {
    [0] =&gt;
    int(379)
    [1] =&gt;
    string(6) &quot;&lt;?php
&quot;
    [2] =&gt;
    int(1)
  }
  [1] =&gt;
  array(3) {
    [0] =&gt;
    int(382)
    [1] =&gt;
    string(1) &quot;
&quot;
    [2] =&gt;
    int(2)
  }
  [2] =&gt;
  array(3) {
    [0] =&gt;
    int(378)
    [1] =&gt;
    string(23) &quot;/**
 * @use Foo\Bar
 */&quot;
    [2] =&gt;
    int(3)
  }
  [3] =&gt;
  array(3) {
    [0] =&gt;
    int(382)
    [1] =&gt;
    string(1) &quot;
&quot;
    [2] =&gt;
    int(5)
  }
  [4] =&gt;
  array(3) {
    [0] =&gt;
    int(361)
    [1] =&gt;
    string(5) &quot;class&quot;
    [2] =&gt;
    int(6)
  }
  [5] =&gt;
  array(3) {
    [0] =&gt;
    int(382)
    [1] =&gt;
    string(1) &quot; &quot;
    [2] =&gt;
    int(6)
  }
  [6] =&gt;
  array(3) {
    [0] =&gt;
    int(319)
    [1] =&gt;
    string(3) &quot;Foo&quot;
    [2] =&gt;
    int(6)
  }
  [7] =&gt;
  array(3) {
    [0] =&gt;
    int(382)
    [1] =&gt;
    string(1) &quot;
&quot;
    [2] =&gt;
    int(6)
  }
  [8] =&gt;
  string(1) &quot;{&quot;
  [9] =&gt;
  array(3) {
    [0] =&gt;
    int(382)
    [1] =&gt;
    string(1) &quot;
&quot;
    [2] =&gt;
    int(7)
  }
  [10] =&gt;
  string(1) &quot;}&quot;
}
</code></pre>
<p>上面可以很明顯的看出來，經過<code>token_get_all</code>的洗禮之後，程式碼片段被拆解成一個一個的碎片(slice)，每一個碎片都有一個type做定義(參考: <a href="http://php.net/manual/en/tokens.php">http://php.net/manual/en/tokens.php</a>)，透過定義好的type我們可以蠻快知道哪些是註解<br>
所以，如果我需要砍掉沒辦法受我控制的註解我可以這樣做</p>
<script src="https://gist.github.com/michael34435/a5027403c3a6860ff210ea9aeafa9de9.js"></script>
<p>透過built-in的constant判斷不是註解的部分，再重新組合整份程式碼，這樣就可以用正則撈這個php file裡面引入什麼package啦</p>
<h3 id="function">那說好的function分析呢？</h3>
<p>前面不是提到我怎麼判斷這個php file有引入什麼package嗎？<br>
除此之外還遇到了下面的情況:</p>
<ol>
<li>我要怎麼判斷我用了private function或是protected function呢？</li>
<li>我要怎麼判斷繼承呢？</li>
<li>判斷使用很簡單，怎麼做到判斷沒用到的function？</li>
</ol>
<h4 id>一切的一切都是建立在良好的分析工具上面</h4>
<p><code>token_get_all</code>是很好的工具，不過很可惜的是它再怎樣都沒辦法做有結構性的檔案分析，於是嘗試了稍微專業一點的PHP Parser (<a href="https://github.com/nikic/PHP-Parser">https://github.com/nikic/PHP-Parser</a>)<br>
它跟<code>token_get_all</code>有什麼樣的不同呢？最大的區別是它可以把同一個statement(敘述)的歸類在一起，舉一個簡單的宣告來說好了</p>
<script src="https://gist.github.com/michael34435/f1e3bddb6fdaac377f96e8194a3f1420.js"></script>
<p>如果用<code>token_get_all</code>的話可以看到會變成像是這樣子</p>
<pre><code>array(13) {
  [0] =&gt;
  array(3) {
    [0] =&gt;
    int(379)
    [1] =&gt;
    string(6) &quot;&lt;?php &quot;
    [2] =&gt;
    int(1)
  }
  [1] =&gt;
  array(3) {
    [0] =&gt;
    int(320)
    [1] =&gt;
    string(4) &quot;$foo&quot;
    [2] =&gt;
    int(1)
  }
  [2] =&gt;
  array(3) {
    [0] =&gt;
    int(382)
    [1] =&gt;
    string(1) &quot; &quot;
    [2] =&gt;
    int(1)
  }
  [3] =&gt;
  string(1) &quot;=&quot;
  [4] =&gt;
  array(3) {
    [0] =&gt;
    int(382)
    [1] =&gt;
    string(1) &quot; &quot;
    [2] =&gt;
    int(1)
  }
  [5] =&gt;
  array(3) {
    [0] =&gt;
    int(305)
    [1] =&gt;
    string(3) &quot;new&quot;
    [2] =&gt;
    int(1)
  }
  [6] =&gt;
  array(3) {
    [0] =&gt;
    int(382)
    [1] =&gt;
    string(1) &quot; &quot;
    [2] =&gt;
    int(1)
  }
  [7] =&gt;
  array(3) {
    [0] =&gt;
    int(319)
    [1] =&gt;
    string(3) &quot;Foo&quot;
    [2] =&gt;
    int(1)
  }
  [8] =&gt;
  array(3) {
    [0] =&gt;
    int(390)
    [1] =&gt;
    string(1) &quot;\&quot;
    [2] =&gt;
    int(1)
  }
  [9] =&gt;
  array(3) {
    [0] =&gt;
    int(319)
    [1] =&gt;
    string(3) &quot;Bar&quot;
    [2] =&gt;
    int(1)
  }
  [10] =&gt;
  string(1) &quot;(&quot;
  [11] =&gt;
  string(1) &quot;)&quot;
  [12] =&gt;
  string(1) &quot;;&quot;
}
</code></pre>
<p>那<code>PHP Parser</code>呢？</p>
<pre><code>array(1) {
  [0] =&gt;
  class PhpParser\Node\Expr\Assign#9 (3) {
    public $var =&gt;
    class PhpParser\Node\Expr\Variable#2 (2) {
      public $name =&gt;
      string(3) &quot;foo&quot;
      protected $attributes =&gt;
      array(2) {
        ...
      }
    }
    public $expr =&gt;
    class PhpParser\Node\Expr\New_#8 (3) {
      public $class =&gt;
      class PhpParser\Node\Name#7 (2) {
        ...
      }
      public $args =&gt;
      array(0) {
        ...
      }
      protected $attributes =&gt;
      array(2) {
        ...
      }
    }
    protected $attributes =&gt;
    array(2) {
      'startLine' =&gt;
      int(1)
      'endLine' =&gt;
      int(1)
    }
  }
}
</code></pre>
<p>差異在於<code>token_get_all</code>是逐字分析，所以會需要做iterator做判斷，比較麻煩而且容易出錯；可是<code>PHP Parser</code>的話則是採用一句statement判斷，所以識別上更容易判斷這是同一段code。</p>
<h3 id="phpparserfunction">關於PHP Parser的用法 - 怎麼拉出我要的function呢</h3>
<p>一般而言一個class裡面會由很多function組成，大概像是這樣的感覺<br>
<img src="https://blog.capslock.tw/content/images/2016/10/Untitled-Diagram-2.png" alt="[廢廢心得文]談程式碼靜態分析開發心得"></p>
<p>每一個class底下的function都形成一個封閉的空間，像是<code>$foo</code>的話可以在其他地方使用嗎？<code>不行</code>，所以我們的目標很明確了，要把所有的class底下的function拉出來做分析<br>
像是<code>Example.php</code></p>
<script src="https://gist.github.com/michael34435/e84b1e4b734a6dcfb0717dbf5e9c5865.js"></script>
<p>拆開之後會變成像是這樣</p>
<script src="https://gist.github.com/michael34435/3e1d9eaff547b497cade4aab377d91f8.js"></script>
<p>接著就是解開單一個function就好了喔！</p>
<h3 id>幾個需要特別注意的東西</h3>
<p>PHP裡面有幾個關鍵字比較特殊一點，它們分別為<code>this</code>、<code>parent</code>、<code>self</code>、<code>static</code>，它們的關係可以簡單畫成下面這張圖<br>
<img src="https://blog.capslock.tw/content/images/2016/10/Untitled-Diagram-3.png" alt="[廢廢心得文]談程式碼靜態分析開發心得"></p>
<p>只有parent獨立出來(只能拉extends)，其他的繼承parent又可以算是自己的function，這裡都需要特別處理，譬如<code>this</code>可以拉到parent及自己，但是<code>parent</code>只能拉到自己類似這些情境。</p>
<h3 id="issueprstar">最後，奉上專案位置，有問題歡迎給issue或是PR，可以的話順便留star吧！</h3>
<p><a href="https://github.com/CapsLock-Studio/FilterClass">https://github.com/CapsLock-Studio/FilterClass</a></p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>