<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>面試｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/%E9%9D%A2%E8%A9%A6/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>面試｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/%E9%9D%A2%E8%A9%A6/</link><width>144</width><height>144</height></image>
    <description>林鼎淵（Dean Lin）的部落格：生成式 AI 企業內訓講師、外商資安公司軟體專家、7 本書作者。400 多篇文章用白話分享生成式 AI 實戰、Claude Code 與 Vibe Coding 教學、團隊合作與職場成長。</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <copyright>© 2020–2026 林鼎淵 Dean Lin</copyright>
    <lastBuildDate>Fri, 17 May 2024 18:26:30 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/%E9%9D%A2%E8%A9%A6/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>面試資深職位時，主管最在意的是這點！ ── 下班有約系列文</title>
      <link>https://deanlin.net/blog/202405/senior-interview-what-managers-want/</link>
      <pubDate>Fri, 17 May 2024 18:26:30 +0800</pubDate>
      <guid>https://deanlin.net/blog/202405/senior-interview-what-managers-want/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>最近跟一位擔任技術主管的朋友聊天，我問他：「面試過程中，你最看重求職者哪些經驗與特質？」 他說：「如果面試 Senior 以上的職位，我更看重他過去是否有處理正式環境 Critical issue 的經驗；因為技術可以靠後天學習，但有能力處理正式環境 issue 的人並不多。」</description>
      <media:content url="https://deanlin.net/blog/202405/senior-interview-what-managers-want/images/senior-interview-what-managers-want-og_hu_f40601b0c51589df.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>最近跟一位擔任技術主管的朋友聊天，我問他：「面試過程中，你最看重求職者哪些經驗與特質？」</p>
<p>他說：「如果面試 Senior 以上的職位，我更看重他過去是否有處理正式環境 Critical issue 的經驗；因為技術可以靠後天學習，但有能力處理正式環境 issue 的人並不多。」</p>
<p>停頓了一下，又補充一句：「技術能力只能用來衡量平時的工作效率，但遇到緊急狀況時，在壓力下是否有足夠的膽量與決策能力就很看人格特質了。」</p>
<p>接著向我分享一個系統升級遇到意外的案例。</p>
<p><img src="https://deanlin.net/blog/202405/senior-interview-what-managers-want/images/senior-interview-what-managers-want-01.webp" width="1120" height="626" alt="面試資深職位時，主管最在意的是這點！ ── 下班有約系列文（圖 1）" fetchpriority="high" decoding="async"></p>
<h2 id="為什麼正式環境發生意外時很難處理">為什麼正式環境發生意外時很難處理？<a class="h-anchor" href="#%e7%82%ba%e4%bb%80%e9%ba%bc%e6%ad%a3%e5%bc%8f%e7%92%b0%e5%a2%83%e7%99%bc%e7%94%9f%e6%84%8f%e5%a4%96%e6%99%82%e5%be%88%e9%9b%a3%e8%99%95%e7%90%86" aria-label="這個段落的連結"></a></h2>
<p>如果公司有一定的制度與規模，系統在升級前往往有完善的單元測試，並通過 QA 團隊在不同情境的模擬測試。</p>
<p>因為事前已經考慮得相當周全，所以當問題發生時，就代表大家根本沒想過這個情境。</p>
<p>加上系統升級會對外公告維護時間，如果沒在期限內解決問題就出包了。</p>
<p>這邊我簡單描述一下問題是怎麼發生的：</p>
<p><strong>A 系統升級時，會需要跑一個一次性的腳本向 B 系統取得資料；但 B 系統的資料不完整，所以 B 系統還要向 C 系統取得部分細節資料。</strong></p>
<p>在測試環境跑腳本時，一切順利，但上到正式環境後，跳出了「timeout（超時）」的錯誤訊息。</p>
<hr>
<h2 id="如何定位問題">如何定位問題？<a class="h-anchor" href="#%e5%a6%82%e4%bd%95%e5%ae%9a%e4%bd%8d%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<p>出現「timeout（超時）」的問題後，大家並沒有很緊張，覺得只要放寬 A 系統呼叫 API 的時間就好。</p>
<p>但時間放寬後，timeout 的問題依舊存在，所以接著把目光轉移到 B 系統與 C 系統上，並將程式與 Nginx 的 timeout 時間拉長。</p>
<p>原本想說這樣問題總能解決了吧？</p>
<p>但結果不如人意，timeout 的錯誤依舊頑強地顯示。</p>
<p><strong>為了更精準定位問題，大家把 A 系統、B 系統、C 系統的 API 分開測試，最終發現根本的問題在 C 系統。</strong></p>
<hr>
<h2 id="不要讓自己陷入只有一個解決方案的思維">不要讓自己陷入只有一個解決方案的思維<a class="h-anchor" href="#%e4%b8%8d%e8%a6%81%e8%ae%93%e8%87%aa%e5%b7%b1%e9%99%b7%e5%85%a5%e5%8f%aa%e6%9c%89%e4%b8%80%e5%80%8b%e8%a7%a3%e6%b1%ba%e6%96%b9%e6%a1%88%e7%9a%84%e6%80%9d%e7%b6%ad" aria-label="這個段落的連結"></a></h2>
<p>儘管成功定位了問題，但不知道為什麼 C 系統怎麼放寬 timeout 的設定都沒有生效。</p>
<p>而此時系統維護的時間剩不到半小時，想透過優化演算法來加快效率已經來不及了。</p>
<p>經過討論後，大家決定不解決 timeout 的問題，而是想辦法不要讓 API timeout。</p>
<p><strong>最終的方案也非常簡單，只是將 B 系統取得的資料多拆成多份，以 C 系統絕對不會 timeout 的資料量傳送就解決了問題。</strong></p>
<hr>
<h2 id="這個案例有什麼值得學習與警惕的地方">這個案例有什麼值得學習與警惕的地方<a class="h-anchor" href="#%e9%80%99%e5%80%8b%e6%a1%88%e4%be%8b%e6%9c%89%e4%bb%80%e9%ba%bc%e5%80%bc%e5%be%97%e5%ad%b8%e7%bf%92%e8%88%87%e8%ad%a6%e6%83%95%e7%9a%84%e5%9c%b0%e6%96%b9" aria-label="這個段落的連結"></a></h2>
<p>從結論來看，加 3 行程式碼就解決了問題。</p>
<p>但要做出這個決策並不容易，因為 A、B、C 系統是由不同的團隊進行開發，如果沒有良好的溝通能力，可能連一開始的問題定位都做不到。</p>
<p>找到問題點後，如果沒有人同時對 A、B、C 系統有一定的掌握度，我想大部分的人會把目標放在解決 timeout 問題，而非調整程式邏輯。</p>
<p><strong>以這個案例來說，解決問題的人不一定要程式能力很強，但要具備良好的溝通能力，並對多個系統有一定的掌握度，且擁有靈活的思維，不會只想用一種方法解決問題。</strong></p>
<p>我想，擁有如上特質的人，是市場上真正的稀缺人才。</p>
<p><strong>最後提醒一下大家，雖然解決問題很帥，但以這個案例來說，只要在測試時考量到正式環境與測試環境的資料量差異，並加上對應的限制與分頁設計，就有機會避免問題發生。</strong></p>
<a class="linkcard" href="https://deanlin.net/series/after-office/">
  <span class="lc-body">
    <span class="lc-title">工程師下班有約</span>
    <span class="lc-desc">同名著作與 YouTube 節目的延伸文章。一個技職生花了 15 年，成為外商工程師、作家與講師，這裡記錄一路上的選擇、失敗與心法。</span>
    <span class="lc-site"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M5 12h14M13 6l6 6-6 6"/></svg>林鼎淵 Dean Lin</span>
  </span>
</a>

<p><strong>如果你對目前的職涯感到迷茫，想從不同視角檢視自己的選擇，相信《<a href="https://www.tenlong.com.tw/products/9786264142083">工程師下班有約</a>》這本書能為你帶來靈感與啟發：</strong></p>
<figure class="figure"><img src="https://deanlin.net/blog/202405/senior-interview-what-managers-want/images/senior-interview-what-managers-want-02.webp" width="1400" height="319" alt="林鼎淵著作《工程師下班有約：企業內訓講師帶你認清職涯真相！》" loading="lazy" decoding="async">
  <figcaption><a href="https://www.tenlong.com.tw/products/9786264142083">https://www.tenlong.com.tw/products/9786264142083</a></figcaption>
</figure>

]]></content:encoded>
    </item>
    <item>
      <title>你成功引起我的注意了！</title>
      <link>https://deanlin.net/blog/202404/resume-that-gets-noticed/</link>
      <pubDate>Thu, 25 Apr 2024 22:12:20 +0800</pubDate>
      <guid>https://deanlin.net/blog/202404/resume-that-gets-noticed/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>給我 30 分鐘，帶你打造吸引面試官的亮眼履歷</description>
      <media:content url="https://deanlin.net/blog/202404/resume-that-gets-noticed/images/resume-that-gets-noticed-og_hu_72de270362cc4121.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>在人才市場上，「人」就是商品，包裝精美的容易溢價，賣相較差的容易折價。</p>
<p>所有想要的一切都要靠自己主動爭取，在職場懂得「包裝」才有更多機會！</p>
<p><img src="https://deanlin.net/blog/202404/resume-that-gets-noticed/images/resume-that-gets-noticed-01.webp" width="1400" height="788" alt="你成功引起我的注意了！（圖 1）" fetchpriority="high" decoding="async"></p>
<p>上個月受 iThome 邀約，到鐵人講堂分享職涯經驗。</p>
<p>在準備這次分享的過程中，我在想，如果時光能倒流，我會給過去的自己哪些職涯建議。</p>
<p>跟許多工程師朋友討論後，我最後選擇了「履歷」這個主題，因為每個人一定都會用到。</p>
<p>這堂講座並不會要大家馬上做出重大改變，而是給予一些建議，讓大家可以在熟悉的環境中多做一點事來認識、調整自己。</p>
<h2 id="課程大綱">課程大綱<a class="h-anchor" href="#%e8%aa%b2%e7%a8%8b%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<h3 id="一準備履歷不只是為了面試">一、準備履歷不只是為了面試！<a class="h-anchor" href="#%e4%b8%80%e6%ba%96%e5%82%99%e5%b1%a5%e6%ad%b7%e4%b8%8d%e5%8f%aa%e6%98%af%e7%82%ba%e4%ba%86%e9%9d%a2%e8%a9%a6" aria-label="這個段落的連結"></a></h3>
<ul>
<li>了解履歷對職涯的重要性</li>
<li>求職履歷該有哪些元素？</li>
<li>撰寫讓別人想認識你的「重點」</li>
<li>擴充自己的履歷</li>
</ul>
<h3 id="二你要對自己的履歷瞭若指掌">二、你要對自己的履歷瞭若指掌<a class="h-anchor" href="#%e4%ba%8c%e4%bd%a0%e8%a6%81%e5%b0%8d%e8%87%aa%e5%b7%b1%e7%9a%84%e5%b1%a5%e6%ad%b7%e7%9e%ad%e8%8b%a5%e6%8c%87%e6%8e%8c" aria-label="這個段落的連結"></a></h3>
<ul>
<li>工程師履歷範本</li>
<li>寫好履歷後要做的功課</li>
<li>面試官從履歷中看到了什麼</li>
</ul>
<h3 id="三用簡報讓面試官集中注意力">三、用簡報讓面試官集中注意力<a class="h-anchor" href="#%e4%b8%89%e7%94%a8%e7%b0%a1%e5%a0%b1%e8%ae%93%e9%9d%a2%e8%a9%a6%e5%ae%98%e9%9b%86%e4%b8%ad%e6%b3%a8%e6%84%8f%e5%8a%9b" aria-label="這個段落的連結"></a></h3>
<ul>
<li>準備面試簡報有哪些好處？</li>
<li>簡報要有哪些內容</li>
<li>提升簡報穩定度的 4 個方法</li>
</ul>
<p>如果想優化自己的履歷、增加面試時的穩定度，點擊下方連結，現在只要註冊 iThome 會員就可以「免費」觀看嚕：</p>
<p><a href="https://itplus.ithome.com.tw/webinar-page/201">https://itplus.ithome.com.tw/webinar-page/201</a></p>
<figure class="figure"><img src="https://deanlin.net/blog/202404/resume-that-gets-noticed/images/resume-that-gets-noticed-02.webp" width="1364" height="816" alt="你成功引起我的注意了！（圖 1）" loading="lazy" decoding="async">
  <figcaption><a href="https://itplus.ithome.com.tw/webinar-page/201">https://itplus.ithome.com.tw/webinar-page/201</a></figcaption>
</figure>

]]></content:encoded>
    </item>
    <item>
      <title>本科系 VS 補習班：對工程師職涯有那麼大的影響嗎？</title>
      <link>https://deanlin.net/blog/202305/cs-degree-vs-bootcamp/</link>
      <pubDate>Wed, 03 May 2023 15:00:00 +0800</pubDate>
      <guid>https://deanlin.net/blog/202305/cs-degree-vs-bootcamp/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>大學要讀 4 年，而補習班大約 3~6 個月；普遍來說，我們會覺得大學本科畢業的能力會比較強，但這幾年看下來還真的不一定，這麼為什麼呢？</description>
      <media:content url="https://deanlin.net/blog/202305/cs-degree-vs-bootcamp/images/cover_hu_211b2ee306bb48f0.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>大學要讀 4 年，而補習班大約 3~6 個月；普遍來說，我們會覺得大學本科畢業的能力會比較強，但這幾年看下來還真的不一定，這麼為什麼呢？</p>
<h2 id="心態上的差異">心態上的差異<a class="h-anchor" href="#%e5%bf%83%e6%85%8b%e4%b8%8a%e7%9a%84%e5%b7%ae%e7%95%b0" aria-label="這個段落的連結"></a></h2>
<p>我們通常是 18 歲上大學，此時的我們還沒有遭受過社會的毒打，並不知道畢業後將要面臨什麼樣的挑戰。</p>
<p>所以大部分的人都是用輕鬆愉快的心情去讀大學，儘管表面上讀了 4 年，但實際上有多少時間在讀書，就真的只有自己才知道了。</p>
<p>而選擇靠補習班轉職的人，大多有一定的社會歷練，他們是認知到目前的職涯並不是自己想要的，所以下定決心轉換跑道。</p>
<p>跟大學生比起來，他們通常是抱持著「改變命運」的想法去上課，因此這兩者的學習積極度是完全不同的。</p>
<p>有些大學生會翹課，或是在上課滑手機；但報名補習班的人通常都會準時出席，上課認真做筆記，下課找老師問問題，假日繼續研究。</p>
<blockquote>
<p>補習班報名費是自己的血汗錢，沒理由打混摸魚浪費掉。</p>
</blockquote>
<h2 id="面試的表現">面試的表現<a class="h-anchor" href="#%e9%9d%a2%e8%a9%a6%e7%9a%84%e8%a1%a8%e7%8f%be" aria-label="這個段落的連結"></a></h2>
<p>許多本科系畢業的學生並沒有認真準備面試，很多都是憑本能在回答問題的；而補習班出來的人，通常都有經歷過模擬面試，知道不同問題該如何應對。</p>
<p>師父領進門，修行在個人；畢業證書只不過是一張入場卷，讓你有機會被更多人看見。</p>
<p>在人才市場上，「人」就是商品，包裝精美的容易溢價，賣相較差的容易折價；不要期待公司主動理解你的內涵，在職場上要懂得「包裝」才有更多機會！</p>
<h2 id="並不是每個人都適合當工程師">並不是每個人都適合當工程師<a class="h-anchor" href="#%e4%b8%a6%e4%b8%8d%e6%98%af%e6%af%8f%e5%80%8b%e4%ba%ba%e9%83%bd%e9%81%a9%e5%90%88%e7%95%b6%e5%b7%a5%e7%a8%8b%e5%b8%ab" aria-label="這個段落的連結"></a></h2>
<p>筆者是資工系畢業，但身旁有些同學儘管畢業了，但還是不太會寫程式。</p>
<p>也許會有人好奇這樣是要怎麼畢業？不是都有「上機考」測驗嗎？</p>
<p>恩…他們為了畢業，硬生生把幾百行、上千行的程式碼給背下來了。</p>
<p>對！你沒看錯！是「背下來」而不是靠自己對程式的理解寫出來的；其實這也側面說明，就算是本科系畢業，他的天賦也未必適合當一名工程師。</p>
<p>很多人大學選擇科系是憑「直覺」，或是看哪個科系未來比較「賺錢」；但如果發現自己不適合又堅持不轉系，就會白白浪費 4 年光陰。</p>
<p>相比下來，如果你對一個領域一無所知，補習班 3~6 個月的長度是更為合適的，他能讓你先體驗看看自己到底適不適合這個領域</p>
<h2 id="未來發展">未來發展<a class="h-anchor" href="#%e6%9c%aa%e4%be%86%e7%99%bc%e5%b1%95" aria-label="這個段落的連結"></a></h2>
<p>人生很長，在長達 30~50 年的職涯面前，是不是本科系就沒有那麼重要了。</p>
<p>也許在職涯初期，本科系出身的人表現更好、知識更全面；但隨著時間推移，如果沒有持續精進自己，那這些領先的優勢將會慢慢消失。</p>
<p>學歷在前兩份工作有參考的作用，但越往後走，越看重你在實務方面的表現，以及與團隊溝通協調的能力。</p>
<blockquote>
<p>如果一個人都 40、50 歲了，還只能拿自己的學歷來炫耀，那代表他出社會後混得很差。</p>
</blockquote>
<hr>
]]></content:encoded>
    </item>
    <item>
      <title>遠端面試要注意的 7 大重點，3 點錯誤記得要避開！</title>
      <link>https://deanlin.net/blog/202304/remote-interview-tips/</link>
      <pubDate>Mon, 24 Apr 2023 15:00:00 +0800</pubDate>
      <guid>https://deanlin.net/blog/202304/remote-interview-tips/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>如果面試的是工程師職位，現在有許多公司的前幾道關卡都是採「遠端面試」；筆者在這篇文章會綜合面試官與求職者的角度，向大家分享新手常犯的錯誤與面試加分的小技巧。</description>
      <media:content url="https://deanlin.net/blog/202304/remote-interview-tips/images/cover_hu_937642195addfc9c.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>如果面試的是工程師職位，現在有許多公司的前幾道關卡都是採「遠端面試」；筆者在這篇文章會綜合面試官與求職者的角度，向大家分享新手常犯的錯誤與面試加分的小技巧。</p>
<h2 class="h-nomark" id="-錯誤-1沒有下載桌面版軟體不熟悉操作">😈 錯誤 1：沒有下載桌面版軟體、不熟悉操作<a class="h-anchor" href="#-%e9%8c%af%e8%aa%a4-1%e6%b2%92%e6%9c%89%e4%b8%8b%e8%bc%89%e6%a1%8c%e9%9d%a2%e7%89%88%e8%bb%9f%e9%ab%94%e4%b8%8d%e7%86%9f%e6%82%89%e6%93%8d%e4%bd%9c" aria-label="這個段落的連結"></a></h2>
<p>遠端面試常用的軟體有 Zoom、Google Meet、Teams，其中 Zoom 跟 Teams 儘管可以使用「網頁版」，但有時會遇到一些特殊的狀況（ex：螢幕分享一片黑），因此建議下載桌面版會比較穩定。</p>
<p>另外記得在面試前要先熟悉軟體的基礎操作（ex：螢幕分享、開啟攝像頭），並設定好相關權限（ex：允許螢幕分享、允許使用喇叭＆攝像頭），否則在面試過程你可能會需要多次重開軟體，給面試官留下「不專業」的印象。</p>
<h2 class="h-nomark" id="-錯誤-2直接穿家居服面試ex睡衣">😈 錯誤 2：直接穿家居服面試（ex：睡衣）<a class="h-anchor" href="#-%e9%8c%af%e8%aa%a4-2%e7%9b%b4%e6%8e%a5%e7%a9%bf%e5%ae%b6%e5%b1%85%e6%9c%8d%e9%9d%a2%e8%a9%a6ex%e7%9d%a1%e8%a1%a3" aria-label="這個段落的連結"></a></h2>
<p>筆者真的遇過穿吊嘎的面試官與穿睡衣的求職者，我尊重每個人的穿衣風格，但印象分數照扣。</p>
<p>除非你真的強到逆天，對方也非你不可，否則印象分數還是相當重要的。</p>
<p>不要因為遠端面試就隨便穿，建議比照現場面試的服裝儀容；當你穿著正裝時，也會給自己一個這是重要會議的心理暗示。</p>
<h2 class="h-nomark" id="-錯誤-3忘記關閉會干擾面試的軟體網頁">😈 錯誤 3：忘記關閉會干擾面試的軟體、網頁<a class="h-anchor" href="#-%e9%8c%af%e8%aa%a4-3%e5%bf%98%e8%a8%98%e9%97%9c%e9%96%89%e6%9c%83%e5%b9%b2%e6%93%be%e9%9d%a2%e8%a9%a6%e7%9a%84%e8%bb%9f%e9%ab%94%e7%b6%b2%e9%a0%81" aria-label="這個段落的連結"></a></h2>
<p>如果在面試過程中跳出奇怪的訊息是非常尷尬的，而且訊息不斷跳出的提示音也很吵，千萬不要在面試時犯下如此低級的錯誤。</p>
<p>下面筆者列出常見的通訊軟體、網頁：</p>
<ul>
<li><strong>通訊軟體</strong>：LINE、Messenger、WeChat</li>
<li><strong>社群網頁</strong>：Facebook、LinkedIn、Twitter</li>
<li><strong>電子郵件</strong>（這個超容易忘記）</li>
</ul>
<h2 class="h-nomark" id="-小技巧-1在開始時確認雙方的聲音畫面都是清楚的">💪 小技巧 1：在開始時確認雙方的聲音、畫面都是清楚的<a class="h-anchor" href="#-%e5%b0%8f%e6%8a%80%e5%b7%a7-1%e5%9c%a8%e9%96%8b%e5%a7%8b%e6%99%82%e7%a2%ba%e8%aa%8d%e9%9b%99%e6%96%b9%e7%9a%84%e8%81%b2%e9%9f%b3%e7%95%ab%e9%9d%a2%e9%83%bd%e6%98%af%e6%b8%85%e6%a5%9a%e7%9a%84" aria-label="這個段落的連結"></a></h2>
<p>如果面試官的聲音模糊或太小聲，一定要即時反應不要怕；如果你聽不清楚題目，怎麼可能有辦法回答得好？</p>
<p>如果你有分享螢幕，一定要先確認對方有看到再繼續說；避免自己分享錯螢幕，或是對方沒看到的窘境。</p>
<h2 class="h-nomark" id="-小技巧-2盡量找有乾淨背景合適光源的場地">💪 小技巧 2：盡量找有乾淨背景、合適光源的場地<a class="h-anchor" href="#-%e5%b0%8f%e6%8a%80%e5%b7%a7-2%e7%9b%a1%e9%87%8f%e6%89%be%e6%9c%89%e4%b9%be%e6%b7%a8%e8%83%8c%e6%99%af%e5%90%88%e9%81%a9%e5%85%89%e6%ba%90%e7%9a%84%e5%a0%b4%e5%9c%b0" aria-label="這個段落的連結"></a></h2>
<p>如果在家進行遠端面試，請盡量找乾淨的背景，或使用軟體內建的虛擬背景（也可以上傳自己喜歡的背景）。</p>
<p>同時注意光源，背光會導致你的臉黑掉。</p>
<blockquote>
<p><strong>小提醒</strong>：在網路環境不佳的環境下使用糊化背景，你的人也會被糊化。</p>
</blockquote>
<h2 class="h-nomark" id="-小技巧-3盡量找安靜的環境">💪 小技巧 3：盡量找安靜的環境<a class="h-anchor" href="#-%e5%b0%8f%e6%8a%80%e5%b7%a7-3%e7%9b%a1%e9%87%8f%e6%89%be%e5%ae%89%e9%9d%9c%e7%9a%84%e7%92%b0%e5%a2%83" aria-label="這個段落的連結"></a></h2>
<p>如果周圍環境音太多會嚴重影響面試品質；如果在家面試，要提醒家人不要在面試時打擾自己。</p>
<p>同時建議不要在咖啡廳面試，因為大多數的咖啡廳都會播放背景音樂，附近的人如果講話太大聲也會影響收聽。</p>
<h2 class="h-nomark" id="-小技巧-4最好有雙螢幕">💪 小技巧 4：最好有雙螢幕<a class="h-anchor" href="#-%e5%b0%8f%e6%8a%80%e5%b7%a7-4%e6%9c%80%e5%a5%bd%e6%9c%89%e9%9b%99%e8%9e%a2%e5%b9%95" aria-label="這個段落的連結"></a></h2>
<p>這樣你在分享螢幕時較不容易錯亂，同時也方便即時做筆記、看小抄。</p>
<p>如果只有一個螢幕，那在簡報時就只能自己把講稿背下來了；但如果有雙螢幕，你就可以透過備忘錄提醒自己該講什麼。</p>
<h2 id="結語">結語<a class="h-anchor" href="#%e7%b5%90%e8%aa%9e" aria-label="這個段落的連結"></a></h2>
<p>面試過程發生的意外，對面試官來說是一種必然，因為他們只會聽一次而已。</p>
<p>建議每次面試前，都參考這篇文章檢核一次，不要讓這些可控管的風險，毀掉自己努力的成果！</p>
<hr>
]]></content:encoded>
    </item>
    <item>
      <title>你合作的獵頭有專業、夠了解你嗎？</title>
      <link>https://deanlin.net/blog/202304/is-your-headhunter-professional/</link>
      <pubDate>Wed, 19 Apr 2023 15:00:00 +0800</pubDate>
      <guid>https://deanlin.net/blog/202304/is-your-headhunter-professional/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>在工作一段時間後，如果將自己的履歷放到 LinkedIn、CakeResume 等平台，那你可能三不五時會收到獵頭（Headhunter）的信件與私訊。</description>
      <media:content url="https://deanlin.net/blog/202304/is-your-headhunter-professional/images/cover_hu_ac98fa846cc90c7e.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>在工作一段時間後，如果將自己的履歷放到 LinkedIn、CakeResume 等平台，那你可能三不五時會收到獵頭（Headhunter）的信件與私訊。</p>
<p>過去很多人對獵頭的第一印象是「專業」，認為透過他們就能找到一份高薪的工作。</p>
<p>但筆者與多位獵頭溝通合作後，發現並不是每個獵頭都具備「專業」，有些獵頭更像是用碰運氣的方式大量私訊工程師；但這種亂槍打鳥的結果，就是在與工程師溝通時，連他擅長的領域都搞不清楚…</p>
<p>會發生這種亂象，筆者認為是有些獵頭並沒什麼工作經驗，甚至少部分獵頭的第一份工作就是獵頭（聽起來有點荒謬對吧？）</p>
<blockquote>
<p><strong>給獵頭的良心建議</strong>：</p>
<p>每個人都有自己的專業，如果你今天要幫工程師媒合，至少要對他們的專業有基礎認知；像是媒合網頁工程師，你至少要理解前端、後端的差異，並且對常見的框架有基礎認知（起碼名字跟對應的程式語言要知道）。</p>
</blockquote>
<p><strong>筆者遇過最扯的獵頭</strong>，是傳送錯誤的面試時間…直接讓那場面試報銷（當天還有請特休…）。</p>
<p>不知道讀者看到這裡會不會覺得有點荒誕？但現在獵頭的專業水平真的落差極大，筆者也是有遇過幾個優秀的獵頭，現在這份工作也是在獵頭的幫助下才找到的。</p>
<p>以筆者與周圍朋友的經驗來說，與獵頭培養好的關係是有必要的，但絕對要「謹慎」選擇合作的對象，我會用以下幾點評估：</p>
<ol>
<li><strong>他了解你的背景與專業嗎？</strong><br>我不想吐槽，但真的有些獵頭連你的履歷都沒看過，就直接打電話聯繫…這簡直在敗壞獵頭名聲；如果不幸遇到這種的，筆者建議直接列入黑名單。</li>
<li><strong>他了解你現在從事的產業嗎？</strong><br>同樣是網頁工程師，在硬體廠、金融業、新創公司、政府單位…所擁有的專業技能其實有一定程度的差異，如果推薦不適合的工作機會，可能會讓求職者無法發揮所長（當然有些人是考量到職涯發展，特別選擇自己沒接觸過的產業）。</li>
<li><strong>獵頭過去的工作經驗，在這一行待多久了？</strong><br>誰都有是菜鳥的時候，但筆者相信絕大多數人都不太願意把攸關自己人生走向的任務交給經驗不豐富的人。</li>
<li><strong>過去主要推薦哪些產業的人才？</strong><br>人無全才，每個獵頭都有自己擅長的產業，你也可以先了解自己感興趣的產業對方是否熟悉，再思考後續的合作可行性。</li>
</ol>
<p>專業的獵頭能快速找出符合你需求的公司，就算他擅長的產業與你的需求不符，也會介紹其他同事幫你找，這就是我說與獵頭建立合作關係的好處。</p>
<p>一般來說我們不會沒事到處搜集公司資訊，但獵頭每天的工作就是搜集這些資料，把槓桿用在對的地方就能獲得更大的效益。</p>
<blockquote>
<p><strong>個人經驗</strong>：</p>
<p>獵頭相較於人資，更能帶給求職者公司內部的實際情況；畢竟大部分的人資不會透露自己公司的問題，但獵頭因為手上公司多，相對來說可以更客觀的分享這些資訊。</p>
</blockquote>
<hr>
]]></content:encoded>
    </item>
    <item>
      <title>為什麼你應該要「定期」面試？</title>
      <link>https://deanlin.net/blog/202304/why-interview-regularly/</link>
      <pubDate>Mon, 17 Apr 2023 15:00:00 +0800</pubDate>
      <guid>https://deanlin.net/blog/202304/why-interview-regularly/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>筆者知道準備面試很煩很累，在有工作的狀態下面試還要請假很違反人性；但我認為這麼做是值得的，在舒適圈待久了，我們容易忘記外面世界的空氣，相信每場面試都有你能學到的東西。</description>
      <media:content url="https://deanlin.net/blog/202304/why-interview-regularly/images/cover_hu_374f17cb0ea37b07.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>良心建議：「不要等到要轉職的時候才開始面試。」</p>
</blockquote>
<p>筆者知道準備面試很煩很累，在有工作的狀態下面試還要請假很違反人性；但我認為這麼做是值得的，在舒適圈待久了，我們容易忘記外面世界的空氣，相信每場面試都有你能學到的東西。</p>
<h2 id="看見自己的盲點">看見自己的盲點<a class="h-anchor" href="#%e7%9c%8b%e8%a6%8b%e8%87%aa%e5%b7%b1%e7%9a%84%e7%9b%b2%e9%bb%9e" aria-label="這個段落的連結"></a></h2>
<p>習慣現在的工作模式後，我們通常會選擇自己最有把握的方式來解決問題。</p>
<p>久而久之，我們成長的只有年資，擁有的只有這間公司所需要的技能、以及公司裡面的人脈；如果沒有足夠的外力刺激，我們是看不到自己問題的。</p>
<p>而面試是一個非常棒的機會，因為對方不認識你，所以沒有預設立場；為了確保你是合適的人選，他們問的問題可不會那麼客氣。</p>
<p>在這一來一往的過程中，你更容易看見自己的盲點。</p>
<h2 id="提升自己的表達能力">提升自己的表達能力<a class="h-anchor" href="#%e6%8f%90%e5%8d%87%e8%87%aa%e5%b7%b1%e7%9a%84%e8%a1%a8%e9%81%94%e8%83%bd%e5%8a%9b" aria-label="這個段落的連結"></a></h2>
<p>有些技術儘管我們每天使用，但並不代表我們了解它，有辦法用對方能夠理解的語言表達它。</p>
<p>下面列幾個常見的問題，螢幕面前的你，有辦法清楚明瞭的回答嗎？</p>
<ul>
<li>可以說明一下最近專案的系統架構嗎？</li>
<li>在資料庫設計上，你會注重哪些細節？</li>
<li>過去工作上有遇過哪些挫折，你是如何面對與解決的？</li>
</ul>
<p>也許你知道每個題目的解答，但如果要用嘴巴說出來卻很容易卡住。</p>
<p>因為表達能力是需要訓練的，否則你陳述的內容會斷斷續續缺乏邏輯體系。</p>
<h2 id="保持危機感">保持危機感<a class="h-anchor" href="#%e4%bf%9d%e6%8c%81%e5%8d%b1%e6%a9%9f%e6%84%9f" aria-label="這個段落的連結"></a></h2>
<p>了解自己在其他公司眼中的「市場價值」。</p>
<p>很多人認為自己值得更好的薪水，但在離職後才發現是自己「高估」了。</p>
<p>現在市場的輪動速度相當快，技術也是日新月異；也許你現在能領到高薪，是因為「過去」的市場真的需要這門技術，但這並不代表「現在」的市場依舊需要。</p>
<blockquote>
<p>當然公司能給多少薪水也會跟「大環境」有關，如果在景氣不好的時候離職，即使有能力也未必能找到更好的工作。</p>
</blockquote>
<h2 id="增加自己的談判籌碼">增加自己的談判籌碼<a class="h-anchor" href="#%e5%a2%9e%e5%8a%a0%e8%87%aa%e5%b7%b1%e7%9a%84%e8%ab%87%e5%88%a4%e7%b1%8c%e7%a2%bc" aria-label="這個段落的連結"></a></h2>
<p>有些公司在明知你有卓越貢獻的狀態下也不願意加薪，如果對這間公司還有留戀；我的建議絕對不是什麼加強向上管理能力，或繼續努力總有一天會被發現的廢話，因為這些做法是否有效完全取決於上級的良心。</p>
<p>與其長期投資一個不確定的未來，又將最終決定權交到對方手上，還不如自己主動獲得談判權；如果你成功在其他公司拿到不錯的 Offer，不僅可以做為跟公司談判的籌碼，也保留了談判破局的後路。</p>
<p>不過在談判時，筆者建議用比較委婉的方式跟上級溝通，比如：「我不是很在意錢，只是最近剛好遇到了 XXX 的機會，他開了 YYY 的薪水讓我了解到自己的市場價值；但比起那些，我更想要留在這間公司跟大家繼續打拼，想了解有沒有調整的空間。」</p>
<blockquote>
<p><strong>這是殺手鐧，一間公司用一次就是極限了</strong>，即使是暗示也沒有人喜歡被威脅；部分公司會因為這些話給你貼上「不忠」的標籤，絕對要經過謹慎分析與思慮後再使用這招。</p>
</blockquote>
<h2 id="結語">結語<a class="h-anchor" href="#%e7%b5%90%e8%aa%9e" aria-label="這個段落的連結"></a></h2>
<p>有些人認為「面試」是一種對公司不忠的表現，但我認為面試是一種「提醒」自己與市場差距的行為。</p>
<p>如果等到「受夠了」才醒悟，那很多時候一切都已經晚了…</p>
<hr>
]]></content:encoded>
    </item>
    <item>
      <title>用 ChatGPT 快速產出面試加分的武器</title>
      <link>https://deanlin.net/blog/202303/chatgpt-interview-prep/</link>
      <pubDate>Sun, 19 Mar 2023 20:58:29 +0800</pubDate>
      <guid>https://deanlin.net/blog/202303/chatgpt-interview-prep/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>這篇文章將向您展示如何利用 ChatGPT 快速產出一系列讓您在面試中脫穎而出的有力武器。從自我介紹到推薦信，您將學會如何將自己的面試水平提升到新的高度。</description>
      <media:content url="https://deanlin.net/blog/202303/chatgpt-interview-prep/images/chatgpt-interview-prep-og_hu_1457bc548c808f38.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>前兩篇文章教你如何用 ChatGPT 產出亮眼的<a href="https://deanlin.net/blog/202303/chatgpt-resume-writing/">中文</a>、<a href="https://deanlin.net/blog/202303/chatgpt-english-resume/">英文</a>履歷，以期獲得更多面試機會。</p>
