<?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:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by TD on Medium]]></title>
        <description><![CDATA[Stories by TD on Medium]]></description>
        <link>https://medium.com/@tsungtingdu?source=rss-7f85089fd22e------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*1v7ZyRuOCyn7qGr0Ym-IhQ.jpeg</url>
            <title>Stories by TD on Medium</title>
            <link>https://medium.com/@tsungtingdu?source=rss-7f85089fd22e------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 00:27:02 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@tsungtingdu/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[2023 review]]></title>
            <link>https://tsungtingdu.medium.com/2023-review-3b835dd6b989?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/3b835dd6b989</guid>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Mon, 01 Jan 2024 14:22:10 GMT</pubDate>
            <atom:updated>2024-01-01T14:22:10.223Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aSoULMcJvA8zwDXQ3wzAEg.jpeg" /><figcaption>Source: <a href="https://unsplash.com/photos/orange-tabby-cat-lying-on-white-and-red-textile-PfmNfCXcrdw">https://unsplash.com/photos/orange-tabby-cat-lying-on-white-and-red-textile-PfmNfCXcrdw</a></figcaption></figure><p>還沒生出 2022 的 review，2023 就已經結束了</p><h4><strong>入厝</strong></h4><p>CQ 和我從 2021 年開始計畫買房，在網路上看了將近 200 間、實際走訪將近 30 間屋子，過程中遭遇各種事件，不得不一次次的重新規劃時程與預算，總算在去年 12/26 搬入新房子。</p><p>買房子是一個想快也快不起來的 project，在有限時間內的物件也是有限的，想看更多就需要等待，而要遇到各方面條件適合我們的屋子也完全看運氣，但只要遇到了對的房子，其實走進去就會馬上知道了。</p><p>經過這麼長的時間和許多努力後，完成任務的當下其實並沒有特別的興奮，倒是有種如釋重負的感覺，心裡非常踏實。</p><h4><strong>弄瓦</strong></h4><p>2/9 女兒出生，展開全新無限期的 project。這段時間一直在回想自己是怎麼成長的，一方面不希望下一代同樣面對我所面對過的遭遇，一方面也在思考應該用怎麼的姿態與上一代、下一代建立關係。</p><p>養育下一代很不簡單，但不代表我們能夠完全的控制他們、或期待他們滿足我們所有的想望。即便有直系血親這層關係，人與人之間最基本的尊重、溝通、理解、同理、陪伴等也是需要存在的，而大量心力的投入就是不可避免的。</p><p>漸漸的，我認為「我所面對過的遭遇」不是一種「錯誤」，而我們需要有更強大的心智，更多的學習與反思、同理與觀察，來避免同樣的事情發生。</p><p>5/22 開始送她去 infant care，踏出認識這個美麗世界的第一步，希望她能夠一直抱持好奇與好玩的心成長。我知道我沒辦法陪她一輩子，但我希望能夠陪她走得夠久。</p><p>在我想這些的同時，要感謝 CQ 無微不至的照顧女兒，以及打點家中大小事物。</p><p>當爸爸之後大家最常問我的問題，除了有沒有睡飽之外，就是「當爸爸的心得是什麼」？我認為有了小孩，是一個很好的機會去重新認識這個世界，我希望能夠跟孩子站在同樣的角度，保持同樣的好奇心去探索這個世界，對於習以為常的事物重新提問與發現。</p><h4><strong>工作</strong></h4><p>在轉職工程師 30 個月之後，在今年三月成為 senior software engineer。這個轉變來的有點意外，同時也疑惑著到底怎樣才算是 senior software engineer。</p><p>我不見得有比較多的技術知識與經驗，不過結合一些過去的經驗，主動嘗試各種新的（或沒人要做的）挑戰，也嘗試在不同面向上發揮一些影響力（或自己開闢戰場），能夠在現有的團隊當中做出一些貢獻。</p><p>網路上各種職涯經驗分享非常的多，不過有機會（有空）我再來分享一下敝司的工程文化與自己小小的經驗好了。</p><h4>出行</h4><p>8/19 去了 Bintan，當作是帶女兒回台灣前的測試。第一次入住 ClubMed，一個可以全然放鬆不需要思考太多行程與 logistic。第一次帶女兒看海、踩沙灘。</p><p>10/13 回台灣，是 2020 年一月以來第一次回台灣，先後在台中、台北、宜蘭各待了一段時間，見了好久不見的朋友們，初嚐了不少美食。（吃了黃家園蒸餃、向上水餃、麻辣鴨血、臭豆腐、鹽水雞、三生有幸牛肉麵、通庵熟成咖哩、做菜茶、慈聖宮前的小吃、薑母鴨、人和園、巷子龍、阿城鵝肉，喝了 Coffee Stopover、After5、Mud）</p><p>第一次在台北開車，去了寧靜莊嚴的農禪寺，繞了北海岸一圈，還把車開進了基隆廟口。在宜蘭看龜山島日出、體驗了地震與國家級警報，在吉姆老爹啤酒工場眺望開展的蘭陽平原，再訪噶瑪蘭威士忌酒廠。</p><p>花了半天的時間丟了一堆書、衣物和雜物，其中也包含了球衣。隨著實體物件的消失，相關的記憶也會逐漸消逝。忘記一些過去的事物令人有點難過，但我想它們都已經成為自己的一部分。</p><h4><strong>閱讀</strong></h4><p><strong>2022</strong></p><ul><li>如何培養美感</li><li>美感努力運動</li><li>硬美學</li><li>Leading with Questions</li><li>加密貨幣的政治與經濟</li><li>The Long Game</li><li>身份政治 Identity (Francis Fukuyama)</li><li>為什麼我們需要公共哲學 Public Philosophy (Michael Sandel)</li><li>跨越斷層 The Great Disruption (Francis Fukuyama)</li><li>觀看的方式 Ways of Viewing (John Berger)</li><li>不平等的樣貌 This Is What Inequality Looks Like (Teo You Yenn)</li><li>人慈 Human Kind (Butger Bregman)</li></ul><p><strong>2023</strong></p><ul><li>薩提爾的對話練習</li><li>Real Food for Pregnancy (Lily Nichols)</li><li>What to Expect the First Year</li><li>極限返航 Project Hail Mary (Andy Weir)</li><li>The Confident Parent (Jane Scott , Stephanie Land)</li><li>地圖之外 Off The Map (Alastair Bonnett)</li><li>記憶的玩物 Recursion (Blake Crouch)</li><li>怎樣談科學 Why Science Needs Story (Randy Olson)</li><li>美麗新世界 Brave New World (Aldous Leonard Huxley)</li><li>從東南亞到東協 From South East Asia to ASEAN</li><li>資訊大歷史 History of Information</li><li>人月神話 The Mythical Man-Month</li><li>A Philosophy of Software Design</li><li>上街去吧！森山大道的街拍意見</li><li>如何欣賞電影 How To Watch Movies (Ann Hornaday)</li></ul><p><strong>讀一半的書</strong></p><ul><li>想像的共同體 Imagined community (Benedict Anderson)</li><li>Reinventing Parenting: Can we raise our kids to be creative? (Eyal Doron)</li><li>Positive Discipline for Preschoolers: For Their Early Years－Raising Children Who Are Responsible, Respectful, and Resourceful (Jane Nelsen, Cheryl Erwin, Roslyn Ann Duffy)</li></ul><h4>運動</h4><p>剛到新加坡的時候還有在打壘球，但一方面球場實在太遠，另一方面因為後續肩膀跟手腕的傷，導致身體完全不知道怎麼丟球。決定告別一直以來最愛的棒壘球的時候，彷彿也在向我過去一段精彩的人生告別。</p><p>大概在 2020 年底開始在打網球，主要是想延續運動的習慣。初期嘗試自己學習，並轉換過去棒球的運動經驗，但很快地就遇到了瓶頸，於是在 2023 開始找教練學習。</p><p>後來發現棒球跟網球的一些差異</p><ul><li>棒球只有在跑壘與守備的時候需要移動，而且這些移動也不是連續性的，需要比較多的是爆發力。而網球需要的是持續運動的能力，在連續擊球之後如何保持身心的穩定與擊球品質就很重要</li><li>棒球打擊的容錯率相對較低，所以需要很高的專注度，同時需要確保身體在最佳的反應狀態。網球擊球的容錯率則相對高一點，且有更多的思考時間</li></ul><p>上了 20 堂教練課之後，除了技術上的調整之外，教練告訴我需要的是 calm down ：以前會一直想著如何贏下這一分，而現在需要想的是如何有效率且冷靜的回擊每一球，然後在球場上打得更久。</p><p>運動不單純只是身體的活動，而是身、心、腦三者的共同活動（其實我想做任何事情都應該如此），需要對這三者不斷的思考與覺察，像是</p><ul><li>覺察身體各部位的現況、對於身體運動的控制與反應能力</li><li>覺察運動過程中情緒的起伏、練習掌握情緒而不影響運動表現</li><li>思考如何有效驅動身體與表現、解讀每一個 play、在身體反應與思考之間取得平衡與控制、覺察自己是如何決策等</li></ul><p>運動過程中遇到的各種挫折還有傷痛，都是自我修煉的一部分。</p><h4>連結</h4><p>12 月有一天發現我的某篇 post 被 facebook 給刪除（然後我還不知道是哪一篇），有朋友留言說「不知道離開 facebook 之後還能去哪裡」，這讓我開始思考人與人之間要如何建立連結。</p><p>有其他許多網站、部落格、Youtube 可以取代 facebook 的部分功能，像是表達自我、參與討論、獲得新知等，比較特別的是，「獲知他人近況」讓人有「連結」的感覺。在 facebook 上我們可以快速的（隨機的）知道某位朋友的近況，讓我們被動產生了連結感，即便我們可能最後整年都沒有聊上一句話。</p><p>社群網站定義了人與人之間連結與互動的方式，而我們不自覺被動的接受了它。我目前不認為 Facebook 無可取代或不可取代，心中的問題主要是</p><ul><li>我需要目前的連結方式嗎？</li><li>如果這不是我想要的連結，那我想與人的連結又是什麼方式呢？</li></ul><p>我本來就不是一個很會維持連結的人，我想這會是我在 2024 年的課題之一。在找到方向之前，先嘗試暫時離開 facebook （大概是 2–3 週才上去晃一下的頻率）</p><p>回顧 2023，覺得能夠順利的度過這一年已經心滿意足。生活和工作上的新角色都還需要再花點時間摸索，希望在 2024 能找到合適的姿勢走下去。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3b835dd6b989" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2021 review]]></title>
            <link>https://tsungtingdu.medium.com/2021-review-56d15b26622d?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/56d15b26622d</guid>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Sun, 02 Jan 2022 16:06:53 GMT</pubDate>
            <atom:updated>2022-01-05T01:22:02.563Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RxO4v_BwVvpwekpghEGM3g.jpeg" /></figure><p>經過了 2020 年的洗禮，2021 發生什麼事情好像也見怪不怪。</p><p><strong>生活方面</strong></p><p>許多計畫同樣因為 COVID 肆虐被各種打亂，像是無法回台灣、買房 delay 等等。雖然環境無法轉換，不過持續嘗試在廚房當中探索新天地。另一方面，也很慶幸有每週可以一起打球的朋友、一起吃飯的朋友。</p><p>相比去年，今年有更多的時間和 CQ 相處，欣賞了更多的展覽和戲劇，對於未來能夠一起完成的事情也有更多的想像。</p><p><strong>工作方面</strong></p><p>到目前為止還算順利，也嘗試了不同的挑戰，像是 Angular schematics、 Angular libray、 shell script 等，以及持續超過一個月、跨 repo/projects 的任務，還有帶 intern 等等。</p><p>不過整體來說，卻感覺自己忙著解決問題，但沒有好好學習。這不代表一路上沒有學到新東西，而是沒有處在一個穩定成長的學習道路上。要做到這一步，需要更有意識地去學習，或，嘗試 <strong>unlearn</strong></p><h3>Q&amp;A</h3><p>和去年同樣的五個問題：</p><p><strong>Q: 我和 2021 年的我，最大的不同是？</strong></p><p>A: 我當了一年的 software engineer 了</p><p><strong>Q: 2021 年我學到最重要的一課是？</strong></p><p>A: 過去在同樣的環境待了很長一段時間 (7年)，養成了我的思考方式與處理事情的方式。然而離開那樣的環境之後，發現身邊並不是每個人都有同樣的處事方式、目標和價值觀。</p><p>我相信我自己的想法不是唯一真理，要如何包容、接受，並能夠用他人能夠接受的方式溝通，或提供合適的協助，會是一大挑戰，也是必須學習去克服的問題（但什麼是合適？）。</p><p>這一課還沒有學完，我想還有很長的路要走。</p><p><strong>Q: 2021 年我做的最棒的決定是？</strong></p><p>A: 好像沒有一個特別「最棒的決定」，不過至少持續保持著開放的態度去接受、創造新事物。</p><p>想到一個比較特別的，就是帶著 camping chairs 到 public multi-storey carpark 上看書吧。倒不是說上面的風景有多美，而是我為自己創造了一個新的生活空間與角度。</p><p>另外，學了 10 分鐘的街舞。還有後面提到的 Hair for Hope 活動與學 bass。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cZQYZrBKsMK3gL2Ls5DWdQ.jpeg" /></figure><p><strong>Q: 2021 年我為他人做的最重要的事情是？</strong></p><p>A: 參與了 <a href="https://www.hairforhope.org.sg/shavee/tsung-ting-tu-9060?fbclid=IwAR3ob6y0iR8DnDIhRIvKiTS1diTe6qlFAkBZ2qsBeQxbkCaEEgxb-hXJ7a8"><strong>Hair For Hope</strong></a> 活動。這是一個為癌症兒童募款的活動，藉由分享自己理髮後的照片與活動頁面來募款。一開始覺得只是剪個頭髮應該不難，但實際坐在理髮店裡的時候，突然覺得緊張與不安，這樣的心情是我完全沒有預期到的。</p><p>不過另一方面，意外看到自己還不認識的一面（這裡並非指外表，而是無法預期的反應，以及放不下的事物），覺得還得多修煉。</p><p>最後共募到了 SGD 5,100，非常感謝所有匿名的捐款者 &lt;(_ _)&gt;</p><p><strong>Q: 2021 年我鼓起最大的勇氣做的事情是？</strong></p><p>A: 除了參與 Hair For Hope 之外，另外一件鼓起勇氣做的事情就是開始學 bass。高中玩過口琴之後，就沒有想要玩樂器的念頭，直到最近看到 Max 打鼓，以及在某次談話當中喚起了過去對於音樂的相關經驗，就覺得應該可以嘗試一下。不過我也只是想想，想了一段時間，然後就被 CQ 推了一把（一腳踢下去），上了第一堂課。</p><p>這是一件完全踏出自己舒適圈的事情，前方是前所未有的未知，不過我也很期待能夠開啟另外一個探索、體驗世界的大門。</p><p><strong>以下為流水帳…</strong></p><p><strong>Books I read …</strong></p><ul><li><a href="https://www.books.com.tw/products/0010826319">哈佛最熱門的國際關係課 Understanding Global Conflict and Cooperation: An Introduction to Theory and History</a></li><li><a href="https://www.books.com.tw/products/0010691317?sloc=main">叔本華的辯論藝術 Die Kunst, Recht zu behalten</a></li><li><a href="https://www.books.com.tw/products/0010865688?sloc=main">諸神的起源 Living With the Gods: On Beliefs and Peoples</a></li><li><a href="https://www.books.com.tw/products/0010897875?sloc=main">專家之路：從學徒到大師 Expert: Understanding the Path to Mastery</a></li><li><a href="https://www.books.com.tw/products/0010884072?sloc=main">別讓我走 Never let me go</a></li><li><a href="https://www.books.com.tw/products/0010877100?sloc=main">INPUT 最高學習法</a></li><li><a href="https://www.books.com.tw/products/0010852800?sloc=main">OUTPUT 最高學以致用法</a></li><li><a href="https://www.books.com.tw/products/0010862692?sloc=main">你是誰，比你做什麼更重要 Letters on Life and Its Challenges</a></li><li><a href="https://www.books.com.tw/products/0010901306?sloc=main">沙丘 Dune (book 1)</a></li></ul><p><strong>Movies I watched …</strong></p><ul><li>Hidden Figures</li><li>Soul</li><li>刻在你心裡的名字 Your Name Engraved Herein</li><li>孤味 Little Big Women</li><li>返校 Detention</li><li>André &amp; his olive tree</li><li>Minimalism</li><li><a href="https://en.wikipedia.org/wiki/Ghost_in_the_Shell_(1995_film)">Ghost In The Shell (1995)</a></li><li>Blade Runner 2049</li><li>Black Widow</li><li>Shang-Chi</li><li>Eternals</li><li>14 Peaks</li><li>Evangelion: 1.0 You Are (Not) Alone</li><li>The Matrix</li><li>The Matrix Reloaded</li><li>The Matrix Revolutions</li><li>Don’t Look Up</li></ul><p><strong>What I cooked (new attempts)…</strong></p><ul><li><strong>Western</strong>: beef bourguignon, paella, tomato soup, alle vongole, beer chicken stew</li><li><strong>Chinese/Taiwanese</strong>: 麻婆豆腐 (mapo tofu), sesame oil egg mee sua</li><li><strong>Japanese</strong>: 鮭魚炊飯, 豬肉味增湯 (pork and miso soup), spinach ohitashi, miso yogurt salmon, 大根煮 (dai-con), kinpira renkon, mazutaki nabe, oyako udon, yasai itame, fried tofu with egg, salmon chazuke, miso grilled eggplant</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=56d15b26622d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Update (2021.09.29)]]></title>
            <link>https://medium.com/tds-note/update-2021-09-29-468646c38c76?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/468646c38c76</guid>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Wed, 29 Sep 2021 01:27:05 GMT</pubDate>
            <atom:updated>2021-09-29T01:27:05.463Z</atom:updated>
            <content:encoded><![CDATA[<p>雖然已經很久沒有發文了，不過發現陸續還是會有人進來看。謝謝願意花時間觀看的各位。</p><p>今年其實我都把文章發在<strong> </strong><a href="https://tsungtingdu.coderbridge.io/"><strong>https://tsungtingdu.coderbridge.io/</strong></a><strong> </strong>，雖然版面看起來沒有 Medium 好看，但是因為支援 markdown，對我來說是相對方便寫作的地方。</p><p>我想要養成持續寫作的習慣，所以過去一段時間嘗試了一些不同的平台（甚至自己蓋一個 blog site），不過如果想要建立習慣的話，就需要盡可能排除不必要的阻礙，所以最後（暫時）選了 codebridge。</p><p>所以如果有興趣想看我最近寫的東西，可以到 <a href="https://tsungtingdu.coderbridge.io/"><strong>https://tsungtingdu.coderbridge.io/</strong></a><strong> </strong>走走。</p><p>另一方面，今年也繼續參加了鐵人賽，主題是「<a href="https://ithelp.ithome.com.tw/users/20116003/ironman/4221">幫自己搞懂物件導向和設計模式</a>」，歡迎留言討論</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=468646c38c76" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tds-note/update-2021-09-29-468646c38c76">Update (2021.09.29)</a> was originally published in <a href="https://medium.com/tds-note">TD’s note</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2020 review]]></title>
            <link>https://medium.com/tds-note/2020-review-1dd35ac566cb?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/1dd35ac566cb</guid>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Sun, 10 Jan 2021 08:12:07 GMT</pubDate>
            <atom:updated>2021-01-10T08:32:37.572Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Fde6XOBSyANWwuaWgQeGRg.jpeg" /><figcaption><em>source: </em><a href="https://images.unsplash.com/photo-1532010687432-fdc6e0da459c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=2250&amp;q=80"><em>https://images.unsplash.com/photo-1532010687432-fdc6e0da459c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=2250&amp;q=80</em></a></figcaption></figure><p>我想 2020 年對全世界大部分的人來說，都是紛亂的一年。COVID-19 (a.k.a 武漢肺炎) 突如其來的降臨，一舉打亂了大家對生活、工作，甚至是人生計畫。但這倒也提醒了自己，意外總會是發生，沒有什麼事情都是理所當然的。</p><p>COVID-19 當然也影響到我原本的計畫，但我不會說這是個特別糟糕的一年，反而 2020 帶來了意想不到的改變、挑戰和反思，還有多那麼一點點點點的平靜。當外面越亂的時候，越要往自己的心裡面去看。</p><blockquote>在刺激與回應之間存在一個空間，這空間是我們選擇回應的力量所在。我們選擇回應的能力也意謂著我們的成長和自由。</blockquote><blockquote>Between stimulus and response there is a space. In that space is our power to choose our response. In our response lies our growth and our freedom.<br>― Viktor E. Frankl</blockquote><h3>人生大事</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iIQW38PSSHjBaOxNs2cABA.jpeg" /></figure><p>2019 年 7 月 7 日在嘉義覺醒音樂祭求婚後，決定要在 2020 辦婚禮。於是 2019 年後半年就開始找婚禮場地，以及準備其他大大小小的事項。</p><p>CQ 和我很有共識，不打算找婚攝來拍婚紗，因此特別計畫在 1 月初回台灣的時候，在大稻埕、溪頭還有台中市區「自拍」，另外在 2 月底的時候和在新加坡的朋友們一起去 “photo walk”，拍了一系列的照片。</p><p>原本很期待能在 40 樓的高空酒吧舉辦簡單的婚禮，不過後來 <a href="https://en.wikipedia.org/wiki/2020_Singapore_circuit_breaker_measures">circuit breaker</a> (CB) 開始，解除之日遙遙無期，念頭一轉，就決定在自己家裡舉辦 “online” solemnization。所有朋友都在線上，連證婚人也都在線上，後來想想其實也蠻有趣的，非常特別。</p><p>結婚之後，生活沒有太多的改變，倒是會思考和討論更多關於未來的規劃。</p><p>能夠和另一半自在的相處、溝通，並且做真實的自己，我想應該就是最大的幸福了。</p><h3>工作方面</h3><p>2 月初離開了待了六年多的 ALPHA Camp (AC)，然後在 11 月加入 Ascenda 擔任 software engineer。關於轉職的心得，部分紀錄在<strong> </strong><a href="https://medium.com/tds-note/%E9%97%9C%E6%96%BC%E8%BD%89%E8%81%B7-%E6%88%91%E6%83%B3%E8%AA%AA%E7%9A%84%E6%98%AF-e6c688632d14"><strong>這篇文章</strong></a><strong> </strong>當中。</p><p><strong>Left AC</strong></p><p>其實在 2018–19 年左右就有轉職的念頭，不過為了好好的交接（和離開），一直到今年初才正式離職。離開的原因，最主要是我想要在新加坡長期發展，另一方面，也想要給自己一個全新的挑戰。</p><p>在過去六年，AC 培育了幾百位成功轉職、或開創另一番事業的學生，而身爲 AC 一份子的我，心中一直有個渴望，想要親身體驗自家產品與成效，以及實際走入求職市場，看看過去的學生都遇到什麼樣的問題與挑戰。</p><p>從 2017 年開始，自己就利用工作之餘的時間學習 Ruby on Rails 與 JavaScript，2019 後半年正式的參與課程的進度學習 Node.js。</p><p>離開 AC 這件事情本身還有另外一個挑戰：是否能將自己所一手打造的系統，順利的交接出去，讓整個系統在「沒有我」的情況下還能夠正常運作。雖然我手上的工作也不是多複雜或是多難，但是也只有在這樣的情況下，才剛好有個機會逼自己思考這件事情。剛開始工作的時候，會覺得「事情交給我就會完成」，現在則可能會想「如何讓事情沒有我也會被順利完成」。</p><p>回過頭來，我覺得能夠在離開學校之後加入 AC 真的是非常幸運，能夠參與一個團隊幾乎從零開始一路成長的過程，接觸到優秀的前輩、熱血的夥伴、擁有各種不同故事的學生，以及我在一般的公司可能不會有的視野。</p><p>引用我自己 <a href="https://medium.com/tds-note/%E9%97%9C%E6%96%BC%E8%BD%89%E8%81%B7-%E6%88%91%E6%83%B3%E8%AA%AA%E7%9A%84%E6%98%AF-e6c688632d14"><strong>這篇文章</strong></a><strong> </strong>當中提到的：</p><blockquote>在不管是工作階段還是學習階段，AC 都讓我成為了現在的我。這是人生當中一段非常豐富的旅程。</blockquote><p>很感謝可愛的同事們還特別跟 CQ 計畫好，在最後給我一個難忘的驚喜</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1eKx2_zfsDLQ7798UGpC7g.jpeg" /></figure><p><strong>Joined Ascenda</strong></p><p>關於成為 software engineer 兩個月的回顧，我想我另外寫一篇文章好了。</p><p><strong>About remote work</strong></p><p>在 AC 的最後 1–2 年，基本上就處在遠端工作的模式。剛開始也沒有意識到這就是遠端工作，只是覺得需要額外的努力去提高工作與溝通的效率，以及接受跟所有同事不在同一個辦公空間時，所產生的「低密度資訊」的狀態。</p><p>「低密度」是一個相對的的形容詞。有人可能會覺得，所有事情都公開透明在 slack 或是 email 當中討論，不會有不對稱的問題，那怎麼可能會有低密度的問題呢？</p><p>舉例來說，若要展開一個 meeting，基本上大家都可以收到會前相關資訊，以及會後的紀錄，包含 remote 工作者。不過對於處在同一個辦公室工作的工作者來說，可以接收到更多「資訊」，譬如進入會議室之前的閒聊/交流、會議當中每位參與者臉上的表情、眼神、肢體動作，以及離開會議之後、到下一次工作會議前的所有閒聊/交流，而這些都是 remote 工作者無法獲得的。</p><p>雖然這些「不會影響」工作的進行，但是那感覺就是在相對稀薄的大氣中工作。如果大部分的人都是 remote，或者有少數在地同事一起工作，我想就比較不會有這種感覺。</p><p>總之，本來以為加入新公司之後可以暫時擺脫 remote 的狀態，結果因為 COVID-19 的關係，團隊開始輪流 work from home。不過我想這應該就是這個世代的常態了。</p><p>現在回想，其實六年多前剛加入 AC 的前幾個月，其實就是 remote 的狀態（超前部署？）</p><h3>Q&amp;A</h3><p>以下借用友人提供的問題來做個回顧：</p><p><strong>Q: 我和 2020 年的我，最大的不同是？</strong></p><p>A: 結婚、換工作</p><p><strong>Q: 2020 年我學到最重要的一課是？</strong></p><p>A:</p><ul><li>學會不要追逐表面上的成就，需要更專注在真正的問題與價值上</li><li>學會分辨並避開噪音，並更能夠沈住氣去完成的計畫</li></ul><p><strong>Q: 2020 年我做的最棒的決定是？</strong></p><p>A:</p><ul><li>本來想說「結婚」跟「換工作」，但其實這兩個都在 2019 年就決定了。</li><li>開始學前端開發：在開始求職之後，發現前端的機會相對較多，因此開始計劃並執行前端的學習，最後成功應徵到新工作（職缺開前端，但實際工作內容是前後端都有）</li><li>找學習夥伴：自學的過程是孤獨的，因此主動找了 SML 請他當我的學習夥伴，一起討論學習計畫，以及之後定期的 check in。這對我來說幫助很大。</li><li>在 COVID 爆發、大家開始搶口罩前，一月份就先在台灣買好口罩飛回新加坡。我覺得這個決定還不錯，紀錄一下。</li><li>看了一些不錯的書。</li></ul><p><strong>Q: 2020 年我為他人做的最重要的事情是？</strong></p><p>A: 幫助他人度過低潮、成功拿到理想中的工作，和家人朋友分享自己做的料理</p><p><strong>Q: 2020 年我鼓起最大的勇氣做的事情是？</strong></p><p>A: 其實沒有什麼印象。如果硬要說的話，大概就是拒絕了 3–4 個工作機會，堅持完成自己的目標吧。</p><h3>2021 年自我期許</h3><ul><li>讓身邊重要的人過得更好</li><li>避開更多雜音，更專心在自己的目標上</li><li>成為能夠獨當一面的 software engineer</li><li>看更多好書</li><li>精進廚藝</li></ul><p>再見 2020，然後要更認真努力的度過 2021 的每一天</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*604RPC31oqecI5kQeBZO9w.jpeg" /><figcaption>source: <a href="https://images.unsplash.com/photo-1604440977273-f22b2fa6ea5b?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=2018&amp;q=80">https://images.unsplash.com/photo-1604440977273-f22b2fa6ea5b?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=2018&amp;q=80</a></figcaption></figure><h4>以下為流水帳…</h4><h4><strong>看過的書</strong></h4><p>本來以為有機會看多一點的書，後來其實花了決大部分的時間在學習程式技術上。不過今年看過的書，自己覺得都蠻不錯的：</p><ul><li><a href="https://www.amazon.com/Peak-Performance-Elevate-Burnout-Science-ebook/dp/B01N1RNP9N/ref=sr_1_1?dchild=1&amp;keywords=PEAK+performance&amp;qid=1609667437&amp;s=digital-text&amp;sr=1-1">Peak Performance</a> / <a href="https://www.books.com.tw/products/0010814765">一流的人如何保持顛峰</a></li><li><a href="https://www.amazon.com/12-Rules-Life-Antidote-Chaos-ebook/dp/B01FPGY5T0/ref=tmm_kin_swatch_0?_encoding=UTF8&amp;qid=1609667490&amp;sr=1-1">12 Rules for Life: An Antidote to Chaos</a> /<a href="https://www.books.com.tw/products/0010820641?sloc=main"> 生存的12條法則</a></li><li><a href="https://www.amazon.com/How-Will-Measure-Your-Life-ebook/dp/B006ID0CH4/ref=sr_1_1?crid=LJE6DN0F13KY&amp;dchild=1&amp;keywords=how+to+measure+your+life&amp;qid=1609667586&amp;s=digital-text&amp;sprefix=how+to+measure+your+l%2Cdigital-text%2C224&amp;sr=1-1">How Will You Measure Your Life?</a> / <a href="https://www.books.com.tw/products/0010780469?sloc=main">你要如何衡量你的人生？</a></li><li><a href="https://www.amazon.com/Lessons-21st-Century-Yuval-Harari-ebook/dp/B079WM7KLS/ref=sr_1_1?crid=1TF1ZXVVYDSMY&amp;dchild=1&amp;keywords=21+lessons+for+the+21st+century&amp;qid=1609667653&amp;s=digital-text&amp;sprefix=21+les%2Cdigital-text%2C393&amp;sr=1-1">21 Lessons for the 21st Century</a> / <a href="https://www.books.com.tw/products/0010815762?sloc=main">21世紀的21堂課</a></li><li><a href="https://www.amazon.com/Upheaval-Nations-Cope-Crisis-Change-ebook/dp/B07H7Y5FFG/ref=sr_1_1?crid=3DDBW8N3DSC93&amp;dchild=1&amp;keywords=upheaval&amp;qid=1609667724&amp;s=digital-text&amp;sprefix=uphea%2Cdigital-text%2C457&amp;sr=1-1">Upheaval: How Nations Cope with Crisis and Change</a> /<a href="https://www.books.com.tw/products/0010840756?sloc=main"> 動盪：國家如何化解危局、成功轉型？</a></li><li><a href="https://www.amazon.com/Factfulness-Reasons-World-Things-Better-ebook/dp/B0756J1LLV/ref=sr_1_1?crid=3K9MTX0F2U4IL&amp;dchild=1&amp;keywords=factfulness&amp;qid=1609667780&amp;s=digital-text&amp;sprefix=fac%2Cdigital-text%2C389&amp;sr=1-1">Factfulness</a> / <a href="https://www.books.com.tw/products/0010790443?sloc=main">真確</a></li><li><a href="https://www.amazon.com/Trillion-Dollar-Coach-Leadership-Playbook/dp/0062839268/ref=sr_1_1?dchild=1&amp;keywords=Trillion+Dollar+Coach%EF%BC%9AThe+Leadership+Playbook+of+Silicon+Valley%E2%80%99s+Bill+Campbell&amp;qid=1609668384&amp;sr=8-1">Trillion Dollar Coach</a> / <a href="https://www.books.com.tw/products/0010859317?sloc=main">教練</a></li><li><a href="https://www.amazon.com/Negotiating-Rationally-Max-H-Bazerman/dp/0029019869/ref=sr_1_1?dchild=1&amp;keywords=Negotiating+Rationally+Margaret+Neale&amp;qid=1609668328&amp;sr=8-1">Negotiating Rationally</a> / <a href="https://www.books.com.tw/products/0010861702?sloc=main">頂尖名校必修的理性談判課</a></li><li><a href="https://www.amazon.com/Undoing-Project-Friendship-Changed-Minds-ebook/dp/B01GI6S7EK/ref=sr_1_1?dchild=1&amp;keywords=THE+UNDOING+PROJECT+A+FRIENDSHIP+THAT+CHANGED+OUR+MINDS&amp;qid=1609668202&amp;s=digital-text&amp;sr=1-1">The Undoing Project</a> / <a href="https://www.books.com.tw/products/0010782516?sloc=main">橡皮擦計畫</a></li><li><a href="https://www.amazon.com/Hillbilly-Elegy-Memoir-Family-Culture-ebook/dp/B079L5DDB4/ref=sr_1_1?dchild=1&amp;keywords=Hillbilly+Elegy&amp;qid=1609668159&amp;s=digital-text&amp;sr=1-1">Hillbilly Elegy: A Memoir of a Family and Culture in Crisis</a> / <a href="https://www.books.com.tw/products/0010765378?sloc=main">絕望者之歌</a></li><li><a href="https://www.amazon.com/Atlas-Unusual-Borders-territories-geographical-ebook/dp/B07WSYLD78/ref=sr_1_1?dchild=1&amp;keywords=The+Atlas+of+Unusual+Borders&amp;qid=1609668103&amp;s=digital-text&amp;sr=1-1">The Atlas of Unusual Borders</a> / <a href="https://www.books.com.tw/products/0010861751?sloc=main">不尋常的邊界地圖集</a></li><li><a href="https://www.books.com.tw/products/0010870347?sloc=main">棄貓 關於父親，我想說的事</a></li><li><a href="https://www.books.com.tw/products/0010858359?sloc=main">抗老化，你需要大重量訓練</a></li><li><a href="https://www.amazon.com/Maybe-You-Should-Talk-Someone/dp/1911617044/ref=sr_1_1?dchild=1&amp;keywords=Maybe+You+Should+Talk+to+Someone%3A+A+Therapist%2C+HER+Therapist%2C+and+Our+Lives+Revealed&amp;qid=1609668443&amp;sr=8-1">Maybe You Should Talk To Someone</a> / <a href="https://www.books.com.tw/products/0010853179">也許你該找人聊聊</a></li></ul><h4>煮過的料理</h4><p>一段時間沒工作，加上因為 circuit breaker 被關在家裡，於是就有很多機會開始下廚。想念什麼味道，就自己想辦法生出來。</p><p>做料理是一個充滿探索和創造的過程，同時我也相信「料理可以帶給人們幸福」</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0U-2efc4XVL2vvnI0FgFuw.jpeg" /></figure><p>簡單記錄一下過去一年做過的料理：</p><p><strong>台式/中式</strong></p><p>九層塔炒蛤蜊、九層塔炒蛋，鹽酥雞、炸杏鮑菇、炸地瓜球，三杯雞、鹽水雞、蔥油雞、薑母鴨、麻油雞、滷味、白菜滷、醉雞、番茄炒蛋、馬鈴薯燉肉、醃蘿蔔。酸辣湯、蛤蜊湯、蔥雞湯、剝皮辣椒雞湯、玉米濃湯（台灣便當店版）</p><p><strong>日式</strong></p><p>Yakisoba、溏心蛋（味付け玉子）、沖繩炒苦瓜 (Okinawa goya champuru ゴーヤチャンプル)、關東煮（oden）、日式咖哩、親子丼、日式蛋花湯</p><p><strong>西式/其他</strong></p><p>Gordon Ramsay’s scrambled eggs、南瓜濃湯、羅宋湯、洋蔥湯、熱紅酒、泰式蒸魚、煎雞腿排</p><p><strong>甜點</strong></p><p>提拉米蘇、檸檬塔</p><blockquote>CQ 票選前三名：滷味、沖繩炒苦瓜、提拉米蘇</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1dd35ac566cb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tds-note/2020-review-1dd35ac566cb">2020 review</a> was originally published in <a href="https://medium.com/tds-note">TD’s note</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[關於轉職，我想說的是…]]></title>
            <link>https://medium.com/tds-note/%E9%97%9C%E6%96%BC%E8%BD%89%E8%81%B7-%E6%88%91%E6%83%B3%E8%AA%AA%E7%9A%84%E6%98%AF-e6c688632d14?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/e6c688632d14</guid>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Sat, 28 Nov 2020 09:10:40 GMT</pubDate>
            <atom:updated>2020-11-29T11:30:13.239Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-LsshevY6PG6hev_VOnLfg.jpeg" /><figcaption>source: <a href="https://images.unsplash.com/photo-1515002246390-7bf7e8f87b54?ixlib=rb-1.2.1&amp;ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&amp;auto=format&amp;fit=crop&amp;w=1050&amp;q=80">https://images.unsplash.com/photo-1515002246390-7bf7e8f87b54?ixlib=rb-1.2.1&amp;ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&amp;auto=format&amp;fit=crop&amp;w=1050&amp;q=80</a></figcaption></figure><p>好久沒有寫文章了，一來就先用標題和圖片來賺流量（誤）</p><p>標題是關於轉職，更精確一點，今天想要分享的是「轉職為軟體工程師」的一些小事。</p><p>我想關於轉職這件事情，在網路上隨便都找得到看不完的資料，像是介紹職場趨勢與機會、學習攻略、求職攻略等等，經驗分享的文章當中多半也會提到轉職動機、心路歷程，學習歷程，與準備面試的過程。</p><p>如果你還沒有看過相關的文章，或是覺得還看不夠的話，推薦你可以看看</p><ul><li><a href="https://ithelp.ithome.com.tw/articles/10236685?sc=iThelpR">半路出家轉職工程師？你該知道的 30 件事</a></li><li><a href="https://harry811016.medium.com/%E8%BD%89%E8%81%B7%E9%AC%A5%E5%A3%AB%E7%9A%84%E8%88%AA%E9%81%93-%E9%95%B7%E7%AF%87-79cbe9cc963c">轉職鬥士的航道！（完整長篇）</a></li><li><a href="https://tw.alphacamp.co/blog">ALPHA Camp Blog</a></li></ul><p>除了攻略與經驗分享之外，許多文章當中也會討論到一些問題，像是</p><ul><li>轉職的動機是什麼？夠不夠強烈？</li><li>怎麼知道自己適不適合這條路？怎麼知道自己有沒有天份？</li><li>自己應該要多努力才行？自己這樣夠不夠努力？</li></ul><p>我想這些問題，應該就可以困擾想要轉職的朋友好一段時間。</p><p>不過，我今天「沒有」想要分享我是如何回應、或者我是如何知道上面這些問題的答案，我想從另外一個角度出發，來回顧我轉職的過程。撇開動機、天份、努力，我想說的是…</p><blockquote><strong><em>人生是一個不斷累積的過程</em></strong></blockquote><p>我一直認為，在人生中走過的路都不會白走，那些自己所經歷過的點點滴滴，都會轉化成不同的形式，在未來出現。</p><p>如果以佛學的角度來說，就是過去的因就會產生未來的果，所以未來的自己，其實就是許多過去的自己所累積而成。</p><p>所以與其要分享我「為什麼想要轉職成為軟體工程師」或是「如何轉職成為工程師」，我覺得分享「為什麼現在的我是這樣的我」對我來說會更有意義。</p><p>所以這篇文章「不會」是一篇轉職的攻略或心得文，到比較像是我自己人生經驗的小小回顧。</p><h3>第一次寫程式</h3><p>回想我在高中的時候，從來沒有想過未來要當一個工程師，甚至還有一點點排斥當工程師，雖然那時候還不知道工程師到底在幹嘛。後來在大學如願進入了自己理想中的科系，可以全心投入自己喜歡的領域。</p><p>不過回過頭來，我覺得在大學當中，最有趣的是</p><blockquote>可以用最低的成本，探索這個世界</blockquote><p>不管是學習新知、交朋友、玩社團、打球、談戀愛，甚至是其他的活動（打電動、夜衝 … 族繁不及備載），都是「探索」的其中一種方式。</p><p>所以有趣的是，當年系上規定，系上學生一定要修一門跟程式相關的課程。系上本來就有開一門相關的課，算是相對輕鬆，但偏偏我的好朋友拉我去修隔壁資工系的程式設計。那時候我們真的是不怕死，上課還要坐在第一排。第一天上課老師就問：「你們這兩個外系的坐在這邊幹嘛？」</p><p>老師發了一本講義給每一位同學，接著跟大家說，這學期要教的內容都在上面了，細節可以自己看，而這學期的 final project，每一個人都要做一個小遊戲出來 demo 給大家看。</p><p>第一次學寫程式，然後又要做一個小專案出來，那時候還真的有感覺到壓力，每週都在寫作業和 debug 的深淵當中求生存。印象最深刻的是，有一份作業的 deadline 剛好在系上的聖誕晚會之前，當大家都已經出發然後我還在 debug，真的是欲哭無淚。</p><p>雖然過程辛苦，充滿挑戰，但也讓我第一次知道</p><blockquote>原來可以透過程式，自己創造出有趣的事物</blockquote><p>如果當年只有期末考而沒有做出 final project 的小遊戲，我想我對於程式設計的印象應該會很差吧。這裡得感謝清大資工系陳煥宗教授，設計如此有趣的學習體驗。我想他應該也不會想到， 那個亂入的外系學生，在 10 多年過後也開始把程式當作吃飯的工具。（其實當年我系上的同學，現在很多都是 software engineer 或是 data scientist 了）</p><h3>關於嘗試</h3><p>但其實，那個學期之後，我就再也沒有碰過程式。大學畢業後，跑去念了 MBA，發現好像跟想像中的不太一樣，於是只唸了一年就跑去當兵，退伍後就在 ALPHA Camp 開始工作。</p><p>ALPHA Camp 是個 coding school，不過我的工作內容跟程式也沒有什麼太大的關係，中間轉換過不少的角色，從 BD, project manager operations manger 到 team manager 等。不過突然有一天，我決定再次開始學習程式。我認為環境因素的影響很大，因為身邊不管是老師、學生、助教還是合作夥伴，大家都在寫程式，於是我也想試試看。</p><p>也由於當年在大學的短短經驗，讓我知道程式其實可以拿來做些不一樣的事情，而不是只是做看起來不知道可以幹嘛的 todo list，因此我學了一點 Ruby on Rails 之後，就開始打造一些小工具來處理工作上的事情。之後就不小心越做越多，變成工作有一半的時間都在寫程式，但更重要的是，我知道我樂此不疲，以及</p><blockquote>I can do this all day</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*K5qZtCZT7wA5rfKLPFd2SA.gif" /><figcaption>source: <a href="https://nerdist.com/article/captain-america-best-moments-gifs-quotes-marvel-mcu/">https://nerdist.com/article/captain-america-best-moments-gifs-quotes-marvel-mcu/</a></figcaption></figure><p>特別挑了一個 Steven Rogers 還沒變強壯的 gif，那是因為 “I can to this all day” 這件事情，不會是當自己變成英雄還是武林高手的時候才知道的，這件事情會是在「自己什麼都還不是」的時候努力嘗試，自然而然就會發現。</p><p>我覺得有幾件重要的事情：</p><ul><li>願不願意嘗試新事物</li><li>有沒有意識到嘗試的意義</li><li>不要害怕失敗</li></ul><p>這幾件事情其實相輔相成。做出嘗試，意識到嘗試的意義後，下一次就會更有機會做出嘗試，也不會因為嘗試失敗，然後害怕做出下一次的嘗試。</p><p>可能有些人不太習慣做出改變，原因是「我不知道做這個可以幹嘛？」或是「我怕失敗」，於是就放棄了嘗試的機會。</p><p>呼應到我先前提到的大學經驗，我很慶幸自己在大學的時候嘗試過許多事物，而不僅僅只是本科系的學習而已。在每一次嘗試的當下，其實我沒有特別想過什麼長遠的計畫，或是去想做這個到底可以幹嘛，不過在每一次嘗試的過程中，我嘗試思考這件事帶給我什麼不一樣的思考方式或世界觀，因為我相信</p><blockquote>每一段人生經驗都是學習和累積，在未來都會以不同的形式再次出現</blockquote><p>舉例來說，我從來沒有想過，在上班前一時興起跑去學 Ramda 和 OOP Design Pattern，這個經驗在後來工作上使用 Angular 和 RxJS 會有一些幫助。也沒有想過以前學過、但已經多年沒碰的 Ruby on Rails 的經驗，可能未來就會用在工作之上（現在公司用 Ruby 和 Hanami）。</p><h3>關於耐心練習</h3><p>在現在這個時代，大家的腳步越來越快，想要在行動之後更快地獲得一些回應，其實也就是想要「速成」，而忽略了耐心練習和累積這兩件事情。</p><p>這世界上有太多事情是沒有辦法速成，隨便舉個例子來說，譬如重訓，是需要長時間的練習與累積，才能夠明顯增強自己的肌力。</p><p>更慘的情況是找不到練習的意義。譬如棒球選手，除了要練習投球、打擊、守備等之外，其實也需要做重量訓練，來提升自己的運動能力，以及保護自己。但如果覺得重量訓練沒有意義，只顧著練前面提到的東西的話，發展會相當受限。</p><p>上面的例子可能沒有舉得很好，這裡舉另外一個例子。曾經聽過有人說「阿工程師面試大部分都不會考 linked list 或是 tree，我不知道我練習這些演算法或資料結構的題目要幹嘛？有什麼意義？」</p><p>面試可能真的不會考這些，或是出現這些題目的機率不高。但是對我來說，練習這些可能不會考的題目，本身就像是棒球選手「重訓」的過程，棒球選手不會在棒球場上做出那些重訓的動作，但是重訓所帶來的效果，譬如提升後爆發力、肌耐力，都可以讓棒球選手有更好的表現。</p><p>而練習這些可能不會考的題目，目的在練習如何解題、嘗試不同的解題方法、吸收他人的解題思路等等。就像是重訓一樣，那些所累積的成果，未來會以不同的形式出現。</p><p>所以以學習 web development 來說，看起來當下沒有什麼用的基本功也是很重要，像是如果只想著如何用套件快速做出漂亮的前端畫面，卻沒有花點時間熟悉基本的切版，或是只想快速做出可以運作的應用程式，卻沒有花時間理解其背後運作的邏輯，我覺得可能都是很危險的事情。</p><h3>關於改變</h3><p>從原本在高中的時候想要成為一個科學家，到後來在一間 startup 工作，其實「轉變」非常的大，但這樣的改變，通常不會拿來跟在職場上的「轉職」相提並論，我想原因是前面的這個「轉變」的成本相對較小，而之後的「轉職」的成本則相對的巨大，這個成本可能是要放棄原本的薪水、經驗，然後扛著更重的經濟壓力。</p><p>但本質上來說，我認為一樣都是「改變」，在人生當中沒有什麼是不變的。希臘哲學家很早就說過：</p><blockquote>Change is the only constant in life.</blockquote><p>如果從這個角度來看，其實「轉職」也就不是什麼大不了的事情，每個人也都很有機會可以轉職，不管是轉換職場角色、位階、職能，還是轉換公司，甚至是產業，而這些轉變應該是隨時隨地都有人在上演。</p><p>不過想改變也不是說變就變，需要思考它的 pros and cons，但如果過去的人生經驗中有過相關經驗或嘗試，也許就能幫助自己做出判斷，或是可以讓這個改變的結果變得更好。</p><p>反過來說，現在自己會做出什麼樣的改變，可能也和自己過去所累積的經驗有關。有過不一樣的嘗試和經驗，可能就會做出更不一樣的改變。</p><h3>關於轉職</h3><p>前面說了不少東西，但好像還有很多東西沒講，不過我想就先在這裡打住。總之我想說的是，對許多人來說，「轉職」本身看起來是一件「當下」做的一個重大決定，但是對我來說，看起來其實一部分像是我過去人生的累積。</p><p>這可能聽起來非常的馬後砲，但是回頭想想</p><ul><li>如果我沒有跑去修資工系的程設，我可能會更晚才會知道寫程式的樂趣</li><li>如果我沒有嘗試改變考個 MBA 跑到台北，就沒有機會加入 ALPHA Camp</li><li>如果我沒有自己沒事下班自學 Ruby on Rails，那我可能就不會在工作當中打造那些未來會出現在我求職履歷上的 projects</li><li>如果我當初不願意改變、調離台北，那麼我今天就不會在新加坡當工程師</li></ul><p>我當然也可以分享因為我有多努力或是多幸運，所以我成功轉職拿到了一份工作，但我希望也能夠讓別人知道，這是一段累積的過程。</p><p>關於累積，你可能會覺得每個人的成長和工作背景都不一樣，所以我的經驗對你來說有什麼意義？沒錯，所以除了在網路上看那些成功經驗之外，我認為有機會的話，可以回頭看看自己累積了什麼，更重要的是也提醒自己，在每一個當下那些看起來沒有意義的事情，在未來可能會變得很有意義。</p><p>當然這不是說我當下隨便做什麼都可以，而是要意識到「事物的本質」和「其所可能帶來的更長遠的價值」。</p><h3>轉職紀錄</h3><p>既然是關於轉職，還是來點跟轉職過程中相關的內容吧</p><h4>關於學習</h4><ul><li>2016–17 自學 Ruby on Rails</li><li>2018 自學 Google App Script (JavaScript)</li><li>2019 參與 ALPHA Camp 的線上全端開發課程</li><li>2020 參與 ALPHA Camp 的 <a href="https://tw.alphacamp.co/a-plus">A+ Program</a> (不過是拿到 offer 之後才參加)</li></ul><p>一些過程中我有學過的教材</p><ul><li><a href="https://www.udemy.com/course/master-the-coding-interview-data-structures-algorithms/">Master the Coding Interview: Data Structures + Algorithms Your progress</a></li><li><a href="https://www.udemy.com/course/advanced-node-for-developers/">Node JS: Advanced Concepts</a></li><li><a href="https://www.udemy.com/course/understand-javascript/">Understanding the Weird Parts</a></li><li><a href="https://www.udemy.com/course/docker-and-kubernetes-the-complete-guide/">Docker and Kubernetes: The Complete Guide</a></li><li><a href="https://www.youtube.com/user/AmosLee1">CSScoke</a> 金魚切版系列</li><li><a href="https://www.udemy.com/course/react-lean-by-doing/">React 边做边学（从零开始，包含Hooks</a>）<em>(沒有特別推薦)</em></li><li><a href="https://www.books.com.tw/products/0010870186">從 Hooks 開始，讓你的網頁 React 起來</a></li></ul><h4>關於求職</h4><p>2020 年四月開始在新加坡求職，剛好遇到 COVID 爆發，<a href="https://en.wikipedia.org/wiki/2020_Singapore_circuit_breaker_measures">CB</a> 開始，傳聞許多科技公司開始裁員、人事凍結，失業人口大幅增加。後來政府開始推出各種措施，鼓勵當地企業招募本地人，也因此變相提升外國人求職的難度。</p><ul><li>過程中投了 84 間公司</li><li>10 間公司邀請面試</li><li>被 16 間公司拒絕</li><li>曾經拿到一個口頭 offer，但是要我回台灣辦公室工作，所以被我拒絕了</li><li>最後拿到一個正式 offer。距離第一次面試已經過了 182 天</li></ul><p>求職初期本來的目標是後端工程師，途中經過觀察和分析過後，決定將目標轉換為前端工程師。</p><p>順道一提，我不是拿 EP，而是拿 LTVP + PLOC/LOC 得以工作。</p><p>過程中推掉了至少 3–4 個以上的 project/program manager 的機會，感謝這些公司和朋友們願意提供機會。</p><h4>感謝</h4><ul><li><a href="https://tw.alphacamp.co/">ALPHA Camp</a> 不管是工作階段還是學習階段，都讓我成為了現在的我。這是人生當中一段非常豐富的旅程。</li><li>A+ Program 與同學們，讓我在準備上班前能夠持續 (快樂的) 學習</li><li><a href="https://medium.com/schaoss-blog">Gary</a> 給予所有 (有形或無形) 的支持與鼓勵</li><li><a href="https://www.facebook.com/pjchender/">PJCHENer</a> 的指導讓我不斷地把自己推向更遠的地方</li><li><a href="https://smlpoints.com/">SML</a> 願意當我在學習前端開發路上的夥伴，彼此一路砥礪，互相學習</li><li><a href="https://medium.com/@theyenting">Yenting</a> 在求職過程中所提供各方面專業的建議</li></ul><p>最後要感謝 CQ 給予我所有的支持，在這個相對有挑戰的時刻，讓我還能堅持自己最後達成目標。</p><p>謝謝你的閱讀，如果對於我的分享有興趣多聊聊的話，可以在 <a href="https://www.facebook.com/canopus.t">FB</a> 上找到我 :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e6c688632d14" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tds-note/%E9%97%9C%E6%96%BC%E8%BD%89%E8%81%B7-%E6%88%91%E6%83%B3%E8%AA%AA%E7%9A%84%E6%98%AF-e6c688632d14">關於轉職，我想說的是…</a> was originally published in <a href="https://medium.com/tds-note">TD’s note</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Leetcode 260 & Lowbit function]]></title>
            <link>https://medium.com/tds-note/leetcode-260-lowbit-function-8b7a8caf098b?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/8b7a8caf098b</guid>
            <category><![CDATA[bitwise-operator]]></category>
            <category><![CDATA[leetcode]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Sat, 29 Aug 2020 05:33:04 GMT</pubDate>
            <atom:updated>2020-08-29T07:02:33.977Z</atom:updated>
            <content:encoded><![CDATA[<h3>Leetcode 260 Single Number &amp; bitwise operation</h3><p>Leetcode 260 題 <a href="https://leetcode.com/problems/single-number-iii/">Single Number III</a> 又是一道只能使用 bitwise operation 才能達到要求的題目，除了需要使用基本的 AND, XOR 之外，還引入了 lowbit function 的概念。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zn8Px1ooTVwUCsnrxbpqtQ.jpeg" /><figcaption><a href="https://images.unsplash.com/photo-1554175940-c7d7ede2ecb9?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=crop&amp;w=1650&amp;q=80">https://images.unsplash.com/photo-1554175940-c7d7ede2ecb9?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=crop&amp;w=1650&amp;q=80</a></figcaption></figure><h3>Leetcode 260 — description</h3><blockquote>Given an integer array nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once. You can return the answer in <strong>any order</strong>.</blockquote><blockquote><strong>Follow up: </strong>Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?</blockquote><p>提供一組元素全為「數字」的陣列，其中「只有兩個」元素是唯一存在，其他元素「都會重複兩次」。</p><p>如果要達到任務，在時間複雜度 O(N)、空間複雜度 O(N) 的要求下其實不難，但偏偏題目要求時間複雜度 O(N)，然後空間複雜度要 O(1)。</p><p>在不能增加空間來儲存元素的額外資訊（譬如出現次數，或有沒有看過）的情況下，一時想到能做的，只有透過修改元素值來當作標記，通常的做法是將正數改為負數，但這題有趣的是，題目提供的陣列當中，數字包含負數，所以類似下面這種做法就完全行不通：</p><pre>let result = []<br>for (let i = 0; i &lt; nums.length; i++){<br>  let index = Math.abs(nums[i]) - 1<br>  if (nums[index] &lt; 0) {<br>    result.push(index + 1)<br>  } else {<br>    nums[index] = -nums[index]<br>  }<br>}</pre><p>也無法使用 Cyclic sort 或類似的方法。我想破頭也想不出來，只好求教於他人的解法了。</p><h3>Solution with O(1) space complexity</h3><p>廢話不說，直接呈上解法</p><pre>var singleNumber = function(nums) {<br>  <br>  let xor = 0<br>  <br>  for (let i = 0; i &lt; nums.length; i++) {<br>    xor ^= nums[i]<br>  } <br>   <br>  xor &amp;= -xor</pre><pre>  let result = [[],[]]<br> <br>  for (let i = 0; i &lt; nums.length; i++) {<br>    if ((xor &amp; nums[i]) === 0 ) {<br>      result[0] ^= nums[i]<br>    } else {<br>      result[1] ^= nums[i]<br>    }<br>  }</pre><pre>  return result<br>}</pre><p>第一次看到這個解法，完全不知道在幹嘛，花了一些時間才慢慢釐清脈絡。接下來，我希望能夠用我自己的方法說明，讓讀者有機會更容易瞭解這個解法。</p><h3>Solution — core concepts</h3><p>首先，先說明一下這個解法核心概念</p><ol><li>找到一種「連續運算」方法，能讓重複出現 2 次的數字自己消失，所以剩下來的就只會是那兩個只出現 1 次的數字的運算結果。（所以會發現，其實局限性很高）</li><li>不過只有上面那個方法還不夠，因為我們只會拿到運算結果，而不是分開的兩個數字。因此，我們需要有另外一個方法，來區隔那兩個數字</li></ol><h4>1) 連續運算，消除重複出現的數字</h4><p>關於第一點，也許第一時間會想到數學上的減法，但實際上是行不通的。舉例來說：</p><pre>[1,2,1,3,2,5]</pre><pre>1 - 2 - 1 - 3 - 2 - 5 不會等於 3 - 5</pre><p>而我們需要找的是一種運算，如果假設它叫做 “@”，會達成以下結果</p><pre>1 @ 2 @ 1 @ 3 @ 2 @ 5 正好等於 3 @ 5</pre><p>在 bitwise 的運算當中，XOR 完美地展現這個特性。以剛剛的例子來看：</p><pre>1 ^ 2 ^ 1 ^ 3 ^ 2 ^ 5      // 得到 6，或二進位的 110<br>3 ^ 5                      // 得到 6，或二進位的 110</pre><p>也就是解法前半部在做的事情</p><pre>var singleNumber = function(nums) {  <br>  <br>  <strong>let xor = 0</strong><br><strong>  for (let i = 0; i &lt; nums.length; i++) {<br>    xor ^= nums[i]<br>  }</strong></pre><pre>  ...<br>}</pre><h4>2) 區隔不同數字</h4><p>回到剛剛的例子，先假設我們已經知道我們要找的數字是 3 和 5，接下來，我們想個辦法分辨出這兩個數字，從二進位的角度來看，3 和 5 分別是</p><pre>011   // 二進位的 3<br>101   // 二進位的 5</pre><p>觀察一下，兩者第一個 bit 的值都相同，而第二個和第三個 bit 的值不同。雖然我們可以直接去比較所有的 bit，但我們也可以只比較「第一個出現差異的 bit」，也就是第二個 bit 的值，就知道兩者的不同。</p><p>所以接下來的想法是，我們只要找到一個合適的遮罩 (mask)，當這兩個數跟這個遮罩進行運算的時候，確定會得到不同的結果。</p><p>但其實，拿隨便一個數字，都會得到不同的結果，另一個更糟糕的是，其實我們也不知道不同的結果是什麼，所以沒辦法設定條件去做區分。</p><p>因此，這裡我們要限縮目標，也就是</p><blockquote>找到一個合適的遮罩 (mask)，當這兩個數跟這個遮罩進行運算的時候，其中一個得到的答案會是 0</blockquote><p>如果找到的話，我們就可以建立下面這樣的判斷式</p><pre>if ( <strong>arr[i] do something with someone === 0</strong>) {<br>   ...<br>} else {<br>   ...<br>}</pre><p>透過運算結果，區分兩個數字，並丟到不同的地方去處理。</p><p>現在我們知道要往哪個方向走了，回顧一下剛走完第一步的狀況，現在我們手上有兩筆資訊</p><ul><li>把所有元素透過 XOR 運算完的結果（以上帝視角來看就是 3 ^ 5）</li><li>原陣列當中的所有元素</li></ul><p>接下來，就是要想辦法找到那個可以區隔兩個數字的遮罩！</p><h3>Mask &amp; lowbit function</h3><p>Lowbit function 在做的事情是：找到第一個（最低位）bit 不為 0 的位置。舉例來說</p><pre>0000     // 得到 0<br>0010     // 得到 10<br>0011     // 得到 1<br>0100     // 得到 100<br>0101     // 得到 1<br>0110     // 得到 10<br>0111     // 得到 1<br>1000     // 得到 1000</pre><p>這對我們來說有什麼幫助呢？如果回到剛剛的 3 和 5 的例子，我們已經知道他們最低的差異點在第二個 bit，所以如果我們分別拿 3 和 5 跟二進位的 0010 來做 AND 運算，就會得到：</p><pre>011 &amp; 010  // 得到 010<br>101 &amp; 010  // 得到 000</pre><p>會發現得到我們之前想要的結果，也就是</p><ul><li>找到一個遮罩，分別和兩個數字運算過後，確定會得到不同的結果</li><li>其中一個的結果會是 0</li></ul><p>換另外一個例子，假設是 7 和 28，兩者最低的差異點在第一個 bit，所以遮罩是 1，運算如下：</p><pre>00111 &amp; 00001  // 得到 00001<br>11100 &amp; 00001  // 得到 00000</pre><p>同樣會得到不同的結果，且其中一個結果是 0。</p><p>那麼，最後一個問題就是，我們要怎麼算出遮罩呢？其實我們已經有 XOR 的結果了，這個結果告訴我們「兩個數字相比，有不同值的 bit 的位置」，舉例來說</p><pre>011 ^ 101       // 得到 110<br>00101 ^ 11100   // 得到 11001 </pre><p>不過我們其實只要最低位差異就行，也就是最低位的 1（第一次出現 1 的地方）就行，這時候就可以靠剛剛提到的 lowbit function 來幫我們取出來</p><p>Lowbit function 出奇的簡單：</p><pre>function lowbit (x) {<br>  return x &amp; (-x)<br>}</pre><p>二進位的負數表示法是以補數的方式呈現，根據 <a href="https://zh.wikipedia.org/wiki/%E4%BA%8C%E8%A3%9C%E6%95%B8">wiki 的說明</a> ：</p><blockquote><strong>二補數（英語：2’s complement</strong>）是一種用<a href="https://zh.wikipedia.org/wiki/%E4%BA%8C%E9%80%B2%E4%BD%8D">二進位</a>表示有號數的方法，也是一種將數字的正負號變號的方式，常在<a href="https://zh.wikipedia.org/wiki/%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8">電腦科學</a>中使用。二補數以有符號位元的二進位數定義。</blockquote><blockquote>正數和 0的二補數就是該數字本身。負數的二補數則是將其對應正數按位元取反再加1。</blockquote><p>舉例來說如下</p><pre>2          // 00000010<br>-2         // 11111110<br>3          // 00000011<br>-3         // 11111101<br>4          // 00000100<br>-4         // 11111100<br>5          // 00000101<br>-5         // 11111011</pre><p>然後很神奇的，只要把數字取負數之後，再來個 AND，就可以得到最低位的差異的 bit，譬如</p><pre>2 &amp; -2    // 得到 0010，也就是二進位當中的 2(0010) 第一個不為 0 的值<br>3 &amp; -3    // 得到 0001，也就是二進位當中的 3(0011) 第一個不為 0 的值<br>4 &amp; -4    // 得到 0100，也就是二進位當中的 4(0100) 第一個不為 0 的值<br>5 &amp; -5    // 得到 0001，也就是二進位當中的 5(0101) 第一個不為 0 的值</pre><h3>Solution — step by step</h3><p>上面提到了解題的核心概念，以及介紹需要使用的工具，最後我們再來看一次解法，並逐步詳細解說</p><pre>var singleNumber = function(nums) {<br>  <br>  let xor = 0<br>  <br>  for (let i = 0; i &lt; nums.length; i++) {<br>    xor ^= nums[i]<br>  } <br>   <br>  xor &amp;= -xor</pre><pre>let result = [[],[]]<br> <br>  for (let i = 0; i &lt; nums.length; i++) {<br>    if ((xor &amp; nums[i]) === 0 ) {<br>      result[0] ^= nums[i]<br>    } else {<br>      result[1] ^= nums[i]<br>    }<br>  }</pre><pre>return result<br>}</pre><pre><strong>// example</strong><br>let nums = [1,2,1,3,2,5]<br>singleNumber(nums)</pre><ol><li>首先，將所有元素進行 XOR 的運算，所以最後會得到等同於 3 ^ 5 的結果，也就是 110 ，存放在變數 XOR當中。</li><li>接這，利用 lowbit function 建立遮罩，這裡會得到結果 010 ，並存放在變數 XOR當中。</li><li>建立儲存最後結果的變數。已知只有兩個結果，所以直接建立帶有兩個元素的陣列（這裡其實不一定要這麼做）</li><li>最後，再次跑過所有元素，並用遮罩來區個數字。這裡我們就直接列舉</li></ol><pre>xor 為 101</pre><pre>a[0] 為 1，二進位為 001，001 &amp; 010 為 000<br>a[1] 為 2，二進位為 010，010 &amp; 010 為 010<br>a[2] 為 1，二進位為 001，001 &amp; 010 為 000<br>a[3] 為 3，二進位為 011，011 &amp; 010 為 010<br>a[4] 為 2，二進位為 010，010 &amp; 010 為 010<br>a[5] 為 5，二進位為 101，101 &amp; 010 為 000</pre><p>這時候就會發現，數字 1, 5 會滿足 (xor &amp; nums[i]) === 0 條件進入 result[0] ^= nums[i] 當中，而 2, 3 會進入另外一邊。</p><p>分開之後，接下來做的事情，其實就跟第一步完全一樣， 所以result[0] 和 result[1] 的結果分別是</p><pre>result[0] 等於 1 ^ 1 ^ 5 等於 5<br>result[1] 等於 2 ^ 2 ^ 3 等於 3</pre><p>因為最後得到的結果就是 [5 ,3]</p><p>也因為這樣的作法無法產出有序的陣列結果，所以題目不要求產出的陣列元素順序。反過來說，其實題目是為了這樣的特殊解法，而特別設計這麼多的限制條件。</p><p>在 Leetcode 上常常會看到特殊的題目與解法，有時候可能會覺得這在工作上一點用也沒有。不過就當作是個有趣的發現，也許哪天，這個發現會在意想不到的地方，用截然不同的形式浮上腦海。</p><h3>About me</h3><p><em>Self-taught and trained in software development knowledge and skills, I am passionate about creating changes through technology.</em></p><p><em>Find more at </em><a href="https://github.com/tsungtingdu"><strong><em>Github</em></strong></a><em>, </em><a href="https://www.linkedin.com/in/tsung-ting-tu/"><strong><em>LinkedIn</em></strong></a><em>, </em><a href="https://lighthouse.alphacamp.co/users/3247/ta_profile"><strong><em>Teaching at ALPHA Camp</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8b7a8caf098b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tds-note/leetcode-260-lowbit-function-8b7a8caf098b">Leetcode 260 &amp; Lowbit function</a> was originally published in <a href="https://medium.com/tds-note">TD’s note</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Array.prototype.sort() works in V8?]]></title>
            <link>https://medium.com/tds-note/how-array-prototype-sort-works-in-v8-d69ff0a7b663?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/d69ff0a7b663</guid>
            <category><![CDATA[v8-engine]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[sorting-algorithms]]></category>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Fri, 03 Jul 2020 18:55:33 GMT</pubDate>
            <atom:updated>2020-07-03T18:55:51.228Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ahnXacoKNx7vmcEdZPuqlg.jpeg" /><figcaption>Source: <a href="https://images.unsplash.com/photo-1571218027918-a734b7f264c8?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=crop&amp;w=1650&amp;q=80">https://images.unsplash.com/photo-1571218027918-a734b7f264c8?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=crop&amp;w=1650&amp;q=80</a></figcaption></figure><p><em>同學提問：執行下面的程式碼</em></p><pre>let arr = [1, 3, 2]<br>arr.sort((a, b) =&gt; {<br>  console.log(`Now comparing a = ${a} &amp; b = ${b}`);<br>  return a - b;<br>});</pre><p><em>得到結果會是</em></p><pre>Now comparing a = 3 &amp; b = 1<br><strong>Now comparing a = 2 &amp; b = 3<br>Now comparing a = 2 &amp; b = 3</strong><br>Now comparing a = 2 &amp; b = 1</pre><p><em>為什麼其中的 2 和 3 會被比較兩次呢？</em></p><p>在 Array.sort() 裡面放入 console.log() 還真是我沒有想過的點子，非常有趣，但一放進去就發現問題，讓我花了半天的時間尋找答案。那麼同樣的，接下來就歡迎有興趣的朋友跟著我一起探索吧！</p><h3>先講結論</h3><p>根據經驗，大概有 70% 以上的人不會把文章看完，所以這裡先講結論</p><p>在目前的 V8 engine 當中，處理 array.prototype.sort() 的時候是使用 <a href="https://en.wikipedia.org/wiki/Timsort"><strong>Timsort</strong></a>（跟我同名的）排序法。</p><p>Timsort 的概念是，將一大筆資料拆成小批次的資料，在小批次的資料當中使用 Binary Inserttion Sort，之後再將已經排序好的小批次資料，用 Merge Sort 把所有小批次資料整合在一起。</p><p>不過在進行 Binary Insertion Sort 之前，Timsort 會進行一個特別的動作，就是它會先去搜尋「小批次資料當中前面的『有序』資料」，把這些資料抓出來之後，這段資料就不需要進行 Binary Insertion Sort。</p><p>因此剛剛的結果，其實是 console.log 分別在不同的階段被呼叫（因為資料量小，所以沒有動用到 Merge）：</p><pre>Now comparing a = 3 &amp; b = 1            <strong>// 尋找有序資料 (nature run)</strong><br>Now comparing a = 2 &amp; b = 3            <strong>// 尋找有序資料 (nature run)</strong><br>Now comparing a = 2 &amp; b = 3            <strong>// Binary Insertion Sort</strong><br>Now comparing a = 2 &amp; b = 1            <strong>// Binary Insertion Sort</strong></pre><h3>Start from Array.prototype.sort()</h3><p>在 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort">MDN</a> 的說明，我們在使用 array.sort() 的時候，可以傳入一個 compare function：</p><pre><em>arr.sort([compareFunction])</em></pre><p>根據定義，如果沒有傳入 compareFunction，那麼它就會自動將陣列當中的 elements 轉成字串，並依照 UTF-16 code units order 排序。這也就是為什麼 “80” 會排在 “9” 的前面。</p><p>如果有傳入 compareFunction，那麼 compareFunction 就會依照 timsort 演算法抓取兩個 elements 進行比較，然後回傳結果。case 如下：</p><ul><li>如果 compareFunction(a, b) 回傳 0，那麼 a 排在 b 前面</li><li>如果 compareFunction(a, b) 回傳大於 0 的值，那麼 b 排在 a 前面</li><li>如果 compareFunction(a, b) 回傳小於 0 的值 ，那麼 a 排在 b 前面</li></ul><p>所以通常如果想要遞增排序，會將回傳值設定為 a-b，這樣一來，如果前面的 a 小於後面的 b，那麼 a 減 b 就會是小於 0 的值，根據上面的規則，a 就會排在 b 的前面，整個陣列也就會是遞增排序。</p><pre>arr.sort((a, b) =&gt; {<br>  return a - b;<br>});</pre><p>反之如果要遞減排序，那麼就只要將回傳值改成 b-a</p><p>看到這裡，其實就會發現我們可以在 compareFunction 裡面做任何事情，然後配合剛剛提到的規則來影響最後排序結果。當然我們也可以在 compareFunction 裡面放入 console.log 來看看演算法是如何實際抓取 elements 來進行比較。</p><p>但是看到這裡，並沒有辦法回答我們的問題，而且我們也沒有實際看到 Timsort。依照過去的經驗，如果我們想要了解一個套件是如何運作，可以查看他的原始碼，在使用 Node.js 開發的情況下，套件也通常都是由 JavaScript 所寫成。不過，如果我們想要知道 JavaScript 本身是怎麼運作的，那我們又應該去哪裡找他的原始碼呢？</p><h3>V8 — a JavaScript engine</h3><p>在 Chrome browser 或是 Node.js 的環境下，JavaScript 程式碼仰賴 <a href="https://en.wikipedia.org/wiki/V8_(JavaScript_engine)">V8 engine</a> 進行編譯，然後才能被執行。在不同的環境下會有不同的 <a href="https://en.wikipedia.org/wiki/JavaScript_engine">JavaScript engines</a>，而不同的 engines 所執行的 JavaScript 雖然都符合 ECMAscript 的規範，但在細節上可能還是會有所不同，譬如 Array.prototype.sort() 所採用的演算法。</p><p>因此這篇文章談的是 “How Array.prototype.sort() works in V8”，如果是在不同的 JavaScript engine 上，結果可能就會不同。</p><p>所以如果要知道 Array.prototype.sort() 實際上是怎麼運作的，那麼就不得不去看一下 V8 engine。</p><h3>Getting things sorted in V8</h3><p><a href="https://v8.dev/blog/array-sort">Getting things sorted in V8</a> 這篇文章提到 Array.prototype.sort() 是如何被實現的，我得承認我沒有完全看懂，不過有以下幾個 takeaways</p><ul><li>V8 原本是使用 Quick Sort 和 Insertion Sort 演算法來處理，不過目前改用 Timsort</li><li>實際執行 sorting 之前，V8 會針對 array 進行前處理（不同的 engine 會有不同的處理結果）</li><li>V8 引入了 torque 這個新的程式語言</li><li>Timsort 不是遞迴執行的 (it works iteratively)</li><li>Timsort 會從 array 由左到右建立 “runs”，也就是在整個資料中先取出小批次資料進行處理</li><li>Nature run 是指在排序前「已經有序」的資料</li><li>每次執行一個 “run” 的時候，如果有非 nature run 的資料，那麼就會使用 Binary Insertion Sort 讓整個 run 有序</li><li>在一些 runs 完成排序之後，就會開始逐步將部份 runs 整合成一筆有序的資料。最後的結果是，所有的 runs 將被整合成一個 sorted array 並回傳</li></ul><p>看完這些，對於 V8 如何處理 sorting 有個概念，但還是不知道為什麼 2 &amp; 3 要被比較兩次。於是，就毅然決然地跳入 V8 engine 的原始碼去看</p><h3>V8 engine source code</h3><p>我們可以在 <a href="https://github.com/v8/v8/blob/master/third_party/v8/builtins/array-sort.tq"><strong>這裡</strong></a><strong> </strong>看到 V8 處理 array sorting 的程式碼。會發現這裡的程式碼灰色一片，應該是編輯器還沒有支援 torque 這個語言。</p><p>在這裡我們不會看完所有 functions， 如果直接來到檔案的最下面，可以看到 ArrayPrototypeSort 主要呼叫 ArrayTimSort() 來執行任務</p><pre>transitioning javascript builtin<br><strong>ArrayPrototypeSort(...)</strong>{ <br>  ... <br>  <strong>ArrayTimSort(context, sortState)</strong>;<br>  ...<br>}</pre><p>在 ArrayTimSort() 當中，看起來主要執行排序任務的是 ArrayTimSortImp()</p><pre>transitioning builtin<br><strong>ArrayTimSort(...)</strong>{<br>  ...  <br>  <strong>ArrayTimSortImpl(context, sortState, numberOfNonUndefined)</strong>;  <br>  ...<br>}</pre><p>因為當初問題本身的資料量算少，這裡我們就重點來看 Timsort 當中的 Binary Insertion Sort 的運作，其他關於 Merge 的部分就大膽的暫時跳過。</p><p>這次主要看以下四個 functions:</p><ul><li><a href="https://github.com/v8/v8/blob/5ab2769012ac66820a987d1b5a9446986ddf3d73/third_party/v8/builtins/array-sort.tq#L1246">ArrayTimSortImpl()</a></li><li><a href="https://github.com/v8/v8/blob/5ab2769012ac66820a987d1b5a9446986ddf3d73/third_party/v8/builtins/array-sort.tq#L1165">ComputeMinRunLength()</a></li><li><a href="https://github.com/v8/v8/blob/5ab2769012ac66820a987d1b5a9446986ddf3d73/third_party/v8/builtins/array-sort.tq#L565">CountAndMakeRun()</a></li><li><a href="https://github.com/v8/v8/blob/5ab2769012ac66820a987d1b5a9446986ddf3d73/third_party/v8/builtins/array-sort.tq#L500">BinaryInsertionSort()</a></li></ul><p>以下依照程式執行的流程說明 functions 之間的內容，為了方便，就容我用條列式的方式說明。</p><p>另外，也假設我們目前在執行下面這段程式碼，其中目標陣列為 [1, 3, 2]</p><pre><strong>let arr = [1, 3, 2]</strong><br>arr.sort((a, b) =&gt; {<br>  return a - b;<br>});</pre><h4>Stage 1: ArrayTimSortImpl()</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f125792b7a0b1884b6d1300ab6bf0552/href">https://medium.com/media/f125792b7a0b1884b6d1300ab6bf0552/href</a></iframe><ul><li><strong>L2:</strong> length 是資料陣列的長度。</li><li><strong>L4:</strong> 宣告變數 remaining 為陣列長度，也就是 3</li><li><strong>L9:</strong> 宣告變數 low 為 0</li><li><strong>L10:</strong> 要找到 Timsort 當中 run 的最小執行長度，因此呼叫 ComputeMinRunLength() function，並傳入 remaining ，這個值目前為陣列總長度 3</li></ul><h4>Stage 2: ComputMinRunLength()</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/04ec0af11acc2ac774d80b5947baf833/href">https://medium.com/media/04ec0af11acc2ac774d80b5947baf833/href</a></iframe><ul><li><strong>L12:</strong> 宣告變數 n 為傳入的參數 nArg ，也就是剛剛傳入的陣列長度 3</li><li><strong>L13:</strong> 宣告變數 r 為 0</li><li><strong>L16:</strong> 如果 nArg (陣列長度) 超過 64，就一路除以 2。其中 r 的設計是，如果 n 大於且為 2 的次方，那麼最後執行完 while 迴圈 r 的值為 0；若不為 2 的次方，那麼就是 1。不過在這裡因為陣列長度太小，根本不會執行 while 迴圈，n 仍然為 3、r 為 0</li><li><strong>L21:</strong> 宣告變數（也是最後的回傳值）minRunLength 為 3</li></ul><h4>Stage 3: ArrayTimSortImpl()</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f125792b7a0b1884b6d1300ab6bf0552/href">https://medium.com/media/f125792b7a0b1884b6d1300ab6bf0552/href</a></iframe><ul><li><strong>L10:</strong> 於是，minRunLength 的值為 3</li><li><strong>L12:</strong> 要計算先前提到的「已經存在的有序資料」，呼叫 CountAndMakeRun() function，傳入的參數值分別為 0, 3</li></ul><h4>Stage 4: CountAndMakeRun()</h4><p>這裡主要的任務是去找「已經存在的有序資料」也就是 nature run</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/518aea795c5f480ca786991fa99c1cca/href">https://medium.com/media/518aea795c5f480ca786991fa99c1cca/href</a></iframe><ul><li><strong>L23: </strong>引入目標陣列，也就是 [1, 3, 2] 作為 workArray</li><li><strong>L25:</strong> 宣告變數 low ，也就是傳入的第一個參數 low + 1，也就是 1</li><li><strong>L26:</strong> 此時 high 是傳入的第二個參數，也就是 3，和 low 不相等，因此不會執行</li><li><strong>L28:</strong> 宣告變數 runLength 為 2</li><li><strong>L30:</strong> 宣告變數 elementLow 為 workArray[1] ，其值為 3</li><li><strong>L31:</strong> 宣告變數 elementLowPre 為 workArray[0] ，其值為 1</li><li><strong>L32: </strong>呼叫 compareFunction 出場，由於原本的 arr.sort() 裡面只有 return a-b，所以這裡的 compareFunction 回傳結果是 2。變數 order 為 2 。特別注意的是，<strong>這裡是第一次 compareFunction 被呼叫的地方 (1 &amp; 3)</strong>，因此 compareFunction 當中的 console.log 會被呼叫</li><li><strong>L37: </strong>nature run 的狀態判斷，因為 order 為 2，因此 isDescending 為 false，代表透過前兩個 elements 判斷，接下來要找的 nature run 會是個遞增排序的資料。</li><li><strong>L39: </strong>宣告變數<strong> </strong>previousElement ，其值為 3</li><li><strong>L41: </strong>宣告變數 currentElement ，由於 idx 為 low + 1，值為 2，因此 currentElement 也就是 workArray[2]，值為 2</li><li><strong>L42: </strong>判斷 currentElement 與 previousElement 之間的 order ，這裡的 order 為 2 – 3= -1。<strong>這裡是第二次 compareFunction &amp; console.log 被呼叫的地方 (2 &amp; 3)</strong></li><li><strong>L44: </strong>負責判斷是否要繼續往前找 nature run。由於目前 isDescending 為 false，且 order 小於 0，因此會執行 break，跳出 for loop。因此，目前找到的 nature run 為 [1, 3]</li><li><strong>L45: </strong>如果找到的 nature run 是反序的，譬如 [3, 1]，那麼就會直接反轉變成正序（備註：這裡的正反序是根據 compareFunction 而定，如果當中改成 return b-a，那麼上面的結果都會反過來）</li><li><strong>L58: </strong>回傳 runLength ，值為 2</li></ul><h4>Stage 5: ArrayTimSortImpl()</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f125792b7a0b1884b6d1300ab6bf0552/href">https://medium.com/media/f125792b7a0b1884b6d1300ab6bf0552/href</a></iframe><p>中場整理一下，關於目前有的變數值</p><ul><li>low 為 0</li><li>minRunLength 為 3</li><li>currentRunLength 為 2</li><li>remaining 為 3</li></ul><p>接下來繼續來看程式碼</p><ul><li><strong>L17:</strong> forcedRunLength 為 minRunLength 或 remaining 之間的最小值。不過因為兩者都為 3，因此 forcedRunLength 為 3</li><li><strong>L18:</strong> 終於，要進入了 BinaryInsertionSort()，這裡傳入的參數值分別為 0, 2, 3</li></ul><h4>Stage 6: BinaryInsertionSort()</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6ea2fc776d515033e7ae958a7e83829f/href">https://medium.com/media/6ea2fc776d515033e7ae958a7e83829f/href</a></iframe><p>這裡其實就是執行 Binary Insertion Sort，如果對於這個排序方法還不是很理解，可以觀看<a href="https://youtu.be/-OVB5pOZJug?t=195">這個影片</a>的說明。</p><p>這裡先幫大家整理一下變數：</p><ul><li>傳入的參數 low, startArg, high 分別為 0, 2, 3 (承接上一步)</li><li>start 為 startArg ，值為 2</li><li>left 為 0</li><li>right 為 start 2</li><li>pivot 為 workArray[right] 也就是 2</li><li>mid 為 0 + ( (2 - 0 ) &gt;&gt; 1)，也就是 1</li></ul><p>接下來，就可以開始跑 Binary Insertion Sort。以下為 Binary Insertion Sort 分解步驟的示意圖：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OPALDE6ujoBNNEIRRhvxNg.jpeg" /></figure><p><strong>Step 1: </strong>拿 pivot 和 workArray[min] 比較，得到結果 order 為 -1，因此 right 的值變為 1。<strong>這裡是第三次 compareFunction &amp; console.log 被呼叫的地方 (2 &amp; 3)</strong></p><p><strong>Step 2: </strong>由於此時 left &lt; right，while loop 會繼續執行。重新計算 mid 的值為 0</p><p><strong>Step 3: </strong>拿 pivot 和 workArray[min] 比較，得到結果 order 為 1，因此 right 的值變為 1。<strong>這裡是第四次 compareFunction &amp; console.log 被呼叫的地方 (1 &amp; 2)</strong>。另外，</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lgtJcKhGkRl_4Kusp4ETzA.jpeg" /></figure><p><strong>Step 4: </strong>left 值變為 mid + 1</p><p><strong>Step 5: </strong>此時left 等於 right，while loop 停止執行。往下執行 array element 的位移。只移動 left 右邊的部分</p><p><strong>Step 6: </strong>執行 workArray.objects[left] = pivot; 完成排序</p><p>在以上的過程中，可以很清楚看到 compareFunction 在哪裡被呼叫：前兩次在 CountAndMakeRun() 當中，後兩次在 BinaryInsertionSort() 當中，也就是</p><pre>Now comparing a = 3 &amp; b = 1            <strong>// CountAndMakeRun()</strong><br>Now comparing a = 2 &amp; b = 3            <strong>// CountAndMakeRun()</strong><br>Now comparing a = 2 &amp; b = 3           <strong> // BinaryInsertionSort()</strong><br>Now comparing a = 2 &amp; b = 1            <strong>// BinaryInsertionSort()</strong></pre><p>也就很清楚的解答了「為什麼 2 和 3 會被比較兩次」</p><p>雖然花了不少時間進行這段探索，但其實也只摸到了Array.prototype.sort 當中小小的一塊，還有一半以上的原始碼沒有機會可以看到。不過越深入走進原始碼，越能夠找到意想不到的答案。</p><p><em>Ref:</em></p><ul><li><a href="https://v8.dev/blog/array-sort#timsort"><em>Getting things sorted in V8</em></a></li><li><a href="https://github.com/v8/v8/blob/master/third_party/v8/builtins/array-sort.tq"><em>V8 (Github)</em></a></li><li><a href="https://www.youtube.com/watch?v=-OVB5pOZJug"><em>Binary Insertion Sort</em></a></li><li><a href="https://gitbook.cn/books/5d81a17cb062ad5751a74880/index.html"><em>https://gitbook.cn/books/5d81a17cb062ad5751a74880/index.html</em></a></li></ul><h3>About me</h3><p><em>Self-taught and trained in software development knowledge and skills, I am passionate about creating changes through technology.</em></p><p><em>Find more at </em><a href="https://github.com/tsungtingdu"><strong><em>Github</em></strong></a><em>, </em><a href="https://www.linkedin.com/in/tsung-ting-tu/"><strong><em>LinkedIn</em></strong></a><em>, </em><a href="https://lighthouse.alphacamp.co/users/3247/ta_profile"><strong><em>Teaching at ALPHA Camp</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d69ff0a7b663" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tds-note/how-array-prototype-sort-works-in-v8-d69ff0a7b663">How Array.prototype.sort() works in V8?</a> was originally published in <a href="https://medium.com/tds-note">TD’s note</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How failure message works in Passport.js]]></title>
            <link>https://medium.com/tds-note/how-failure-message-works-in-passport-js-512e453af8a3?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/512e453af8a3</guid>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[passportjs]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Wed, 01 Jul 2020 05:39:35 GMT</pubDate>
            <atom:updated>2020-07-01T07:10:30.337Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/775/1*vrmts-m3iarxmd5xhgUy5g.png" /><figcaption>source: <a href="http://www.passportjs.org/">http://www.passportjs.org/</a></figcaption></figure><p>歷來學生都不斷地詢問一個問題：</p><blockquote>要如何將 Passport 當中的錯誤訊息顯示出來</blockquote><p>雖然過去在每一班都回覆過類似的問題，但發現每回覆一次，就會發現新的東西，讓我自己覺得不太安寧。因此，決定透過這篇文章，來結束這場戰鬥 😤</p><h3>前情提要</h3><p><a href="http://www.passportjs.org/">Passport.js</a> 是在開發 Node.js 應用程式時，常用到的驗證系統。想了解更多，可以參考以下文章</p><ul><li><a href="https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E7%AD%86%E8%A8%98-%E9%80%8F%E9%81%8E-passport-js-%E5%AF%A6%E4%BD%9C%E9%A9%97%E8%AD%89%E6%A9%9F%E5%88%B6-11cf478f421e">[筆記] 透過 Passport.js 實作驗證機制</a></li><li><a href="https://pjchender.github.io/2017/09/26/node-passport-%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98%EF%BC%88learn-to-use-passport-js%EF%BC%89/">[Node] Passport 學習筆記（Learn to Use Passport JS）</a></li><li><a href="http://toon.io/understanding-passportjs-authentication-flow/">Understanding passport.js authentication flow</a></li></ul><p>這篇文章不會講解 Passport 如何使用，將會著重在 failure message 上</p><h3>先講結論</h3><p>要呈現 Passport 驗證過程中的錯誤訊息，有以下幾種方法</p><h4>1. 直接呼叫 req.flash()</h4><p>在使用 LocalStrategy 的時候 直接呼叫 req.flash() ，這應該是最直接的方法，但是要注意的是，使用這個方法需要 req ，所以上面的 passReqToCallback 一定要設定為 true ，我們才能夠在 verify function 當中傳入並使用 req</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/03b4e2ce8f71d7c2ad5d403c33931b2e/href">https://medium.com/media/03b4e2ce8f71d7c2ad5d403c33931b2e/href</a></iframe><h4>2. 開啟 failureFlash 設定</h4><p>然後可以在 <strong>verify function</strong> 當中的 done 傳入</p><pre><strong>// </strong>flash type 自動設定為 error<strong><br>“I’m error message” </strong>     </pre><pre><strong>// </strong>flash type 自動設定為 error。注意這裡的 key 必須為 message<br><strong>{message: &quot;I’m error message”} </strong>         </pre><pre>// 手動設定flash type 為 hi。注意後面訊息的 key 必須為 message<br><strong>{type: &#39;hi&#39;, message: “I’m error message”}   </strong></pre><p>另外一個可以設定的地方在這裡</p><pre>app.post(&#39;/signin&#39;, passport.authenticate(<br>  &#39;local&#39;, <br>  { ...<strong> <br>    failureFlash: // do something here</strong><br>  }<br>)</pre><p>不過這麼一來就比較無法針對驗證過程當中不同的狀況，給予不同的提示訊息（除非搭配使用上面在 done 當中的設定）</p><p>設定完之後，錯誤訊息就會透過 connect-flash 套件的協助顯示給使用者</p><h4>3. 開啟 failureMessage 設定</h4><p>同樣可以在 <strong>verify function</strong> 當中的 done 傳入</p><pre>// 錯誤訊息字串<strong><br>“I’m error message”</strong></pre><pre>// 錯誤訊息物件<strong>，</strong>注意這裡的 key 必須為 message<strong><br>{message: &quot;I’m error message”}</strong></pre><p>之後，開發者可以在 req.session.messages 陣列當中把值取出，在任何地方使用。</p><p>以下為原始碼的探索，文長慎入</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MkatZrPllQPa3XunjyizNg.png" /></figure><h3>Start from LocalStrategy</h3><p>在驗證使用者的過程中，可能會出現 err、找不到該使用者、密碼錯誤的狀況，如果要讓使用者知道實際發生什麼問題，我們可以在使用 LocalStrategy 的時候加入錯誤訊息。</p><p>以下是使用 LocalStrategy 的範例程式碼</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/03b4e2ce8f71d7c2ad5d403c33931b2e/href">https://medium.com/media/03b4e2ce8f71d7c2ad5d403c33931b2e/href</a></iframe><p>如果來看 LocalStrategy 函式本身，會是</p><pre>function Strategy(options, verify) {<br>  ...<br>}</pre><p>所以在上面的範例程式碼當中，我們傳入了一個物件作為 options，後面接著一個函式做為 verify function</p><p>這裡稍微說明 options 裡面的內容。在 LocalStrategy 當中，預設會使用 usernameField 以及 passwordField 這兩筆資料，資料的來源就是登入表單，而且 LocalStrategy 會自動從 req.body 當中解析這兩筆資料出來。</p><p>在預設的情況下，LocalStrategy 會去找</p><ul><li>usernameField: req.body.username or req.query.username</li><li>passwordField: req.body.password or req.query.password</li></ul><p>但如果我們在 options 當中有特別指定名稱，那麼就會採用開發者設定的名稱，譬如</p><ul><li>usernameField: req.body.email or req.query.email</li></ul><p>最後， passReqToCallback的意義是，我們是否要將 req 傳入 verify 函式當中。如果其值為 true，我們可以將 req 傳入 verify 作為第一個參數</p><pre>// verify function</pre><pre>(<strong>req</strong>, username, password, done) =&gt; {...}</pre><p>談 options 有點離題了，不過在結論當中會提到這一塊，所以特別說明一下。</p><h3>The last argument of verify function</h3><p>這裡其實我們比較在意的是， verify function 中的最後參數 done ，你可能也會看到有人寫成 cb ，也就是 callback 的意思。</p><p>這裡我們不管怎麼命名，其實都是指向下面這個函式</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f10844325d76149c24c66c0a9309482f/href">https://medium.com/media/f10844325d76149c24c66c0a9309482f/href</a></iframe><p>這個 <strong>varified</strong> function 會接收三個參數，第一個參數可以接收 verify function 丟出來 err，第二個參數接收 user 資料，第三個參數為 info。</p><p>實際使用方式，舉例來說像是下面四種 cases（當然有更多種方法）</p><pre>done(err, false)                                                // 1<br>done(null, false, req.flash(&#39;error_messages&#39;, &#39;error&#39;))         // 2<br>done(null, false, { message: &#39;Email not registered!&#39; })         // 3<br>done(null, user)                                                // 4</pre><p>在上面的例子當中，你可以看到 info 有兩種不同的寫法（case 2 &amp; 3），一個是直接呼叫 req.flash ，另外一個是傳入物件。這裡晚點會進一步說明。</p><p>不過其實這個 <strong>verified</strong> function 也沒有做什麼特別的事情，只是根據狀況回傳不同的函式。</p><ul><li>如果有接收到 err，二話不說直接回傳 self.error(err) ，也不用管後面的其他參數（對應到上面的 case 1）</li><li>如果沒有 err，但是也沒有 user 資料，那就直接回傳 self.fail(info) 。不用管 err 和 user 參數（對應到上面的 case 2 &amp; 3）</li><li>如果沒有 err ，同時擁有 user 資料，那麼就開心的回傳 self.success(user, info)（對應到上面的 case 4）</li></ul><p>那麼 self 是什麼呢？這裡的 self 指的是 <strong>LocalStrategy 本身</strong>，不過我們在 LocalStrategy 的原始碼當中，其實找不到 self.error, self.fail, self.success 這三個函式，原因是這些函式是從 <a href="https://github.com/jaredhanson/passport">Passport</a> 繼承而來的。</p><p>因此接下來，我們要回到 <a href="https://github.com/jaredhanson/passport-local">LocalStrategy 原始碼</a>，轉戰到 <a href="https://github.com/jaredhanson/passport">Passport 原始碼</a></p><h3>“failures” array in passport</h3><p>我們可以在 <a href="https://github.com/jaredhanson/passport/blob/master/lib/middleware/authenticate.js">passport/lib/middleware/authenticate.js</a> 裡面找到以下的程式碼</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/11c13d4aefa08b832e9af8e78f6a17d0/href">https://medium.com/media/11c13d4aefa08b832e9af8e78f6a17d0/href</a></iframe><p>首先可以看到他接收了兩個參數，不過在 LocalStrategy 裡面我們只傳入了一參數。如果剛剛我們傳入的 info 是個數字，那麼就會被 “歸類” 為 status，如果不是，那麼就會是 challenge。</p><p>不管我們傳入什麼，都會被當成一個物件的值，像是</p><pre> { challenge: {message: &#39;xxx&#39;}}</pre><p>然後被加入到一個叫做 failures 的陣列當中。</p><h3>Config messages</h3><p>在同樣的一份檔案裡面，可以找到下面的程式碼，這裡定義了我們如何使用 failures 陣列，以及如何產出錯誤訊息給使用者</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0cca2ff31f011c46a85d9b3cffb5f6f4/href">https://medium.com/media/0cca2ff31f011c46a85d9b3cffb5f6f4/href</a></iframe><p>首先，會將剛剛放入的 { challenge: {message: ‘xxx’}} 給取出來，接著會另外取出 challenge 的值，也就是 {message: ‘xxx’}</p><p>接下來有兩個 if 判斷是，分別判斷 options.failureMessage 和 options.failureFlash 的狀態。不過這兩個狀態是從哪裡來的呢？</p><p>在我們使用 passport 進行登入驗證的時候，會對於認證後的處理進行設定，譬如下面這樣</p><pre>app.post(&#39;/signin&#39;, passport.authenticate(<br>  &#39;local&#39;, <br>  { <strong>failureRedirect: &#39;/signin&#39;, <br>    failureMessage: true,<br>    failureFlash: true </strong><br>  }<br>)</pre><p>這裡我們定義了 failureMessage, failureFalsh 的值為何，也就將會進一步決定剛剛的 challenge 的值 {message: ‘xxx’} 如何被處理。</p><h4>failureFlash</h4><p>若值為 true，那麼就會執行下面這段程式碼</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4d96f9d179baf97732dc0eed7484ac86/href">https://medium.com/media/4d96f9d179baf97732dc0eed7484ac86/href</a></iframe><p>讓我來翻譯一下：如果 failureFlash 的值為 true，那麼就會進入下面這段程式碼。但是值為 true 的情況有</p><ul><li>給他一個 true</li><li>給他一個不為 false 的值，譬如一個字串，或是其他東西</li></ul><p>所以如果 failureFlash、也就是後來的 flash 是個字串，那麼就會建立一個物件為</p><pre>flash = { type: &#39;error&#39;, message: flash }</pre><p>如果 flash 不是個字串，那麼就去找是否有 flash.type 值，如果沒有，就設定 flash.type 為 error</p><p>上面的步驟，基本上就是為了使用 connect-flash 做準備。所以準備好了 type，接下來就要準備 msg (message)。</p><p>如果此時 flash 已經是一個物件，且有 flash.message 值，就可以直接採用；若無，則尋找 challenge.message；若無，那麼最後的嘗試就是希望 challenge 本身就是個字串。</p><p>最後，在真正送出 req.flash(type, msg) 會再次檢查 msg 是否為字串，若否，則不會送出 flash message。</p><p>req.flash() 執行之後，使用者就能夠在前端看到 flash message</p><h4>failureMessage</h4><p>若值為 true，那麼就會執行下面這段程式碼</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c431bf496ffb49dbac288baaf1705eeb/href">https://medium.com/media/c431bf496ffb49dbac288baaf1705eeb/href</a></iframe><p>翻譯：如果 failureMessage 是個 boolen 值，那麼 msg 就直接是 challenge.message 或是 challenge。當然這裡 failureMessage 為 true 才會被執行到。</p><p>接著，檢查一下 msg 是不是真的是字串，若是，則會把這個字串放到 req.session.messages 這個「陣列」當中。若原本沒有 req.session.messages 這個陣列，就當下創造一個空陣列出來，然後把 msg 放進去 。</p><p>之後，開發者就可以利用 req.session.messages ，在任何想要呈現錯誤訊息的地方呈現它</p><p><em>Ref:</em></p><ul><li><a href="https://github.com/jaredhanson/passport-local"><em>https://github.com/jaredhanson/passport-local</em></a></li><li><a href="https://github.com/jaredhanson/passport"><em>https://github.com/jaredhanson/passport</em></a></li><li><a href="https://medium.com/tds-note/read-the-source-code-of-connect-flash-9e51691decda"><em>https://medium.com/tds-note/read-the-source-code-of-connect-flash-9e51691decda</em></a></li></ul><h3>About me</h3><p><em>Self-taught and trained in software development knowledge and skills, I am passionate about creating changes through technology.</em></p><p><em>Find more at </em><a href="https://github.com/tsungtingdu"><strong><em>Github</em></strong></a><em>, </em><a href="https://www.linkedin.com/in/tsung-ting-tu/"><strong><em>LinkedIn</em></strong></a><em>, </em><a href="https://lighthouse.alphacamp.co/users/3247/ta_profile"><strong><em>Teaching at ALPHA Camp</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=512e453af8a3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tds-note/how-failure-message-works-in-passport-js-512e453af8a3">How failure message works in Passport.js</a> was originally published in <a href="https://medium.com/tds-note">TD’s note</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Get pure object from Sequelize instance]]></title>
            <link>https://medium.com/tds-note/get-pure-object-from-sequelize-instance-8be199610ae5?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/8be199610ae5</guid>
            <category><![CDATA[sequelize]]></category>
            <category><![CDATA[handlebars]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Tue, 30 Jun 2020 02:29:58 GMT</pubDate>
            <atom:updated>2020-06-30T07:02:21.550Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/811/1*fygqfizATFjt6ALek2W1jg.png" /><figcaption>Source: <a href="https://hackernoon.com/drafts/h3wk32zy.png">https://hackernoon.com/drafts/h3wk32zy.png</a></figcaption></figure><p>[quick note]</p><p>Handlebars 升級到 4.6 之後，會限制傳入的資料規格，不允許帶有 prototype properties 的物件。</p><blockquote>From version 4.6.0 on, Handlebars forbids accessing prototype properties and methods of the context object by default. The reason are various security issues that arise from this possibility. The following options can be used to control this access.</blockquote><p><em>(Ref: </em><a href="https://handlebarsjs.com/api-reference/runtime-options.html"><em>https://handlebarsjs.com/api-reference/runtime-options.html</em></a><em>)</em></p><p>雖然仍然給予使用者調整限制的權限，不過官方不建議自己調整。</p><p>因此，當我們透過 Sequelize 從資料庫取出資料之後，需要讓他變成 pure object，目前已知有以下幾種做法</p><h4>在 query option 當中設定 raw: true</h4><pre>let users = await User.findAll({ raw: true, nest: true })</pre><p>這個方法適合所有的查詢方式，特別是多筆資料的查詢。</p><h4>取出 dataValue</h4><pre>let user = await User.findOne({ where: { id: req.user.id } })<br>user = user.dataValue</pre><h4>用 .get() 方法</h4><pre>let user = await User.findOne({ where: { id: req.user.id } })<br>user = user.get()</pre><p>以上兩個方法適用在只取出一筆資料的情況。另外，其實 .get() 本身其實就是回傳 sequelize instance 的 dataValue。</p><p>.get() 原本的使用方式是，可以透過傳入欄位 key 值，取得欄位的 value，譬如：</p><pre>let user = await User.findOne({ where: { id: req.user.id } })</pre><pre>user = user<strong>.get(&#39;name&#39;) </strong>   <strong>// &#39;td&#39;</strong></pre><p>不過如果沒有傳入 key 值，就會回傳整個查詢結果的 dataValue</p><p><em>(Ref: </em><a href="https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/instance.js#L179"><em>https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/instance.js#L179</em></a><em>)</em></p><h4>用 .toJSON() 方法</h4><pre>let user = await User.findOne({ where: { id: req.user.id } })<br>user = user.toJSON()</pre><p>如果查看原始碼，其實他也只是呼叫了 .get() ，只不過傳入了額外的 option</p><pre>Instance.prototype.toJSON = function() {  <br>  return <strong>this.get({    </strong><br>   <strong> plain: true  </strong><br> <strong> });</strong><br>};</pre><p>也就是說，其實使用 .toJSON() 的結果，跟單純使用 .get() 的結果應該會不一樣！</p><p>我目前觀察到的差別在，如果取出的資料有包含關聯資料，使用 .get() 只會「刪去」主資料繼承而來的屬性，但關聯資料當中繼承而來的屬性還會存在。如果使用 .toJSON() ，那麼連關聯資料當中繼承而來的屬性也會一併消失。</p><p><em>(Ref: </em><a href="https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/instance.js#L1061"><em>https://github.com/sequelize/sequelize/blob/3e5b8772ef75169685fc96024366bca9958fee63/lib/instance.js#L1061</em></a><em>)</em></p><p>(😳 等等，如果關聯資料裡面還有繼承而來的屬性，那這樣還叫做 pure object 嗎 😳)</p><h4>JSON.parse() &amp; JSON.stringify()</h4><p>最後，當然也可以用 JavaScript 原生的 JSON 方法</p><pre>let user = await User.findOne({ where: { id: req.user.id } })<br>user = JSON.parse(JSON.stringify(user))</pre><p>效果跟先前提到的 .toJSON() 一模一樣</p><p>最後記得，如果要對資料進行修改、刪除，記得不要將資料轉成 pure object，否則會無法使用 sequelize instance 當中的方法對資料庫進行正確的操作。</p><h4>About me</h4><p><em>Self-taught and trained in software development knowledge and skills, I am passionate about creating changes through technology.</em></p><p><em>Find more at </em><a href="https://github.com/tsungtingdu"><strong><em>Github</em></strong></a><em>, </em><a href="https://www.linkedin.com/in/tsung-ting-tu/"><strong><em>LinkedIn</em></strong></a><em>, </em><a href="https://lighthouse.alphacamp.co/users/3247/ta_profile"><strong><em>Teaching at ALPHA Camp</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8be199610ae5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tds-note/get-pure-object-from-sequelize-instance-8be199610ae5">Get pure object from Sequelize instance</a> was originally published in <a href="https://medium.com/tds-note">TD’s note</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is a “variable” and “declaring a variable”?]]></title>
            <link>https://medium.com/tds-note/what-is-a-variable-and-declaring-a-variable-1b099beb935b?source=rss-7f85089fd22e------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b099beb935b</guid>
            <dc:creator><![CDATA[TD]]></dc:creator>
            <pubDate>Wed, 17 Jun 2020 09:45:30 GMT</pubDate>
            <atom:updated>2020-06-30T02:49:03.990Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MkatZrPllQPa3XunjyizNg.png" /></figure><p>當助教有時候需要幫學生解 bug，但我覺得更有趣的是，用不一樣的方法來解釋原本已知的知識，過程中有時會思考到事物的本質。也因此，我希望能把自己的一些想法給紀錄下來。</p><p>上週遇到一位學生問：</p><blockquote>我一直搞不懂變數是什麼，宣告是什麼？</blockquote><p>電腦能幫我們做很多事情，但前提是，電腦需要知道</p><ul><li>要處理什麼資料</li><li>怎麼處理資料</li></ul><p>就像是老闆跟我說「誒 td，把去年的財務報表從檔案庫當中拿出來給我」，這時候我就知道</p><ul><li>要處理什麼資料：財務報表</li><li>怎麼處理資料：從檔案庫當中拿出給老闆</li></ul><p>但實際上，「財務報表」也只是個名詞而已，他對應到的，實際上是一疊 A4 大小的紙，上面有財務數據。</p><p>回頭來看看電腦怎麼運作，假設我希望電腦幫我「把我的薪水印出來」，這時候電腦需要知道</p><ul><li>要處理什麼資料：薪水</li><li>怎麼處理資料：印出來</li></ul><p>但實際上這時候，電腦程式空空如也，既不知道薪水是什麼，也不知道怎麼印出來，因此我們就會給他一些指令，像是</p><pre>console.log(<strong>salary</strong>)            // 印出 salary</pre><p>但這時候電腦還是不知道 salary 裡面究竟是什麼東西，就像是不知道所謂的「財務報表」長什麼樣子，因此，我們需要讓電腦知道，大聲的宣告「我的薪水就是兩萬元啦！」</p><pre>let <strong>salary</strong> = 20000             // <strong>宣告</strong> salary</pre><p>好了，如果把上面兩行程式碼放在一起，那麼就可以順利的讓電腦完成任務：</p><pre>let <strong>salary</strong> = 20000    <br>console.log(<strong>salary</strong>)            // 看到輸出結果為 20000</pre><p>那你可能會問，為什麼不直接印出來就好（如下）</p><pre>console.log(20000)</pre><p>其實也不是不行，但隨著年資增長，我的「薪水」也會慢慢增加，也就是說 salary 裡面的資料其實會變動。</p><p>如果在 console.log() 裡面將數字寫死，那麼就無法反應我的薪水的實際狀況。 另外，如果程式的其他地方也需要薪水的資料，那麼我就要在很多地方寫上 20000，假設哪一天我的薪水有變動，我就需要同步在所有地方更改這些數字。</p><p>說到這裡，其實「變數」就是存放需要讓電腦處理的資料，我們可以自行命名這個變數的名稱，並給這個變數資料，譬如</p><pre>let <strong>salary</strong> = 20000  <br>let <strong>mySalary</strong> = 30000  <br>let <strong>tdSalary</strong> = 40000</pre><p>建立一個新的變數的過程，就是「宣告」。</p><p>當一個變數被宣告之後，就可以在程式的其他地方被使用。像是我先前的例子，當我們宣告（詔告）大家：這疊 A4 紙的資料就是「財務報表」，之後，大家只要提到「財務報表」，就知道我們指的資料、需要處理的資料就是剛剛那疊 A4 紙。</p><h3>About me</h3><p><em>Self-taught and trained in software development knowledge and skills, I am passionate about creating changes through technology.</em></p><p><em>Find more at </em><a href="https://github.com/tsungtingdu"><strong><em>Github</em></strong></a><em>, </em><a href="https://www.linkedin.com/in/tsung-ting-tu/"><strong><em>LinkedIn</em></strong></a><em>, </em><a href="https://lighthouse.alphacamp.co/users/3247/ta_profile"><strong><em>Teaching at ALPHA Camp</em></strong></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b099beb935b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tds-note/what-is-a-variable-and-declaring-a-variable-1b099beb935b">What is a “variable” and “declaring a variable”?</a> was originally published in <a href="https://medium.com/tds-note">TD’s note</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>