<p>而這篇文章將向您展示如何利用 ChatGPT 快速產出一系列讓您在面試中脫穎而出的有力武器。從自我介紹到推薦信，您將學會如何將自己的面試水平提升到新的高度。</p>
<blockquote>
<p><strong>警告</strong>：<br>ChatGPT 在面試過程只是擔任「輔助」的角色，讀者還是要自行把關產出的「品質」，並將這些資訊內化到大腦，這樣才能在面試現場表現出自己最好的一面。</p>
</blockquote>
<figure class="figure"><img src="https://deanlin.net/blog/202303/chatgpt-interview-prep/images/chatgpt-interview-prep-01.webp" width="1135" height="640" alt="Image made by Midjourney" fetchpriority="high" decoding="async">
  <figcaption>Image made by Midjourney</figcaption>
</figure>

<pre tabindex="0"><code>大綱

▋ 用履歷產生不同長度的自我介紹
▋ 針對產業來產生自我介紹
▋ 將經歷用 STAR 的原則呈現
▋ Bonus：產生推薦信草稿，讓教授、長官、同事幫你一把
▋ 結語：如果沒花時間練習，那武器再好也沒用
</code></pre><h2 id="-用履歷產生不同長度的自我介紹">用履歷產生不同長度的自我介紹<a class="h-anchor" href="#-%e7%94%a8%e5%b1%a5%e6%ad%b7%e7%94%a2%e7%94%9f%e4%b8%8d%e5%90%8c%e9%95%b7%e5%ba%a6%e7%9a%84%e8%87%aa%e6%88%91%e4%bb%8b%e7%b4%b9" aria-label="這個段落的連結"></a></h2>
<p>不同公司的面試形式不同，有些公司可以讓你用簡報自我介紹，你就算講個 10 分鐘都沒問題。</p>
<p>但有些公司只給你 3 分鐘，甚至是 1 分鐘的時間。</p>
<p>此時，事先準備好不同長度的自我介紹就非常重要了，你可以嘗試這麼做。</p>
<p><strong>😀 我</strong>：「請依照下方提供的履歷，幫我用充滿自信的語氣、專業的詞彙，寫出一份讓現場面試官滿意的自我介紹，講重點就好。</p>
<ul>
<li>在 Node.js 中擁有豐富的開發經驗，能夠開發出穩定且可靠的後端服務。例如，我曾經利用 Node.js 開發一個點餐系統，其處理速度比起傳統的點餐系統快了 30%。</li>
<li>能夠設計和實現 RESTful API，我曾經設計和開發了一個測驗系統的 RESTful API，並且能夠讓 1000 個同時的用戶快速地進行測驗。</li>
<li>熟悉 MySQL、PostgreSQL 等關聯式資料庫，我曾經優化一個 RMA 系統的資料庫設計，使其在大量的數據處理下能夠維持穩定性。</li>
<li>在點餐系統、測驗系統、RMA 系統等專案中擔任領導者的角色，我負責協調團隊成員，制定開發計劃，並能夠有效地解決問題。</li>
<li>具備將應用程序部署到 GCP 的經驗，我曾經部署一個測試系統到 GCP 上，能夠最大化地利用雲端資源進行開發。」</li>
</ul>
<p>🤖 <strong>ChatGPT</strong>：「非常感謝您給我這次面試的機會，我很高興能夠在這裡介紹一下自己。我是一名後端工程師，對於 Node.js 開發有著豐富的經驗，能夠開發出穩定且可靠的後端服務。例如，我曾經利用 Node.js 開發一個點餐系統，其處理速度比起傳統的點餐系統快了 30%。這是因為我能夠充分發揮 Node.js 的優勢，進行優化和改進，提高了系統的效率和性能。</p>
<p>我也非常擅長設計和實現 RESTful API，我曾經設計和開發了一個測驗系統的 RESTful API，並且能夠讓 1000 個同時的用戶快速地進行測驗。我認為一個好的 API 設計應該是簡潔、清晰、易於理解和使用的，這樣才能滿足用戶的需求。我熟悉 MySQL、PostgreSQL 等關聯式資料庫，並且在一個 RMA 系統的開發中進行了資料庫設計的優化，以便在大量的數據處理下能夠維持穩定性。這些經驗都讓我能夠在開發過程中更好地運用數據庫技術，並且進行數據庫性能優化，以提高系統的效率和穩定性。</p>
<p>在點餐系統、測驗系統、RMA 系統等專案中，我都擔任領導者的角色，我負責協調團隊成員，制定開發計劃，並能夠有效地解決問題。我的領導風格是鼓勵和支持，鼓勵團隊成員發揮創造力和想象力，以創造更好的產品。在我的領導下，團隊可以高效地協同工作，以最短的時間開發出高品質的產品。</p>
<p>最後，我具備將應用程序部署到 GCP 的經驗，我曾經部署一個測試系統到 GCP 上，能夠最大化地利用雲端資源進行開發。我對 GCP 的熟悉程度可以讓我更好地理解 GCP 的特點和優勢，並且能夠快速地部署和管理應用程序。</p>
<p>總的來說，我相信我的技能和經驗可以讓我成為貴公司的一員。我對開發工作充滿熱情，並且總是保持著學習和進步的態度。我期待著和您們的團隊一起工作，共同創造出更好的產品。謝謝您的聆聽。」</p>
<p>上面 ChatGPT 產出的自我介紹大約「600 字」，而人一分鐘大約講 100~200 字，所以這是一份長度約「4 分鐘」的自我介紹；<strong>如果你想要讓他更精練、濃縮，可以這麼做。</strong></p>
<p>😀 <strong>我</strong>：「只要突出我的<code>實戰經驗</code>、<code>領導能力</code>就好，幫我把它濃縮到 150 字。」</p>
<p>🤖 <strong>ChatGPT</strong>：「您好，非常感謝您給我這次面試的機會。我是一名擁有豐富實戰經驗的後端工程師，尤其擅長使用 Node.js 開發穩定且可靠的後端服務。在我曾經開發的點餐系統中，我成功優化了系統，使其處理速度比起傳統的點餐系統快了 30%。此外，我也負責設計和實現 RESTful API，並在測驗系統中成功地讓 1000 個用戶同時進行測試。</p>
<p>除此之外，我在點餐系統、測驗系統、RMA 系統等專案中擔任領導者的角色，負責協調團隊成員，制定開發計劃，並有效地解決問題。我的領導風格是鼓勵和支持，激勵團隊成員發揮創造力和想象力，以創造更好的產品。我相信我的技能和經驗可以為貴公司帶來價值，期待和貴公司的團隊一起工作，共同成長。謝謝！」</p>
<p>儘管我們的要求是 150 字，但 ChatGPT 最後給出的答案還是有 250 字；剩下的優化還是需要自己微調，以下是筆者優化的版本：</p>
<p>「<em>您好，我是一名擁有豐富實戰經驗的後端工程師，尤其擅長使用 Node.js。在我曾經開發的點餐系統中，我成功優化了系統，使其處理速度比起傳統的點餐系統快了 30%。此外，我設計的測驗系統也成功地讓 1000 個用戶同時進行測試。</em></p>
<p><em>我在多個專案中擔任領導者的角色，負責協調團隊成員，制定開發計劃，並有效地解決問題。我相信我的技能和經驗可以為貴公司帶來價值，期待和貴公司的團隊一起工作，共同成長。謝謝！</em>」</p>
<blockquote>
<p>好的自我介紹除了可以應用在面試，還能在交流研討會中幫你拓展人脈，讓更多人對你留下深刻的印象。</p>
</blockquote>
<hr>
<h2 id="-針對產業來產生自我介紹">針對產業來產生自我介紹<a class="h-anchor" href="#-%e9%87%9d%e5%b0%8d%e7%94%a2%e6%a5%ad%e4%be%86%e7%94%a2%e7%94%9f%e8%87%aa%e6%88%91%e4%bb%8b%e7%b4%b9" aria-label="這個段落的連結"></a></h2>
<p>我們都知道如果能針對面試的產業、對象，去撰寫自我介紹能提高不少錄取率。</p>
<p>但過去這件事太耗時耗力，只能針對幾間公司特別做客製化；但有了 ChatGPT 後，這一切便不再是問題。</p>
<p>😀 <strong>我</strong>：「我要面試的是<code>金融產業</code>，對方是<code>技術長</code>。<br>請依照下方提供的履歷，產生專業性的自我介紹，語氣要充滿自信，在結尾用前面的案例證明自己適合這個產業。</p>
<ul>
<li>在 Node.js 中擁有豐富的開發經驗，能夠開發出穩定且可靠的後端服務。例如，我曾經利用 Node.js 開發一個點餐系統，其處理速度比起傳統的點餐系統快了 30%。</li>
<li>能夠設計和實現 RESTful API，我曾經設計和開發了一個測驗系統的 RESTful API，並且能夠讓 1000 個同時的用戶快速地進行測驗。</li>
<li>熟悉 MySQL、PostgreSQL 等關聯式資料庫，我曾經優化一個 RMA 系統的資料庫設計，使其在大量的數據處理下能夠維持穩定性。</li>
<li>在點餐系統、測驗系統、RMA 系統等專案中擔任領導者的角色，我負責協調團隊成員，制定開發計劃，並能夠有效地解決問題。</li>
<li>具備將應用程序部署到 GCP 的經驗，我曾經部署一個測試系統到 GCP 上，能夠最大化地利用雲端資源進行開發。」</li>
</ul>
<p>🤖 <strong>ChatGPT</strong>：「尊敬的技術長，您好！感謝您抽出寶貴的時間來面試我，我是一名擅長於 Node.js 後端開發的工程師。我擁有豐富的開發經驗，曾經開發過一個點餐系統，使用 Node.js 能夠快速地處理大量訂單，提高了點餐效率。同時，我也設計和實現過一個能夠讓 1000 個同時用戶快速進行測驗的 RESTful API，並優化過一個 RMA 系統的資料庫設計，確保其能夠在大量數據處理下保持穩定性。</p>
<p>在過去的工作中，我擔任過領導者的角色，負責協調團隊成員，制定開發計劃，並且有效地解決問題。我也具備將應用程序部署到 GCP 的經驗，曾經將測試系統部署到 GCP 上，並能夠最大化地利用雲端資源進行開發。</p>
<p>我對金融行業非常有興趣，我相信我的技能和經驗可以為這個產業做出貢獻。我曾經開發的點餐系統不僅能夠提高效率，也能夠確保交易的安全性；而我設計的測試系統 RESTful API，也能夠確保高可用性和安全性。我相信這些經驗和技能可以幫助我更好地應對金融領域的挑戰，進一步提升金融服務的質量。</p>
<p>再次感謝您抽出寶貴的時間來面試我，我期待有機會加入貴公司的團隊，並為貴公司的發展做出貢獻。」</p>
<blockquote>
<p><em><strong>小提醒</strong>：<br>筆者不是要大家把 ChatGPT 完整的回應給背下來，而是要去了解我們能從什麼角度做切入，讓面試官感受到我們的「誠意」。</em></p>
</blockquote>
<hr>
<h2 id="-將經歷用-star-的原則呈現">將經歷用 STAR 的原則呈現<a class="h-anchor" href="#-%e5%b0%87%e7%b6%93%e6%ad%b7%e7%94%a8-star-%e7%9a%84%e5%8e%9f%e5%89%87%e5%91%88%e7%8f%be" aria-label="這個段落的連結"></a></h2>
<p>在自我介紹結束後，面試官會根據自己感興趣的部分做更深入的詢問。</p>
<p>如果你想在經歷被問到時能回答得更有說服力，那你一定要好好利用 STAR 的回答模板：</p>
<ul>
<li><strong>Situation （情境）</strong>：描述你所面臨的挑戰。</li>
<li><strong>Task （任務）</strong>：在上述情境下你被賦予了什麼任務。</li>
<li><strong>Action （行動）</strong>：為了解決問題，你採取哪些行動。</li>
<li><strong>Result （結果）</strong>：根據你的行動，最終達成的結果。</li>
</ul>
<p>看完上面的描述後覺得準備起來很麻煩嗎？沒關係！讓 ChatGPT 為您代勞。</p>
<p>😀 <strong>我</strong>：「我正在準備面試，想請你扮演擅長 STAR 原則的教練，將下面的經歷用 STAR 原則呈現。</p>
<p>專案：全球售後服務系統<br>職位：Tech Lead<br>任務：</p>
<ol>
<li>提供公司內部處理產品保固、維修，以及庫存查詢的平台</li>
<li>客戶可以在平台上查詢產品保固資訊、維修進度</li>
<li>此系統串接泰坦 T100 工廠端資料，以及蓋亞 BPM 簽核系統」</li>
</ol>
<p>🤖 <strong>ChatGPT</strong>：「<br><strong>Situation</strong>（情境）：<br>我擔任全球售後服務系統的 Tech Lead，我的任務是提供公司內部處理產品保固、維修和庫存查詢的平台，以及允許客戶在平台上查詢產品保固資訊和維修進度。此外，我們的系統還需要與泰坦 T100 工廠端資料和蓋亞 BPM 簽核系統進行串接。</p>
<p><strong>Task</strong>（任務）：<br>我的主要任務是負責設計、開發和實施這個全球售後服務系統，並與其他部門進行溝通，以確保系統能夠正確地與泰坦 T100 工廠端資料和蓋亞 BPM 簽核系統進行串接。</p>
<p><strong>Action</strong>（行動）：</p>
<ol>
<li>在設計階段，我們花了很多時間了解公司和客戶的需求，以確保我們的系統能夠滿足所有利益相關者的要求。</li>
<li>為了實現系統的功能，我們選擇了一些先進的技術，例如 REST API 和 AWS Lambda，並使用敏捷開發方法來迭代開發，以確保系統能夠按時交付。</li>
<li>我負責與其他部門和團隊進行緊密的合作，包括銷售和行銷團隊，以確保他們了解系統的功能和優勢。同時，我們還需要與泰坦 T100 工廠端資料和蓋亞 BPM 簽核系統的開發團隊進行合作，以確保系統可以正確地與這些系統進行交互。</li>
<li>在實施階段，我們使用了自動化測試和持續集成的方法，以確保系統的穩定性和可靠性。我們還設置了監視系統，以及故障排除和恢復計劃，以確保系統在發生故障時能夠快速恢復。</li>
</ol>
<p><strong>Result</strong>（結果）：<br>由於我們的系統能夠提供全面的售後服務，使得公司內部和客戶可以更加方便地處理產品保固、維修和庫存查詢。客戶也可以透過系統輕鬆地查詢產品保固資訊和維修進度，這大大提高了客戶的滿意度和忠誠度。</p>
<p>此外，我們成功地將系統與泰坦 T100 工廠端資料和蓋亞 BPM 簽核系統進行串接，使得系統可以更加高效地處理售後服務相關的事務。系統也能夠快速地進行故障排除和恢復，保證了系統的穩定性和可靠性。</p>
<p>最終，我們的全球售後服務系統成功地提高了客戶滿意度和忠誠度，並大大提高了公司的售後服務效率和效益。」</p>
<blockquote>
<p>不得不說，ChatGPT 似乎比我更了解這個專案 😅；這邊還是提醒一下讀者，如果 ChatGPT 所寫出的內容不符合事實，請自行更正！</p>
</blockquote>
<hr>
<h2 id="-bonus產生推薦信草稿讓教授長官同事幫你一把">Bonus：產生推薦信草稿，讓教授、長官、同事幫你一把<a class="h-anchor" href="#-bonus%e7%94%a2%e7%94%9f%e6%8e%a8%e8%96%a6%e4%bf%a1%e8%8d%89%e7%a8%bf%e8%ae%93%e6%95%99%e6%8e%88%e9%95%b7%e5%ae%98%e5%90%8c%e4%ba%8b%e5%b9%ab%e4%bd%a0%e4%b8%80%e6%8a%8a" aria-label="這個段落的連結"></a></h2>
<p>有些職位我們會需要以前的教授、長官、同事幫忙寫推薦信，不過這封推薦信「通常」是自己先「擬好」一份草稿給對方參考。</p>
<p>不過筆者很清楚絕大多數人都不擅長寫推薦信，如果要模仿對方的語氣來寫推薦信難度又上升一個台階。</p>
<p>不過現在我們輕鬆多了，只要把自己的履歷餵給 ChatGPT，然後請他用給「學生、下屬、同事」的口吻來寫推薦信就完工嚕！</p>
<p>😀 <strong>我</strong>：「我要面試的是<code>後端工程師</code>的職位，下面是我的個人履歷，請你使用<code>指導教授</code>的口吻來撰寫一封推薦信。</p>
<ul>
<li>在 Node.js 中擁有豐富的開發經驗，能夠開發出穩定且可靠的後端服務。例如，我曾經利用 Node.js 開發一個點餐系統，其處理速度比起傳統的點餐系統快了 30%。</li>
<li>能夠設計和實現 RESTful API，我曾經設計和開發了一個測驗系統的 RESTful API，並且能夠讓 1000 個同時的用戶快速地進行測驗。</li>
<li>熟悉 MySQL、PostgreSQL 等關聯式資料庫，我曾經優化一個 RMA 系統的資料庫設計，使其在大量的數據處理下能夠維持穩定性。</li>
<li>在點餐系統、測驗系統、RMA 系統等專案中擔任領導者的角色，我負責協調團隊成員，制定開發計劃，並能夠有效地解決問題。</li>
<li>具備將應用程序部署到 GCP 的經驗，我曾經部署一個測試系統到 GCP 上，能夠最大化地利用雲端資源進行開發。」</li>
</ul>
<p>🤖 <strong>ChatGPT</strong>：「尊敬的招聘委員會：</p>
<p>我很榮幸向您推薦我的學生，他將面試您公司的後端工程師職位。作為他的指導教授，我很自豪地說，他是一位優秀的後端工程師，擁有非常豐富的開發經驗和卓越的技術能力。</p>
<p>在 Node.js 開發方面，他有豐富的經驗，能夠開發出穩定且可靠的後端服務。例如，他曾經利用 Node.js 開發一個點餐系統，其處理速度比起傳統的點餐系統快了 30%。他也能夠設計和實現 RESTful API，過去曾開發了一個測驗系統，並且能夠讓 1000 個同時的用戶快速地進行測驗。</p>
<p>此外，他也非常熟悉 MySQL、PostgreSQL 等關聯式資料庫，曾經優化一個 RMA 系統的資料庫設計，使其在大量的數據處理下能夠維持穩定性。在點餐系統、測驗系統、RMA 系統等專案中擔任領導者的角色，他負責協調團隊成員，制定開發計劃，並能夠有效地解決問題。</p>
<p>他還具備將應用程序部署到 GCP 的經驗，曾經部署一個測試系統到 GCP 上，能夠最大化地利用雲端資源進行開發。</p>
<p>總的來說，他是一位非常優秀的後端工程師，有豐富的開發經驗和卓越的技術能力，擅長解決問題和協調團隊成員。我相信他能夠在您公司為後端開發團隊做出卓越的貢獻。</p>
<p>謝謝您的時間！</p>
<p>真誠地，</p>
<p>[您的名字]」</p>
<blockquote>
<p>推薦信中如果有不合理的稱謂、描述，請自行調整。</p>
</blockquote>
<hr>
<h2 id="-結語如果沒花時間練習那武器再好也沒用">結語：如果沒花時間練習，那武器再好也沒用<a class="h-anchor" href="#-%e7%b5%90%e8%aa%9e%e5%a6%82%e6%9e%9c%e6%b2%92%e8%8a%b1%e6%99%82%e9%96%93%e7%b7%b4%e7%bf%92%e9%82%a3%e6%ad%a6%e5%99%a8%e5%86%8d%e5%a5%bd%e4%b9%9f%e6%b2%92%e7%94%a8" aria-label="這個段落的連結"></a></h2>
<p>儘管 ChatGPT 能在短時間產出許多輔助面試的素材，但這並不代表求職者不需要付出努力。</p>
<p>如果在對這些素材一知半解，又或是不夠熟練的狀態下去面試；那當面試官細問經歷、技術等問題時，你肯定會出包。</p>
<blockquote>
<p>師父領進門，修行在個人。</p>
</blockquote>
<p>希望這篇文章能給準備面試的讀者帶來幫助，讓你藉著 AI 工具的輔助在職場更進一步！</p>
<blockquote>
<p><strong>小提醒</strong>：<br>筆者了解對許多人來說，英文面試才是最大的難關；所以提醒大家，文中 ChatGPT 所做出的回應，可以請 ChatGPT 扮演翻譯專家，將內容轉換成「英文」喔！</p>
</blockquote>
<a class="linkcard" href="https://deanlin.net/categories/ai/">
  <span class="lc-body">
    <span class="lc-title">AI 實戰</span>
    <span class="lc-desc">實測生成式 AI 工具、Vibe Coding 與自動化流程的第一手心得，把 AI 變成每天都用得上的即戰力。</span>
    <span class="lc-site"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M5 12h14M13 6l6 6-6 6"/></svg>林鼎淵 Dean Lin</span>
  </span>
</a>

<p><strong>想將 AI 導入自己的職場與生活嗎？歡迎<a href="https://www.books.com.tw/products/0011004216">購買筆者出版的新書</a></strong>，幫助您<strong>在 AI 的浪潮中站穩腳步，並在未來的職場競爭中佔據優勢！</strong></p>
<figure class="figure"><img src="https://deanlin.net/blog/202303/chatgpt-interview-prep/images/chatgpt-interview-prep-02.webp" width="1400" height="319" alt="林鼎淵著作《ChatGPT 與 AI 繪圖效率大師（新增 Copilot、Gamma、Runway、Suno）》" loading="lazy" decoding="async">
  <figcaption><a href="https://www.books.com.tw/products/0011004216">https://www.books.com.tw/products/0011004216</a></figcaption>
</figure>

]]></content:encoded>
    </item>
    <item>
      <title>面試的性向測驗竟然準到讓人心裡發寒？── 下班有約系列文</title>
      <link>https://deanlin.net/blog/202204/personality-tests-in-interviews/</link>
      <pubDate>Fri, 01 Apr 2022 10:48:15 +0800</pubDate>
      <guid>https://deanlin.net/blog/202204/personality-tests-in-interviews/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>許多企業會在面試時加入「性向測驗（人格測驗）」的環節，過去筆者認為這份測驗參考價值不大；沒想到在與一些 HR、用人主管聊天後，發現不少人把這份測驗當作求職者是否聘用的重要標準之一，今天我們就來聊聊這個話題吧。</description>
      <media:content url="https://deanlin.net/blog/202204/personality-tests-in-interviews/images/personality-tests-in-interviews-og_hu_16d168352b9da0f5.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>許多企業會在面試時加入「性向測驗（人格測驗）」的環節，過去筆者認為這份測驗參考價值不大；沒想到在與一些 HR、用人主管聊天後，發現不少人把這份測驗當作求職者是否聘用的重要標準之一，今天我們就來聊聊這個話題吧。</p>
<p><img src="https://deanlin.net/blog/202204/personality-tests-in-interviews/images/personality-tests-in-interviews-01.webp" width="1400" height="742" alt="面試的性向測驗竟然準到讓人心裡發寒？── 下班有約系列文（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>大綱
一、為什麼有些人這麼重視這份測驗?
二、我要偽造自己的性格來獲取 Offer 嗎？
三、反映性向測驗的現實案例：我就是生來顛覆
四、反映性向測驗的現實案例：每個人都有自己的優點
五、總結
</code></pre><h2 id="一為什麼有些人這麼重視這份測驗">一、為什麼有些人這麼重視這份測驗?<a class="h-anchor" href="#%e4%b8%80%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%89%e4%ba%9b%e4%ba%ba%e9%80%99%e9%ba%bc%e9%87%8d%e8%a6%96%e9%80%99%e4%bb%bd%e6%b8%ac%e9%a9%97" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>「我以為這個東西大家都是亂填的沒啥參考價值，但在部門物是人非後，發現它根本準到令人心寒。」 ── 一位歷經滄桑的主管悲嘆</p>
</blockquote>
<p><strong>通常受過傷的人才會特別重視這項測驗</strong>，有朋友就曾表示當組織誕生問題兒童時，他會先回去翻他入職時的性向測驗，此時往往會發現，這些「問題兒童」的誕生並非偶然。</p>
<p>性向測驗並非「玄學」而是「科學」，只要求職者用直覺去選擇答案，他就有相當大的參考價值。</p>
<blockquote class="pullquote"><p>跟用星座決定是否錄取的老闆相比，性向測驗實在科學太多了 XD</p>
</blockquote>
<hr>
<h2 id="二我要偽造自己的性格來獲取-offer-嗎">二、我要偽造自己的性格來獲取 Offer 嗎？<a class="h-anchor" href="#%e4%ba%8c%e6%88%91%e8%a6%81%e5%81%bd%e9%80%a0%e8%87%aa%e5%b7%b1%e7%9a%84%e6%80%a7%e6%a0%bc%e4%be%86%e7%8d%b2%e5%8f%96-offer-%e5%97%8e" aria-label="這個段落的連結"></a></h2>
<p>有部分心理系出身，或是對心理學有所研究的人，他們可以輕易地在性向測驗上偽造自己的性格。（筆者相信大部分的人都是善良且誠實的，這頂多算是潛意識的習慣？）</p>
<p>但除非他們連在面試現場的表現都能偽造的天衣無縫，不然肯定會被面試官、HR 覺得這個人怪怪的，而錄取「怪人」往往會給組織帶來許多未知的風險。</p>
<p>況且每間公司在不同職位上需要的人才不同，你怎麼能肯定自己偽造出來的性格就是該公司需要的？</p>
<p>如果每個求職者都表現出強大的積極性，那想要找穩紮穩打人才的公司就會很困擾。</p>
<p>性向測驗的結果沒有絕對的好壞，就是看你與面試公司的氣場是否合適。</p>
<blockquote>
<p>雖然話是這樣講，但大部分公司還是傾向打保守牌，如果受測者的「情緒穩定度、服從性」分數超級低，用人主管還是會好好評估的。</p>
</blockquote>
<hr>
<h2 id="三反映性向測驗的現實案例我就是生來顛覆">三、反映性向測驗的現實案例：我就是生來顛覆<a class="h-anchor" href="#%e4%b8%89%e5%8f%8d%e6%98%a0%e6%80%a7%e5%90%91%e6%b8%ac%e9%a9%97%e7%9a%84%e7%8f%be%e5%af%a6%e6%a1%88%e4%be%8b%e6%88%91%e5%b0%b1%e6%98%af%e7%94%9f%e4%be%86%e9%a1%9b%e8%a6%86" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>如果錄取一個不合適的人，影響到的可能是全公司的收益。</p>
</blockquote>
<p>在一個適合借酒澆愁的夜晚，擔任部門主管的朋友跟我分享了一個血淋淋的案例。</p>
<p>當時公司有個職位空缺太久，在一直找不到適合人選的狀態下，部門主管挑選了一個勉強堪用的求職者。</p>
<p>原本也沒期待她能夠有多大的輸出，只求能夠完成本分的事情就夠了；但隨著時間推移，組織內部的氣氛越來越奇怪。</p>
<p>直到某一天，<strong>她突然無預警的被開除</strong>，大家才知道裡面發生了一個怎麼樣的故事，也了解為什麼<strong>用錯了一個人會導致整個部門完全失去向心力。</strong></p>
<h3 id="step-1利用人喜歡聽八卦的本性">STEP 1：利用人喜歡聽八卦的本性<a class="h-anchor" href="#step-1%e5%88%a9%e7%94%a8%e4%ba%ba%e5%96%9c%e6%ad%a1%e8%81%bd%e5%85%ab%e5%8d%a6%e7%9a%84%e6%9c%ac%e6%80%a7" aria-label="這個段落的連結"></a></h3>
<p>在公司與人建立聯繫最快的方式就是聊八卦，而八卦的內容儘管無法確認真假，卻能潛移默化的影響一個人看待他人的目光。</p>
<p>最開始她先用無傷大雅的八卦與同事建立聯繫，並且製造各種巧合讓這些八卦一一被印證，讓大家相信她的情報是有價值的。</p>
<h3 id="step-2告訴你無法與本人確認真假的八卦">STEP 2：告訴你無法與本人確認真假的八卦<a class="h-anchor" href="#step-2%e5%91%8a%e8%a8%b4%e4%bd%a0%e7%84%a1%e6%b3%95%e8%88%87%e6%9c%ac%e4%ba%ba%e7%a2%ba%e8%aa%8d%e7%9c%9f%e5%81%87%e7%9a%84%e5%85%ab%e5%8d%a6" aria-label="這個段落的連結"></a></h3>
<p>而下一步，她向團隊成員散播不利於所有人的八卦消息，而這些八卦訊息你是很難跟本人求證的，舉例：</p>
<ol>
<li>在主管最近無理由的挺她、幫她說話的狀況下；跟你說主管對她有意思，還想要約她出去玩，並附上對話截圖。</li>
<li>如果發現其他女同事跟大家相處愉快，就跟主管打小報告說這個同事作風不檢點，想用不正當手段在職場生存，害她被排擠。</li>
<li>用誘導的提問方式讓你說其他人的壞話，並將其錄音、截圖給本人。</li>
</ol>
<p>如果你是下屬，會不會懷疑主管跟她有不可告人的秘密？</p>
<p>如果你是主管，會不會用不一樣的角度來解讀這個女同事的所作所為？</p>
<p>如果你收到錄音、截圖，會不會與他人產生猜忌？</p>
<blockquote>
<p>上面這些涉及個人隱私的內容，相信大多數人都不可能與本人求證。</p>
</blockquote>
<h3 id="step-3用資訊不對等製造內部糾紛">STEP 3：用資訊不對等製造內部糾紛<a class="h-anchor" href="#step-3%e7%94%a8%e8%b3%87%e8%a8%8a%e4%b8%8d%e5%b0%8d%e7%ad%89%e8%a3%bd%e9%80%a0%e5%85%a7%e9%83%a8%e7%b3%be%e7%b4%9b" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>在心態改變後，一句再普通不過的話語都可能被視為挑釁。</p>
</blockquote>
<p>經歷了上面的步驟後，已經有許多成員被影響；而那些沒被影響的成員更慘，因為被重點抹黑。</p>
<p>團隊成員不信任彼此的狀態下，只要小小的摩擦，就足以產生燎原的野火。</p>
<h3 id="故事尾聲">故事尾聲<a class="h-anchor" href="#%e6%95%85%e4%ba%8b%e5%b0%be%e8%81%b2" aria-label="這個段落的連結"></a></h3>
<p>點火的人就如開頭所說，事跡敗露後被開除了，但她給部門製造的傷害除了績效上的損失外，間接導致的部門形象受損、人才流失的影響更為深遠。</p>
<p>後來朋友回去看這個人的性向測驗，<strong>發現她在「情緒穩定、服從性」的指標接近為 0</strong>。</p>
<hr>
<h2 id="四反映性向測驗的現實案例每個人都有自己的優點">四、反映性向測驗的現實案例：每個人都有自己的優點<a class="h-anchor" href="#%e5%9b%9b%e5%8f%8d%e6%98%a0%e6%80%a7%e5%90%91%e6%b8%ac%e9%a9%97%e7%9a%84%e7%8f%be%e5%af%a6%e6%a1%88%e4%be%8b%e6%af%8f%e5%80%8b%e4%ba%ba%e9%83%bd%e6%9c%89%e8%87%aa%e5%b7%b1%e7%9a%84%e5%84%aa%e9%bb%9e" aria-label="這個段落的連結"></a></h2>
<p>上面的案例是鬼故事，接下來這個案例應該就是「天生我材必有用」？</p>
<p>朋友公司有個成員的做事速度較慢，因此他把這個情況向主管匯報，希望主管可以跟他溝通一下，不要拖到大家的進度。</p>
<p>不過主管在了解狀況後並沒有選擇跟做事較慢的組員溝通，而是拿出了性向測驗結果，跟朋友說道：「其實他做事比較慢這點我很早就發現了，也曾經想著有沒有要去跟他溝通；<strong>但在發現他性向測驗的敏捷性是 0，可是細緻性是接近滿分後，我思考良久，就沒有多說什麼了</strong>；畢竟組織中如果每個人都在往前衝，那也是很危險的一件事情。」</p>
<blockquote>
<p>「一個方案的好壞，不要只看激動人心的部分，反而要注意風險的控制。 」— 理想之城</p>
</blockquote>
<hr>
<h2 id="五總結">五、總結<a class="h-anchor" href="#%e4%ba%94%e7%b8%bd%e7%b5%90" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>在性向測驗時做自己，比較有機會進入與自己契合的工作環境。</p>
</blockquote>
<p>技術、知識都可以透過後天教育和學習成長，但人的性格是長期養成出來的。</p>
<p>想要改變一個人的性格極為困難，就算當下勉強改了，也有很高的機率故態復萌（就像渣男劈腿一次的通常會劈腿第二次？）。</p>
<p>與其戴上面具，改變自己的性格去工作，還不如去找一份跟自己性格匹配的工作。</p>
<blockquote class="pullquote"><p>再次提醒，性向測驗的結果沒有絕對的好壞；勉強自己的後果可能會導致被放到不對的位置。</p>
</blockquote>
<p><strong>如果你對目前的職涯感到迷茫，想從不同視角檢視自己的選擇，相信《<a href="https://www.tenlong.com.tw/products/9786264142083">工程師下班有約</a>》這本書能為你帶來靈感與啟發：</strong></p>
<figure class="figure"><img src="https://deanlin.net/blog/202204/personality-tests-in-interviews/images/personality-tests-in-interviews-02.webp" width="1400" height="319" alt="林鼎淵著作《工程師下班有約：企業內訓講師帶你認清職涯真相！》" loading="lazy" decoding="async">
  <figcaption><a href="https://www.tenlong.com.tw/products/9786264142083">https://www.tenlong.com.tw/products/9786264142083</a></figcaption>
</figure>

<pre tabindex="0"><code>下班有約系列文
▋ [前端]新手工程師容易卡住的問題
▋ [前端＆後端]工程師常犯的錯誤
▋ 讓開發團隊更好協作的方式
▋ 談談 Pair Programing
▋ 談談工程師的話語權
▋ 出社會後，新鮮人要及早知道的 5 件事
▋ 面試的性向測驗竟然準到讓人心裡發寒？(本篇)
▋ 反其道而行的任務分配
▋ 現在的 Daily Scrum 真的適合團隊嗎？
▋ 用低成本也能請到好員工？資深 HR 不想告訴你的秘密！
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>[面試][人格特質]當你分享工作經驗時會被問到的種種問題</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-24/</link>
      <pubDate>Sat, 09 Oct 2021 08:16:12 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-24/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>從第二份工作開始，幾乎每場面試你都會被問到：「過去在公司或是專案上扮演過什麼角色？遇到過什麼樣的困難、如何解決？」</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-24/images/cover_hu_7901ace094c16d3b.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>每個人都有自己的人生經驗，本篇文章是從筆者的角度出發，希望可以給讀者帶來不同的視角與觀點。</p>
</blockquote>
<p>從第二份工作開始，幾乎每場面試你都會被問到：「過去在公司或是專案上扮演過什麼角色？遇到過什麼樣的困難、如何解決？」</p>
<p>只要出現在你履歷上的工作經驗都可能會被詢問，所以<mark>請務必準備</mark>。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>在工作中有遇過哪些挫折、衝突，以及你的解決方式</strong>
<ul>
<li>1.1 同事刻意挑選你無法處理問題的時間捅你一刀</li>
<li>1.2 常常快到下班時間臨時開會</li>
<li>1.3 跨部門溝通時遇到合作意願低又甩鍋的同事</li>
<li>1.4 跨部門開會時遇到用階級壓人的長官</li>
<li>1.5 開會冗長且不斷偏離主題</li>
</ul>
</li>
<li><strong>描述一下你在這份工作中擔任的角色、負責的任務</strong>
<ul>
<li>2.1 敘述本職工作</li>
<li>2.2 與產品經理溝通</li>
<li>2.3 處理主管交代的事情</li>
<li>2.4 分配任務給組員</li>
<li>2.5 分享印象最深刻的專案</li>
</ul>
</li>
<li><strong>常見問題</strong>
<ul>
<li>3.1 如果把事情搞砸了，你會怎麼辦？</li>
<li>3.2 如果跟主管意見衝突如何處置</li>
<li>3.3 如果擔任專案經理，你會如何與多個部門溝通，制定共同遵守的流程</li>
<li>3.4 公司有跑 Scrum 嗎？你們是如何運作的？</li>
<li>3.5 [狀況題]公司高層突然在週五下班前要求提前部署，你會如何應對？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-在工作中有遇過哪些挫折衝突以及你的處理方式">1. 在工作中有遇過哪些挫折、衝突，以及你的處理方式<a class="h-anchor" href="#1-%e5%9c%a8%e5%b7%a5%e4%bd%9c%e4%b8%ad%e6%9c%89%e9%81%87%e9%81%8e%e5%93%aa%e4%ba%9b%e6%8c%ab%e6%8a%98%e8%a1%9d%e7%aa%81%e4%bb%a5%e5%8f%8a%e4%bd%a0%e7%9a%84%e8%99%95%e7%90%86%e6%96%b9%e5%bc%8f" aria-label="這個段落的連結"></a></h2>
<p>下面的內容可能由求職者主動說出來，或是被面試官問到，無論如何謹記一個原則：「<mark>要說出你是如何面對、處理這些事情，千萬不要淪為單純的抱怨！</mark>」</p>
<blockquote>
<p>大家為人處世的方法不同，筆者的做法僅供參考；主要是為了讓讀者在遇到問題前，可以先思考自己的處理方式，不至於手忙腳亂。</p>
</blockquote>
<h3 class="h-nomark" id="11-同事刻意挑選你無法處理問題的時間捅你一刀">1.1 同事刻意挑選你無法處理問題的時間捅你一刀<a class="h-anchor" href="#11-%e5%90%8c%e4%ba%8b%e5%88%bb%e6%84%8f%e6%8c%91%e9%81%b8%e4%bd%a0%e7%84%a1%e6%b3%95%e8%99%95%e7%90%86%e5%95%8f%e9%a1%8c%e7%9a%84%e6%99%82%e9%96%93%e6%8d%85%e4%bd%a0%e4%b8%80%e5%88%80" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>事件描述</strong><br>過去上班時有碰過同事，遇到問題不在上班時間詢問，而是<mark>等你離開辦公室後才在有總經理的群組 tag 你，說你的專案有 bug</mark>。</li>
<li><strong>處理方式</strong>
<ul>
<li><strong>先作反思</strong><br>我會先回憶過去有什麼地方惹到他了，或是部門間是否有利益衝突；因為一個正常人沒道理在對方無法處理問題的時候，故意在有高階長官的群組 tag 他。</li>
<li><strong>盡快回覆</strong><br>儘管是在下班時段，但因為對方是在有高階長官的群組發佈訊息；所以我會<mark>盡快回覆，但不管對自己的程式多有自信，我都不會強調專案沒 bug</mark>，因為你此時可能在吃飯、運動、逛街，根本無法確認這個問題是否存在；筆者的回覆範例：「<mark>專案每個版本在釋出前有撰寫完整的測試案例確保功能穩定性，且通過測試部門的嚴密測試，針對 XXX 所提出的問題，我會盡快確認，並於明早 10:00 做好彙整向大家報告。</mark>」這段回覆有幾個重點：
<ul>
<li>
<p>專案是經過測試，確認穩定後才釋出的。</p>
</li>
<li>
<p>你有即時看到這則訊息。</p>
</li>
<li>
<p>給出明確的報告時間。</p>
<blockquote>
<p>這邊舉例的是不影響系統運行且無法確認是否為真的 bug，<mark>如果是系統當機這類重大問題，你還是得即時處理</mark>。</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>彙整報告</strong>
<ul>
<li>
<p><strong>真的有 bug</strong><br>老實承認錯誤，並承諾<mark>bug 的修復時間</mark>。</p>
</li>
<li>
<p><strong>如果沒 bug</strong><br>筆者報告完後，會用這段話做結尾：「<mark>感謝 XXX 在下班時間努力工作，可能是因為他太累了才會有這個錯誤回報；這部分我會再跟 XXX 溝通，希望他之後可以先跟負責的工程師釐清問題，避免類似的烏龍再次發生，抱歉讓各位長官受驚了。</mark>」</p>
<blockquote>
<p>筆者不會趁這個機會打壓對方，將對方的問題點出來、釐清責任歸屬就好；如果你沒辦法讓對方離開公司，那請<mark>做人留一線</mark>。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-常常快到下班時間臨時開會">1.2 常常快到下班時間臨時開會<a class="h-anchor" href="#12-%e5%b8%b8%e5%b8%b8%e5%bf%ab%e5%88%b0%e4%b8%8b%e7%8f%ad%e6%99%82%e9%96%93%e8%87%a8%e6%99%82%e9%96%8b%e6%9c%83" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>事件描述</strong><br>有些人一整天上班時間都不找你討論，偏偏等到<mark>下班前 10 分鐘才傳訊息跟你說有問題需要討論</mark>。</li>
<li><strong>處理方式</strong>
<ul>
<li>
<p><strong>如果對方是故意的</strong><br>偶爾發生就算了，但如果對方是刻意如此，<mark>請明確地告知對方，你希望在上班時間討論</mark>；筆者的訊息範例：「<mark>如果有問題請在下班前一小時找我討論，這樣不會耽誤到彼此的下班時間，也有比較好的討論品質。</mark>」</p>
</li>
<li>
<p><strong>如果勸告無效</strong><br>如果對方在你明確告知後依然故我，筆者建議<mark>直接請上級提醒對方</mark>；有些人加班不是因為工作做不完，只是想在長官面前刷存在感，自己想刷存在感沒關係，但如果影響到其他同事就真的不好了。</p>
<blockquote>
<p>不是所有事情都一定要靠自己解決，適時的請上級支援也是有必要的。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-跨部門溝通時遇到合作意願低又甩鍋的同事">1.3 跨部門溝通時遇到合作意願低又甩鍋的同事<a class="h-anchor" href="#13-%e8%b7%a8%e9%83%a8%e9%96%80%e6%ba%9d%e9%80%9a%e6%99%82%e9%81%87%e5%88%b0%e5%90%88%e4%bd%9c%e6%84%8f%e9%a1%98%e4%bd%8e%e5%8f%88%e7%94%a9%e9%8d%8b%e7%9a%84%e5%90%8c%e4%ba%8b" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>職場奇葩多，遇到問題先甩鍋。</p>
</blockquote>
<ul>
<li><strong>事件描述</strong><br>有時專案需要多個部門一起合作才能完成，但有些部門成員就是<mark>努力地拖延進度</mark>；你可能已經在 2 個月前就把功能完成並交付到對方手上，可是對方拖到最近才開始實作，但當上級問他功能怎麼還沒做完時，<mark>他會第一時間把責任丟到你身上，說是因為你功能不夠完善導致他無法作業</mark>。</li>
<li><strong>處理方式</strong>
<ul>
<li><strong>留存證據</strong><br>職場上難搞的人太多，明明是自己的問題，卻把責任都推到對方身上；如果原本公司就有<mark>使用專案管理軟體</mark>就是一個很好的證據；如果沒有的話，建議要<mark>截圖對話紀錄並且每封 Email 都要把上級加入 Loop</mark>，以此保護自己。</li>
<li><strong>尋找關鍵人物</strong><br>如果你是專案負責人，但對方總是有各種理由拖延；<mark>到最後專案無法如期交付時，上級會找你麻煩，而不是找那個沒做事情的人麻煩</mark>。<br>如果對方是因為部門不同而不願意配合，筆者建議你<mark>直接找對方上級溝通</mark>；但請在<mark>問題發生初期就去溝通</mark>，如果等到時程緊繃才去溝通就晚了，對方也會想辦法推卸責任。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="14-跨部門開會時遇到用階級壓人的長官">1.4 跨部門開會時遇到用階級壓人的長官<a class="h-anchor" href="#14-%e8%b7%a8%e9%83%a8%e9%96%80%e9%96%8b%e6%9c%83%e6%99%82%e9%81%87%e5%88%b0%e7%94%a8%e9%9a%8e%e7%b4%9a%e5%a3%93%e4%ba%ba%e7%9a%84%e9%95%b7%e5%ae%98" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>事件描述</strong><br>在組織結構龐大的公司，有時開會的結論<mark>不是看哪個部門的方案比較好</mark>；而是看出席會議的長官中，<mark>誰的階級比較大</mark>。</li>
<li><strong>處理方式</strong>
<ul>
<li>
<p><strong>先了解會議出席人員</strong><br>會有高階長官出席的會議不可能沒有會議通知；筆者會先向上級<mark>詢問這些長官在開會時的習性</mark>，因為有些長官會用階級來判斷你是否有跟他說話的資格。</p>
</li>
<li>
<p><strong>獲得相同話語權</strong><br>如果不幸遇到只看階級說話的長官，我會請主管<mark>邀請能與之抗衡的長官</mark>在會議列席，這樣在溝通上才有<mark>平等的話語權</mark>。</p>
<blockquote>
<p>直接請一個擁有相同話語權的人，會遠比自己以下犯上好很多；這裡是現實社會，不要以為自己是故事的主角。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="15-開會冗長且不斷偏離主題">1.5 開會冗長且不斷偏離主題<a class="h-anchor" href="#15-%e9%96%8b%e6%9c%83%e5%86%97%e9%95%b7%e4%b8%94%e4%b8%8d%e6%96%b7%e5%81%8f%e9%9b%a2%e4%b8%bb%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>事件描述</strong><br>相信大家都有這個經驗，我們為了 A 的議題開會，但常常<mark>會開到一半就冒出 B、C、D 的延伸議題</mark>；不但造成會議時間大幅延長，有時甚至在會議結束後<mark>連 A 的議題都沒有結論</mark>。</li>
<li><strong>處理方式</strong>
<ul>
<li><strong>以會議大綱為討論主軸</strong><br><mark>很多會議只有主題沒有大綱</mark>，這造成了很多參與人員根本沒搞清楚開會目的，所以偏移主題實屬正常。<br>如果在會議前就有把<mark>大綱發給與會人員</mark>，並在會議開始時<mark>先說明要解決的議題</mark>；把情境限制住後，就能減少非常多的延伸議題。</li>
<li><strong>延伸議題在其他會議討論</strong><br>開會過程中產生延伸議題實屬正常，但不建議在同一場會議中討論新的議題；除了會議<mark>容易失焦</mark>外，會議的時間延長也會使人<mark>無法集中精神</mark>。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="2-描述一下你在這份工作中擔任的角色負責的任務">2. 描述一下你在這份工作中擔任的角色、負責的任務<a class="h-anchor" href="#2-%e6%8f%8f%e8%bf%b0%e4%b8%80%e4%b8%8b%e4%bd%a0%e5%9c%a8%e9%80%99%e4%bb%bd%e5%b7%a5%e4%bd%9c%e4%b8%ad%e6%93%94%e4%bb%bb%e7%9a%84%e8%a7%92%e8%89%b2%e8%b2%a0%e8%b2%ac%e7%9a%84%e4%bb%bb%e5%8b%99" aria-label="這個段落的連結"></a></h2>
<p>這塊原則上都是<mark>依照個人經歷敘述</mark>，面試官會挑感興趣的部分做深入詢問；下面是筆者統整常見的問題以及個人的回答方式。</p>
<h3 class="h-nomark" id="21-敘述本職工作">2.1 敘述本職工作<a class="h-anchor" href="#21-%e6%95%98%e8%bf%b0%e6%9c%ac%e8%81%b7%e5%b7%a5%e4%bd%9c" aria-label="這個段落的連結"></a></h3>
<p>目前擔任 Backend lead 並兼任專案經理。</p>
<ul>
<li><strong>Backend lead</strong><br>對程式 Code Review、思考專案優化方向、負責新人的教育訓練。</li>
<li><strong>專案經理</strong><br>與產品經理溝通了解需求，規劃專案時程、分配工作以及追蹤進度。</li>
</ul>
<p><mark>對內，讓團隊工程師實力成長，提升部門價值；對外，讓產品符合客戶需求，提升公司價值。</mark></p>
<blockquote>
<p><strong>備註</strong>：其實跟自我介紹很像，說出面試官可能會感興趣的<code>關鍵字</code>。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="22-與產品經理溝通">2.2 與產品經理溝通<a class="h-anchor" href="#22-%e8%88%87%e7%94%a2%e5%93%81%e7%b6%93%e7%90%86%e6%ba%9d%e9%80%9a" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>如果多項任務要完成</strong><br>我會先詢問每項任務的 Priority、要完成的時間；並初估開發所需時間以及人力資源配置。</li>
<li><strong>需要跨團隊合作的任務</strong><br>我會建議產品經理找相關的 key man 一起討論，避免雙方的認知差異，導致最終結果不符預期。</li>
<li><strong>遇到不熟悉的需求與技術</strong><br>如果碰上不熟悉的領域，我會先研究完這方面的資訊後，再跟產品經理說我預估的時程，而不會在一開始就直接承諾。</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-處理主管交代的事情">2.3 處理主管交代的事情<a class="h-anchor" href="#23-%e8%99%95%e7%90%86%e4%b8%bb%e7%ae%a1%e4%ba%a4%e4%bb%a3%e7%9a%84%e4%ba%8b%e6%83%85" aria-label="這個段落的連結"></a></h3>
<p>大部分的<mark>主管只會交代方向</mark>，比如：「幫忙規劃下個階段的企劃書。」</p>
<p><mark>如果在不了解方向、時程、人力的狀態下直接開工，有大機率最後的成果與主管心中的答案不符</mark>，所以在收到任務時，我會先確認自己的思路是否與主管一致，舉例來說：</p>
<ul>
<li>
<p><strong>方向</strong><br>之前跟產品經理開會時，他有提出幾項客戶期待優化的需求，您看是否以這個為改版的主軸；其次再規劃新的功能。</p>
</li>
<li>
<p><strong>時程</strong><br>我預計在週四早上將相關規劃整理好交給您，請問這個時間點可以嗎？</p>
<blockquote>
<p>記得<mark>盡量避免在週五以及平日下班前交企劃</mark>，這個時間點交企劃是希望主管加班嗎？</p>
</blockquote>
</li>
<li>
<p><strong>人力</strong><br>這次規劃的功能在人力資源上，除了團隊內部成員外，還需要與 XXX 部門合作；我這邊會先向他們的主管詢問方便合作的 schedule。</p>
</li>
</ul>
<p>有時主管在指派任務時也沒有太多想法，所以要<mark>多做溝通釐清需求</mark>；如果時間允許，筆者會<mark>在初期提供幾個方案讓主管評估</mark>，否則加班加點到最後弄出來的產品不是公司需要的就真的悲劇了。</p>
<hr>
<h3 class="h-nomark" id="24-分配任務給組員">2.4 分配任務給組員<a class="h-anchor" href="#24-%e5%88%86%e9%85%8d%e4%bb%bb%e5%8b%99%e7%b5%a6%e7%b5%84%e5%93%a1" aria-label="這個段落的連結"></a></h3>
<p>如果你擔任專案經理，<mark>上面交代給你的任務可能很模糊，但你向下分配的時候可不能模糊</mark>。</p>
<p>千萬不要把組員當成你心中的蛔蟲，指派任務時都要有明確的需求規格，像是：</p>
<ul>
<li>需求來源</li>
<li>需求描述</li>
<li>影響範圍</li>
<li>驗收標準</li>
<li>預估工作天數</li>
<li>負責組員</li>
</ul>
<p><mark>如果需求規格寫的不夠明確，除了組員會時常跑來問你問題外，還可能做出完全無法使用的成果</mark>；儘管這個階段很費力氣，但真的要寫仔細，並且確認組員都明白自己要做的事。</p>
<blockquote>
<p>每個工程師的工作效率相差很大，但在分配工作時還是要盡量做到平衡；<mark>不要讓能者過勞的狀況頻繁發生</mark>。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="25-分享印象最深刻的專案">2.5 分享印象最深刻的專案<a class="h-anchor" href="#25-%e5%88%86%e4%ba%ab%e5%8d%b0%e8%b1%a1%e6%9c%80%e6%b7%b1%e5%88%bb%e7%9a%84%e5%b0%88%e6%a1%88" aria-label="這個段落的連結"></a></h3>
<p>在我<mark>入職 3 年後</mark>，很幸運擔任一個<mark>千萬級別專案</mark>的負責人；除了撰寫程式外，<mark>需求訪談、時程規劃、易用性測試、教育訓練</mark>等工作都需要由我來做安排。</p>
<p>當時這個專案主要的使用者年齡落在 40~60 歲，我需要設計一個系統取代過去他們用紙本及 Excel 的作業流程；但是在<mark>溝通以及開發的過程是非常挫折的</mark>，因為網頁系統對他們來說是一個很陌生的東西，他們並不清楚自己要的是什麼；而當時的我只有開發經驗，對需求與規劃一竅不通，因此處處碰壁；再向周圍的前輩請教後才知道<mark>在前期可以先透過 Mind-Map 了解客戶需求，並利用 Wireframe 向客戶說明規劃</mark>。</p>
<p>不過 Wireframe 跟真實系統畢竟還是兩個概念，在<mark>系統進入第一次易用性測試後就是噩夢的開始</mark>；因為當時的流程規劃是跟承辦人還有一些長官討論出來的，但他們<mark>並不是這個系統的主要使用人員</mark>；導致系統在易用性測試時發現並不符合真實使用者的需求。</p>
<p>因此在易用性測試後，<code>專案需求大幅更動，但截止日期沒有改變，且時程只剩下 1/3</code>；我在這個時機點做了一個冒險的決定：「<mark>導入 Scrum 機制。</mark>」以此充分了解每個團隊成員的進度、遇到的困難、需要什麼幫助。在完成每個 Sprint 後，會請客戶試用確保符合需求，最後順利<mark>在時程內完成系統驗收</mark>。</p>
<p>一開始在承接這個案子的時候，我覺得他們有很多行為是無理取鬧；但到後來我體悟到：「<mark>只有你真正在乎的工作才會去爭辯，如果你不在乎大可讓他隨便。</mark>」所以我把自己的角色重新定位，帶著更多的同理心，從對方的視角去看問題，最後討論出來的答案往往是<mark>藉由工程師的專業以及他們的經驗揉合出來的成果</mark>。</p>
<p>一開始承接這個案子的時候我整天被客戶罵，但是在最後結案時，客戶主動打電話到公司向老闆稱讚我：「做事認真負責、工作效率高、溝通技巧良好；多虧了他才做出我們想要的系統。」</p>
<p><mark>從一開始的四處碰壁，到最後得到甲方的尊重與認同；我現在回想起這段經歷，雖然苦澀但還是非常驕傲。</mark></p>
<ul>
<li><strong>故事需要包含的元素</strong><br>筆者選擇用說故事的方式來回答這個問題，這邊整理故事中包含的元素。
<ul>
<li>發生時間</li>
<li>你擔任的角色</li>
<li>遇到了什麼困難</li>
<li>解決困難的方式</li>
<li>自己從這個專案中學到了什麼</li>
<li>客戶對你的回饋</li>
<li>自己對這份專案的感悟</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="3-常見問題">3. 常見問題<a class="h-anchor" href="#3-%e5%b8%b8%e8%a6%8b%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-如果把事情搞砸了你會怎麼辦">3.1 如果把事情搞砸了，你會怎麼辦？<a class="h-anchor" href="#31-%e5%a6%82%e6%9e%9c%e6%8a%8a%e4%ba%8b%e6%83%85%e6%90%9e%e7%a0%b8%e4%ba%86%e4%bd%a0%e6%9c%83%e6%80%8e%e9%ba%bc%e8%be%a6" aria-label="這個段落的連結"></a></h3>
<p>我會明確說明自己在什麼時間點能夠解決，並盡力在預估的時間點前解決任務。</p>
<blockquote>
<p><strong>備註 1</strong>：第一時間千萬不要找理由搪塞，這只會讓主管更火；他此時關注的只有<mark>什麼時候可以解決問題</mark>。</p>
</blockquote>
<blockquote>
<p><strong>備註 2</strong>：如果你真的是因為有其他的重要的事耽擱了，請<mark>把任務完成後</mark>再去跟主管解釋，此時的主管才聽得進去。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="32-如果跟主管意見衝突如何處置">3.2 如果跟主管意見衝突如何處置<a class="h-anchor" href="#32-%e5%a6%82%e6%9e%9c%e8%b7%9f%e4%b8%bb%e7%ae%a1%e6%84%8f%e8%a6%8b%e8%a1%9d%e7%aa%81%e5%a6%82%e4%bd%95%e8%99%95%e7%bd%ae" aria-label="這個段落的連結"></a></h3>
<p>不同職位看事情的角度也會不一樣，我會<mark>先做一個聆聽者了解主管的想法</mark>，畢竟主管的經驗通常比我豐富，他會選擇這個做法可能是有一些我沒有考慮到的問題。</p>
<p>在了解意見的全貌後，如果我認為自己的想法有更多好處，我會<mark>先用詢問的方式，問看看主管有沒有考慮到這一塊</mark>，畢竟一個人能想到的內容是有極限的，透過一些思維的碰撞可以彌補彼此欠缺的地方，我相信<mark>有效的溝通是建立在雙方願意聆聽彼此的想法上面</mark>。</p>
<hr>
<h3 class="h-nomark" id="33-如果擔任專案經理你會如何與多個部門溝通制定共同遵守的流程">3.3 如果擔任專案經理，你會如何與多個部門溝通，制定共同遵守的流程<a class="h-anchor" href="#33-%e5%a6%82%e6%9e%9c%e6%93%94%e4%bb%bb%e5%b0%88%e6%a1%88%e7%b6%93%e7%90%86%e4%bd%a0%e6%9c%83%e5%a6%82%e4%bd%95%e8%88%87%e5%a4%9a%e5%80%8b%e9%83%a8%e9%96%80%e6%ba%9d%e9%80%9a%e5%88%b6%e5%ae%9a%e5%85%b1%e5%90%8c%e9%81%b5%e5%ae%88%e7%9a%84%e6%b5%81%e7%a8%8b" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>STEP 1</strong>：我會先找合作部門的負責人協調，確認流程可行性與需要調整的地方。</li>
<li><strong>STEP 2</strong>：各部門負責人都認可流程後，我們會各自向部門成員再次確認是否有不周之處。</li>
<li><strong>STEP 3</strong>：各部門成員都接受後，我會開一個正式會議來公佈，以取得「全員共識」。</li>
<li><strong>STEP 4</strong>：在會議後發佈「正式流程」。</li>
</ul>
<hr>
<h3 class="h-nomark" id="34-公司有跑-scrum-嗎你們是如何運作的">3.4 公司有跑 Scrum 嗎？你們是如何運作的？<a class="h-anchor" href="#34-%e5%85%ac%e5%8f%b8%e6%9c%89%e8%b7%91-scrum-%e5%97%8e%e4%bd%a0%e5%80%91%e6%98%af%e5%a6%82%e4%bd%95%e9%81%8b%e4%bd%9c%e7%9a%84" aria-label="這個段落的連結"></a></h3>
<p>在我們公司<mark>每個人都需要當 Scrum Master</mark>；如果只專注在自己的任務上，有時會偏離專案的方向。</p>
<p>我認為這樣跑 Scrum 的好處有：</p>
<ul>
<li>培養從團隊的角度來看專案</li>
<li>快速回顧每個人的工作</li>
<li>解決成員卡住的問題</li>
<li>在當下確認任務，並協商出一個完成時間</li>
<li>每個 Sprint 結束後去思考有什麼部分可以再改進</li>
</ul>
<p>開 Scrum 時會要求每個人說明以下內容（盡量控制在 3 分鐘內）：</p>
<ul>
<li>昨天完成了哪些任務</li>
<li>有卡在哪些問題</li>
<li>今天要完成的事情</li>
<li>有需要哪些部門、同事的協作</li>
</ul>
<hr>
<h3 class="h-nomark" id="35-狀況題公司高層突然在週五下班前要求提前部署你會如何應對">3.5 [狀況題]公司高層突然在週五下班前要求提前部署，你會如何應對？<a class="h-anchor" href="#35-%e7%8b%80%e6%b3%81%e9%a1%8c%e5%85%ac%e5%8f%b8%e9%ab%98%e5%b1%a4%e7%aa%81%e7%84%b6%e5%9c%a8%e9%80%b1%e4%ba%94%e4%b8%8b%e7%8f%ad%e5%89%8d%e8%a6%81%e6%b1%82%e6%8f%90%e5%89%8d%e9%83%a8%e7%bd%b2%e4%bd%a0%e6%9c%83%e5%a6%82%e4%bd%95%e6%87%89%e5%b0%8d" aria-label="這個段落的連結"></a></h3>
<p>並不是高層交代下來的事情都必須答應，如果情境特殊，應該要先思考幾個問題：</p>
<ul>
<li>這個任務是不是你一個人就能解決的？</li>
<li>如果搞砸了，會不會需要其他人的協助？會不會影響到公司？</li>
<li>這個任務一定要在這個時間點處理嗎？有什麼特別的理由？</li>
<li>如果按造原定計畫執行，會造成什麼損失？</li>
</ul>
<p>筆者認為<mark>所有問題都是可以討論的，不要一開始就給自己否定答案</mark>；像是這個狀況題，如果真的發生意外，後果可能非常很嚴重，讓我們看看不同選擇帶來的後續影響：</p>
<ul>
<li><strong>接受</strong><br>如果順利部署那當然是皆大歡喜，但萬一不順利呢？
<ul>
<li>
<p><strong>注意力不集中</strong><br>今天是<mark>禮拜五而且快要下班了，你敢說自己還有 100% 的注意力</mark>在部署上？<br>如果中間漏掉某個步驟、少了一個指令都可能導致部署失敗。</p>
<blockquote>
<p>若沒有 IaC（Infrastructure-as-code），在這個時間點部署根本是賭運氣；就算有，筆者也不建議在這個時間點部署。</p>
</blockquote>
</li>
<li>
<p><strong>影響到同事</strong><br>如果部署失敗，<mark>問題你無法一個人解決</mark>，但已經到下班時間了；你可能晚上沒事，但你的同事呢？他們會不會有約會？<br>大家急迫想下班的心情，可能會使洞越補越大，最後甚至變成<mark>整個部門留下來加班</mark>。</p>
</li>
<li>
<p><strong>沒有太多好處</strong><br>你部署成功，高層只會認為<mark>這是應該的</mark>，對整體職涯沒有太大幫助。<br>但萬一出錯，或是假日才察覺到錯誤，那這件事對你在這間公司的職涯可說是<mark>毀滅性打擊</mark>。</p>
<blockquote>
<p>完全沒必要承擔高風險低報酬的任務。</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>拒絕</strong><br>當然<mark>不能用快要下班了這種理由拒絕</mark>，我們可以換個說法。
<ul>
<li>
<p><strong>詢問提前部署原因</strong><br>是因為客戶在假日有急迫的使用需求嗎？如果在下週一早上部署會造成什麼損失嗎？</p>
<blockquote>
<p>有時提前部署這件事，只是因為高層想要炫一波，並沒有特別原因。</p>
</blockquote>
</li>
<li>
<p><strong>說明按造原定計劃的好處</strong><br>如果在周一部署有這些好處：「<mark>系統穩定性高、可有效追蹤部署後的狀況、如果發生意外可以盡快處理</mark>。」</p>
</li>
</ul>
</li>
</ul>
<hr>
<p>本篇文章匯集了筆者與朋友在面試、工作上的應對進退，希望文章的內容對大家有幫助，也歡迎讀者在留言區分享自己的經驗。</p>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-24/images/fullstack-career-notes-day-24-01.webp" width="1024" height="233" alt="3.5 \[狀況題\]公司高層突然在週五下班前要求提前部署，你會如何應對？（圖 1）" fetchpriority="high" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][白板題]設計一個簡易的抽獎程式</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-23/</link>
      <pubDate>Fri, 08 Oct 2021 08:34:07 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-23/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>相比於系統設計，白板題往往需要寫出能夠運行的程式，或者提供面試官認為可行性高的演算法；大部分的白板題難度會落在 LeeCode Easy 的等級，有程式基礎的人基本上都能夠給出答案，但除了給出答案外，程式的正確率與執行效率也會列入評分。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-23/images/cover_hu_30d0e6a3c38c3069.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>白板題跟系統設計問題的相同點，就是重視<mark>釐清問題</mark>與<code>溝通</code>。</p>
</blockquote>
<p>相比於系統設計，白板題往往需要寫出<mark>能夠運行的程式</mark>，或者提供面試官認為<mark>可行性高的演算法</mark>；大部分的白板題難度會落在 LeeCode Easy 的等級，有程式基礎的人基本上都能夠給出答案，但除了給出答案外，程式的<mark>正確率與執行效率</mark>也會列入評分。</p>
<p>本篇文章筆主要是分享<mark>答題思路</mark>以及<mark>完善程式</mark>，演算法並非筆者所擅長；如果想提升演算法的能力，除了買一本書好好閱讀外，同時也要去 LeetCode 刷題累積實戰經驗。</p>
<blockquote>
<p>有些公司是考 LeeCode Medium/Hard 等級的題目，除非天賦異稟；否則這個難度需要花大量時間（可能半年以上）練習，才能在現場穩定發揮。</p>
</blockquote>
<hr>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>初探白板題</strong>
<ul>
<li>1.1 白板題的類型</li>
<li>1.2 過程有時比解答更重要</li>
</ul>
</li>
<li><strong>設計一個簡易的抽獎程式</strong>
<ul>
<li>2.1 問題敘述</li>
<li>2.2 面試官為什麼會問？</li>
<li>2.3 面試官想從答案確認什麼？</li>
<li>2.4 筆者提供的簡答</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 如果使用者輸入的機率有小數點呢？</li>
<li>3.2 你有考慮到使用者可能輸入不合理的機率嗎？</li>
<li>3.3 你會用什麼方式來驗證程式的正確性？</li>
<li>3.4 留給讀者優化與思考的問題</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-初探白板題">1. 初探白板題<a class="h-anchor" href="#1-%e5%88%9d%e6%8e%a2%e7%99%bd%e6%9d%bf%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-白板題的類型">1.1 白板題的類型<a class="h-anchor" href="#11-%e7%99%bd%e6%9d%bf%e9%a1%8c%e7%9a%84%e9%a1%9e%e5%9e%8b" aria-label="這個段落的連結"></a></h3>
<p>白板題大概分成以下幾種形式：</p>
<ul>
<li>
<p><strong>在白板回答</strong><br>通常用白板回答的只要寫出演算法跟架構就好。</p>
<blockquote>
<p>相較於其他形式，更注重與面試官的「溝通」與「確認問題」。</p>
</blockquote>
</li>
<li>
<p><strong>紙上作業</strong><br>用來觀察求職者在沒有 IDE 輔助下，是否具備寫程式的能力。</p>
<blockquote>
<p>現在的 IDE 實在太方便了，函式會自動補全、錯誤會直接 Highlight，很多工程師脫離 IDE 後真的是武功全廢。</p>
</blockquote>
</li>
<li>
<p><strong>上機考</strong><br>通常會給你一個時間限制，要求你在時間內寫出一個可以 Run 的 MVP；大多數的面試官會看著你寫 Code 的過程。</p>
<blockquote>
<p>有些人會因為不適應有人盯著寫程式，導致現場發揮失常。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-解題過程比解答更重要">1.2 解題過程比解答更重要<a class="h-anchor" href="#12-%e8%a7%a3%e9%a1%8c%e9%81%8e%e7%a8%8b%e6%af%94%e8%a7%a3%e7%ad%94%e6%9b%b4%e9%87%8d%e8%a6%81" aria-label="這個段落的連結"></a></h3>
<p>也許你已經非常熟悉 LeeCode 的解題模式；但白板題跟 LeeCode 不同的地方在於<mark>通常沒有測資、題目相對模糊</mark>。</p>
<p>如果你接到題目就直接解題，可能會<mark>忽略一些隱性條件</mark>；在了解題目時如果發現模糊地帶，請與<mark>面試官釐清問題</mark>，有時自己想像出來的條件反而會增加題目的複雜度。</p>
<p>通常在你給出第一版的解答後，面試官會以你的解答為基礎<mark>循序漸進問一些延伸性的問題</mark>，這部分除了考核程式能力外，也是判斷求職者在開發過程中<mark>能否與團隊成員溝通</mark>；畢竟隨著專案規模的擴增，單打獨鬥的情境已不多見。</p>
<p>如果遇上你無法解決的白板題，請以積極的態度<mark>與面試官溝通你想到的解題方式和卡住的點</mark>；相比於求職者的程式能力，有些面試官更加看重求職者的<mark>人格特質</mark>；有些人也許程式能力不足，但能理解面試官的問題，並在討論過程中作出改善；若能在解題過程中被面試官<mark>視為有潛力的人才</mark>，也是有機會獲得 Offer 的。</p>
<blockquote>
<p><strong>備註 1</strong>：解題過程中並不是所有面試官都是以「討論」的形式與你溝通，有些是以「質問」的態度面對求職者；遇到用「質問」態度的面試官，你也要思考<mark>自己是否能夠與這樣的同事一起共事</mark>。</p>
</blockquote>
<blockquote>
<p><strong>備註 2</strong>：白板題需要有良好的程式基礎與抗壓性，才可能在面試現場表現優良。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="2-設計一個簡易的抽獎程式">2. 設計一個簡易的抽獎程式<a class="h-anchor" href="#2-%e8%a8%ad%e8%a8%88%e4%b8%80%e5%80%8b%e7%b0%a1%e6%98%93%e7%9a%84%e6%8a%bd%e7%8d%8e%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h2>
<p>為了方便讀者閱讀與驗證，本篇文章以<code>上機考</code>的形式向大家分享。</p>
<h3 class="h-nomark" id="21-問題敘述">2.1 問題敘述<a class="h-anchor" href="#21-%e5%95%8f%e9%a1%8c%e6%95%98%e8%bf%b0" aria-label="這個段落的連結"></a></h3>
<p>請你設計一個抽獎系統，獎品有「一獎、二獎、三獎」，其餘都是感謝獎。</p>
<p>一獎、二獎、三獎的中獎機率可以自由輸入，若沒輸入則默認一獎（1%）、二獎（2%）、三獎（3%）。</p>
<p><strong>時間限制</strong>：5 分鐘</p>
<hr>
<h3 class="h-nomark" id="22-面試官為什麼會問">2.2 面試官為什麼會問？<a class="h-anchor" href="#22-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<p>這個題目雖然簡單，卻有很多<mark>延伸的議題可以討論</mark>；是一個瞭解求職者<code>基本功</code>的經典題目。</p>
<hr>
<h3 class="h-nomark" id="23-面試官想從答案確認什麼">2.3 面試官想從答案確認什麼？<a class="h-anchor" href="#23-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>你的程式是否保留擴充空間</li>
<li>你會用什麼演算法解決這個問題</li>
<li>是否有驗證使用者輸入的參數</li>
<li>如何驗證你的程式是符合需求的</li>
</ul>
<hr>
<h3 class="h-nomark" id="24-筆者提供的簡答">2.4 筆者提供的簡答<a class="h-anchor" href="#24-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>在面對白板題時，筆者是<mark>先求有再求好</mark>；如果一昧追求高效但自己不熟悉的演算法，有時會有意想不到的錯誤。</p>
<p>筆者的邏輯是先把籤放入籤筒，再透過亂數抽獎印出結果，以符合題目的基礎要求為目標：</p>
<ul>
<li>使用者可以透過<mark>調整參數</mark>變更中獎機率。</li>
<li>有<code>默認</code>的中獎機率：一獎（1%）、二獎（2%）、三獎（3%）。</li>
<li>用 <code>Math.random()</code> 函式模擬亂數抽獎。</li>
<li>會印出<mark>中獎結果</mark>。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// 寫一個函式，有默認的抽獎機率：一獎(1%)、二獎(2%)、三獎(3%)
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> lottery(first_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>, second_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2</span>, third_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">3</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> lickbox <span style="color:#ff7b72;font-weight:bold">=</span> [];
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 放入籤筒
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">var</span> i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>; i <span style="color:#ff7b72;font-weight:bold">&lt;</span> <span style="color:#a5d6ff">100</span>; i<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (first_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;一獎&#34;</span>);
</span></span><span style="display:flex;"><span>      first_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (second_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;二獎&#34;</span>);
</span></span><span style="display:flex;"><span>      second_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (third_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;三獎&#34;</span>);
</span></span><span style="display:flex;"><span>      third_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;感謝獎&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 抽獎
</span></span></span><span style="display:flex;"><span>  console.log(lickbox[Math.floor(Math.random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">99</span>)]);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>lottery();
</span></span></code></pre></div><p>上面的程式先不論演算法優劣，<mark>還有許多細節是沒有被考慮到的</mark>；在完成 MVP 後有些面試官會先問你：「之前有寫過類似的程式嗎？」如果沒有相關經驗就老實說沒有吧；接下來面試官會以這個程式為基礎，跟你討論哪裡還可以做改善。</p>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. 衍伸問題<a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-如果使用者輸入的機率有小數點呢">3.1 如果使用者輸入的機率有小數點呢？<a class="h-anchor" href="#31-%e5%a6%82%e6%9e%9c%e4%bd%bf%e7%94%a8%e8%80%85%e8%bc%b8%e5%85%a5%e7%9a%84%e6%a9%9f%e7%8e%87%e6%9c%89%e5%b0%8f%e6%95%b8%e9%bb%9e%e5%91%a2" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：求職者對浮點數的處理</mark></p>
<p>很顯然，如果使用者輸入的機率包含小數點，現在的函式無法應付，下面是筆者的處理方式：</p>
<ul>
<li>取得輸入得獎機率中<mark>最長的小數點</mark>有幾位（N）。</li>
<li>將所有得獎機率乘以 10 的 N 次方，這樣機率<mark>就沒有小數點</mark>了。</li>
<li>將 10 的 N 次方再乘以 100，以此為<mark>籤筒的總數量</mark>。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> lottery(first_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>, second_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2</span>, third_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">3</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 取得最長小數點
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> tmp <span style="color:#ff7b72;font-weight:bold">=</span> [first_prize, second_prize, third_prize];
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> longest <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;
</span></span><span style="display:flex;"><span>  tmp.forEach((prize) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> after_point <span style="color:#ff7b72;font-weight:bold">=</span> prize.toString().split(<span style="color:#a5d6ff">&#34;.&#34;</span>)[<span style="color:#a5d6ff">1</span>];
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (after_point) {
</span></span><span style="display:flex;"><span>      <span style="color:#ff7b72">if</span> (after_point.length <span style="color:#ff7b72;font-weight:bold">&gt;</span> longest) {
</span></span><span style="display:flex;"><span>        longest <span style="color:#ff7b72;font-weight:bold">=</span> after_point.length;
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 讓機率變整數
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> multiple <span style="color:#ff7b72;font-weight:bold">=</span> Math.pow(<span style="color:#a5d6ff">10</span>, longest);
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 沒有用 Math.floor 會出現 js 小數點結尾的 bug，ex:11100.000000000002
</span></span></span><span style="display:flex;"><span>  first_prize <span style="color:#ff7b72;font-weight:bold">=</span> Math.floor(first_prize <span style="color:#ff7b72;font-weight:bold">*</span> multiple);
</span></span><span style="display:flex;"><span>  second_prize <span style="color:#ff7b72;font-weight:bold">=</span> Math.floor(second_prize <span style="color:#ff7b72;font-weight:bold">*</span> multiple);
</span></span><span style="display:flex;"><span>  third_prize <span style="color:#ff7b72;font-weight:bold">=</span> Math.floor(third_prize <span style="color:#ff7b72;font-weight:bold">*</span> multiple);
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 籤筒的總數量
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> max <span style="color:#ff7b72;font-weight:bold">=</span> multiple <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">100</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> lickbox <span style="color:#ff7b72;font-weight:bold">=</span> [];
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 放入籤筒
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">var</span> i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>; i <span style="color:#ff7b72;font-weight:bold">&lt;</span> max; i<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (first_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;一獎&#34;</span>);
</span></span><span style="display:flex;"><span>      first_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (second_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;二獎&#34;</span>);
</span></span><span style="display:flex;"><span>      second_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (third_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;三獎&#34;</span>);
</span></span><span style="display:flex;"><span>      third_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;感謝獎&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 抽獎
</span></span></span><span style="display:flex;"><span>  console.log(lickbox[Math.floor(Math.random() <span style="color:#ff7b72;font-weight:bold">*</span> (max <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>))]);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>lottery(<span style="color:#a5d6ff">1.11</span>, <span style="color:#a5d6ff">2.34</span>, <span style="color:#a5d6ff">3.567</span>);
</span></span></code></pre></div><hr>
<h3 class="h-nomark" id="32-你有考慮到使用者可能輸入不合理的機率嗎">3.2 你有考慮到使用者可能輸入不合理的機率嗎？<a class="h-anchor" href="#32-%e4%bd%a0%e6%9c%89%e8%80%83%e6%85%ae%e5%88%b0%e4%bd%bf%e7%94%a8%e8%80%85%e5%8f%af%e8%83%bd%e8%bc%b8%e5%85%a5%e4%b8%8d%e5%90%88%e7%90%86%e7%9a%84%e6%a9%9f%e7%8e%87%e5%97%8e" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：能舉例會發生錯誤的情境，並設計程式來驗證傳入的參數</mark></p>
<p>如果沒有對輸入參數做驗證，程式的穩定性真的是極低，下面列舉幾個要做的基礎驗證：</p>
<ul>
<li><mark>中獎機率不可能超過 100%</mark>，如果一獎、二獎、三獎加起來超過 100 是不合理的。</li>
<li><mark>中獎機率要是正數</mark>，輸入負的機率也是不合理。</li>
<li>如果參數輸入<mark>文字或是符號就一定是錯的</mark>。</li>
</ul>
<p>這邊筆者透過撰寫一個函式（validate_input）來驗證輸入的參數，如果<mark>不符規則就會跳出錯誤訊息</mark>。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> validate_input(first_prize, second_prize, third_prize) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 確認是否為正數
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">var</span> reg <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">/^(?=.+)(?:[1-9]\d*|0)?(?:\.\d+)?$/</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>(reg.test(first_prize) <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> reg.test(second_prize) <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> reg.test(third_prize))) {
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">&#34;請確認輸入參數皆為正數！&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 確認沒爆表
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">if</span> (first_prize <span style="color:#ff7b72;font-weight:bold">+</span> second_prize <span style="color:#ff7b72;font-weight:bold">+</span> third_prize <span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#a5d6ff">100</span>) {
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">&#34;中獎機率超過 100 %，爆表了！&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> argsArray <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>  [<span style="color:#a5d6ff">100</span>, <span style="color:#a5d6ff">2.34</span>, <span style="color:#a5d6ff">3.567</span>], <span style="color:#8b949e;font-style:italic">//中獎率不可能超過100
</span></span></span><span style="display:flex;"><span>  [<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">2.34</span>, <span style="color:#a5d6ff">3.567</span>], <span style="color:#8b949e;font-style:italic">//機率不可為負
</span></span></span><span style="display:flex;"><span>  [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">&#34;錯誤&#34;</span>, <span style="color:#a5d6ff">3.567</span>], <span style="color:#8b949e;font-style:italic">//含有文字
</span></span></span><span style="display:flex;"><span>  [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">2.34</span>, <span style="color:#a5d6ff">3.567</span>],
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>argsArray.forEach((args) =&gt; {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;驗證: &#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> args);
</span></span><span style="display:flex;"><span>  validate_input.apply(<span style="color:#ff7b72">this</span>, args);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><hr>
<h3 class="h-nomark" id="33-你會用什麼方式來驗證程式的正確性">3.3 你會用什麼方式來驗證程式的正確性？<a class="h-anchor" href="#33-%e4%bd%a0%e6%9c%83%e7%94%a8%e4%bb%80%e9%ba%bc%e6%96%b9%e5%bc%8f%e4%be%86%e9%a9%97%e8%ad%89%e7%a8%8b%e5%bc%8f%e7%9a%84%e6%ad%a3%e7%a2%ba%e6%80%a7" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：是否有自動化的測試方案來驗證程式</mark></p>
<p>程式的正確性並不是用嘴說的，面試官想了解你會用什麼方法<mark>驗證程式的正確性</mark>。</p>
<p>筆者這邊會寫一個測試函式來蒐集抽獎結果，並對原本程式做如下調整：</p>
<ul>
<li>籤筒會反覆使用，所以把它獨立成函式（initLickbox）。</li>
<li>lottery 函式改為純粹抽籤用。</li>
<li>建立測試函式（qa），可調整測試次數，在執行時會印出實際機率與期望機率的比對。</li>
</ul>
<p>這裡附上調整過的完整程式：</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> initLickbox(first_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>, second_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2</span>, third_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">3</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 先驗證輸入參數
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>validate_input(first_prize, second_prize, third_prize)) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 取得最長小數點
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> tmp <span style="color:#ff7b72;font-weight:bold">=</span> [first_prize, second_prize, third_prize];
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> longest <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;
</span></span><span style="display:flex;"><span>  tmp.forEach((prize) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> after_point <span style="color:#ff7b72;font-weight:bold">=</span> prize.toString().split(<span style="color:#a5d6ff">&#34;.&#34;</span>)[<span style="color:#a5d6ff">1</span>];
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (after_point) {
</span></span><span style="display:flex;"><span>      <span style="color:#ff7b72">if</span> (after_point.length <span style="color:#ff7b72;font-weight:bold">&gt;</span> longest) {
</span></span><span style="display:flex;"><span>        longest <span style="color:#ff7b72;font-weight:bold">=</span> after_point.length;
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 讓機率變整數
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> multiple <span style="color:#ff7b72;font-weight:bold">=</span> Math.pow(<span style="color:#a5d6ff">10</span>, longest);
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 沒有用 Math.floor 會出現 js 小數點結尾的 bug，ex:11100.000000000002
</span></span></span><span style="display:flex;"><span>  first_prize <span style="color:#ff7b72;font-weight:bold">=</span> Math.floor(first_prize <span style="color:#ff7b72;font-weight:bold">*</span> multiple);
</span></span><span style="display:flex;"><span>  second_prize <span style="color:#ff7b72;font-weight:bold">=</span> Math.floor(second_prize <span style="color:#ff7b72;font-weight:bold">*</span> multiple);
</span></span><span style="display:flex;"><span>  third_prize <span style="color:#ff7b72;font-weight:bold">=</span> Math.floor(third_prize <span style="color:#ff7b72;font-weight:bold">*</span> multiple);
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 籤筒的總數量
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> max <span style="color:#ff7b72;font-weight:bold">=</span> multiple <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">100</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> lickbox <span style="color:#ff7b72;font-weight:bold">=</span> [];
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 放入籤筒
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">var</span> i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>; i <span style="color:#ff7b72;font-weight:bold">&lt;</span> max; i<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (first_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;一獎&#34;</span>);
</span></span><span style="display:flex;"><span>      first_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (second_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;二獎&#34;</span>);
</span></span><span style="display:flex;"><span>      second_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (third_prize <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;三獎&#34;</span>);
</span></span><span style="display:flex;"><span>      third_prize<span style="color:#ff7b72;font-weight:bold">--</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>      lickbox.push(<span style="color:#a5d6ff">&#34;感謝獎&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> lickbox;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> lottery(lickbox <span style="color:#ff7b72;font-weight:bold">=</span> []) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> max <span style="color:#ff7b72;font-weight:bold">=</span> lickbox.length;
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 抽獎
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> lickbox[Math.floor(Math.random() <span style="color:#ff7b72;font-weight:bold">*</span> (max <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>))];
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> result;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> validate_input(first_prize, second_prize, third_prize) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 確認是否為正數
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">var</span> reg <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">/^(?=.+)(?:[1-9]\d*|0)?(?:\.\d+)?$/</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span>(reg.test(first_prize) <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> reg.test(second_prize) <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> reg.test(third_prize))) {
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">&#34;請確認輸入參數皆為正數！&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 確認沒爆表
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">if</span> (first_prize <span style="color:#ff7b72;font-weight:bold">+</span> second_prize <span style="color:#ff7b72;font-weight:bold">+</span> third_prize <span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#a5d6ff">100</span>) {
</span></span><span style="display:flex;"><span>    console.log(<span style="color:#a5d6ff">&#34;中獎機率超過 100 %，爆表了！&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">false</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> <span style="color:#79c0ff">true</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> qa(test_times <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">10000</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 設定中獎機率
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> first_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.5</span>,
</span></span><span style="display:flex;"><span>    second_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2.33</span>,
</span></span><span style="display:flex;"><span>    third_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">3.98</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> thanks_prize <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">100</span> <span style="color:#ff7b72;font-weight:bold">-</span> first_prize <span style="color:#ff7b72;font-weight:bold">-</span> second_prize <span style="color:#ff7b72;font-weight:bold">-</span> third_prize;
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> lickbox <span style="color:#ff7b72;font-weight:bold">=</span> initLickbox(first_prize, second_prize, third_prize);
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> first <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>,
</span></span><span style="display:flex;"><span>    second <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>,
</span></span><span style="display:flex;"><span>    third <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>,
</span></span><span style="display:flex;"><span>    thanks <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">var</span> i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>; i <span style="color:#ff7b72;font-weight:bold">&lt;</span> test_times; i<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> lottery(lickbox);
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (result <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#34;一獎&#34;</span>) {
</span></span><span style="display:flex;"><span>      first<span style="color:#ff7b72;font-weight:bold">++</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (result <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#34;二獎&#34;</span>) {
</span></span><span style="display:flex;"><span>      second<span style="color:#ff7b72;font-weight:bold">++</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (result <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#34;三獎&#34;</span>) {
</span></span><span style="display:flex;"><span>      third<span style="color:#ff7b72;font-weight:bold">++</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#ff7b72">else</span> {
</span></span><span style="display:flex;"><span>      thanks<span style="color:#ff7b72;font-weight:bold">++</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;一獎:&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> ((first <span style="color:#ff7b72;font-weight:bold">/</span> test_times) <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">100</span>).toFixed(<span style="color:#a5d6ff">2</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#34;% (&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> first_prize <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#34;%)&#34;</span>);
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;二獎:&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> ((second <span style="color:#ff7b72;font-weight:bold">/</span> test_times) <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">100</span>).toFixed(<span style="color:#a5d6ff">2</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#34;% (&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> second_prize <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#34;%)&#34;</span>);
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;三獎:&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> ((third <span style="color:#ff7b72;font-weight:bold">/</span> test_times) <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">100</span>).toFixed(<span style="color:#a5d6ff">2</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#34;% (&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> third_prize <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#34;%)&#34;</span>);
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;感謝獎:&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> ((thanks <span style="color:#ff7b72;font-weight:bold">/</span> test_times) <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">100</span>).toFixed(<span style="color:#a5d6ff">2</span>) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#34;% (&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> thanks_prize <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#34;%)&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>qa();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// console.log(lottery(initLickbox()))
</span></span></span></code></pre></div><p>圖片為程式測試的結果：</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-23/images/fullstack-career-notes-day-23-01.webp" width="976" height="145" alt="3.3 你會用什麼方式來驗證程式的正確性？（圖 1）" fetchpriority="high" decoding="async"></p>
<hr>
<h3 class="h-nomark" id="34-留給讀者優化與思考的問題">3.4 留給讀者優化與思考的問題<a class="h-anchor" href="#34-%e7%95%99%e7%b5%a6%e8%ae%80%e8%80%85%e5%84%aa%e5%8c%96%e8%88%87%e6%80%9d%e8%80%83%e7%9a%84%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p>上面只是筆者在面對白板題時的簡單發想，這邊還有一些問題留給讀者思考：</p>
<ul>
<li>需不需要限制浮點數在小數點後的位數？</li>
<li>筆者的解決方案超級土法煉鋼，有沒有更好的演算法可以優化？</li>
<li>如果改成抽籤不放回的機制，程式要如何調整？</li>
<li>假使有四獎、五獎、六獎&hellip;程式架構要如何優化？</li>
</ul>
<p>即使是抽獎系統這種看似簡單的題目，如果深入探討還是有許多的細節需要完善。</p>
<p>筆者上面所提出的<mark>衍伸問題</mark>，其實是求職者<mark>一開始要跟面試官釐清的細節</mark>，如果能夠在開始作業前就<mark>定義好需求</mark>，在開發的過程中就能<mark>避免不合適的設計</mark>。</p>
<p>不過有些面試官會傾向等求職者<mark>完成 MVP 後再做細節討論</mark>，筆者就曾遇過面試官出完白板題後就離開 15 分鐘讓求職者作答；所以實際狀況還是需要讀者在現場<mark>隨機應變</mark>。</p>
<hr>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-23/images/fullstack-career-notes-day-23-02.webp" width="1024" height="233" alt="3.4 留給讀者優化與思考的問題（圖 2）" loading="lazy" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][系統設計]如何設計一個像 Facebook 的社交平台</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-22/</link>
      <pubDate>Thu, 07 Oct 2021 08:35:20 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-22/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>其實前面 10 幾篇文章的內容都是系統設計的一環，只是問題的方向是從你過往的履歷、技術或是面試公司的需求出發，透過詢問細節來評核你技術的硬實力。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-22/images/cover_hu_fd12157e912393a9.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>擅長<del>嘴砲</del>溝通的人在這個環節有較多優勢。</p>
</blockquote>
<p>其實前面 10 幾篇文章的內容都是系統設計的一環，只是問題的方向是從你過往的履歷、技術或是面試公司的需求出發，透過詢問細節來評核你技術的硬實力。</p>
<p>再確認求職者的硬實力過關後，有些面試官會<mark>給你一個情境來做系統設計</mark>；然後依據你在這道題目上的應對進退，<mark>決定要給你 Junior 還是 Senior 的職稱</mark>。</p>
<blockquote>
<p>如果你面試的職缺是 Tech Lead，這題回答得不好有高機率直接出局。</p>
</blockquote>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>初探系統設計</strong>
<ul>
<li>1.1 Junior 跟 Senior 工程師的差異</li>
<li>1.2 系統設計的題目來源</li>
<li>1.3 面試官在找的是同事</li>
</ul>
</li>
<li><strong>如何設計一個像 Facebook 的社交平台</strong>
<ul>
<li>2.1 縮小問題範圍，確認具體需求</li>
<li>2.2 先提出一個簡單的解決方案</li>
<li>2.3 提出減少耗能的解決方案</li>
<li>2.4 從自己擅長的面向切入</li>
<li>2.5 不要有預設立場</li>
<li>2.6 主動提出不同設計的優缺點</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 你有使用過 Mind Map 跟 Sitemap 嗎？它們的使用時機是？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-初探系統設計">1. 初探系統設計<a class="h-anchor" href="#1-%e5%88%9d%e6%8e%a2%e7%b3%bb%e7%b5%b1%e8%a8%ad%e8%a8%88" aria-label="這個段落的連結"></a></h2>
<p>筆者在第一次遇到系統設計的問題時表現得很差，因為我把它當成一個技術性的問題，所以<mark>不停的在輸出自己的想法及觀點</mark>，面試完的當下還覺得自己表現的超棒，肯定會拿到 Offer。</p>
<p>果然在兩天後收到 Offer Get 的信件，但核薪結果沒有到達自己的期待；當時我並不了解自己在哪個環節犯了錯，於是向人資詢問主管對我的評價，我收到的評價是：「技術完全沒問題、經驗豐富、自我學習能力高；<mark>但在系統架構的設計上能力稍顯不足，主觀思維太多</mark>。」</p>
<p>在這之後我才知道面試有「系統設計」這個類型的題目，也明白當時的回答並不是面試官期待的答案。</p>
<h3 class="h-nomark" id="11-junior-跟-senior-工程師的差異">1.1 Junior 跟 Senior 工程師的差異<a class="h-anchor" href="#11-junior-%e8%b7%9f-senior-%e5%b7%a5%e7%a8%8b%e5%b8%ab%e7%9a%84%e5%b7%ae%e7%95%b0" aria-label="這個段落的連結"></a></h3>
<p>Junior 工程師在看到問題後會想<mark>直接提出解決方案</mark>；而 Senior 工程師則會先釐清問題，<mark>從整體架構去思考</mark>。</p>
<p>會有這樣的差異是很正常的，因為 Junior 工程師在入職後，往往是<mark>接受任務分配</mark>的一方，所以他們已經習慣「<mark>收到問題 → 解決問題</mark>」的作業模式；很少人會去思考<mark>為什麼要這樣做、能不能這樣做</mark>，以至於可以完成交辦的任務，卻缺乏對整體架構的思維。</p>
<p>而 Senior 工程師除了要寫程式外，有時還要<mark>跟 PM 討論需求、規劃系統架構、分配工作給 Junior 工程師、幫 Junior 工程師 Debug</mark>；有些工程師討厭做這些事情，因為會耽誤到自己寫程式的時間，但正因為這些事情讓他有<mark>更完整的架構思維與溝通能力</mark>。</p>
<hr>
<h3 class="h-nomark" id="12-系統設計的題目來源">1.2 系統設計的題目來源<a class="h-anchor" href="#12-%e7%b3%bb%e7%b5%b1%e8%a8%ad%e8%a8%88%e7%9a%84%e9%a1%8c%e7%9b%ae%e4%be%86%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<p>雖然系統設計的範圍很廣，但面試官愛考的就大概就是以下幾種：</p>
<ul>
<li><strong>經典面試題</strong><br>很多面試官都是去題庫裡面找題目，我這裡也是建議讀者<mark>先熟悉題庫</mark>；透過反覆練習題庫，除了掌握基本盤外；在面對沒有接觸過的系統也能做到基本的應對進退。</li>
<li><strong>面試公司的系統</strong><br>有些知名企業喜歡向求職者詢問：「如果是你，會如何設計我們公司的系統？」<br>這個問題除了考系統設計外，其實也是想知道<mark>求職者對面試公司的系統了解有多深</mark>。</li>
<li><strong>你履歷上的技能</strong><br>像是筆者在履歷上有<mark>網頁爬蟲</mark>的經歷，面試官就會以此詢問筆者：「如何設計一個網頁爬蟲的系統？」</li>
</ul>
<blockquote>
<p><strong>備註：</strong></p>
<p>每個面試官在意的細節都不一樣；有些題目除了要系統設計外，他還要你提供不同方案的<mark>人力資源配置、功能時程預估、所需硬體資源&hellip;</mark>，要你設計一套可以讓組織運轉的流程。</p>
<p>如果沒有一定的實務基礎，面對這些延伸的問題基本沒有招架之力。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="13-面試官在找的是同事">1.3 面試官在找的是同事<a class="h-anchor" href="#13-%e9%9d%a2%e8%a9%a6%e5%ae%98%e5%9c%a8%e6%89%be%e7%9a%84%e6%98%af%e5%90%8c%e4%ba%8b" aria-label="這個段落的連結"></a></h3>
<p>問題走向完全看面試官的喜好，因為沒有固定的解答，所以<mark>回答是否被接受也是看你跟面試官頻率是否契合</mark>。</p>
<p>今天面試官<mark>要找的是同事</mark>，在確認技術過關後；他想透過這個關卡更進一步了解求職者：</p>
<ul>
<li>是否具備分析問題的能力。</li>
<li>會經過討論再做出設計，而不是一開始就有許多預設立場。</li>
<li>能夠清晰闡述自己的策略。</li>
<li>溝通時雙方在同一個頻率，不會出現雞同鴨講的狀況。</li>
<li>能評估自己提出方案的優缺點，並提出可行的優化方案。</li>
</ul>
<p>與其說是考試，<mark>更像是同事間在討論一個議題</mark>；有時也會遇到你認為自己的方案沒問題，但面試官就是覺得不可行的狀況；此時你可以先了解面試官顧慮的點，可能真的有自己忽略的細節。</p>
<p>有時面試官的技術能力未必高於求職者，也許你提出的方案更好，但因為已經超過面試官知識範圍所以不被採納；此時<mark>並不建議堅持己見</mark>，你可以說：「也許有我忽略的細節，您的考慮應該是對的。」然後<mark>轉移到下一個話題</mark>。</p>
<p>如果你遇到一個自己完全不熟悉的領域，連回答的頭緒都沒有，<mark>請跟面試官討論你想解決的問題，並說明為什麼會卡住，千萬不要不懂裝懂瞎掰答案</mark>；多跟面試官互相分享彼此的想法，面試官自己也很清楚求職者是不可能在短時間內給出完美的答案，面對自己不熟悉的議題，就算有不會知識也是很正常的。</p>
<ul>
<li>系統設計不只是單純的考試，而是評估你在工作上會如何討論問題。</li>
<li>雙方有來有往聊得越開心，就算你的回答有犯錯也容易被忽略。</li>
</ul>
<p><mark>會錄取的求職者未必技術能力最好，但絕對是在面試過程中溝通流暢的</mark>。</p>
<hr>
<h2 class="h-nomark" id="2-如何設計一個像-facebook-的社交平台">2. 如何設計一個像 Facebook 的社交平台<a class="h-anchor" href="#2-%e5%a6%82%e4%bd%95%e8%a8%ad%e8%a8%88%e4%b8%80%e5%80%8b%e5%83%8f-facebook-%e7%9a%84%e7%a4%be%e4%ba%a4%e5%b9%b3%e5%8f%b0" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-縮小問題範圍確認具體需求">2.1 縮小問題範圍，確認具體需求<a class="h-anchor" href="#21-%e7%b8%ae%e5%b0%8f%e5%95%8f%e9%a1%8c%e7%af%84%e5%9c%8d%e7%a2%ba%e8%aa%8d%e5%85%b7%e9%ab%94%e9%9c%80%e6%b1%82" aria-label="這個段落的連結"></a></h3>
<p>大部分主管在交代事情時，只會給一個方向，並不會把細節說清楚；所以面試官也是想透過這個問題來了解在未來交辦工作時，你會如何面對。</p>
<ul>
<li>
<p><strong>縮小問題範圍</strong><br>Facebook 是一個經歷十幾年改版、無數菁英灌注青春與熱血的系統，它所<mark>提供的服務實在是太多了</mark>，所以要先確認問題回答的方向，比如說：「Facebook 有提供蠻多種服務的，像是發文動態、Messager、廣告系統；我們要先從哪一塊來做討論？」</p>
</li>
<li>
<p><strong>不要急著提出解決方案</strong><br>當年筆者沒搞清楚狀況，把它當成高流量架構設計的問題；直接丟出一堆像是 Load Balance、Repliaction、DB Sharding&hellip;的技術方案；我當時說的很開心，但<mark>回答的內容完全偏離了面試官想要了解的方向</mark>。</p>
<blockquote>
<p>使用什麼技術可以等需求確定後再討論，如果一開始在分析問題時就走偏了，那後面再多的努力也沒有用。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-先提出一個簡單的解決方案">2.2 先提出一個簡單的解決方案<a class="h-anchor" href="#22-%e5%85%88%e6%8f%90%e5%87%ba%e4%b8%80%e5%80%8b%e7%b0%a1%e5%96%ae%e7%9a%84%e8%a7%a3%e6%b1%ba%e6%96%b9%e6%a1%88" aria-label="這個段落的連結"></a></h3>
<p>假設面試官把問題縮小到<mark>打造一個 Messager 的系統</mark>，那核心功能就非常明確；此時你只要<mark>簡單敘述設計概念</mark>就好；不要急著說出使用 Node.js + Socket.io + MongoDB 這些工具來打造系統。</p>
<ul>
<li><strong>簡述基礎解決方案</strong><br>我們會需要一個<mark>聊天伺服器</mark>來處理訊息間的溝通，當用戶發送訊息時，訊息會<mark>先傳送到伺服器並儲存</mark>；如果接收方在線上就直接<mark>傳送訊息</mark>給他，如果不在線就透過<mark>推播通知</mark>。<br>在 UI 介面上，使用者發送訊息成功時會有<mark>時間戳記</mark>；當接收方點開訊息時也會有<mark>已讀的標記</mark>。</li>
</ul>
<blockquote>
<p>通常考題都會涵蓋「前端、後端、資料庫」的設計；這樣面試官才能依照自己在意的點深入詢問。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="23-提出可以減少耗能的可行方案">2.3 提出可以減少耗能的可行方案<a class="h-anchor" href="#23-%e6%8f%90%e5%87%ba%e5%8f%af%e4%bb%a5%e6%b8%9b%e5%b0%91%e8%80%97%e8%83%bd%e7%9a%84%e5%8f%af%e8%a1%8c%e6%96%b9%e6%a1%88" aria-label="這個段落的連結"></a></h3>
<p>資源永遠是有限的，更高的硬體規格代表更多的預算，此時就需要 <code>trade-off（權衡）</code>。</p>
<p>接著讓我們從資源消耗的角度來探討 Messager 這個系統中，<mark>好友上線顯示綠燈的功能</mark>要如何設計。</p>
<p>最簡單的做法是當用戶一上線，就向所有好友發送上線通知；但這個設計在系統成長到<mark>百萬用戶</mark>，且特定時段上線的<mark>活躍用戶數量高達 30 萬</mark>時，這種發送通知的方法<mark>對資源的消耗極大</mark>。</p>
<ul>
<li><strong>有什麼方式可以減輕資源消耗？</strong><br>很多人打開 Messager 只是簡單回覆訊息就關掉，所以系統其實沒必要爲剛上線的用戶立刻發送通知；我會建議把發送上線通知的策略改為<mark>顯示活躍用戶</mark>，可能要在線 5 分鐘以上再發送通知；透過<mark>減少請求的次數來降低對系統資源的消耗</mark>。</li>
</ul>
<hr>
<h3 class="h-nomark" id="24-從自己擅長的面向切入">2.4 從自己擅長的面向切入<a class="h-anchor" href="#24-%e5%be%9e%e8%87%aa%e5%b7%b1%e6%93%85%e9%95%b7%e7%9a%84%e9%9d%a2%e5%90%91%e5%88%87%e5%85%a5" aria-label="這個段落的連結"></a></h3>
<p>在提出基礎的解決方案後，題目就會往細節發展，此時你可以<mark>主動向面試官確認一些系統需求</mark>，假設你擅長大流量高併發的議題，你就故意問面試官：「<mark>這個系統在設計上要考量到大流量以及高併發的問題嗎？</mark>」</p>
<p>主動引導面試官將話題轉向自己擅長的領域是很重要的；<mark>但如果面試官對這個議題沒有興趣，千萬不要自顧自的一直講</mark>。</p>
<blockquote>
<p>如果你對某個領域不擅長就<mark>不要挖坑給自己跳</mark>，假如你主動提及資料庫相關問題，面試官可能就因為你履歷上有 MySQL 跟 PostgreSQL 的經驗，而詢問你兩者的優缺點及使用時機，如果你答不出來反而會被扣分。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="25-不要有預設立場">2.5 不要有預設立場<a class="h-anchor" href="#25-%e4%b8%8d%e8%a6%81%e6%9c%89%e9%a0%90%e8%a8%ad%e7%ab%8b%e5%a0%b4" aria-label="這個段落的連結"></a></h3>
<p>Messager 系統除了傳送訊息這個基礎功能外，還有<mark>建立聊天群組、回覆特定訊息、移除訊息、對訊息傳送表情符號&hellip;</mark>等眾多功能。</p>
<p>如果你打算在這些功能上開闢戰場，<mark>請先詢問面試官是否需要這些功能</mark>；而不是沒有經過溝通，自己覺得理所當然就直接開始設計。</p>
<blockquote>
<p><strong>經驗分享</strong></p>
<p>在系統開發上，如果你要做的功能在需求規格以外，一定要先跟 PM 溝通；你認為必要的功能對客戶來說可能根本不需要，甚至系統有可能因為多出來的功能而產生無法預期的 Bug；<mark>千萬不要自作聰明</mark>。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="26-主動提出不同設計的優缺點">2.6 主動提出不同設計的優缺點<a class="h-anchor" href="#26-%e4%b8%bb%e5%8b%95%e6%8f%90%e5%87%ba%e4%b8%8d%e5%90%8c%e8%a8%ad%e8%a8%88%e7%9a%84%e5%84%aa%e7%bc%ba%e9%bb%9e" aria-label="這個段落的連結"></a></h3>
<p>如果面試官把話題帶到 <code>DB Sharding</code> 上面，比如：「隨著用戶成長，儲存用戶的 Table 資料量太大，效率越來越低，有什麼解決方案嗎？」</p>
<p><strong>筆者會用下面的角度來回答：</strong></p>
<p>考慮到用戶數量會持續成長，為了減輕資料庫的負擔，我會將這個 Table 切分到多個資料庫來分散壓力，這邊提供兩種切分策略：</p>
<ul>
<li>
<p><strong>Range-based partitioning</strong><br>第一種是挑選一個合適的<mark>欄位作為切分條件</mark>，像是以用戶的「年齡」做為切分條件；不過使用這種方式可能會造成<mark>不均勻的 Server load</mark>，因為每個年齡段的用戶數量不同。</p>
</li>
<li>
<p><strong>Hash partitioning</strong><br>為了解決 Server load 不均勻的問題，第二種方案則是挑選一個欄位用 <code>Hash Function</code> 計算出一個值，用這個值來決定資料要放到哪個資料庫。</p>
<blockquote>
<p>可以採用 <code>Consistent Hashing</code> 讓資料均勻分布在不同 DB；這個方案在 DB 數量有增減時，也能盡量避免原有的 Key 被重新分配。</p>
</blockquote>
</li>
</ul>
<p><mark>系統設計沒有標準答案，如果你有能力提出多種解決方案，並能說出各自的利弊是非常加分的。</mark></p>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. 衍伸問題<a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-你有使用過-mind-map-跟-sitemap-嗎它們的使用時機是">3.1 你有使用過 Mind Map 跟 Sitemap 嗎？它們的使用時機是？<a class="h-anchor" href="#31-%e4%bd%a0%e6%9c%89%e4%bd%bf%e7%94%a8%e9%81%8e-mind-map-%e8%b7%9f-sitemap-%e5%97%8e%e5%ae%83%e5%80%91%e7%9a%84%e4%bd%bf%e7%94%a8%e6%99%82%e6%a9%9f%e6%98%af" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：設計系統架構時的輔助工具</mark></p>
<ul>
<li><strong>Mind-Map</strong><br>在專案做需求訪談時，我會先透過 Mind Map 來幫助發想與記錄想法，透過這張圖即使開會偏離重點也可以快速拉回；在會後我會透過它向同事說明需求的關聯性、以及一些功能的限制條件，讓他們快速理解這個專案的目標。</li>
<li><strong>Sitemap</strong><br>在需求明確後，我會用 Sitemap 規劃系統架構，設計頁面之間的關聯性與階層，完成流程上的模擬；並檢視規劃是否有下列問題：
<ul>
<li>
<p>如果需要 SEO 的頁面被埋得太深就要重新規劃，避免 Google 爬蟲不到。</p>
</li>
<li>
<p>檢查是否有規劃不良導致的孤兒網頁。</p>
<blockquote>
<p><strong>孤兒網頁</strong>：系統沒有連結可以抵達的網頁。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="筆者碎碎念">筆者碎碎念<a class="h-anchor" href="#%e7%ad%86%e8%80%85%e7%a2%8e%e7%a2%8e%e5%bf%b5" aria-label="這個段落的連結"></a></h3>
<p>即使經驗豐富，面對一個從未思考過的問題，也很難提出合適的設計；這邊建議讀者可以透過以下方式，有意識地培養自己系統設計的能力：</p>
<ul>
<li>上網找題庫，吸收他人在系統設計上的經驗。</li>
<li>找 Mentor 做模擬面試，熟悉答題方向。</li>
<li>在閒暇時思考知名的系統是如何設計出來的（ex：Youtube、Google、Twitter&hellip;）。</li>
</ul>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://www.youtube.com/watch?v=zomYKjlvJGU&amp;ab_channel=HackBear%E6%B3%B0%E7%91%9E">系統設計面試 6 個技巧｜我面 Google 前的準備方法</a></li>
<li><a href="https://medium.com/jktech/acf6ab1f502f">軟體工程師系統設計面試準備指南</a></li>
<li><a href="https://wizardforcel.gitbooks.io/gainlo-interview-guide/content/5.html">系統設計面試（第一部分）</a></li>
<li><a href="https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md">donnemartin/system-design-primer</a></li>
<li><a href="https://www.yesharris.com/sitemap-seo/">從 Sitemap 的應用，談 SEO 的學習</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-22/images/fullstack-career-notes-day-22-01.webp" width="1024" height="233" alt="參考資源（圖 1）" fetchpriority="high" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][設計模式]Code Review 會注意哪些事？會依照什麼原則對程式做 Refactoring？</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-21/</link>
      <pubDate>Wed, 06 Oct 2021 08:38:32 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-21/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>「沒有最好的程式，只有更好的程式。」在完成功能後 Code Review 是非常重要的事情；不只看自己的程式碼，也要多觀摩其他人的程式碼，這個動作除了可以優化程式碼品質外，還可以幫助團隊了解彼此的工作進度。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-21/images/cover_hu_5ae278293ba548a2.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>在功能穩定後，你對程式碼還有要求嗎？</p>
</blockquote>
<p>「<mark>沒有最好的程式，只有更好的程式。</mark>」在完成功能後 Code Review 是非常重要的事情；不只看自己的程式碼，也要多觀摩其他人的程式碼，這個動作除了可以<mark>優化程式碼品質</mark>外，還可以幫助團隊<mark>了解彼此的工作進度</mark>。</p>
<p>人無完人，很多時候第一時間想到的解法未必是最好的；<mark>解決問題的方法不只一種</mark>，可能有更簡潔的寫法或是更好的 Pattern 可以套用，只是開發者不具備這塊的知識儲備；此時 Code Review 就是<mark>提升團隊程式水平</mark>的方式。</p>
<p>找到需要改善的問題後，<code>Refactoring（重構）</code>就是另一個開始；如果為了盡快完成功能而埋頭狂寫，容易導致日後維護以及交接的困難性；專案越大這個問題會越明顯，所以在完成功能後對程式做 Refactoring 是必要的任務，它能<mark>提升你日後解決 Bug、需求變更時的工作效率</mark>。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>Code Review 會注意哪些事？會依照什麼原則對程式做 Refactoring？</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 SOLID 原則</li>
<li>2.2 Design Pattern 是什麼？解決了什麼問題？</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 有處理 Legacy Code 的經驗嗎？</li>
<li>3.2 有用什麼工具讓團隊有一致的 Coding Style</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-code-review-會注意哪些事會依照什麼原則對程式做-refactoring">1. Code Review 會注意哪些事？會依照什麼原則對程式做 Refactoring？<a class="h-anchor" href="#1-code-review-%e6%9c%83%e6%b3%a8%e6%84%8f%e5%93%aa%e4%ba%9b%e4%ba%8b%e6%9c%83%e4%be%9d%e7%85%a7%e4%bb%80%e9%ba%bc%e5%8e%9f%e5%89%87%e5%b0%8d%e7%a8%8b%e5%bc%8f%e5%81%9a-refactoring" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-面試官為什麼會問">1.1 面試官為什麼會問？<a class="h-anchor" href="#11-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<p>只要是軟體工程師的職位，這題可以說是超級<mark>常見面試題</mark>；因為它能問得很淺也能夠問得很深，如果是相對資淺的職位，求職者只要能說出<mark>關鍵字並給出基本解釋</mark>就能過關；如果是資深的位置，除了關鍵字外，面試官會繼續詢問你在<mark>專案中是否有實踐</mark>。</p>
<hr>
<h3 class="h-nomark" id="12-面試官想從答案確認什麼">1.2 面試官想從答案確認什麼？<a class="h-anchor" href="#12-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>瞭解求職者 Code Review 會在意的點</li>
<li>有使用哪些方法對程式做 Refactoring</li>
<li>對 SOLID 原則有基礎認知</li>
<li>知道 Design Pattern 且能舉出實務上的應用</li>
<li>有用過什麼工具讓團隊有一致的 Coding Style</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-筆者提供的簡答">1.3 筆者提供的簡答<a class="h-anchor" href="#13-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>我會先觀察<mark>變數與函式的命名</mark>是否符合團隊規範、<mark>變數的宣告</mark>是否合理（var/let/const 的使用時機）；然後看程式在設計上是否有<mark>符合 SOLID 原則</mark>；在功能完成後如果發現有合適的 <mark>Pattern 可以更好地解決問題</mark>，就會再對程式做 Refactoring。</p>
<hr>
<h2 class="h-nomark" id="2-回答問題所需具備的知識">2. 回答問題所需具備的知識<a class="h-anchor" href="#2-%e5%9b%9e%e7%ad%94%e5%95%8f%e9%a1%8c%e6%89%80%e9%9c%80%e5%85%b7%e5%82%99%e7%9a%84%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h2>
<p>其他章節的知識，可能換一個時空背景就用不到了；但今天這個章節的內容，是每位開發人員都需要<mark>將其內化成為本能的</mark>。</p>
<h3 class="h-nomark" id="21-solid-原則">2.1 SOLID 原則<a class="h-anchor" href="#21-solid-%e5%8e%9f%e5%89%87" aria-label="這個段落的連結"></a></h3>
<p>SOLID 是一個<mark>物件導向</mark>的設計原則，核心目標是為了讓程式碼更乾淨、易讀、好維護，讓程式朝著 <code>Clean Code</code>（無瑕的程式碼）的方向前進。</p>
<ul>
<li>
<p><strong>S（Single Responsibility Principle）單一責任原則</strong><br><mark>一次只做好一件事</mark>；如果把系統比喻成樂高，那單一責任原則就是樂高的每一塊積木。</p>
</li>
<li>
<p><strong>O（Open-Closed Principle）開放封閉原則</strong><br>程式的架構要容易擴充功能；而這個<mark>功能的擴充是新增程式，而非修改過去的程式</mark>，這麼做可以維持原有程式的穩定性。</p>
</li>
<li>
<p><strong>L（Liskov Substitution Principle）里氏替換原則</strong><br>在繼承中衍生的子類別，要能<mark>完全繼承父類別的功能</mark>；實作時還需顧慮到單一責任原則，<mark>避免日後功能拆分或刪減時的困擾</mark>。<br>以軟體的角度來說，使用者期待<code>版本從 1.0 ➞ 1.1 時行為是一致的，不會更新完舊功能就壞掉</code>。</p>
<blockquote>
<p><strong>子類別 Override 時要注意</strong></p>
<ul>
<li><strong>不可改變父類別先決條件</strong><br>假設籃球比賽的參與者年齡限制在 15~80 歲；裡面可能區分為青年組、中年組、老年組，青年組的年齡「可以是 15~30」；但「不可以是 14~30」。</li>
<li><strong>子類別的後置條件不該被削弱</strong><br>籃球的得分有「2」分球、「3」分球，都是數字；如果某個比賽改用「三分球」這個字串紀錄得分就是不可以的。</li>
</ul>
</blockquote>
</li>
<li>
<p><strong>I（Interface Segregation Principles）介面隔離原則</strong><br>設計程式時不應該預設使用者一定會使用到介面中的多個功能，我們應該盡量讓介面的功能單一；以後端 API 來舉例，我們<mark>不要讓一個 API 可以做好多事情，但做每件事情時只用到其中幾個參數</mark>。</p>
</li>
<li>
<p><strong>D（Dependency Inversion Principle）依賴反轉原則</strong><br><mark>子類別一定會依賴父類別</mark>，這樣單向的關係是乾淨的；如果是雙向的依賴就會導致程式碼邏輯難以追蹤。</p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-design-pattern-是什麼解決了什麼問題">2.2 Design Pattern 是什麼？解決了什麼問題？<a class="h-anchor" href="#22-design-pattern-%e6%98%af%e4%bb%80%e9%ba%bc%e8%a7%a3%e6%b1%ba%e4%ba%86%e4%bb%80%e9%ba%bc%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p><code>Pattern</code> 是指在不同場景下的解釋，包含 Context（情境敘述）、Problem（問題）、Solution（解決方案）。</p>
<p>而 <code>Design Pattern</code> 就是過去人們發現解決問題的套路；學習越多 Design Pattern 在遇到問題時可以有更多的思路，並縮短與其他工程師討論的時間，因爲 Pattern 本身就包含了對問題的基礎解決方案，可以省下說明的時間。</p>
<p>下面分享一些常見的 Design Pattern 幫大家建立基礎概念，在看完後讀者也許會恍然大悟，原來我一直都有在使用 Pattern！</p>
<ul>
<li>
<p><strong>Factory Method 工廠方法</strong><br><strong>簡述</strong>：<mark>工廠負責生產客戶需要的產品</mark><br>假設今天有一個飲料<code>工廠</code>，他會生產很多種類的<code>產品</code>，像是「紅茶、綠茶、烏龍茶」；也許這些飲料的製作過程很複雜，但對客戶來講只要說出自己想要什麼口味的飲料就好了。</p>
</li>
<li>
<p><strong>Strategy 策略</strong><br><strong>簡述</strong>：<mark>一個策略介面底下有很多靈活的方法供選擇</mark><br>把計算機的<code>計算</code>當成一個介面，這個介面提供<mark>加減乘除</mark>的方法；如果想加入<mark>次方、開根號</mark>的功能也能夠輕鬆擴充。</p>
</li>
<li>
<p><strong>Factory Method 與 Strategy 混合運用</strong><br><strong>簡述</strong>：<mark>有時解決方案是多個 Pattern 的組合</mark><br>假設今天開一間飲料店，飲料會分成茶類、咖啡；每杯飲料又有冰量、甜度的選擇，這裡我們可以用 <code>Factory Method</code> 來建立產品；而飲料在販售時，不同組合有對應的折扣，這裡就可以用 <code>Strategy</code> 提供對應的方法來計算總金額。</p>
</li>
<li>
<p><strong>Singleton 單例</strong><br><strong>簡述</strong>：<mark>希望在程式每個位置都能呼叫統一的 Instance</mark><br>在寫程式時會希望某些資源可以<mark>重複利用</mark>，並且在不同檔案引用時這些資料是<code>一致</code>的，像是共用的計時器、資料庫等物件。</p>
</li>
<li>
<p><strong>Decorator 裝飾</strong><br><strong>簡述</strong>：<mark>將需求獨立，依據實際需求取得動態的結果</mark><br>假設一間餐廳提供三種餐點</p>
<ul>
<li>商業午餐（主餐）</li>
<li>簡餐（主餐 + 飲料）</li>
<li>套餐（主餐 + 飲料 + 甜點）</li>
</ul>
<p>我們發現上面餐點是有疊加性的，所以可以用繼承的方式來撰寫餐點間的關係；但如果今天老闆想要更換簡餐的內容組合（ex：贈送水果），而不想改變套餐的內容，就會導致程式必須改寫。<br>面對這個問題，我們可以用 <code>Decorator</code> 作為解決方案，將主餐、飲料、甜點、水果各自獨立，依據商業午餐、簡餐、套餐實際的內容來做組合；這樣就能減少耦合性的問題。</p>
</li>
<li>
<p><strong>Observer 觀察者</strong><br><strong>簡述</strong>：<mark>觀察者要能掌握被觀察者的狀態改變，較常用於 GUI 的設計</mark><br>像是 Vue.js 的 watch 就是用來監聽一個「值」的變化去做一系列的事情（ex：UI 上的調整）。</p>
</li>
<li>
<p><strong>Command 命令</strong><br><strong>簡述</strong>：<mark>將請求的物件和執行的物件分開</mark><br>路邊攤的老闆通常是一個人負責備料、點餐、料理、清潔…等到生意做起來有店面了，他才會考慮雇用一些人手來幫忙內場、外場，如果把這個解決方案用 Command 來舉例：</p>
<ul>
<li><strong>客人（Client）</strong>：跟服務生說自己想點什麼餐（Concrete Command）。</li>
<li><strong>服務生（Invoker）</strong>：紀錄客人的餐點（Concrete Command），並呼叫命令。</li>
<li><strong>廚師（Receiver）</strong>：收到命令後，開始料理餐點（Concrete Command）。</li>
</ul>
</li>
<li>
<p><strong>Builder 建造者</strong><br><strong>簡述</strong>：<mark>依照自己的需求客製化產品</mark><br>一台筆電是由許多零件組裝而成，你可以選擇<code>標配</code>也能夠用<code>選配</code>來客製化它；為了讓選配的零件規格有彈性，<code>Builder</code> 就是一個很棒的解決方案；它讓你先選完自己所需的零件後，再產生最後的產品。</p>
</li>
</ul>
<blockquote>
<p><strong>備註</strong>：如果你熟悉的 Pattern 剛好能解決目前遇到的問題那是皆大歡喜；<mark>萬一沒有合適的 Pattern 可以套用，千萬不要為了設計而設計</mark>；開發人員的目標應該是想透過 Pattern 解決問題，而不是從 Pattern 出發去設計問題。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. 衍伸問題<a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-有處理-legacy-code-的經驗嗎">3.1 有處理 Legacy Code 的經驗嗎？<a class="h-anchor" href="#31-%e6%9c%89%e8%99%95%e7%90%86-legacy-code-%e7%9a%84%e7%b6%93%e9%a9%97%e5%97%8e" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>除非是新創團隊或是接案類型的公司；不然絕大多數新人入職後都要接手前人遺留的程式碼（Legacy Code），如果求職者只有從零開始的經驗，在一開始會有陣痛期。</p>
</blockquote>
<p><mark>考點：了解求職者是否接觸過 Legacy Code、碰到時如何快速掌握</mark></p>
<p>之前有因為<mark>專案擴編</mark>而去支援的經驗，因為當時有時程上的壓力；所以我在加入團隊後除了重點了解<mark>自己要負責的 Feature、團隊的 Coding Style</mark> 外，會主動向<mark>定下這些需求的 PM 討論</mark>，以此了解因果關係並快速融入團隊。</p>
<p>也曾接手過要<mark>維護及擴充的專案</mark>，因為當時沒有相關文件可以參考，為了快速掌握專案，我一邊操作系統一邊畫<code>心智圖</code>幫助自己釐清功能；了解系統架構後再去找<mark>合作窗口確認需求</mark>，避免實作方向與客戶需求不同。</p>
<p>在修改 Legacy Code 時，我會特別注意前人是否有<mark>撰寫測試案例</mark>，在程式能有效測試的狀態下才能安心的修改與擴充。</p>
<p>以正在運行的專案來說，我會希望<mark>建立團隊 Code Review 機制</mark>，讓每個人熟悉彼此的程式、使用共同的 Coding Style，<mark>當知識共享與風格統一後，就能減少 Legacy Code 的產生</mark>。</p>
<hr>
<h3 class="h-nomark" id="32-有用什麼工具讓團隊有一致的-coding-style">3.2 有用什麼工具讓團隊有一致的 Coding Style<a class="h-anchor" href="#32-%e6%9c%89%e7%94%a8%e4%bb%80%e9%ba%bc%e5%b7%a5%e5%85%b7%e8%ae%93%e5%9c%98%e9%9a%8a%e6%9c%89%e4%b8%80%e8%87%b4%e7%9a%84-coding-style" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：是否有善用工具解決問題</mark></p>
<p>我在 VSCode 有安裝一個 <a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier</a> 的外掛，靠它就能非常有效的將程式碼格式化；不過有時為了保證團隊的 Coding Style 會導入 ESLint 來統一風格，但 Prettier 與 ESLint 一起使用時會遇到衝突，所以我有再安裝一個 <a href="https://marketplace.visualstudio.com/items?itemName=rvest.vs-code-prettier-eslint">Prettier ESLint</a> 的外掛來解決這個問題；讓程式不僅符合團隊的 Coding Style，還可以透過快速鍵將程式碼格式化。</p>
<hr>
<p>寫完這篇文章時，我的心裡突然冒出一段話：「一開始我們在追求問題的解答；到後來，我們在學習解決問題的方法。」</p>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://wyattkidd.medium.com/fca4e2e16752">【翻譯】JavaScript 設計模式</a></li>
<li><a href="https://ithelp.ithome.com.tw/articles/10234830">什麼是 Design Pattern?</a></li>
<li><a href="https://ithelp.ithome.com.tw/articles/10201706">我為什麼想學設計模式 ( Design Pattern )</a></li>
<li><a href="https://web.archive.org/web/20210226100242/http://www.andrewchen.tw/2017/04/09/20170409_NOTE_%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F%E4%BA%94%E5%A4%A7%E5%9F%BA%E6%9C%AC%E5%8E%9F%E5%89%87SOLID/" title="原網頁已失效，連到網際網路檔案館的存檔">設計模式五大基本原則 SOLID</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-21/images/fullstack-career-notes-day-21-01.webp" width="1024" height="233" alt="參考資源（圖 1）" fetchpriority="high" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][資料庫]如何解決高併發情境的商品秒殺問題</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-20/</link>
      <pubDate>Tue, 05 Oct 2021 08:10:05 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-20/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>如果今天有上萬人在同一時間搶限量商品，昨天分享的方案基本撐不住。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-20/images/cover_hu_2ecc5fe7cdd785a0.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>如果今天有<mark>上萬人在同一時間搶限量商品</mark>，昨天分享的方案基本撐不住。</p>
<p>不過面對這個情境，Redis 表示終於輪到我了！今天這篇文章會以 <code>Node.js + Redis</code> 為範例，帶讀者一起解決這個問題。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>如何解決高併發情境的商品秒殺問題</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 先了解 Redis 的基礎知識</li>
<li>2.2 使用 Node.js + Redis 解決高併發秒殺問題</li>
<li>2.3 模擬秒殺情境確認商品沒有超賣</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 如果有數萬用戶同時查看 TOP100 的點數排行榜，資料庫如何設計？</li>
<li>3.2 Redis 跟 Memcached 的差異</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-如何解決高併發商品秒殺問題">1. 如何解決高併發商品秒殺問題<a class="h-anchor" href="#1-%e5%a6%82%e4%bd%95%e8%a7%a3%e6%b1%ba%e9%ab%98%e4%bd%b5%e7%99%bc%e5%95%86%e5%93%81%e7%a7%92%e6%ae%ba%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-面試官為什麼會問">1.1 面試官為什麼會問？<a class="h-anchor" href="#11-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<p>這算是資料庫的<mark>常見面試題</mark>，除了<mark>金融、電商</mark>喜歡考這題外；<mark>社交平台、新創公司、遊戲產業</mark>也會換個形式考類似的題目。</p>
<p>因為這算是<mark>職缺所需具備的技術</mark>，如果求職者沒有相關經驗可能就會止步於這個關卡。</p>
<blockquote>
<p>如果有去以上產業的打算，最好有一定的 NoSQL（ex：Redis、MongoDB） 基礎再去面試；因為在面試官的眼中<mark>有基礎跟完全不會的差距很大</mark>，求職者可以沒有實務經驗，但明知產業會碰到這些技術還不去學習就是態度問題了。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="12-面試官想從答案確認什麼">1.2 面試官想從答案確認什麼？<a class="h-anchor" href="#12-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>能提出高併發情境商品秒殺的解決方案</li>
<li>如果解決方案為 NoSQL，對它的認知有多少</li>
<li>是否接觸過同類型的 NoSQL</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-筆者提供的簡答">1.3 筆者提供的簡答<a class="h-anchor" href="#13-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>通常這類型的秒殺活動都有固定檔期，我會先將活動用的<mark>商品及庫存數量同步到 Redis 資料庫</mark>；為了避免超賣，在 Client 端下單時會<mark>執行 Lua 腳本</mark>，當秒殺到指定數量或是時間結束後就不再接受請求，活動結束後<mark>將 Redis 的資料同步到關聯式資料庫</mark>。</p>
<hr>
<h2 class="h-nomark" id="2-回答問題所需具備的知識">2. 回答問題所需具備的知識<a class="h-anchor" href="#2-%e5%9b%9e%e7%ad%94%e5%95%8f%e9%a1%8c%e6%89%80%e9%9c%80%e5%85%b7%e5%82%99%e7%9a%84%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-先了解-redis-的基礎知識">2.1 先了解 Redis 的基礎知識<a class="h-anchor" href="#21-%e5%85%88%e4%ba%86%e8%a7%a3-redis-%e7%9a%84%e5%9f%ba%e7%a4%8e%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h3>
<p>如果有<mark>短時間大量訪問、需要性能提升</mark>的需求，往往會先想到 Redis 這個記憶體資料庫。</p>
<ul>
<li>
<p><strong>Redis 為什麼快？</strong></p>
<ul>
<li>
<p><strong>記憶體資料庫</strong><br>記憶體讀寫本來就快。</p>
</li>
<li>
<p><strong>單執行緒（single-threaded）</strong><br>如果使用<mark>多執行緒</mark>會耗費時間在<mark>上下文的切換</mark>以及<code>加鎖</code>上面；而<mark>單執行緒</mark>會依照<mark>請求順序執行</mark>，不需考慮同步以及加鎖帶來的效能問題。<br>Redis 是基於記憶體操作，所以效能的瓶頸不在 CPU 而在記憶體、網路頻寬；因為 CPU 不是瓶頸，所以可以<mark>開多個 Redis 建立 Cluster</mark> 分散壓力。</p>
</li>
<li>
<p><strong>I/O 多路複用（multiplexing）</strong><br>Redis 使用了 <mark>epoll IO 多路複用</mark>，可以用一條執行緒處理併發的網路請求。</p>
<blockquote>
<p><strong>這個有點抽象，我用<mark>繳交作業</mark>為情境說明</strong>：<br>假設你是一個老師，採用多路復用的原則批改作業，那你批改的順序就是<mark>看誰先繳交作業</mark>，而不是按照學號順序批改（此作法中間有人沒交作業就會卡住）；這樣就能避免大量無用操作，為非阻塞模式的實現。</p>
</blockquote>
</li>
</ul>
</li>
<li>
<p><strong>Redis 資料保存方案</strong><br>可以使用 <code>RDB 、AOF</code> 來做持久化。</p>
<ul>
<li><strong>RDB 持久化（Redis Database）</strong><br>在指定的時間間隔內將資料 dump 到硬碟；因為是<mark>定期操作</mark>，如果 Redis <mark>當機會遺失部分資料</mark>，此方案適合<mark>大規模資料恢復</mark>。</li>
<li><strong>AOF 持久化（Append Only File）</strong><br>這個方案可以<mark>完整紀錄</mark>所有資料的變化，因為採用日誌追加的方式，所以<mark>就算當機也不會影響已經儲存的日誌，災難復原的完成度高</mark>；缺點是<mark>檔案比 RDB 大</mark>、大規模資料<mark>恢復速度較 RDB 慢</mark>。</li>
</ul>
</li>
<li>
<p><strong>Redis 資料淘汰機制</strong><br>Redis 主要<mark>保存的都是熱點資訊</mark>，在儲存資料有限的狀態下（記憶體不足，無法寫入新資料）；就要合理的設定淘汰機制：</p>
<ul>
<li>選擇性移除有設定<mark>過期時間</mark>的資料
<ul>
<li><strong>volatile-lru</strong>：挑選最近較少使用的資料淘汰。</li>
<li><strong>volatile-ttl</strong>：挑選即將過期的資料淘汰。</li>
<li><strong>volatile-random</strong>：挑選隨機資料淘汰。</li>
</ul>
</li>
<li><strong>allkeys-lru</strong>：挑選最近較少使用的資料淘汰。</li>
<li><strong>allkeys-random</strong>：挑選隨機資料淘汰。</li>
<li><strong>no-enviction</strong>：禁止淘汰；若選用這個設定，當數據到達 maxmemory 時會回傳 OOM 錯誤。</li>
</ul>
<blockquote>
<p>原則上淘汰策略以「<code>volatile-lru、allkeys-lru</code>」為主（淘汰較少使用的資料）。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-使用-nodejs--redis-解決高併發秒殺問題">2.2 使用 Node.js + Redis 解決高併發秒殺問題<a class="h-anchor" href="#22-%e4%bd%bf%e7%94%a8-nodejs--redis-%e8%a7%a3%e6%b1%ba%e9%ab%98%e4%bd%b5%e7%99%bc%e7%a7%92%e6%ae%ba%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>目標</strong>
<ul>
<li>產生秒殺商品基礎資訊（商品名稱、庫存）</li>
<li>模擬秒殺情境，確認是否會超賣</li>
<li>確認有保存購買人資訊</li>
</ul>
</li>
<li><strong>使用技術</strong>
<ul>
<li>
<p><strong>Redis</strong><br>本篇文章使用 Redis 的 Hash、List type 來儲存資訊。</p>
<blockquote>
<p>如果完全沒有相關基礎，建議先參考<a href="https://medium.com/dean-lin/8d0b45062f9">這篇文章</a>來安裝 Redis 以及它的 GUI 工具。</p>
</blockquote>
</li>
<li>
<p><strong>Lua 腳本</strong><br>為了避免超賣，這裡採用 Lua 腳本；<mark>在 Redis Server 執行 EVAL 指令時，在結果回傳前只會執行當下 Lua 腳本的邏輯</mark>，其他 Client 端的命令須等待直到 EVAL 執行完為止。</p>
<blockquote>
<p>Lua 腳本的邏輯應盡量簡單以保證執行效率，否則會影響 Client 端的體驗。</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>程式架構</strong>
<ul>
<li>
<p><strong>主程式：redisSecKill.js</strong></p>
<ul>
<li>先啟用 Redis Client 端（如未安裝相關套件，請在專案資料夾下輸入 <code>yarn add ioredis</code>）。</li>
<li>用 <code>prepare</code> 函式，以 Hash type 建立參加秒殺的產品庫存。</li>
<li>用 <code>secKill</code> 函式模擬使用者購買行為，緩存並執行 Lua 腳本。</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> fs <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;fs&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> Redis <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;ioredis&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> redis <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> Redis({
</span></span><span style="display:flex;"><span>  host<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;127.0.0.1&#34;</span>,
</span></span><span style="display:flex;"><span>  port<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">6379</span>,
</span></span><span style="display:flex;"><span>  password<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>redis.on(<span style="color:#a5d6ff">&#34;error&#34;</span>, <span style="color:#ff7b72">function</span> (error) {
</span></span><span style="display:flex;"><span>  console.error(error);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> prepare(item_name) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 參加秒殺活動的商品庫存
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">await</span> redis.hmset(item_name, <span style="color:#a5d6ff">&#34;Total&#34;</span>, <span style="color:#a5d6ff">100</span>, <span style="color:#a5d6ff">&#34;Booked&#34;</span>, <span style="color:#a5d6ff">0</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> secKillScript <span style="color:#ff7b72;font-weight:bold">=</span> fs.readFileSync(<span style="color:#a5d6ff">&#34;./secKill.lua&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> secKill(item_name, user_name) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 1. 緩存腳本取得 sha1 值
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> sha1 <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> redis.script(<span style="color:#a5d6ff">&#34;load&#34;</span>, secKillScript);
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// console.log(sha1);
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// 2. 透過 evalsha 執行腳本
</span></span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// redis Evalsha 命令基本語法如下
</span></span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// EVALSHA sha1 numkeys key [key ...] arg [arg ...]
</span></span></span><span style="display:flex;"><span>  redis.evalsha(sha1, <span style="color:#a5d6ff">1</span>, item_name, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">&#34;order_list&#34;</span>, user_name);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">function</span> main() {
</span></span><span style="display:flex;"><span>  console.time(<span style="color:#a5d6ff">&#34;secKill&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> item_name <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;item_name&#34;</span>;
</span></span><span style="display:flex;"><span>  prepare(item_name);
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">var</span> i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>; i <span style="color:#ff7b72;font-weight:bold">&lt;</span> <span style="color:#a5d6ff">10000</span>; i<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> user_name <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;baobao&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> i;
</span></span><span style="display:flex;"><span>    secKill(item_name, user_name);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  console.timeEnd(<span style="color:#a5d6ff">&#34;secKill&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>main();
</span></span></code></pre></div></li>
<li>
<p><strong>Lua 腳本：secKill.lua</strong><br>在 Lua 腳本執行邏輯：「確認下單數量」➜「取得商品庫存」➜「如果庫存足夠就下單」➜「儲存購買者資訊（List type）」。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#ff7b72">local</span> item_name <span style="color:#ff7b72;font-weight:bold">=</span> KEYS[<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">local</span> n <span style="color:#ff7b72;font-weight:bold">=</span> tonumber(ARGV[<span style="color:#a5d6ff">1</span>])
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">local</span> order_list <span style="color:#ff7b72;font-weight:bold">=</span> ARGV[<span style="color:#a5d6ff">2</span>]
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">local</span> user_name <span style="color:#ff7b72;font-weight:bold">=</span> ARGV[<span style="color:#a5d6ff">3</span>]
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> n  <span style="color:#ff7b72;font-weight:bold">or</span> n <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">0</span> <span style="color:#ff7b72">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">local</span> vals <span style="color:#ff7b72;font-weight:bold">=</span> redis.call(<span style="color:#a5d6ff">&#34;HMGET&#34;</span>, item_name, <span style="color:#a5d6ff">&#34;Total&#34;</span>, <span style="color:#a5d6ff">&#34;Booked&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">local</span> total <span style="color:#ff7b72;font-weight:bold">=</span> tonumber(vals[<span style="color:#a5d6ff">1</span>])
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">local</span> booked <span style="color:#ff7b72;font-weight:bold">=</span> tonumber(vals[<span style="color:#a5d6ff">2</span>])
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> total <span style="color:#ff7b72;font-weight:bold">or</span> <span style="color:#ff7b72;font-weight:bold">not</span> booked <span style="color:#ff7b72">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> booked <span style="color:#ff7b72;font-weight:bold">+</span> n <span style="color:#ff7b72;font-weight:bold">&lt;=</span> total <span style="color:#ff7b72">then</span>
</span></span><span style="display:flex;"><span>  redis.call(<span style="color:#a5d6ff">&#34;HINCRBY&#34;</span>, item_name, <span style="color:#a5d6ff">&#34;Booked&#34;</span>, n)
</span></span><span style="display:flex;"><span>  redis.call(<span style="color:#a5d6ff">&#34;LPUSH&#34;</span>, order_list, user_name)
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> n
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">0</span>
</span></span></code></pre></div></li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-模擬秒殺情境確認商品沒有超賣">2.3 模擬秒殺情境確認商品沒有超賣<a class="h-anchor" href="#23-%e6%a8%a1%e6%93%ac%e7%a7%92%e6%ae%ba%e6%83%85%e5%a2%83%e7%a2%ba%e8%aa%8d%e5%95%86%e5%93%81%e6%b2%92%e6%9c%89%e8%b6%85%e8%b3%a3" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>SETP 1</strong>：在專案資料夾下輸入 <code>node redisSecKill.js</code> 模擬秒殺</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-20/images/fullstack-career-notes-day-20-01.webp" width="1019" height="73" alt="2.3 模擬秒殺情境確認商品沒有超賣（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
<li>
<p><strong>SETP 2</strong>：待執行完後，用 Redis GUI 程式確認商品沒有超賣</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-20/images/fullstack-career-notes-day-20-02.webp" width="1024" height="412" alt="2.3 模擬秒殺情境確認商品沒有超賣（圖 2）" loading="lazy" decoding="async"></p>
</li>
<li>
<p><strong>SETP 3</strong>：確認訂單都有對應的買家</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-20/images/fullstack-career-notes-day-20-03.webp" width="1024" height="734" alt="2.3 模擬秒殺情境確認商品沒有超賣（圖 3）" loading="lazy" decoding="async"></p>
</li>
</ul>
<blockquote>
<p><strong>補充</strong></p>
<p>文章程式只是 MVP，現實狀況還有很多要設計的：</p>
<ul>
<li>哪些商品被列為秒殺，如何紀錄。</li>
<li>設定秒殺開始、結束時間。</li>
<li>如何將 Redis 資料存入關聯式資料庫中。</li>
</ul>
</blockquote>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. 衍伸問題<a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-如果有數萬用戶同時查看-top100-的點數排行榜資料庫如何設計">3.1 如果有數萬用戶同時查看 TOP100 的點數排行榜，資料庫如何設計？<a class="h-anchor" href="#31-%e5%a6%82%e6%9e%9c%e6%9c%89%e6%95%b8%e8%90%ac%e7%94%a8%e6%88%b6%e5%90%8c%e6%99%82%e6%9f%a5%e7%9c%8b-top100-%e7%9a%84%e9%bb%9e%e6%95%b8%e6%8e%92%e8%a1%8c%e6%a6%9c%e8%b3%87%e6%96%99%e5%ba%ab%e5%a6%82%e4%bd%95%e8%a8%ad%e8%a8%88" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：對 Redis Zset 這個資料型態的認知與應用</mark></p>
<p>我會使用 Redis 這個記憶體資料庫，建立一個<mark>有序集合（Zset）</mark>來儲存用戶的資訊。</p>
<p>在設計上，用戶（member）是唯一值，且每個用戶都會關聯一個點數（score），這樣用戶就可以按照分數來排序。</p>
<p>功能實現上會透過 <code>zrevrange Leaderboard 0 99 withscores</code> 這段指令來顯示 TOP100 的點數排行榜。</p>
<blockquote>
<ul>
<li><strong>zrevrange</strong>：依照點數（score）檢視排行榜</li>
<li><strong>Leaderboard</strong>：排行榜名稱（可以自行定義）</li>
<li><strong>0 99</strong>：TOP100 的意思</li>
<li><strong>withscores</strong>：連點數（score）一起顯示</li>
</ul>
</blockquote>
<hr>
<h3 class="h-nomark" id="32-redis-跟-memcached-的差異">3.2 Redis 跟 Memcached 的差異<a class="h-anchor" href="#32-redis-%e8%b7%9f-memcached-%e7%9a%84%e5%b7%ae%e7%95%b0" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：是否了解過同類型的技術以及差異</mark></p>
<ul>
<li>Memcached 只支援<mark>簡單的資料型態</mark>，而 Redis <mark>支援多種資料型態</mark>（String、Hash、List、Set、Zset、Stream）。</li>
<li>Memcached <mark>不支援資料持久化</mark>，而 Redis <mark>提供 RDB 跟 AOF 兩種方案</mark>。</li>
</ul>
<p>同樣身為記憶體資料庫，Memcached 提供簡單的使用方式，而 Redis 提供豐富的功能。</p>
<hr>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://medium.com/dean-lin/8d0b45062f9">手把手帶你在 MacOS 安裝 Redis ＆Another Redis Desktop Manager(筆者部落格)</a></li>
<li><a href="https://medium.com/dean-lin/e814fe26a0f2">用 Node.js + Redis 解決高併發秒殺問題(筆者部落格)</a></li>
<li><a href="https://www.huaweicloud.com/articles/6785018d60d6272a49565cb148d661af.html">redis 之 sorted sets 類型及操作</a></li>
<li>使用 redis 的有序集合實現排行榜功能</li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-20/images/fullstack-career-notes-day-20-04.webp" width="1024" height="233" alt="參考資源（圖 4）" loading="lazy" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][資料庫]關聯式資料庫要如何設計避免超賣？</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-19/</link>
      <pubDate>Mon, 04 Oct 2021 08:25:46 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-19/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>網路商城特賣會常常會推出特定商品限量 1 組的搶購活動，在搶購過程除了會產生高併發的問題外，同時也需要考慮到「商品會不會超賣」，在現實生活中聽起來很不可思議；但在網路的世界裡，如果SQL邏輯沒有設計好，真的可能發生明明只限 1 組卻賣了 10 組商品的超賣事件。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/cover_hu_e4b5e96a082dd443.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>庫存只剩 1 件，但卻有 10 個人買到？</p>
</blockquote>
<p>網路商城特賣會常常會推出<mark>特定商品限量 1 組的搶購活動</mark>，在搶購過程除了會產生高併發的問題外，同時也需要考慮到「<mark>商品會不會超賣</mark>」，在現實生活中聽起來很不可思議；但在網路的世界裡，如果SQL邏輯沒有設計好，真的可能發生明明只限 1 組卻賣了 10 組商品的超賣事件。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>關聯式資料庫要如何設計避免超賣？</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 什麼是悲觀鎖（Pessimistic Lock）？</li>
<li>2.2 什麼是樂觀鎖（Optimistic Lock）？</li>
<li>2.3 在 MySQL DB 實作悲觀鎖</li>
<li>2.4 在 MySQL DB 實作樂觀鎖</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 悲觀鎖、樂觀鎖優缺點</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-關聯式資料庫要如何設計避免超賣">1. 關聯式資料庫要如何設計避免超賣？<a class="h-anchor" href="#1-%e9%97%9c%e8%81%af%e5%bc%8f%e8%b3%87%e6%96%99%e5%ba%ab%e8%a6%81%e5%a6%82%e4%bd%95%e8%a8%ad%e8%a8%88%e9%81%bf%e5%85%8d%e8%b6%85%e8%b3%a3" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-面試官為什麼會問">1.1 面試官為什麼會問？<a class="h-anchor" href="#11-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<p>這算是資料庫的<mark>常見面試題</mark>，對<mark>金融、電商</mark>相關產業來說都快要變成必考題了。</p>
<p>通常問完這題後會緊接著詢問：「如果是用 noSQL 有什麼處理方案？」這部分會在<a href="https://deanlin.net/blog/202110/fullstack-career-notes-day-20/">明天的文章</a>跟大家講解。</p>
<hr>
<h3 class="h-nomark" id="12-面試官想從答案確認什麼">1.2 面試官想從答案確認什麼？<a class="h-anchor" href="#12-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>是否有資料庫 Transaction 的概念</li>
<li>了解悲觀鎖（Pessimistic Lock）、樂觀鎖（Optimistic Lock）的概念</li>
<li>知道悲觀鎖（Pessimistic Lock）、樂觀鎖（Optimistic Lock）優缺點、使用情境</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-筆者提供的簡答">1.3 筆者提供的簡答<a class="h-anchor" href="#13-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>我會依照<mark>不同的情境</mark>來做設計，如果<mark>寫入需求較高</mark>，我會選擇使用<code>悲觀鎖（Pessimistic Lock）</code>；因為它是直接使用資料庫 <code>Transaction</code> 的機制，保證了資料的穩定性。</p>
<p>如果<mark>讀取需求較高</mark>，我會選擇使用<code>樂觀鎖（Optimistic Lock）</code>；因為它可以承受較高的吞吐量，不過它鎖的特性會導致<mark>購買順序未必是先到先得</mark>，只是保證不會超賣。</p>
<hr>
<h2 class="h-nomark" id="2-回答問題所需具備的知識">2. 回答問題所需具備的知識<a class="h-anchor" href="#2-%e5%9b%9e%e7%ad%94%e5%95%8f%e9%a1%8c%e6%89%80%e9%9c%80%e5%85%b7%e5%82%99%e7%9a%84%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-什麼是悲觀鎖pessimistic-lock">2.1 什麼是悲觀鎖（Pessimistic Lock）？<a class="h-anchor" href="#21-%e4%bb%80%e9%ba%bc%e6%98%af%e6%82%b2%e8%a7%80%e9%8e%96pessimistic-lock" aria-label="這個段落的連結"></a></h3>
<p>就跟字面上的意思一樣，非常的悲觀；它認為 <mark>Table 裡面的 data 非常不安全，無時無刻都在變動</mark>，當一個 SQL command（可以理解為搶購的用戶）獲得悲觀鎖後，其他的 SQL command 無法對這個 data 進行修改，直到悲觀鎖被釋放後才能執行。</p>
<blockquote>
<p>資料表中用 Synchronized 實現的鎖均為悲觀鎖（ex：行鎖，表鎖，讀鎖，寫鎖）。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="22-什麼是樂觀鎖optimistic-lock">2.2 什麼是樂觀鎖（Optimistic Lock）？<a class="h-anchor" href="#22-%e4%bb%80%e9%ba%bc%e6%98%af%e6%a8%82%e8%a7%80%e9%8e%96optimistic-lock" aria-label="這個段落的連結"></a></h3>
<p>相比悲觀鎖，它認為 <mark>Table 裡面的 data 變動頻率不會太頻繁</mark>，因此他會允許多個 SQL command 來操作 Table；但樂觀並不代表不負責，通常會在 Table 中增加一個 version 的欄位來做更新的確認。因此當 SQL command 想要變更欄位 data 時，會把<mark>之前取出的 version 跟現在的 version 做對比</mark>，如果相同就代表這段期間沒人修改可以執行；如果不同這次的操作就不會生效。</p>
<hr>
<h3 class="h-nomark" id="23-在-mysql-db-實作悲觀鎖">2.3 在 MySQL DB 實作悲觀鎖<a class="h-anchor" href="#23-%e5%9c%a8-mysql-db-%e5%af%a6%e4%bd%9c%e6%82%b2%e8%a7%80%e9%8e%96" aria-label="這個段落的連結"></a></h3>
<p><strong>情境描述：</strong></p>
<p>假設 A 跟 B 都想要買 iphone 手機，但商城的 iphone 庫存只剩下一隻，在不加鎖的狀況下同時下單會導致<mark>庫存變成負號</mark>；請問如何用<code>悲觀鎖</code>解決這個問題呢？</p>
<blockquote>
<p>只要安裝好 MySQL，跟著步驟操作即可模擬 A、B 客戶搶購。</p>
</blockquote>
<ul>
<li>
<p><strong>STEP 1</strong>：建立模擬資料</p>
<ul>
<li>
<p>先用 SQL command 建立一個簡單的 Table 並新增 data。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span><span style="color:#6e7681"> </span>AUTO_INCREMENT,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>VARCHAR(<span style="color:#a5d6ff">45</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">PRIMARY</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">KEY</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span>));<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">INSERT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">INTO</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span>)<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;iphone&#34;</span>,<span style="color:#a5d6ff">1</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>確認資料是否建立成功。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-01.webp" width="404" height="158" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p><strong>STEP 2</strong>：開一個視窗模擬 A 客戶搶購行為</p>
<ul>
<li>
<p>Transaction 的開始要用 <code>begin;</code>，不然 MySQL 會自動提交。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-02.webp" width="353" height="56" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 2）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>在 A 購買前先將 <code>id=1</code> 的 data 加上悲觀鎖，<mark>此時這行 data 只有 A 可以操作</mark>。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">select</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72">from</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">for</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">update</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-03.webp" width="603" height="148" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 3）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p><strong>STEP 3</strong>：<mark>開另一個視窗</mark>模擬 B 客戶搶購行為</p>
<ul>
<li>
<p>一樣將 <code>id=1</code> 的 data 加上悲觀鎖，下完指令後便會<mark>進入等待的模式，要等 A 執行 commit 完成交易或是指令逾時</mark>才能繼續。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-04.webp" width="618" height="99" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 4）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p><strong>STEP 4</strong>：回到 A 客戶的視窗完成交易</p>
<ul>
<li>
<p>讓 A 執行購買的動作，將 data 的 num 減一。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">update</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">set</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>然後檢視修改後的 data 是否符合預期。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">select</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72">from</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>最後下 <code>commit;</code> 結束這次交易。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-05.webp" width="585" height="281" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 5）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p><strong>STEP 5</strong>：當 A 完成交易後回到 B 的視窗<br>你會發現因為 A 釋放了悲觀鎖，所以 B 結束等待並且獲得鎖；但 data 的 num 已經變成 0 了，只能放棄購買。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-06.webp" width="585" height="143" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 6）" loading="lazy" decoding="async"></p>
<blockquote>
<p>7.89 秒是等待獲得鎖的時間。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="24-在-mysql-db-實作樂觀鎖">2.4 在 MySQL DB 實作樂觀鎖<a class="h-anchor" href="#24-%e5%9c%a8-mysql-db-%e5%af%a6%e4%bd%9c%e6%a8%82%e8%a7%80%e9%8e%96" aria-label="這個段落的連結"></a></h3>
<p><strong>情境描述：</strong></p>
<p>假設 A 跟 B 都想要買 iphone 手機，但商城的 iphone 庫存只剩下一隻，在不加鎖的狀況下同時下單會導致<mark>庫存變成負號</mark>；請問如何用<code>樂觀鎖</code>解決這個問題呢？</p>
<blockquote>
<p>觀察兩者實作上的邏輯差異。</p>
</blockquote>
<ul>
<li><strong>STEP 1</strong>：建立模擬資料
<ul>
<li>
<p>先用 SQL command 建立一個簡單的 Table 並插入 data，<mark>比起悲觀鎖的 Table，我們多了一個 version 的欄位</mark>。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span><span style="color:#6e7681"> </span>AUTO_INCREMENT,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>VARCHAR(<span style="color:#a5d6ff">45</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#ff7b72">version</span><span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">PRIMARY</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">KEY</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span>));<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">INSERT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">INTO</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#ff7b72">version</span><span style="color:#ff7b72;font-weight:bold">`</span>)<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;iphone&#34;</span>,<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">0</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>確認資料是否建立成功。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-07.webp" width="427" height="143" alt="2.4 在 MySQL DB 實作樂觀鎖（圖 7）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li><strong>STEP 2</strong>：規劃實作邏輯思路
<ul>
<li>A 跟 B 客戶在購買前會先將 <code>id=1</code> 的 data 找出來。</li>
<li>A 先購買，此時會以 <code>id=1 和 version=0</code> 作為 where 條件來更新 data，更新後 num -1、version +1 變成 <code>id=1、num=0、version=1</code>。</li>
<li>B 購買時會因為同樣是用 <code>id=1 和 version=0</code> 為 where 條件，所以會因為找不到 data 而無法更新。</li>
</ul>
</li>
<li><strong>STEP 3</strong>：模擬搶購情境，A 跟 B 客戶購買前先查詢庫存
<ul>
<li>
<p>A 及 B 執行查詢的 SQL command 會取得相同的 iphone 庫存資訊。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span>num,<span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-08.webp" width="621" height="143" alt="2.4 在 MySQL DB 實作樂觀鎖（圖 8）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li><strong>STEP 4</strong>：A 先執行購買的 SQL command
<ul>
<li>
<p>A 先購買 iphone 並更新 data。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">update</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">set</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span>,<span style="color:#6e7681"> </span><span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">and</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">0</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>檢視修改後的 data 是否符合預期。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span>num,<span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-09.webp" width="978" height="370" alt="2.4 在 MySQL DB 實作樂觀鎖（圖 9）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li><strong>STEP 5</strong>：B 再執行購買的 SQL command
<ul>
<li>
<p>此時 B 的購買就會失敗，在執行購買的 SQL command 後你會發現沒有 Row 被更新，因為<mark>庫存資訊已更新，但 SQL command 還是舊的</mark>。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-10.webp" width="978" height="75" alt="2.4 在 MySQL DB 實作樂觀鎖（圖 10）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. 衍伸問題<a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-悲觀鎖樂觀鎖優缺點">3.1 悲觀鎖、樂觀鎖優缺點<a class="h-anchor" href="#31-%e6%82%b2%e8%a7%80%e9%8e%96%e6%a8%82%e8%a7%80%e9%8e%96%e5%84%aa%e7%bc%ba%e9%bb%9e" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：對於關聯式資料庫「鎖」的認知程度</mark></p>
<ul>
<li><strong>悲觀鎖（Pessimistic Lock）</strong>
<ul>
<li><strong>優點</strong><br>使用資料庫 <code>Transaction</code> 的機制來強迫執行的順序。</li>
<li><strong>缺點</strong><br>一但加入 Transaction 的機制，會導致其他的 SQL command 針對這個 data <mark>除了查詢功能外全部卡死</mark>，如果這段 Transaction 執行時間較長，<mark>會給使用者不良的體驗並造成系統吞吐量下降</mark>。</li>
</ul>
</li>
<li><strong>樂觀鎖（Optimistic Lock）</strong>
<ul>
<li><strong>優點</strong><br>因為沒有在資料庫加鎖，所以 SQL command 都會對 data 進行操作，<mark>只有在更新 data 時才會做驗證</mark>；這樣做就能<mark>避免因使用悲觀鎖而導致吞吐量下降</mark>的問題。</li>
<li><strong>缺點</strong><br>因為樂觀鎖是我們人為實現的，所以如果<mark>換一個業務場景可能會不適用</mark>，甚至可能因為其他的 SQL command 導致錯誤。</li>
</ul>
</li>
</ul>
<hr>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://medium.com/dean-lin/2cabb858726d#55e3">理解資料庫『悲觀鎖』和『樂觀鎖』的觀念(筆者部落格)</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-11.webp" width="1024" height="233" alt="參考資源（圖 11）" loading="lazy" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][資料庫]面對高流量的的系統，會採取哪些措施？</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-18/</link>
      <pubDate>Sun, 03 Oct 2021 07:33:32 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-18/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>除非你的實戰經驗超級豐富，不然在面試時一定會遇到不熟悉的議題；面對無法回答的問題，我個人會老實向面試官表明自己沒有這方面的經驗，會持續學習，瞎掰答案容易讓對方覺得你以後進來公司也會這樣做事。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-18/images/cover_hu_1dbb5eb5480f50de.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>經驗不夠，讀書來湊。</p>
</blockquote>
<p>除非你的實戰經驗超級豐富，不然在面試時一定會遇到不熟悉的議題；面對無法回答的問題，我個人會老實向面試官<mark>表明自己沒有這方面的經驗，會持續學習</mark>，瞎掰答案容易讓對方覺得你以後進來公司也會這樣做事。</p>
<p>不過有些議題雖然實際碰過的人不多，但網路上有很多資源可以學習；如果剛好有研究過相關資料，筆者會回答：「<mark>雖然沒有在工作上使用過，但在下班後我有特別研究這個議題，如果思路上有不對的部分還請各位面試官批評指教。</mark>」</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>面對高流量的的系統，會採取哪些措施？</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 負載平衡（Load Balance）</li>
<li>2.2 主從式架構（Master Slave Repliaction）</li>
<li>2.3 資料庫讀寫分離（read/write splitting）</li>
<li>2.4 垂直切分（vertical-partition）</li>
<li>2.5 水平切分（horizontal-partition）</li>
<li>2.6 分表（Partition Table）</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 有遇過主從複製延遲問題嗎？怎麼解決？</li>
<li>3.2 分散式資料庫（Distributed Database）有哪些優點跟缺點？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-面對高流量的的系統會採取哪些措施">1. 面對高流量的的系統，會採取哪些措施？<a class="h-anchor" href="#1-%e9%9d%a2%e5%b0%8d%e9%ab%98%e6%b5%81%e9%87%8f%e7%9a%84%e7%9a%84%e7%b3%bb%e7%b5%b1%e6%9c%83%e6%8e%a1%e5%8f%96%e5%93%aa%e4%ba%9b%e6%8e%aa%e6%96%bd" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-面試官為什麼會問">1.1 面試官為什麼會問？<a class="h-anchor" href="#11-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<p>如果面試的公司在做<mark>金融、電商、社交、媒體</mark>的系統，那這題算是該<mark>職缺所需具備的技術</mark>；儘管也可以透過後天培養，但如果有現成的人才，誰會想要花時間做訓練。</p>
<hr>
<h3 class="h-nomark" id="12-面試官想從答案確認什麼">1.2 面試官想從答案確認什麼？<a class="h-anchor" href="#12-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>要如何設計才能讓前後端伺服器承受大流量情境</li>
<li>資料庫為了處理大流量，可以做哪些調整</li>
<li>這些處理的方式在實務上是否有遇到問題，如何去驗證你的想法是否正確</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-筆者提供的簡答">1.3 筆者提供的簡答<a class="h-anchor" href="#13-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>前後端我會用 <code>Load Balance</code> 緩解 Server 的壓力；在資料庫方面則採取<mark>主從式架構</mark>、<mark>讀寫分離</mark>來降低壓力。</p>
<p>如果經過上面的設計後，資料庫還是無法負荷，可以先用業務種類作為切分條件；以電商系統來說我會先做<mark>垂直切分（vertical-partition）</mark>，像是將商品、訂單、明細拆成 3 個資料庫，以此大幅降低單一資料庫被存取的流量。</p>
<p>如果垂直切分後還是在某個資料庫遇到瓶頸，像是訂單的的資料庫內容遠大於其他資料庫；此時會再做<mark>水平切分（horizontal-partition</mark>），可能會依照訂單的地區再切分成幾個資料庫。</p>
<hr>
<h2 class="h-nomark" id="2-回答問題所需具備的知識">2. 回答問題所需具備的知識<a class="h-anchor" href="#2-%e5%9b%9e%e7%ad%94%e5%95%8f%e9%a1%8c%e6%89%80%e9%9c%80%e5%85%b7%e5%82%99%e7%9a%84%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-負載平衡load-balance">2.1 負載平衡（Load Balance）<a class="h-anchor" href="#21-%e8%b2%a0%e8%bc%89%e5%b9%b3%e8%a1%a1load-balance" aria-label="這個段落的連結"></a></h3>
<p>無論是電商還是媒體的系統，幾乎長時間都要面臨大流量的議題，想當然<mark>一台伺服器是無法承受高流量的</mark>。</p>
<p>而負載平衡（Load Balance）就是為了解決這個問題而誕生的，既然 1 台 Server 無法承受 10 萬流量；那我們就用 100 台 Server 來分散流量吧！</p>
<p>早期做法是先預估流量，然後開啟對應數量的 Server 來作分流；這樣的做法多開頂多浪費錢，但如果估計錯誤少開就可能導致系統當機。</p>
<p>所以像是 <code>GCP、AWS</code> 這類雲端廠商所提供的負載平衡（Load Balance），就有<mark>依照伺服器健康程度來做自動擴展（auto-scaling）</mark>的服務，讓大家不需要去猜要開多少台才合適；如果有使用 <code>Kubernetes</code> 並設定好相關參數，在遇到大流量時也會<mark>自動擴展 Pod</mark> 來應付。</p>
<blockquote>
<p>有時會被問到你的系統過去遇到最高的 QPS 是多少，QPS 指的是每秒查詢率（Queries Per Second）。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="22-主從式架構master-slave-replication">2.2 主從式架構（Master Slave Replication）<a class="h-anchor" href="#22-%e4%b8%bb%e5%be%9e%e5%bc%8f%e6%9e%b6%e6%a7%8bmaster-slave-replication" aria-label="這個段落的連結"></a></h3>
<p>上面提到的負載平衡只能做到讓網頁成功打開，如果資料庫 Server 沒有做對應的設計，這樣的大流量跟 <code>DDOS</code> 根本沒兩樣，足以讓資料庫 Server 陣亡；所以有時會發現<mark>進入某個網站後可以看到 UI 佈局，卻沒有資料顯示</mark>。</p>
<p>下面我們先探討主從式架構，目標是<mark>先讓資料庫 Server 能承受大流量的檢索</mark>，接著再來考慮寫入問題。</p>
<ul>
<li>
<p><strong>為什麼需要 Master Slave Replication 的架構？</strong></p>
<ul>
<li><strong>網站需要面對大流量</strong><br>有些網站<mark>無論哪個時段都會被大量檢索</mark>，如果資料庫只用一個 Server 來處理會無法負荷高流量。</li>
<li><strong>資料庫內容需要被分析</strong><br>公司如果有<mark>資料分析</mark>部門，他們也會隨時監控資料庫的變化來產生報表，但如果跟一般使用者一樣 query 同一台 Server，會使這台 Server 增加不必要的負荷，甚至導致使用者體驗下降。</li>
</ul>
</li>
<li>
<p><strong>Master Slave Replication 為什麼能夠解決這些問題？</strong></p>
<ul>
<li><strong>主從式架構</strong><br>將資料庫的 Server 區分成 Master(1 台) 與 Slave(N 台) 兩種，<mark>Master 負責資料寫入，Slave 提供資料檢索</mark>；這樣就把壓力分散到幾台 Server 上面。</li>
<li><strong>資料庫同步</strong><br>Slave 會同步 Master 的資料，這樣除了<mark>分散 Server 壓力外，資料也獲得了備份</mark>。</li>
</ul>
</li>
<li>
<p><strong>Master Slave Replication 的架構</strong></p>
<ul>
<li>在 Master 的 Server 執行的 SQL command 會被記錄在 Binary Log 裡面。</li>
<li>Master 將 Binary Log 傳送到 Slave 的 Relay Log。</li>
<li>Slave 依據 Relay Log 做資料的變更。</li>
</ul>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-18/images/fullstack-career-notes-day-18-01.webp" width="1024" height="613" alt="2.2 主從式架構（Master Slave Replication）（圖 1）" fetchpriority="high" decoding="async"></p>
<blockquote>
<p>Relay Log 與 Binary Log 檔案格式、內容都一樣，不同在於 Slave 在執行完 Relay Log 的 SQL 之後，會自己刪除當前的 Relay Log。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-資料庫讀寫分離readwrite-splitting">2.3 資料庫讀寫分離（read/write splitting）<a class="h-anchor" href="#23-%e8%b3%87%e6%96%99%e5%ba%ab%e8%ae%80%e5%af%ab%e5%88%86%e9%9b%a2readwrite-splitting" aria-label="這個段落的連結"></a></h3>
<p>我們知道 Master Server 是用來更新資料，Slave Server 是用來檢索資料；但<mark>它們都是獨立的 ip</mark>，使用者在存取資料庫的時候並不知道要使用哪一台 Server ，除非你的 Slave Server 只用來做資料分析。</p>
<p>因此我們要在中間設計一台 <code>Proxy Server</code>，準確地將 SQL 指令分流到正確的 Server。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-18/images/fullstack-career-notes-day-18-02.webp" width="1024" height="652" alt="2.3 資料庫讀寫分離（read/write splitting）（圖 2）" loading="lazy" decoding="async"></p>
<blockquote>
<p>媒體類型的平台在讀的需求很高，寫的需求相對低；因此多會採用主從式架構做讀寫分離。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="24-垂直切分vertical-partition">2.4 垂直切分（vertical-partition）<a class="h-anchor" href="#24-%e5%9e%82%e7%9b%b4%e5%88%87%e5%88%86vertical-partition" aria-label="這個段落的連結"></a></h3>
<p>在讀寫分離後，儘管讀的部分沒有問題；但把寫入的工作交給一個資料庫負責還是會遇到瓶頸。</p>
<p>此時資料庫的垂直切分（vertical-partition）就派上用場了；以商城舉例，商品、訂單、付款明細<mark>各自的業務量都很大</mark>，所以我們可以先依照業務性質把它<mark>拆分成 3 個資料庫</mark>，如此設計可以分散資料庫被存取的流量，來提升系統穩定度。</p>
<hr>
<h3 class="h-nomark" id="25-水平切分horizontal-partition">2.5 水平切分（horizontal-partition）<a class="h-anchor" href="#25-%e6%b0%b4%e5%b9%b3%e5%88%87%e5%88%86horizontal-partition" aria-label="這個段落的連結"></a></h3>
<p>在垂直切分後，有些資料庫還是會遇到瓶頸，像是訂單的資料庫可能遠遠大於其他資料庫。</p>
<p>這時我們就會需要做水平切分（horizontal-partition），將 DB 中<mark>同一類型的資料依據特定邏輯拆分成多個 DB</mark>；像是訂單可能來自於世界各地，所以我們可能就以地區（ex：亞洲、歐洲、美洲&hellip;）做切分的邏輯。</p>
<hr>
<h3 class="h-nomark" id="26-分區表partition-table">2.6 分區表（Partition Table）<a class="h-anchor" href="#26-%e5%88%86%e5%8d%80%e8%a1%a8partition-table" aria-label="這個段落的連結"></a></h3>
<p>資料庫在經歷了垂直切分與水平切分後，寫入遇到的瓶頸就差不多解決了。</p>
<p>但接下來會遇到另外一個問題，Table 中的資料會隨著時間會不斷增長，如果<mark>資料量龐大到就算建立 Index 都無法提升效能</mark>；此時就要考慮將 Table 依照特定邏輯做切割，通常會選擇<mark>自動產生的 id、日期</mark>這類的欄位作為切割依據，在 Table 被切割後因為資料量變少，所以搜尋效能隨之提升。</p>
<p><strong>特別提醒</strong>：如果這個 Table 有 Primary key 或是 Unique key，<mark>分區鍵（Partition Key）必須是 Primary key 或是 Unique key 組成的一個部分</mark>。</p>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. 衍伸問題<a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-有遇過主從複製延遲問題嗎怎麼解決">3.1 有遇過主從複製延遲問題嗎？怎麼解決？<a class="h-anchor" href="#31-%e6%9c%89%e9%81%87%e9%81%8e%e4%b8%bb%e5%be%9e%e8%a4%87%e8%a3%bd%e5%bb%b6%e9%81%b2%e5%95%8f%e9%a1%8c%e5%97%8e%e6%80%8e%e9%ba%bc%e8%a7%a3%e6%b1%ba" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：瞭解求職者的實務經驗</mark></p>
<p>當時我使用的是 MySQL 資料庫，用 sysbench 這個測試工具模擬<mark>短時間執行大量的 SQL command</mark>（ex：Table 內容大量更新、Index 建立）；但去 Slave 的資料庫確認同步狀況時，卻發現並沒有完全同步 Master 的資料。</p>
<p>經過研究後發現 MySQL 預設的 Process 只有一個，因此累積了很多尚未同步的 SQL Command；將 MySQL 調整成<code>多 Process</code>後，Slave同步的效率就大幅提升了。</p>
<blockquote>
<p>可透過修改「slave_parallel_type、slave_parallel_workers」這兩個變數來調整 Process數量。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="32-分散式資料庫distributed-database有哪些優點跟缺點">3.2 分散式資料庫（Distributed Database）有哪些優點跟缺點？<a class="h-anchor" href="#32-%e5%88%86%e6%95%a3%e5%bc%8f%e8%b3%87%e6%96%99%e5%ba%abdistributed-database%e6%9c%89%e5%93%aa%e4%ba%9b%e5%84%aa%e9%bb%9e%e8%b7%9f%e7%bc%ba%e9%bb%9e" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：對分散式資料庫的認知程度</mark></p>
<ul>
<li><strong>優點</strong>
<ul>
<li><strong>穩定度與可用性提升</strong><br>過去靠一個資料庫打天下，如果這個資料庫掛了一切都涼了；但建立 Replication 制度後，Slave 資料庫除了可以<mark>分擔流量</mark>，在 Master 資料庫發生問題也可以讓其他 <mark>Slave 資料庫轉成 Master</mark>，不影響整體系統。</li>
<li><strong>擴充性</strong><br>因為資料分散儲存在多個資料庫，如果有更多的需求只要增加伺服器時設定即可。</li>
</ul>
</li>
<li><strong>缺點</strong>
<ul>
<li><strong>開發困難度</strong><br>因為資料分散在各個資料庫，所以對開發人員的經驗以及技術要求更高；如果設計不良反而會影響到資料安全性。</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<p>紙上得來終覺淺，絕知此事要躬行。 ── 陸游</p>
</blockquote>
<p>如果大家想要<mark>親手實作</mark>今天的面試題，在參考資源中有分享一些實作流程喔！</p>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://medium.com/dean-lin/16d0a0fa1d04">MySQL Master Slave Replication 主從式架構設定教學(筆者部落格)</a></li>
<li><a href="https://medium.com/dean-lin/72759b1b1b33">Ubuntu 用 Atlas 實現 MySQL Proxy 讀寫分離(筆者部落格)</a></li>
<li><a href="https://medium.com/dean-lin/ec59acea6052">用 sysbench 測試 MySQL 性能(筆者部落格)</a></li>
<li><a href="https://medium.com/dean-lin/67c2aec6b021">解決 MySQL 主從複製的延遲問題(筆者部落格)</a></li>
<li><a href="https://ithelp.ithome.com.tw/articles/10227066">30-26 之資料庫層的擴展 - 分庫分表架構</a></li>
<li><a href="https://codingnote.cc/zh-tw/p/215115/">MySQL 分區</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-18/images/fullstack-career-notes-day-18-03.webp" width="1024" height="233" alt="參考資源（圖 3）" loading="lazy" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][資料庫]設計資料庫時會考量哪些點？</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-17/</link>
      <pubDate>Sat, 02 Oct 2021 08:46:41 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-17/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>如果公司有專門的 DBA，後端工程師就可以把心力放在資料邏輯的處理上；但筆者詢問周圍的朋友，似乎有不少公司都是讓後端工程師兼任 DBA...</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/cover_hu_69932cbb52ce4eda.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>好的設計讓你上天堂，爛的設計讓你下地獄。</p>
</blockquote>
<p>如果公司有專門的 DBA，後端工程師就可以把心力放在資料邏輯的處理上；但筆者詢問周圍的朋友，似乎有不少公司都是讓後端工程師兼任 DBA&hellip;</p>
<p>筆者在這塊也並非專家，主要是分享自己實作中會注意的基礎細節，<mark>避免犯下一些原則性錯誤</mark>。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>設計資料庫時會考量哪些點？</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 在 Table 建立有效的 Index</li>
<li>2.2 資料庫設計與優化</li>
<li>2.3 MySQL 與 PostgreSQL 的區別及應用場景</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 為什麼 Index 不能建立太多？</li>
<li>3.2 寫 DB SQL 時會注意什麼？</li>
<li>3.3 有用過 NoSQL 嗎？什麼應用情境下會使用呢？</li>
<li>3.4 你會用哪些點來評估要使用關聯式資料庫 or 非關聯式資料庫？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-設計資料庫時會考量哪些點">1. 設計資料庫時會考量哪些點？<a class="h-anchor" href="#1-%e8%a8%ad%e8%a8%88%e8%b3%87%e6%96%99%e5%ba%ab%e6%99%82%e6%9c%83%e8%80%83%e9%87%8f%e5%93%aa%e4%ba%9b%e9%bb%9e" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-面試官為什麼會問">1.1 面試官為什麼會問？<a class="h-anchor" href="#11-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<ul>
<li>因為<mark>履歷資訊</mark>中表明擅長多種資料庫（Database）
<ul>
<li>MySQL、MSSQL、PostgreSQL</li>
</ul>
</li>
<li>且<mark>履歷資訊</mark>中還表明有優化資料庫的經驗。
<ul>
<li><strong>XXX 資料庫系統</strong><br>將 Visual Basic 的單機版程式改寫成網頁系統，整合過去冗贅資料表，優化使用者體驗。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-面試官想從答案確認什麼">1.2 面試官想從答案確認什麼？<a class="h-anchor" href="#12-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>選用資料庫前，會思考哪些問題</li>
<li>設計資料庫時會注意哪些細節</li>
<li>同樣都是關聯式資料庫，你知道他們間的差異嗎？</li>
<li>有用過哪些方式優化資料庫效能</li>
<li>你對 NoSQL 的認知有多少</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-筆者提供的簡答">1.3 筆者提供的簡答<a class="h-anchor" href="#13-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>在決定要使用什麼資料庫前，我會先分析專案的<mark>應用場景</mark>：</p>
<ul>
<li>像是<code>商城</code>會有金流，所以資料穩定性與 Transaction 機制是很重要的。</li>
<li>如果要設計<mark>地圖平台</mark>，就會選擇對 GIS 支援度高的資料庫。</li>
</ul>
<p>在設計資料庫時會畫 <code>ER Model</code> 來分析資料間的關聯與屬性；在優化方面，為了要提高搜尋效率會在 Table <code>建立 Index</code>，同時也會依據實際業務設計<mark>欄位適合的長度及 Type</mark>。</p>
<hr>
<h2 class="h-nomark" id="2-回答問題所需具備的知識">2. 回答問題所需具備的知識<a class="h-anchor" href="#2-%e5%9b%9e%e7%ad%94%e5%95%8f%e9%a1%8c%e6%89%80%e9%9c%80%e5%85%b7%e5%82%99%e7%9a%84%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-在-table-建立有效的-index">2.1 在 Table 建立有效的 Index<a class="h-anchor" href="#21-%e5%9c%a8-table-%e5%bb%ba%e7%ab%8b%e6%9c%89%e6%95%88%e7%9a%84-index" aria-label="這個段落的連結"></a></h3>
<p>我們知道在 Table 建立 Index 可以增加搜尋效率，但你知道自己建立的 Index 有沒有派上用場嗎？筆者將在這個小節透過實作，帶大家了解 <code>Coverage Index (覆蓋索引)</code>的意義。</p>
<ul>
<li>
<p><strong>SETP 1</strong>：建立測試用的 <strong>tmp_user</strong> Table</p>
<ul>
<li><strong>id</strong> 為 primary key</li>
<li><strong>name</strong> 為 index</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">create</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">table</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>id<span style="color:#6e7681"> </span>bigint(<span style="color:#a5d6ff">20</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">not</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">null</span><span style="color:#6e7681"> </span>auto_increment<span style="color:#6e7681"> </span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>name<span style="color:#6e7681"> </span>varchar(<span style="color:#a5d6ff">255</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">not</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">null</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>password<span style="color:#6e7681"> </span>varchar(<span style="color:#a5d6ff">255</span>)<span style="color:#6e7681"> </span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#ff7b72">index</span><span style="color:#6e7681"> </span>index_name<span style="color:#6e7681"> </span>(name),<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#ff7b72">primary</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">key</span><span style="color:#6e7681"> </span>(id))<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>engine<span style="color:#ff7b72;font-weight:bold">=</span>innodb<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#ff7b72">default</span><span style="color:#6e7681"> </span>character<span style="color:#6e7681"> </span><span style="color:#ff7b72">set</span><span style="color:#ff7b72;font-weight:bold">=</span>utf8<span style="color:#6e7681"> </span><span style="color:#ff7b72">collate</span><span style="color:#ff7b72;font-weight:bold">=</span>utf8_general_ci;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">insert</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">into</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span>(name,<span style="color:#6e7681"> </span>password)<span style="color:#6e7681"> </span><span style="color:#ff7b72">values</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;寶寶不說&#34;</span>,<span style="color:#a5d6ff">&#34;123&#34;</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p><strong>SETP 2</strong>：下 SQL 指令了解是否使用 Index</p>
<blockquote>
<p>觀察最後「Extra」欄位的資訊有否有 <code>Using index</code></p>
</blockquote>
<ul>
<li>
<p><strong>SQL A</strong>：<code>explain select id from user_table where name= '寶寶不說';</code></p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/fullstack-career-notes-day-17-01.webp" width="1024" height="118" alt="2.1 在 Table 建立有效的 Index（圖 1）" fetchpriority="high" decoding="async"></p>
<p>因為 <code>name</code> 有建立 index，所以在查詢到 <code>name</code> 後可以直接與 <code>id</code> 對應返回結果；因為 name 已經覆蓋了查詢資料的需求，所以稱為 「Coverage Index」。</p>
</li>
<li>
<p><strong>SQL B</strong>：<code>explain select password from user_table where name= '寶寶不說';</code></p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/fullstack-career-notes-day-17-02.webp" width="1024" height="118" alt="2.1 在 Table 建立有效的 Index（圖 2）" loading="lazy" decoding="async"></p>
<p>透過 <code>name</code> 找到「寶寶不說」對應的 <code>id</code>，但接著要透過 id 這個 <code>primary key</code>，在 Table 重新輪詢才能取得這一列的完整資料。</p>
</li>
</ul>
</li>
<li>
<p><strong>SETP 3</strong>：建立 Coverage Index 優化 SQL B</p>
<ul>
<li>
<p><strong>先將資料庫 drop 掉</strong>：<code>drop table tmp_user;</code></p>
</li>
<li>
<p><strong>重新建立測試資料以及「Coverage Index」</strong><br>為了避免用 primary key 再次從 Table 查詢的耗能，我們可以<code>把 select 與 where 用到的欄位(name、password)做聯合索引</code>，這樣就能夠直接使用索引查詢，而不需用 primary key 重新比對，這就是「Coverage Index」的應用。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">create</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">table</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>id<span style="color:#6e7681"> </span>bigint(<span style="color:#a5d6ff">20</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">not</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">null</span><span style="color:#6e7681"> </span>auto_increment<span style="color:#6e7681"> </span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>name<span style="color:#6e7681"> </span>varchar(<span style="color:#a5d6ff">255</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">not</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">null</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>password<span style="color:#6e7681"> </span>varchar(<span style="color:#a5d6ff">255</span>)<span style="color:#6e7681"> </span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72">primary</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">key</span><span style="color:#6e7681"> </span>(id))<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>engine<span style="color:#ff7b72;font-weight:bold">=</span>innodb<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72">default</span><span style="color:#6e7681"> </span>character<span style="color:#6e7681"> </span><span style="color:#ff7b72">set</span><span style="color:#ff7b72;font-weight:bold">=</span>utf8<span style="color:#6e7681"> </span><span style="color:#ff7b72">collate</span><span style="color:#ff7b72;font-weight:bold">=</span>utf8_general_ci;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">insert</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">into</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span>(name,<span style="color:#6e7681"> </span>password)<span style="color:#6e7681"> </span><span style="color:#ff7b72">values</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;寶寶不說&#34;</span>,<span style="color:#a5d6ff">&#34;123&#34;</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">alter</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">table</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span><span style="color:#ff7b72">add</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">index</span><span style="color:#6e7681"> </span>name_password(name,password);<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p><strong>再次執行 SQL B</strong>：<code>explain select password from tmp_user where name = '寶寶不說';</code><br>本次的查詢就可以在「Extra」欄位看到 <code>Using index</code> 嚕～</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/fullstack-career-notes-day-17-03.webp" width="1024" height="116" alt="2.1 在 Table 建立有效的 Index（圖 3）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-資料庫設計與優化">2.2 資料庫設計與優化<a class="h-anchor" href="#22-%e8%b3%87%e6%96%99%e5%ba%ab%e8%a8%ad%e8%a8%88%e8%88%87%e5%84%aa%e5%8c%96" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>使用 ER Model 做規畫</strong><br>在需求規格出來後，我們可以先透過 ER Model 整理資料間的關聯性，然後以此為基礎設計資料庫；向新人說明 Table 間的關聯時，它也是一個非常棒的輔助工具。</li>
<li><strong>設計合適的文字欄位</strong>
<ul>
<li>如果不確定該欄位會填入的文字長度，會選擇 VARCHAR 這種依據文字長度來使用儲存空間的 Type。</li>
<li>以時常變更文字的欄位來說，會選擇 CHAR 而非 VARCHAR；因為 VARCHAR 的欄位在儲存時會需要額外的計算。</li>
<li>盡量<code>避免使用 TEXT/BLOB</code> 這類的 Type，它們在查詢時會消耗更多的效能。</li>
<li>根據實際業務需求設計欄位長度，</li>
</ul>
</li>
<li><strong>減少 SQL 寫入次數</strong><br>以 Insert 來做舉例，假設有 1000 筆資料要寫入 Table；一次插入 1000 筆的執行時間會遠遠小於分 1000 次插入的時間。</li>
<li><strong>適當的增加冗餘欄位</strong><br>在設計資料庫時要盡量符合三大正規化；但 <mark>Table 間的關聯越複雜也會導致查詢效率的下降</mark>，因此有時會適當的增加冗餘欄位，用犧牲磁碟空間的方式換取更高的查詢效率。</li>
<li><strong>定期審視資料庫的規劃</strong><br>如果沒有專門的 DBA，那資料庫通常會有以下問題需要修正：
<ul>
<li>許多欄位因為不確定實際業務需求，直接把<mark>欄位長度開到最大</mark>。</li>
<li>因為新需求不斷產生，在思慮不夠周密的狀況下容易<mark>新增許多無用欄位</mark>。</li>
<li>在系統版本的更迭中，<mark>沒有整理</mark>不符合實際業務需求的<code>檢視表（View）</code>。</li>
<li>後端存取 Table 的欄位改變，但 Table 的 Index 並沒有一起調整，導致<mark>許多 Index 並沒有發揮作用</mark>。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-mysql-與-postgresql-的區別及應用場景">2.3 MySQL 與 PostgreSQL 的區別及應用場景<a class="h-anchor" href="#23-mysql-%e8%88%87-postgresql-%e7%9a%84%e5%8d%80%e5%88%a5%e5%8f%8a%e6%87%89%e7%94%a8%e5%a0%b4%e6%99%af" aria-label="這個段落的連結"></a></h3>
<p><code>MySQL</code> 是世界上最流行的資料庫之一，世界上有非常多的 Web Application 都採用它作為解決方案；但在被收購後分出了非常多的版本，<mark>有免費也有收費的</mark>；而 <code>PostgreSQL</code> 基於 BSD/MIT 的 linecense，為<mark>完全開源的資料庫</mark>。</p>
<ul>
<li><strong>PostgreSQL 的優勢</strong>
<ul>
<li><strong>不僅是關聯式資料庫，還支援 jsonb 的格式</strong><br>相比於 json，jsonb 以二進位格式儲存且可以使用索引（Index），你可以利用它<mark>整合關聯式資料與非關聯式資料</mark>。</li>
<li><strong>有 PostGIS 讓他成為很棒的空間資料庫</strong><br>相比於 MySQL spatial extension，PostGIS 可以<mark>支持「二維、三維、曲線」的空間類型</mark>，並有豐富的空間操作函數。</li>
<li><strong>可以輕鬆與外部資料庫關聯</strong><br>透過 FDW（Foreign Data Wrapper），你可以把外部資料庫（ex：MySQL、Oracle、CSV）當成自己資料庫中的 Table 查詢，讓 PostgreSQL 成為<mark>聯合資料庫</mark>。</li>
</ul>
</li>
<li><strong>MySQL 的優勢</strong>
<ul>
<li><strong>會 MySQL 的工程師更多</strong><br><mark>在招募工程師時更容易</mark>，遇到問題時也有豐富的社群資源提供解答。</li>
<li><strong>MySQL 使用 Thread 而 PostgreSQL 使用 Process</strong>
<ul>
<li>
<p>Process 比 Thread 要花更多時間建立連線。</p>
</li>
<li>
<p>Process 因為缺失 Thread 實作的「共享」特性，使得每次連線建立時，都需要耗費較高的記憶體空間配置。</p>
<blockquote>
<p>PostgreSQL 可以搭配 Connection Pool 的外掛解決這個問題，但架構上就會變更複雜。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>應用場景</strong>
<ul>
<li><strong>MySQL</strong><br>適合業務邏輯相對簡單、較少處理資料一致化問題的網際網路場景。</li>
<li><strong>PostgreSQL</strong><br>專案資料寫入頻繁，且資料庫龐大而複雜，內容需要高度一致性，有 GIS 需求。</li>
</ul>
</li>
</ul>
<blockquote>
<p>其實在 DB 的選擇上，除了考慮專案適用的場景外，還要考量到<mark>團隊內的工程師對哪個比較擅長；思考萬一發生意外時有誰可以救場</mark>，不然災難復原是一場惡夢。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. <strong>衍伸問題</strong><a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-為什麼-index-不能建立太多">3.1 為什麼 Index 不能建立太多？<a class="h-anchor" href="#31-%e7%82%ba%e4%bb%80%e9%ba%bc-index-%e4%b8%8d%e8%83%bd%e5%bb%ba%e7%ab%8b%e5%a4%aa%e5%a4%9a" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：了解 Index 對資料庫效能的影響</mark></p>
<p>Index 的數量太多，會影嚮 DML（Insert、Update、Delete）的效能，<mark>因為 Table 資料更新時也要連帶更新 Index</mark>。</p>
<hr>
<h3 class="h-nomark" id="32-寫-db-sql-時會注意什麼">3.2 寫 DB SQL 時會注意什麼？<a class="h-anchor" href="#32-%e5%af%ab-db-sql-%e6%99%82%e6%9c%83%e6%b3%a8%e6%84%8f%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認求職者的 SQL 基本功</mark></p>
<ul>
<li>不要回傳大量資料，要有條件的<mark>回傳固定筆數</mark>（ex：limit）。</li>
<li>Select 時盡量不要使用「*」，<mark>只抓取需要的欄位</mark>。</li>
<li>Select 充分使用 <code>Index</code> 來做搜尋。</li>
<li><mark>避免使用不兼容的型態</mark>做比較，像是 FLOAT 跟 INT、CHAR 跟 VARCHAR。</li>
</ul>
<hr>
<h3 class="h-nomark" id="33-有用過-nosql-嗎什麼應用情境下會使用呢">3.3 有用過 NoSQL 嗎？什麼應用情境下會使用呢？<a class="h-anchor" href="#33-%e6%9c%89%e7%94%a8%e9%81%8e-nosql-%e5%97%8e%e4%bb%80%e9%ba%bc%e6%87%89%e7%94%a8%e6%83%85%e5%a2%83%e4%b8%8b%e6%9c%83%e4%bd%bf%e7%94%a8%e5%91%a2" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認求職者對 NoSQL 的認知</mark></p>
<blockquote>
<p>即便你的履歷中沒有寫自己會 NoSQL，還是會遇到這類考題，所以建議至少有基礎的認知。</p>
</blockquote>
<ul>
<li><strong>資料表欄位常常變化</strong><br>像是商城這種擁有龐大資料量的系統，在關連式資料庫中如果要 Update 欄位有很多顧慮；因為每個操作都需要重頭到尾輪詢一次 Table，非常消耗時間。<br>如果使用 <code>MongoDB</code> 這類的 NoSQL，就可以很靈活的對欄位進行調整。</li>
<li><strong>需要高速的讀寫</strong><br>像是 <code>Redis</code> 這類記憶體資料庫，單一節點就能應付每秒 10 萬次的讀寫請求。</li>
<li><strong>資料量龐大，有擴充需求</strong><br>NoSQL 具備<mark>水平擴充</mark>能力，只要增加新的伺服器節點就能擴充資料庫容量；且對於伺服器的性能要求較低，可使用<code>較便宜</code>的電腦進行擴充。</li>
</ul>
<hr>
<h3 class="h-nomark" id="34-你會用哪些點來評估要使用關聯式資料庫-or-非關聯式資料庫">3.4 你會用哪些點來評估要使用關聯式資料庫 or 非關聯式資料庫？<a class="h-anchor" href="#34-%e4%bd%a0%e6%9c%83%e7%94%a8%e5%93%aa%e4%ba%9b%e9%bb%9e%e4%be%86%e8%a9%95%e4%bc%b0%e8%a6%81%e4%bd%bf%e7%94%a8%e9%97%9c%e8%81%af%e5%bc%8f%e8%b3%87%e6%96%99%e5%ba%ab-or-%e9%9d%9e%e9%97%9c%e8%81%af%e5%bc%8f%e8%b3%87%e6%96%99%e5%ba%ab" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認求職者是否有評估的能力與經驗</mark></p>
<p>我會考慮的點有<mark>資料量多寡、併發量、實時性、資料一致性、未來擴充性、資料穩定性、維護成本</mark>。</p>
<p>以企業內部管理系統來說，因為資料量少、併發數少、未來擴充需求較低，所以會選擇關連式資料庫；如果是遊戲的排行榜頁面，因為資料量大、併發數高、且要求高的更新頻率，所以我會考慮使用記憶體資料庫（ex：Redis）。</p>
<hr>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li>MySQL 資料庫面試題</li>
<li><a href="https://medium.com/dean-lin/f852af308c27">你在 Table 建立的 Index 真的有效嗎？用範例帶你理解 Coverage Index 的意義(筆者部落格)</a></li>
<li><a href="https://zi.media/@yidianzixun/post/FaABsS">MySQL 與 PostgreSQL 相比哪個更好？</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/fullstack-career-notes-day-17-04.webp" width="1024" height="233" alt="參考資源（圖 4）" loading="lazy" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][前端]請說明你現在專案用到的前端框架</title>
      <link>https://deanlin.net/blog/202109/fullstack-career-notes-day-10/</link>
      <pubDate>Sat, 25 Sep 2021 08:52:12 +0800</pubDate>
      <guid>https://deanlin.net/blog/202109/fullstack-career-notes-day-10/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>隨著時代演進，大部分的公司都採用框架來加速開發效率；自從有了框架，工程師可以更輕鬆，並且在更短的時間完成專案。</description>
      <media:content url="https://deanlin.net/blog/202109/fullstack-career-notes-day-10/images/cover_hu_83957c8481dd7fb.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>用工具完成任務 ≠ 了解工具。</p>
</blockquote>
<p>隨著時代演進，大部分的公司都採用框架來加速開發效率；自從有了框架，工程師可以更輕鬆，並且在更短的時間完成專案。</p>
<p>儘管做事的效率提高了，但因為許多核心概念已經被框架取代，導致部分開發人員做得出來功能，卻無法解釋怎麼做出來的。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>了解文章結構，快速掌握面試技巧</strong>
<ul>
<li>1.1 文章結構</li>
<li>1.2 為什麼用這種角度分享面試經驗？</li>
</ul>
</li>
<li><strong>請說明你現在專案用到的前端框架</strong>
<ul>
<li>2.1 面試官為什麼會問？</li>
<li>2.2 面試官想從答案確認什麼？</li>
<li>2.3 筆者提供的簡答</li>
<li>2.4 [小提醒]簡答的好處</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>3.1 Vue.js</li>
<li>3.2 Vuex</li>
<li>3.3 Vuetify</li>
<li>3.4 Nuxt.js</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>4.1 在 Vue 框架中，watch 跟 computed 的差異及使用時機</li>
<li>4.2 在 Vue 框架中，computed 跟 method 的差異及使用時機</li>
<li>4.3 有使用過 React 嗎？為什麼會選用 Vue 開發呢？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-了解文章結構快速掌握面試技巧">1. 了解文章結構，快速掌握面試技巧<a class="h-anchor" href="#1-%e4%ba%86%e8%a7%a3%e6%96%87%e7%ab%a0%e7%b5%90%e6%a7%8b%e5%bf%ab%e9%80%9f%e6%8e%8c%e6%8f%a1%e9%9d%a2%e8%a9%a6%e6%8a%80%e5%b7%a7" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-文章結構">1.1 文章結構<a class="h-anchor" href="#11-%e6%96%87%e7%ab%a0%e7%b5%90%e6%a7%8b" aria-label="這個段落的連結"></a></h3>
<p>從今天開始，我會分享面試官常問的問題；文章會以下面的結構呈現：</p>
<ul>
<li><strong>面試官為什麼會問？</strong><br>主要分成 5 個方向與讀者分享出題邏輯
<ul>
<li>履歷資訊</li>
<li>職缺所需具備的技術</li>
<li>常見面試題</li>
<li>工作相關問題</li>
<li>人格特質</li>
</ul>
</li>
<li><strong>面試官想從答案確認什麼？</strong>
<ul>
<li><strong>技術面</strong><br>用考題判斷你是否真的掌握這門技術。</li>
<li><strong>經歷面</strong><br>用追問細節的方式核實你經歷的真實性。</li>
<li><strong>人格特質</strong><br>面對各種問題的應對進退是否符合公司需求。</li>
</ul>
</li>
<li><strong>筆者提供的簡答</strong><br>筆者會附上自己在面對問題時的答覆，這塊僅供參考。</li>
<li><strong>回答問題所需具備的知識（技術考題）</strong><br>筆者會統整問題涉及的知識面，讓讀者不會知其然而不知其所以然。</li>
<li><strong>衍伸問題</strong><br>大部分的面試官會根據你的回答，詢問他們想知道的細節；所以我也會分享從這個問題出發的衍伸問題。</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-為什麼用這種角度分享面試經驗">1.2 為什麼用這種角度分享面試經驗？<a class="h-anchor" href="#12-%e7%82%ba%e4%bb%80%e9%ba%bc%e7%94%a8%e9%80%99%e7%a8%ae%e8%a7%92%e5%ba%a6%e5%88%86%e4%ba%ab%e9%9d%a2%e8%a9%a6%e7%b6%93%e9%a9%97" aria-label="這個段落的連結"></a></h3>
<p>就算同為全端工程師，每個人的技能樹也都略有不同；<mark>這個系列文不是為了猜題而寫</mark>，而是希望讀者可以透過它<mark>了解面試官出題邏輯、自己答題方向</mark>；如果可以，你也可以參考筆者的文章架構，做出屬於自己的面試攻略，<mark>建議先把知識面整理完，再用自己的口吻整理答案</mark>。</p>
<blockquote>
<p>網路的解答未必都是正確的，在整理的過程你甚至會發現部分邏輯互相衝突；如果未經思考直接照抄，你會連錯誤的知識一起吸收。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="2-請說明你現在專案用到的前端框架">2. 請說明你現在專案用到的前端框架<a class="h-anchor" href="#2-%e8%ab%8b%e8%aa%aa%e6%98%8e%e4%bd%a0%e7%8f%be%e5%9c%a8%e5%b0%88%e6%a1%88%e7%94%a8%e5%88%b0%e7%9a%84%e5%89%8d%e7%ab%af%e6%a1%86%e6%9e%b6" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-面試官為什麼會問">2.1 面試官為什麼會問？<a class="h-anchor" href="#21-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<p>因為<mark>履歷資訊</mark>中表明面試者具備前端（Frontend）的技能：</p>
<ul>
<li>Nuxt.js：Vue.js &amp; Vuetify &amp; Vuex</li>
<li>HTML5、CSS、JavaScript</li>
</ul>
<p>其中<code>Nuxt.js：Vue.js &amp; Vuetify &amp; Vuex</code>都是框架，面試官想知道你對這些框架了解的深度。</p>
<hr>
<h3 class="h-nomark" id="22-面試官想從答案確認什麼">2.2 面試官想從答案確認什麼？<a class="h-anchor" href="#22-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>想看你能否解釋這些前端框架的關聯性</li>
<li>如果要你單純解釋某個框架的功能，你是否能應對</li>
<li>如果能說出這些框架解決了哪些問題更好</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-筆者提供的簡答">2.3 筆者提供的簡答<a class="h-anchor" href="#23-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>目前使用 <code>Nuxt.js</code> 這個 <code>Vue.js</code> 的應用框架，並使用 <code>Vuetify</code> 提供的 <code>UI Component</code> 來設計版型；同時利用 <code>Vuex</code> 來管理 Vue 框架下的全域變數讓傳值更加直覺。</p>
<hr>
<h3 class="h-nomark" id="24-小提醒簡答的好處">2.4 [小提醒]簡答的好處<a class="h-anchor" href="#24-%e5%b0%8f%e6%8f%90%e9%86%92%e7%b0%a1%e7%ad%94%e7%9a%84%e5%a5%bd%e8%99%95" aria-label="這個段落的連結"></a></h3>
<p>你的簡答必須包含<code>關鍵字</code>，並盡量控制回答時間最多<mark>不超過 1 分鐘</mark>。</p>
<ul>
<li><strong>簡答的好處</strong>
<ul>
<li>面試官會依照自己<mark>感興趣的技術</mark>深入詢問。</li>
<li>因為回答的時間短，如果你的回答面試官<mark>不滿意，這也只是一個小橋段</mark>。</li>
<li>雙方<mark>有來有往</mark>，營造出同事間的交流感。</li>
</ul>
</li>
<li><strong>詳答可能的壞處</strong>
<ul>
<li>如果超級詳盡的解答每個問題，整場面試會<mark>變成只有你在說</mark>。</li>
<li>萬一你回答的方向跟面試官期待不符，你就會在<mark>錯誤的道路越走越遠</mark>還不知道要停下來。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="3-回答問題所需具備的知識">3. 回答問題所需具備的知識<a class="h-anchor" href="#3-%e5%9b%9e%e7%ad%94%e5%95%8f%e9%a1%8c%e6%89%80%e9%9c%80%e5%85%b7%e5%82%99%e7%9a%84%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-vuejs">3.1 Vue.js<a class="h-anchor" href="#31-vuejs" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>一個優秀的 JavaScript 框架</p>
</blockquote>
<ul>
<li><strong>過去只有 JavaScript、HTML、CSS 時有什麼問題？</strong>
<ul>
<li><strong>難以維護</strong><br>專案結構龐大時，JavaScript、HTML、CSS 維護難度會成幾何式上升，各式各樣的邏輯都集中在 JavaScript 裡面導致<mark>程式可讀性極低</mark>。</li>
<li><strong>效能低落</strong><br>如果 JavaScript 頻繁操作 DOM 元素，會不斷觸發 Reflow &amp; Repaint 的流程，頁面重複渲染的過程會<mark>消耗大量效能</mark>。</li>
</ul>
</li>
<li><strong>框架的優勢</strong>
<ul>
<li><strong>資料與 UI 分離</strong><br>過去 JavaScript 要動態的去更新 DOM 來塞入資料，但框架透過資料雙向綁定，工程師可以<mark>專注處理資料的邏輯面</mark>。</li>
<li><strong>模組化的 UI</strong><br>一個網站會有許多重複出現的元素（按鈕、表單、表格…），在框架中可以將這些<mark>常用的元素設計成 Component</mark>，要使用時再根據實際需求填入資料及參數即可，大幅提升工程師理解與維護的能力。</li>
<li><strong>更好的效能</strong><br>採取 Virtual DOM 概念，當資料變動時先計算好要變動的地方，以此<mark>抵銷無意義的變更</mark>，並且重複使用已存在的 DOM 元素。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="32-vuex">3.2 Vuex<a class="h-anchor" href="#32-vuex" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>Vue 過去傳值時面臨什麼問題？</strong><br>過去 Vue 裡面 Component 的參數傳遞都是採用 emit 跟 props ，但如果 Component 的關係並非 parent/child 而是像下圖想要將 Component B 的資訊傳到 Component C ，這種操作不但需要很多 emit 跟 props，還會造成程式碼閱讀困難。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-10/images/fullstack-career-notes-day-10-01.webp" width="875" height="255" alt="3.2 Vuex（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
<li>
<p><strong>如何解決這個問題？</strong><br>根據這個問題，官方推出專門用來「狀態管理」的工具，你可以把它<mark>想像成 Vue 裡面的全域變數</mark>，將狀態（state）全部放在倉庫（store）來進行管理。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-10/images/fullstack-career-notes-day-10-02.webp" width="701" height="551" alt="3.2 Vuex（圖 2）" loading="lazy" decoding="async"></p>
<ul>
<li><strong>更新 state</strong><br>在 Vuex 裡面，儲存狀態的為 State，當 Component 需要更動 State 時，需要透過 Actions 發出一個 Commit 去呼叫 Mutations，再由 Mutations 去更改 State。</li>
<li><strong>取得 state</strong><br>而 Component 的資料更新，可以在 computed 裡面使用 mapGetters 的 function，或是用 mapState 直接取用 state 的資料。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="33-vuetify">3.3 Vuetify<a class="h-anchor" href="#33-vuetify" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>提供前端工程師豐富的 MD Component</strong><br>你可以把 Vuetify 當成 <code>Vue 的 Material Design Framework</code>，他不但省去了你自己手工打造 UI Component 的時間，還為每個 Component 提供許多可以自由定義的 property 讓版型更加靈活。</p>
</li>
<li>
<p><strong>強大的社群支持並穩定更新</strong><br>截至今天在 Github 上有高達 30K 的星星，如果你在開發上遇到問題，在他們 <a href="https://discord.com/invite/s93b7Fv">discord 社區</a>上發問也能很快得到解答。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-10/images/fullstack-career-notes-day-10-03.webp" width="875" height="67" alt="3.3 Vuetify（圖 3）" loading="lazy" decoding="async"></p>
<p>官方每個禮拜都會穩定更新版本，相比於其他 Vue 的 Framework 是非常穩定的。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-10/images/fullstack-career-notes-day-10-04.webp" width="875" height="506" alt="3.3 Vuetify（圖 4）" loading="lazy" decoding="async"></p>
</li>
<li>
<p><strong>自由決定導入的 Component</strong><br>官方稱為 Auto treeshaking，在使用 Vuetify 時你可能覺得他非常「肥」，裡面包了一堆你專案中用不到的 Component，但因為有 Auto treeshaking 的機制，所以<mark>只會導入你有使用的 Component</mark>。</p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="34-nuxtjs">3.4 Nuxt.js<a class="h-anchor" href="#34-nuxtjs" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>Vue.js 的應用框架</strong><br>把 Webpack、Vue loader、Vuex、Router 的設定都集中在一起，減少使用者的配置成本。</p>
</li>
<li>
<p><strong>SSR 伺服器端渲染</strong><br>由 Server 端組成 HTML 後回傳，這樣的做法讓 Google 在 SEO 解析時可以很好的分析 DOM 元素，藉此提升網站的 SEO 水平。</p>
<blockquote>
<p><strong>備註</strong>： Nuxt.js 也是能做 SPA（無 SSR），或產生靜態網頁。</p>
</blockquote>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="4-衍伸問題">4. 衍伸問題<a class="h-anchor" href="#4-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<p>大部分的面試官在聽完你的簡答後，會以此為基礎做深入的詢問；如果要你深入介紹每個框架的職責，你可以參考「3. 回答問題所需具備的知識」來回答；除此之外，下面筆者再分享幾個常見的衍伸問題。</p>
<h3 class="h-nomark" id="41-在-vue-框架中watch-跟-computed-的差異及使用時機">4.1 在 Vue 框架中，watch 跟 computed 的差異及使用時機<a class="h-anchor" href="#41-%e5%9c%a8-vue-%e6%a1%86%e6%9e%b6%e4%b8%adwatch-%e8%b7%9f-computed-%e7%9a%84%e5%b7%ae%e7%95%b0%e5%8f%8a%e4%bd%bf%e7%94%a8%e6%99%82%e6%a9%9f" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認是否掌握框架基礎知識</mark></p>
<p>watch 與 computed 都在觀察「值」的變動：</p>
<ul>
<li><strong>computed</strong><br>有「快取」的特性，根據依賴值的變化去計算一個結果（多對一）。</li>
<li><strong>watch</strong><br>支援「非同步」，監聽一個值的變化去做一系列的事情（一對多）。</li>
</ul>
<hr>
<h3 class="h-nomark" id="42-在-vue-框架中computed-跟-method-的差異及使用時機">4.2 在 Vue 框架中，computed 跟 method 的差異及使用時機<a class="h-anchor" href="#42-%e5%9c%a8-vue-%e6%a1%86%e6%9e%b6%e4%b8%adcomputed-%e8%b7%9f-method-%e7%9a%84%e5%b7%ae%e7%95%b0%e5%8f%8a%e4%bd%bf%e7%94%a8%e6%99%82%e6%a9%9f" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認是否掌握框架基礎知識</mark></p>
<ul>
<li><strong>computed</strong><br>有些複雜邏輯的運算會花很多時間；但靠著「快取」的特性，只要依賴的「值」沒有改變，它就不需重新運算，以此節省效能。</li>
<li><strong>method</strong><br>只要被呼叫，它每次都會執行，就算這些執行結果是相同的；適用於「不需要快取」的計算。</li>
</ul>
<hr>
<h3 class="h-nomark" id="43-有使用過-react-嗎為什麼會選用-vue-開發呢">4.3 有使用過 React 嗎？為什麼會選用 Vue 開發呢？<a class="h-anchor" href="#43-%e6%9c%89%e4%bd%bf%e7%94%a8%e9%81%8e-react-%e5%97%8e%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e9%81%b8%e7%94%a8-vue-%e9%96%8b%e7%99%bc%e5%91%a2" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：你是否了解或是曾經用過同類型的技術，以及現在專案框架選擇的理由</mark></p>
<p>因為前輩先將 Vue 導入公司的專案，所以我是先學習 Vue 來開發網頁前端；對有接觸過 JavaScript 的工程師來說，Vue 算是一個可以快速上手的框架。</p>
<p>我個人在業餘時間有使用 React Native 來撰寫 App，因此覺得如果熟悉 Vue，React 的學習曲線不會太過困難；兩者都是優秀的前端框架，主要還是看團隊成員對哪個比較熟悉。</p>
<hr>
<p>讀者可以透過以上幾個前端框架的問題，初步了解面試官出題邏輯，以及答題方向。</p>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源：<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://medium.com/dean-lin/875a9c69d3fd">5 分鐘帶你搞懂 Vue、Vuex、Vuetify、Nuxt.js 之間的關係(筆者部落格)</a></li>
<li><a href="https://medium.com/itsems-frontend/vue-vuex1-state-mutations-364163b3acac">Vuex 是什麼? 怎麼用?</a></li>
<li><a href="https://tw.alphacamp.co/blog/why-use-front-end-Framework">前端框架的功能與優點</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-10/images/fullstack-career-notes-day-10-05.webp" width="1024" height="233" alt="參考資源：（圖 5）" loading="lazy" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試]準備好要詢問公司的問題，面試就是資訊戰！</title>
      <link>https://deanlin.net/blog/202109/fullstack-career-notes-day-07/</link>
      <pubDate>Wed, 22 Sep 2021 08:37:15 +0800</pubDate>
      <guid>https://deanlin.net/blog/202109/fullstack-career-notes-day-07/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>我認為面試就是在打一場資訊戰，除了公司要了解你之外，你也要了解公司；很多人在面試的時候不敢提出自己內心真正的問題，深怕問錯一個問題就錯失了工作機會；如果你有這些擔心，可能來自於你對自己的專業沒有自信，又或是過去人生的負面經驗。</description>
      <media:content url="https://deanlin.net/blog/202109/fullstack-career-notes-day-07/images/cover_hu_519f085ceb874ab3.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>打著「吃虧就是佔便宜」的口號，許多人別說去爭取不屬於自己的東西，連屬於自己的東西都沒有開口的勇氣。</p>
</blockquote>
<p>我認為<mark>面試就是在打一場資訊戰</mark>，除了公司要了解你之外，你也要了解公司；很多人在面試的時候不敢提出自己內心真正的問題，深怕問錯一個問題就錯失了工作機會；如果你有這些擔心，可能來自於你對自己的專業沒有自信，又或是過去人生的負面經驗。</p>
<p>但老實說，找工作並不是一件可以隨便的事情，如果你能在沒問清楚的狀況下進入一間好公司，那是你洪福齊天；<mark>絕大多數沒搞清楚就進去的都是掉入火坑</mark>。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>在接到人資/獵頭邀約時要詢問哪些基礎資訊</strong>
<ul>
<li>1.1 確認公司沒有觸碰到自己的地雷</li>
<li>1.2 了解職缺基礎資訊</li>
<li>1.3 了解面試流程</li>
</ul>
</li>
<li><strong>準備詢問面試官的問題</strong>
<ul>
<li>2.1 使用的技術與團隊工作方式</li>
<li>2.2 職位期許與可能面對的困難</li>
<li>2.3 讓對方有表現機會的問題</li>
</ul>
</li>
<li><strong>面試官種類</strong></li>
</ol>
<hr>
<h2 class="h-nomark" id="1-在接到人資獵頭邀約時要詢問哪些基礎資訊">1. 在接到人資/獵頭邀約時要詢問哪些基礎資訊<a class="h-anchor" href="#1-%e5%9c%a8%e6%8e%a5%e5%88%b0%e4%ba%ba%e8%b3%87%e7%8d%b5%e9%a0%ad%e9%82%80%e7%b4%84%e6%99%82%e8%a6%81%e8%a9%a2%e5%95%8f%e5%93%aa%e4%ba%9b%e5%9f%ba%e7%a4%8e%e8%b3%87%e8%a8%8a" aria-label="這個段落的連結"></a></h2>
<p>不要一有面試邀約就答應，先確認是否符合自己的期待，並初步確認自己的專業能否勝任該職位。</p>
<h3 class="h-nomark" id="11-確認公司沒有觸碰到自己的地雷">1.1 確認公司沒有觸碰到自己的地雷<a class="h-anchor" href="#11-%e7%a2%ba%e8%aa%8d%e5%85%ac%e5%8f%b8%e6%b2%92%e6%9c%89%e8%a7%b8%e7%a2%b0%e5%88%b0%e8%87%aa%e5%b7%b1%e7%9a%84%e5%9c%b0%e9%9b%b7" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>每個人的地雷不一樣，自己<mark>零容忍的問題一定要先問</mark>，下面只是筆者個人的地雷範例。</p>
</blockquote>
<ul>
<li><strong>是否為博弈產業？</strong><br>如果是博弈相關我會直接謝謝不聯絡。</li>
<li><strong>如果加班，公司會依照勞基法給予補休或是加班費嗎？</strong>（或委婉的問：「如果臨時有工作必須加班，請問會有相關補貼嗎？」）<br>我相信<mark>大部分的求職者都不敢問這個問題</mark>，但台灣真的有部分企業完全無視勞基法；如果你沒有在一開始把這些不合法的企業篩選掉，難道要等到入職後再來抱怨沒有加班費？老闆是慣老闆？</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-了解職缺基礎資訊">1.2 了解職缺基礎資訊<a class="h-anchor" href="#12-%e4%ba%86%e8%a7%a3%e8%81%b7%e7%bc%ba%e5%9f%ba%e7%a4%8e%e8%b3%87%e8%a8%8a" aria-label="這個段落的連結"></a></h3>
<p>我在聽完人資/獵頭對這份工作的簡介後，會先詢問下面的問題，判斷是否要去面試。</p>
<ul>
<li>
<p><strong>實際上班地點</strong></p>
<p>請注意，你的面試地點不一定是上班地點；JD 裡面的公司地址也未必就是上班地點；這個問題能讓你了解<mark>上班的地點是確定還是浮動的</mark>；藉此估算通勤時間成本，如果每天通勤時間超過 2 小時，就算薪資不錯也會讓人猶豫。</p>
<ul>
<li>有些集團類型的公司，你甚至會去很多個地點面試；你最終會被分配到哪個部門，是他們最後開會討論出來的，並不是一開始就確定的。</li>
<li>如果是「駐點工程師」的職缺，就只有面試地點在公司。</li>
</ul>
</li>
<li>
<p><strong>上班時間</strong></p>
<ul>
<li><strong>上班時間幾點到幾點，是否有彈性上下班時間</strong><br>每個人的生活作息都不太一樣，有些公司要求早上 8 點前就要準時抵達，有些則是中午 12 點；你要了解自己的生理時鐘，不然錄取後天天遲到會給人很差的印象。</li>
<li><strong>午休時間多長</strong><br>因為每間公司的午休時間不一樣，從 30 分鐘到 2.5 小時都有，這會影響你的下班時間，如果你希望在下班後有自己的時間也需要考量到這個因素。</li>
</ul>
</li>
</ul>
<blockquote>
<p>「上班地點＋上班時間」是綜合考量條件；如果一間公司距離較遠，又要很早上班，你就要<mark>評估自己是否能長期承受這樣的生活</mark>。</p>
</blockquote>
<ul>
<li><strong>座位是 OA 隔間還是開放式？</strong><br>每個人喜好不同，有些人在開放式的環境工作會有壓力、覺得自己沒有隱私；而有些人在隔間的狀態下會覺得溝通上受到阻礙。</li>
<li><strong>是否需要出差（國內/國外）？會需要跨日的出差嗎？</strong><br>如果家裡有老人小孩需要照顧，是個不能出差的人，一定要問這個問題。
<ul>
<li>不是只有業務需要出差，再做出產品後，有些公司的<mark>業務出差時會帶幾個開發這項產品的工程師</mark>，以處理客戶問到的專業問題。</li>
<li>如果是外包類型的公司，也很常帶<mark>工程師出去 Demo 產品、跟客戶簡報、討論需求</mark>。</li>
</ul>
</li>
<li><strong>公司正式員工人數、部門人數</strong><br>除了要知道公司的規模外，你更要知道<mark>自己面試的部門有多少人</mark>；筆者的朋友曾面試過 2000 人的大公司，但入職後卻是這個部門的第 1 人，他需要從零開始自己 Build 一個 Team。</li>
<li><strong>職缺預算與薪資結構</strong>
<ul>
<li><strong>職缺預算</strong><br>如果你面試的是比較資深的職位，我會建議你在面試前要先了解公司對該職缺的預算，如果你發現自己的期待薪資已經是這個職缺預算的頂，基本我拒絕這個面試機會；我會<mark>挑選期待薪資落在職缺預算中間的</mark>，以增加薪水談判的空間。</li>
<li><strong>薪資結構</strong><br>同樣的年薪，不同的薪資結構也會影響到大家的選擇；如果在<mark>年薪相同</mark>的狀態下，我個人比較推薦你<mark>選擇月薪最高</mark>的那個，這樣你一年無論在哪個月份離職都比較不會心痛。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-了解面試流程">1.3 了解面試流程<a class="h-anchor" href="#13-%e4%ba%86%e8%a7%a3%e9%9d%a2%e8%a9%a6%e6%b5%81%e7%a8%8b" aria-label="這個段落的連結"></a></h3>
<p>這個很重要，如果能提前取得資訊，你就能夠作出相應的準備。</p>
<ul>
<li><strong>面試有幾關，每關大概會面對哪些人、有哪些考試</strong><br>因為每間公司的面試關卡差異很大，有些考技術、有些則是看你人格特質；在不同心裡預期的狀態下，會影響到求職者發揮的水平（ex：原本以為這一關要了解你的人格特質，結果一上來就是白板題）。</li>
<li><strong>如果順利，從面試到最後 Offer get 需要多少時間</strong><br>有些公司的面試流程非常冗長，用人單位跟核薪單位也不同；所以長達 3 個月也是有可能的，因此如果你<mark>急迫需要一份工作，最好問清楚這些細節</mark>。</li>
</ul>
<hr>
<h2 class="h-nomark" id="2-準備詢問面試官的問題">2. 準備詢問面試官的問題<a class="h-anchor" href="#2-%e6%ba%96%e5%82%99%e8%a9%a2%e5%95%8f%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%9a%84%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<p>面試到一半時，面試官通常都會問你：「有什麼問題想問我們的嗎？」</p>
<p>這個不是客氣話，你有問題真的要發問，不要入職後再來懊惱為何自己當初沒問；而且很多面試官會從你的問題來判斷你是否對公司感興趣。</p>
<p>就算面試官沒有主動提出讓你詢問；你也可以<mark>在回答完面試官的問題後進行反問</mark>。</p>
<p><mark>警告！筆者下面所列的問題，請挑自己最在意的提問，如果全問可能會讓面試官崩潰！</mark></p>
<h3 class="h-nomark" id="21-使用的技術與團隊工作方式">2.1 使用的技術與團隊工作方式<a class="h-anchor" href="#21-%e4%bd%bf%e7%94%a8%e7%9a%84%e6%8a%80%e8%a1%93%e8%88%87%e5%9c%98%e9%9a%8a%e5%b7%a5%e4%bd%9c%e6%96%b9%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>筆者在下面列舉的問題是向你未來主管詢問的，如果他答不出來；你可能要對這間公司的技術力打上一個問號。</p>
</blockquote>
<ul>
<li>
<p><strong>工作使用的技術以及專案架構</strong></p>
<ul>
<li>專案使用的前後端框架、資料庫？</li>
<li>公司是否有使用 GCP、AWS 的雲端服務？</li>
</ul>
</li>
<li>
<p><strong>了解團隊分工的模式</strong></p>
<ul>
<li>確認開發團隊大小以及職能分佈（人員配置：Frontend、Backend、Devops、UI/UX、MIS、DBA、SA、PM）</li>
<li>工作是商量過才分配還是直接指派？</li>
<li>會有 Partner 一起完成一個 Feature 還是單獨作業為主？</li>
<li>每個人角色明確還是會有身兼多職的情形？</li>
</ul>
</li>
<li>
<p><strong>開發流程</strong></p>
<ul>
<li>收到一個新需求後，專案經理會如何推動開發的進度？</li>
<li>功能完成後，有專門的測試以及部署人員保證產品的品質嗎？</li>
<li>有導入敏捷（Agile）開發嗎？</li>
</ul>
</li>
<li>
<p><strong>部門資訊</strong></p>
<ul>
<li>因為公司有很多的產品，想了解貴團隊負責的是哪一塊？</li>
<li>負責的產品有多少活躍用戶？過去平均多久會做一次新版本的 release？</li>
</ul>
</li>
<li>
<p><strong>工作所需設備</strong></p>
<ul>
<li>錄取後是否會配筆電以及外接螢幕等工作所需設備？</li>
<li>電腦是否符合開發所需規格？</li>
</ul>
<p>這個問題不是廢話，因為筆者的朋友在<mark>入職後配的電腦根本不堪使用</mark>（i3 / 4G / 500G），他還要自掏腰包買一台 Mac 在工作時使用（大概花了 7 萬）。</p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-職位期許與可能面對的困難">2.2 職位期許與可能面對的困難<a class="h-anchor" href="#22-%e8%81%b7%e4%bd%8d%e6%9c%9f%e8%a8%b1%e8%88%87%e5%8f%af%e8%83%bd%e9%9d%a2%e5%b0%8d%e7%9a%84%e5%9b%b0%e9%9b%a3" aria-label="這個段落的連結"></a></h3>
<p>你要讓面試官體會到，你真心希望加入這間公司，想了解這個職位期待什麼樣的人才、會遇到哪些困難。</p>
<ul>
<li><strong>公司對這個職位的期許</strong>
<ul>
<li>希望擔任這個職位的人有什麼人格特質，在組織中扮演什麼角色？</li>
<li>什麼樣的技術專才、相關經驗是會被肯定或是可以應用在工作上的？</li>
</ul>
</li>
<li><strong>未來工作中可能遇到的考驗</strong>
<ul>
<li>什麼樣的人會在貴公司待下來，什麼樣的人不會？</li>
<li>這個職位在未來要面對哪些挑戰？</li>
</ul>
</li>
<li><strong>職涯發展</strong>
<ul>
<li>方便詢問貴公司過去在人才培養上有什麼補助或是獎勵嗎？</li>
<li>如果有榮幸加入貴公司，我未來的職涯會有哪些發展方向呢？</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-讓對方有表現機會的問題">2.3 讓對方有表現機會的問題<a class="h-anchor" href="#23-%e8%ae%93%e5%b0%8d%e6%96%b9%e6%9c%89%e8%a1%a8%e7%8f%be%e6%a9%9f%e6%9c%83%e7%9a%84%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p>面試時除了求職者要展現自己的實力，也要讓面試官有炫耀自己工作的機會；如果連面試官自己都對現在的工作毫無熱情，你未來的職涯可想而知。</p>
<ul>
<li><strong>成就感</strong>
<ul>
<li>在做專案的過程中，哪個環節帶來最大的成就感？</li>
<li>這間公司的哪個特色讓您對這份工作感到驕傲？</li>
</ul>
</li>
<li><strong>未來展望</strong>
<ul>
<li>公司目前在市場有一些競品，想了解未來發展的方向？</li>
</ul>
</li>
</ul>
<p><mark>在問完面試官問題後，你可以用這句話做結尾：「謝謝您的回覆，我會問這些問題，是因為想確保自己未來在這間公司是會成功、留得下來、能有貢獻的人。」</mark></p>
<blockquote>
<p>我們最初有可能因為薪水選擇了一間公司，但只有在充滿願景的環境，才會全力打拼、持續成長。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="3-面試官的種類">3. 面試官的種類<a class="h-anchor" href="#3-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%9a%84%e7%a8%ae%e9%a1%9e" aria-label="這個段落的連結"></a></h2>
<p>面試官種類很多，雖然面試的感覺不代表一切；但如果你在面試的當下就感覺很不舒服，我認為要<mark>相信自己的直覺</mark>。</p>
<ul>
<li><strong>從你會的技術出發，確認你對技術認知的深淺</strong><br>通常第一關面試你的會是未來的直屬主管以及合作的資深工程師，這一關會著重在你<mark>是否具備個職缺的技術力</mark>。</li>
<li><strong>從你的履歷出發，用聊天的方式互相認識</strong><br>在跟公司高層、人資面試的時候，他們更關注你的<mark>穩定性、發展潛力、人格特質</mark>。</li>
<li><strong>連環提出問題，不給你任何思考時間</strong><br>有些面試官會連續拋出問題，中間不給你喘息時間；通常這樣做是為了知道求職者在<mark>職場上的應對進退</mark>，常發生在內部競爭激烈的公司，這類公司你不只要做產品，還要面對其他部門的壓力。</li>
<li><strong>專門問刁鑽的問題、冷門的問題</strong><br>如果只是其中幾題這樣，那面試官是想看你<mark>面對答不出來的問題時會如何應對</mark>，是瞎掰一個答案、直接承認自己沒接觸過、還是跟面試官討論；如果接連好幾題都是刻意刁難，那可能是面試官想<mark>壓你薪水</mark>。</li>
<li><strong>不斷攻擊你的履歷，用很差的態度面試</strong><br>現在有些公司流行<mark>壓力面試</mark>，也就是用很差的態度面對求職者，職場如戰場，他們會用這種方式面試也是有道理的；因為有些公司的政治氣味濃厚，如果抗壓力不足、隨機應變能力不夠，真的不適合這樣的環境。</li>
</ul>
<p>俗話說：「不是一家人，不進一家門」雖然不是絕對，但透過面試官的風格你可以大概了解這間公司的風氣，也對未來的職場環境有一個心理預期。</p>
<hr>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-07/images/fullstack-career-notes-day-07-01.webp" width="1024" height="233" alt="3. 面試官的種類（圖 1）" fetchpriority="high" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[全端工程師生存筆記]不要等到「受夠了！」才開始進步</title>
      <link>https://deanlin.net/blog/202109/fullstack-career-notes-day-01/</link>
      <pubDate>Thu, 16 Sep 2021 08:45:52 +0800</pubDate>
      <guid>https://deanlin.net/blog/202109/fullstack-career-notes-day-01/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>剛踏入職場時，小明對工作懷抱著憧憬，接手的專案總是格外珍惜且戰戰兢兢，生怕自己哪個地方做不好連試用期都過不了；因此入職的第一年像海綿般瘋狂吸收新的知識，並在錯誤中學習成長。</description>
      <media:content url="https://deanlin.net/blog/202109/fullstack-career-notes-day-01/images/cover_hu_cc9104b323d69658.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>等到「受夠了」才開始進步，一切都已經晚了&hellip;</p>
</blockquote>
<h2 id="前言小明的故事">前言：小明的故事<a class="h-anchor" href="#%e5%89%8d%e8%a8%80%e5%b0%8f%e6%98%8e%e7%9a%84%e6%95%85%e4%ba%8b" aria-label="這個段落的連結"></a></h2>
<p>剛踏入職場時，小明對工作懷抱著憧憬，接手的專案總是格外珍惜且戰戰兢兢，生怕自己哪個地方做不好連試用期都過不了；因此入職的第一年像海綿般瘋狂吸收新的知識，並在錯誤中學習成長。</p>
<p>幾年後，小明成為公司的老鳥；當年需要熬夜才能完成的專案，現在只需要半天就能完成了；剩下的上班時間，他選擇泡杯咖啡犒勞辛苦的自己，一邊跟同事打屁聊天消磨空閒的時光，當然他也養成了躲在廁所滑手機當薪水小偷的習慣。</p>
<p>又過了幾年，小明兩眼無神地看著眼前的電腦，他覺得自己<code>受夠了</code>現在的生活，也不滿意公司給的薪水；於是他打開人力銀行，信心滿滿地認為：「憑藉自己多年的經驗，肯定一堆公司來搶自己！」</p>
<p>一個月過後、兩個月過後&hellip;</p>
<p>小明取得了非常多公司的面試機會，但卻始終沒有獲得理想的 Offer；此時的他才驚覺，除了第一年外，<mark>剩下這些年自己都在原地踏步</mark>。</p>
<hr>
<h2 id="為什麼寫這個主題">為什麼寫這個主題？<a class="h-anchor" href="#%e7%82%ba%e4%bb%80%e9%ba%bc%e5%af%ab%e9%80%99%e5%80%8b%e4%b8%bb%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<p>我相信上面「小明的故事」，大家都不是第一次看到了；但卻很難保證自己不成為故事中的小明，畢竟離開舒適圈是一件很痛苦的事情；所以很多人就算「知道」自己不能再這樣下去，但看到舒適圈外的荊棘之路還是馬上打消了這個念頭。</p>
<p>所以我這個主題並不是想要讓讀者<code>立刻</code>做出重大改變，而是在熟悉的環境中<mark>多做一點事</mark>來認識、調整自己。</p>
<ul>
<li><strong>了解自己現在工作所使用的技術</strong>
<ul>
<li>很多工程師擅長解決問題；但卻無法說明為什麼選用這門技術，以及為何這樣做能解決問題。</li>
<li>有時我們根本不知為何選用某個技術，只是因為公司裡的前輩選擇了，所以就跟著用了。</li>
</ul>
</li>
<li><strong>定期整理自己的履歷</strong>
<ul>
<li>整理履歷是一個很好的習慣，可以審視自己這些日子到底做了些什麼。</li>
<li>很多實力很強的人反而不懂如何表現自己，也不理解自己的價值，導致他在市場被低估。</li>
<li>有些資料都是有時效性的，如果沒有定期整理你可能就會忘記或是找不到。</li>
</ul>
</li>
<li><strong>每年定期面試</strong>
<ul>
<li>在面試時，很多人會發現自己其實不太了解每天在使用的工具。</li>
<li>了解自己在不同產業的價碼，以及自己最在意的點（距離、氛圍、產業、薪水）。</li>
<li>透過面試知道自己的盲點。</li>
</ul>
</li>
<li><strong>做高價值的任務</strong>
<ul>
<li>選擇影響力大的任務，你做的事被大家看到才算是有在做事。</li>
<li>嘗試主動承擔報告與 Demo 的任務，讓公司更多人對你有印象。</li>
</ul>
</li>
</ul>
<hr>
<h2 id="適合哪些人看">適合哪些人看？<a class="h-anchor" href="#%e9%81%a9%e5%90%88%e5%93%aa%e4%ba%9b%e4%ba%ba%e7%9c%8b" aria-label="這個段落的連結"></a></h2>
<ol>
<li>工作遇到瓶頸，想要跳槽的人</li>
<li>覺得自己很有實力，但因為不知道如何表現自己，而沒有得到合適 Offer 的人</li>
<li>想了解面試官是以什麼邏輯出題的人</li>
<li>不知怎麼提問題衡量求職者的新手面試官</li>
<li>想找到自己盲點，在職場不斷進步的人</li>
</ol>
<hr>
<h2 id="這個系列文我能學到什麼">這個系列文我能學到什麼？<a class="h-anchor" href="#%e9%80%99%e5%80%8b%e7%b3%bb%e5%88%97%e6%96%87%e6%88%91%e8%83%bd%e5%ad%b8%e5%88%b0%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h2>
<p>快速檢視自己在職場上所缺少的東西，並教你如何用「技術」及「經歷」來包裝履歷，讓自己在市場上取得應有的價值。</p>
<h3 id="文章大綱">文章大綱<a class="h-anchor" href="#%e6%96%87%e7%ab%a0%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>PART 1 履歷（<a href="https://deanlin.net/blog/202109/fullstack-career-notes-day-02/">Day2</a>~<a href="https://deanlin.net/blog/202109/fullstack-career-notes-day-04/">Day4</a>）</strong>
<ul>
<li>重新認識<mark>履歷對職涯的重要性</mark>，以此整理有效履歷</li>
<li>將履歷精華提煉成面試用的簡報，用<mark>簡報引導面試官提問的方向</mark></li>
</ul>
</li>
<li><strong>PART 2 面試（<a href="https://deanlin.net/blog/202109/fullstack-career-notes-day-05/">Day5</a>~<a href="https://deanlin.net/blog/202110/fullstack-career-notes-day-25/">Day25</a>）</strong>
<ul>
<li>了解通過面試可以<mark>獲得的好處</mark>，並檢核自己是否<mark>做好面試的準備</mark></li>
<li>面試是雙向的，你可以透過<mark>詢問公司問題</mark>；並透過種種細節<mark>判斷適合自己的公司</mark></li>
<li>從<mark>履歷分析面試官可能詢問的問題</mark>
<ul>
<li>讓求職者了解為什麼面試官會問這個問題、他想要了解什麼</li>
<li>每個問題會附上簡答範例，同時提供回答問題前所需具備的知識</li>
<li>問題面向包含<mark>前端、後端、資料庫、設計模式、系統設計、白板題、職場相關</mark></li>
</ul>
</li>
</ul>
</li>
<li><strong>PART 3 職場（<a href="https://deanlin.net/blog/202110/fullstack-career-notes-day-26/">Day26</a>~<a href="https://deanlin.net/blog/202110/fullstack-career-notes-day-30/">Day30</a>）</strong>
<ul>
<li>有哪些方法可以<mark>擴增自己在公司的影響力</mark>
<ul>
<li>讓新人成為有效戰力</li>
<li>讓部門在 Demo 專案成果時一切順利</li>
<li>爭取 Presentation 機會表現自己</li>
</ul>
</li>
<li>職場上與同事<mark>合作的注意事項</mark>
<ul>
<li>問別人問題時要注意的禮節</li>
<li>你是全端工程師，不是全包工程師</li>
<li>建立友善的溝通環境</li>
</ul>
</li>
<li>與公司<mark>薪水談判的秘訣</mark>
<ul>
<li>了解談判前要做哪些準備、如何選擇合適的談判時間</li>
<li>注意談判過程要表達的重點與忌諱</li>
<li>被拒絕時的應對進退</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>文章的篇幅有限，答案僅是提供讀者一個思路；請依自身情境去思考問題，沒有經過思索就接受的答案是經不起考驗的，你要依據自己的經歷寫出合適的腳本。</p>
</blockquote>
<hr>
<h3 id="筆者碎碎念">筆者碎碎念<a class="h-anchor" href="#%e7%ad%86%e8%80%85%e7%a2%8e%e7%a2%8e%e5%bf%b5" aria-label="這個段落的連結"></a></h3>
<p>儘管這個系列文花了很大的篇幅在「PART 2 面試」上，但我不希望讀者只把答案背下來，而是去思考面試官為什麼會這樣問。</p>
<p>說來諷刺，這些面試題並不應該是特別為了面試而準備的，而是你在掌握這個技能時，本就應該理解的；只是許多工程師<mark>誤以為完成任務就等於掌握技術，沒發生意外就不去深入研究</mark>。</p>
<p>筆者會寫出這些話，就是因為自己曾經是一個注重開發速度而不深入理解技術的人；當時甚至沾沾自喜覺得自己好棒棒，完成了別人好幾倍的任務；但經歷了幾次專案的更迭後明白，<mark>欠下的技術債遲早要還的，時間早晚罷了</mark>。</p>
<p>在這個系列文，我會盡其所能把自己知道的都分享出來，讓有緣分看到的人少走一些坑，如果讀者有自己的想法歡迎留言，你的留言或許可以幫助到其他人，希望每個工程師都能夠取得自己應有的價值！</p>
<hr>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://www.cw.com.tw/article/5091949?utm_source=fb_cwbookclub&amp;utm_medium=affiliate&amp;utm_campaign=fb_cwbookclub-affiliate-daily-210811-5091949&amp;fbclid=IwAR27qbHLd2WCi4uo1Hwt92luaLxAz9k3DT4gFM5cR5q6RumMuLHAsyzOq0k">「沒有人該為你的成長負責」 寫一封信給工作疲憊的你</a></li>
<li><a href="https://www.businessweekly.com.tw/careers/blog/3007499?utm_source=facebook.com&amp;utm_medium=social&amp;utm_content=bw&amp;utm_campaign=content&amp;fbclid=IwAR0MydXqnOCyzfG2BAfeJtTErJAy3KQhlg9usp84Ail-W5s6UD9N1pKdWfo">工作想有「突破性成長」的關鍵：懂得切換執行與學習</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-01/images/fullstack-career-notes-day-01-01.webp" width="1024" height="233" alt="參考資源（圖 1）" fetchpriority="high" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
  </channel>
</rss>
