<?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>iT 邦幫忙鐵人賽｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/it-%E9%82%A6%E5%B9%AB%E5%BF%99%E9%90%B5%E4%BA%BA%E8%B3%BD/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>iT 邦幫忙鐵人賽｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/it-%E9%82%A6%E5%B9%AB%E5%BF%99%E9%90%B5%E4%BA%BA%E8%B3%BD/</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>Thu, 16 Feb 2023 19:07:50 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/it-%E9%82%A6%E5%B9%AB%E5%BF%99%E9%90%B5%E4%BA%BA%E8%B3%BD/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>我希望在寫書前就知道的 N 件事 ── 出版之後</title>
      <link>https://deanlin.net/blog/202302/before-writing-a-book/</link>
      <pubDate>Thu, 16 Feb 2023 19:07:50 +0800</pubDate>
      <guid>https://deanlin.net/blog/202302/before-writing-a-book/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>許多人都懷抱著成為作家的夢想來參加 iT 邦幫忙鐵人賽（還是只有我🤔），筆者在去年有寫過一篇文章分享出版經驗，而今年在出版第 2 本書後，又產生了一些心得想跟大家分享。</description>
      <media:content url="https://deanlin.net/blog/202302/before-writing-a-book/images/before-writing-a-book-og_hu_6076b9069db0c4b3.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>許多人都懷抱著成為作家的夢想來參加 iT 邦幫忙鐵人賽（還是只有我🤔），筆者在去年<a href="https://deanlin.net/blog/202107/book-publishing-experience/">有寫過一篇文章</a>分享出版經驗，而今年在出版第 2 本書後，又產生了一些心得想跟大家分享。</p>
<p>如果你也獲得了出版機會，又或是懷抱作家夢準備參加比賽；也許這篇文章能讓你寫作的過程更加順利，並與出版社一同打造出理想的作品。</p>
<div class="gallery-row cols-2">

<figure class="figure"><img src="https://deanlin.net/blog/202302/before-writing-a-book/images/before-writing-a-book-01.webp" width="1400" height="830" alt="我希望在寫書前就知道的 N 件事 ── 出版之後（圖 1）" fetchpriority="high" decoding="async">
</figure>

<figure class="figure"><img src="https://deanlin.net/blog/202302/before-writing-a-book/images/before-writing-a-book-02.webp" width="1400" height="808" alt="感謝讀者們的支持！趁週榜＆月榜第一的時候截圖一波 🤩" loading="lazy" decoding="async">
  <figcaption>感謝讀者們的支持！趁週榜＆月榜第一的時候截圖一波 🤩</figcaption>
</figure>


</div>

<pre tabindex="0"><code>大綱
一、從獲獎到出版的流程
  ① 撰寫出版社要求的資料
  ② 洽談合約
  ③ 撰寫初稿（Word）
  ④ 校稿（PDF）
  ⑤ 正式出版
二、洽談合約時，要評估好自己能「準時」交稿的日期
  ➤ 預計交稿日
  ➤ 預計出版日
三、從 Markdown 到 Word 會遇到的阻礙
  ➤ 文章的階層與樣式
  ➤ 彩色變黑白
  ➤ 文章分頁問題
  ➤ 程式碼顯示
四、撰寫初稿時的注意事項
  ➤ 程式碼格式化
  ➤ 名詞定義
五、繳交初稿時要做的溝通
  ➤ 封面建議等書名確定後再來決定
  ➤ 給編輯格式最齊全的章節做排版
  ➤ 如果想調整樣章的排版
六、校稿時要有的心理建設
  ➤ 這本書是專業書籍，不是文學作品
  ➤ 在校稿過程你將經歷的心境轉換
七、校稿的注意事項
  ➤ 編輯修改後的稿件，自己要再仔細看過一遍
  ➤ 正式送印前，筆者建議要做的檢查
</code></pre><h2 id="一從獲獎到出版的流程">一、從獲獎到出版的流程<a class="h-anchor" href="#%e4%b8%80%e5%be%9e%e7%8d%b2%e7%8d%8e%e5%88%b0%e5%87%ba%e7%89%88%e7%9a%84%e6%b5%81%e7%a8%8b" aria-label="這個段落的連結"></a></h2>
<p>我們先了解整體的流程，而後筆者在針對容易忽略、犯錯的點提出建議。</p>
<h3 class="h-nomark" id="-撰寫出版社要求的資料">① 撰寫出版社要求的資料<a class="h-anchor" href="#-%e6%92%b0%e5%af%ab%e5%87%ba%e7%89%88%e7%a4%be%e8%a6%81%e6%b1%82%e7%9a%84%e8%b3%87%e6%96%99" aria-label="這個段落的連結"></a></h3>
<ol>
<li>作者基本資料表</li>
<li>整理全系列文 Word 檔（用來初估實際成書的頁數）</li>
<li>自我新書規劃</li>
</ol>
<p>交付上述文件後，就靜待出版社的聯繫。</p>
<h3 class="h-nomark" id="-洽談合約">② 洽談合約<a class="h-anchor" href="#-%e6%b4%bd%e8%ab%87%e5%90%88%e7%b4%84" aria-label="這個段落的連結"></a></h3>
<p>過一段時間後，出版社會有專人與你洽談合約，主要商議以下幾點：</p>
<ol>
<li>書籍頁數範圍</li>
<li>印刷色彩</li>
<li>預計交稿日（可能會區分成「樣章、50%稿件、完整稿件」幾個進度）</li>
<li>預計出版日</li>
</ol>
<h3 class="h-nomark" id="-撰寫初稿word">③ 撰寫初稿（Word）<a class="h-anchor" href="#-%e6%92%b0%e5%af%ab%e5%88%9d%e7%a8%bfword" aria-label="這個段落的連結"></a></h3>
<p>你要將得獎作品從 Markdown 轉換成 Word，千萬不要把它理解為單純的 Copy &amp; Paste，你會在許多方面遇到阻礙與迷惘。</p>
<p>假使你對作品的自我要求很高，通常隔幾個月再看自己當初的稿子，會發現許多可以再更臻完善的部分。</p>
<h3 class="h-nomark" id="-校稿pdf">④ 校稿（PDF）<a class="h-anchor" href="#-%e6%a0%a1%e7%a8%bfpdf" aria-label="這個段落的連結"></a></h3>
<p>交稿後就進入「校稿」，在這個階段你需要與編輯密切合作，「出書」對大部分的人來說都是第一次，筆者會在這篇文章中提醒你要注意的細節。</p>
<h3 class="h-nomark" id="-正式出版">⑤ 正式出版<a class="h-anchor" href="#-%e6%ad%a3%e5%bc%8f%e5%87%ba%e7%89%88" aria-label="這個段落的連結"></a></h3>
<p>如果出書是為了圓夢，那到這裡就可以給自己放個假，去好好慶祝了；但如果想達成更多的成就，你就要開始思考宣傳的部分。</p>
<blockquote>
<p>實際作業流程還是看出版社的規範，上述步驟僅供參考。</p>
</blockquote>
<hr>
<h2 id="二洽談合約時要評估好自己能準時交稿的日期">二、洽談合約時，要評估好自己能「準時」交稿的日期<a class="h-anchor" href="#%e4%ba%8c%e6%b4%bd%e8%ab%87%e5%90%88%e7%b4%84%e6%99%82%e8%a6%81%e8%a9%95%e4%bc%b0%e5%a5%bd%e8%87%aa%e5%b7%b1%e8%83%bd%e6%ba%96%e6%99%82%e4%ba%a4%e7%a8%bf%e7%9a%84%e6%97%a5%e6%9c%9f" aria-label="這個段落的連結"></a></h2>
<p>出書是一個重要的里程碑，但完成一本書也需要投資大量的時間。</p>
<h3 id="-預計交稿日">預計交稿日<a class="h-anchor" href="#-%e9%a0%90%e8%a8%88%e4%ba%a4%e7%a8%bf%e6%97%a5" aria-label="這個段落的連結"></a></h3>
<p>以筆者舉例，因為平日的工作任務繁重，只有<strong>下班後</strong>和<strong>假日</strong>才有時間撰稿。</p>
<p>得益於過去出版的經驗，去年比賽時我幾乎是用出書的標準在寫文章，想說如果幸運得獎，我只要微調一點內容就可以出版了。</p>
<p>但儘管如此，我把它<strong>重新整理成 Word 的初稿就花了 2 個月（75~100小時）</strong>，除非你是單純的複製貼上不管優化，不然在整理的過程中一定會發現許多可以更臻完善的部分。</p>
<blockquote>
<p>如果你得獎作品的文字較少，在擴展內容方面會需要花更多的時間處理，這個部分可以參考<a href="https://deanlin.net/blog/202108/expand-your-writing-tips/">筆者去年分享的「文章倍化術」</a>。</p>
</blockquote>
<p>因此除非你對自己的文章品質非常有自信，又或是工作較為輕鬆可以在上班時間偷偷趕稿，不然筆者建議<strong>從「撰寫初稿 Word」到「預計交稿日」至少要 2 個月</strong>。</p>
<p>跟專案管理的概念相同，我們寧可在這個階段多估一點時間，也不要把時程抓得太緊；<strong>否則當你在截稿日交不出東西時，會嚴重影響出版社後續的作業流程</strong>，同時失去雙方合作的信賴感。</p>
<h3 id="-預計出版日">預計出版日<a class="h-anchor" href="#-%e9%a0%90%e8%a8%88%e5%87%ba%e7%89%88%e6%97%a5" aria-label="這個段落的連結"></a></h3>
<p>不是把初稿交給出版社後你就沒事了，對筆者來說，<strong>出版最痛苦的環節不是撰寫初稿，而是反覆校稿</strong>。</p>
<p>我先承認自己是一個對作品很龜毛的人，如果只是單純的想出版一本書，在這個環節也可以大而化之，可能 1 ~ 2 校就直接送印；但如果你<strong>跟筆者一樣對品質有極高的要求，可能會有更多的校稿次數</strong>。</p>
<p><strong>但無論你寫得多好，在短時間內看相同的內容 2 遍以上都會感到精神疲乏；如果想要減少這份不適感，建議在交付稿件給出版社時就力求完美。</strong></p>
<blockquote>
<p>「預計交稿日」到「預計出版日」這段時間中除了校稿外，你還要跟編輯討論並完善書籍的「封面、封底、作者介紹、宣傳語、推薦序…」等資料；因此筆者再次提醒未來的作者們，在「預計交稿日」交付的不是「草稿」，請以最高的標準來看待他。</p>
</blockquote>
<hr>
<h2 id="三從-markdown-到-word-會遇到的阻礙">三、從 Markdown 到 Word 會遇到的阻礙<a class="h-anchor" href="#%e4%b8%89%e5%be%9e-markdown-%e5%88%b0-word-%e6%9c%83%e9%81%87%e5%88%b0%e7%9a%84%e9%98%bb%e7%a4%99" aria-label="這個段落的連結"></a></h2>
<p>我也很希望用<strong>複製貼上</strong>就能搞定這一切，但實際做起卻發現<strong>理想很美好，現實很骨感</strong>。</p>
<h3 id="-文章的階層與樣式">文章的階層與樣式<a class="h-anchor" href="#-%e6%96%87%e7%ab%a0%e7%9a%84%e9%9a%8e%e5%b1%a4%e8%88%87%e6%a8%a3%e5%bc%8f" aria-label="這個段落的連結"></a></h3>
<p>使用 Markdown 時，我們不用去管內文的階層與樣式，直接使用預設的就好了；但轉到 Word 時就頭大了，因為你需要先定義好所有樣式，不然你的排版會亂七八糟。</p>
<p>這邊建議你要區分的層次有：</p>
<ul>
<li>章節大標（ex：Ch 1）</li>
<li>章節小標（ex：Ch 1.1）</li>
<li>段落小標（ex：Ch 1.1.1、或用較大的字體搭配鮮明的樣式）</li>
<li>內文</li>
</ul>
<p>你可能需要設計的樣式有：</p>
<ul>
<li>編號格式（ex：像是你要用 1 2 3，還是 a b c；或是單純用 bullet 區分）</li>
<li>內縮註解（放備註用的）</li>
<li>引文（在正文開始前，你想說的話）</li>
<li>提醒文字框（可能放這章節的總結、段落的提示、重要警語）</li>
</ul>
<h3 id="-彩色變黑白">彩色變黑白<a class="h-anchor" href="#-%e5%bd%a9%e8%89%b2%e8%ae%8a%e9%bb%91%e7%99%bd" aria-label="這個段落的連結"></a></h3>
<p>除非你選擇彩色印刷，不然變成黑白基底的時候，有一些細節你需要重新考量：</p>
<ul>
<li>畫面截圖的顏色對比度夠高嗎？文字是否清晰？</li>
<li>重點都是用粗體顯示嗎？要不要設計一些不同的樣式來表達？</li>
</ul>
<h3 id="-文章分頁問題">文章分頁問題<a class="h-anchor" href="#-%e6%96%87%e7%ab%a0%e5%88%86%e9%a0%81%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p>網頁可以一直往下滾，但書籍是左右翻頁；如果你內文的程式碼佔很大比重，就要考慮是否把它切分成幾個段落，盡可能不要出現跨頁的顯示。</p>
<blockquote>
<p>我是說「盡可能」，因為筆者知道很難每段程式都辦到。</p>
</blockquote>
<h3 id="-程式碼顯示">程式碼顯示<a class="h-anchor" href="#-%e7%a8%8b%e5%bc%8f%e7%a2%bc%e9%a1%af%e7%a4%ba" aria-label="這個段落的連結"></a></h3>
<p>筆者這邊是採用 Word 提供的外掛「<a href="https://appsource.microsoft.com/en-us/product/office/wa104382008?tab=overview">Easy Code Formatter</a>」，它可以讓程式碼在 Word 上顯示的相對優雅。</p>
<p>放程式碼時如果能做到以下幾點會更理想：</p>
<ul>
<li>Word 的左右寬度有限，無法像 IDE 一樣無限延伸，所以盡量不要讓一行太長。</li>
<li>一段程式碼的行數盡量控制在 50 行以內，不然在閱讀上會有跨多頁的問題。</li>
<li>重要的函式、演算法請用粗體標示，讓讀者更容易抓到重點。</li>
</ul>
<hr>
<h2 id="四撰寫初稿時的注意事項">四、撰寫初稿時的注意事項<a class="h-anchor" href="#%e5%9b%9b%e6%92%b0%e5%af%ab%e5%88%9d%e7%a8%bf%e6%99%82%e7%9a%84%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a0%85" aria-label="這個段落的連結"></a></h2>
<h3 id="-程式碼格式化">程式碼格式化<a class="h-anchor" href="#-%e7%a8%8b%e5%bc%8f%e7%a2%bc%e6%a0%bc%e5%bc%8f%e5%8c%96" aria-label="這個段落的連結"></a></h3>
<p>相信能獲獎的都是各領域的大神，在 Coding Style 方面我就不贅述了；但程式碼格式化的部分請多加留心，比如：「結尾分號是否統一」、「縮排 2 格還是 4 格」，如果書中的程式來自不同專案，就很容易有這類型的疏失。</p>
<h3 id="-名詞定義">名詞定義<a class="h-anchor" href="#-%e5%90%8d%e8%a9%9e%e5%ae%9a%e7%be%a9" aria-label="這個段落的連結"></a></h3>
<ol>
<li><strong>前後一致</strong>：當你提及某個專業技術的名詞時，請盡量每個章節都使用統一的名詞，避免讀者混淆以為是不同的技術。</li>
<li><strong>中英文提示</strong>：如果有些章節用英文，有些章節用中文，可能會增加讀者閱讀理解的困難度；如果有發現這種情形，可以在第一次使用時將中英文都列出，後面再擇一顯示即可。</li>
<li><strong>名詞備忘錄</strong>：通常書寫到後面頭都暈了，因此建議自己建立一個名詞比對表作為備忘錄。</li>
</ol>
<hr>
<h2 id="五繳交初稿時要做的溝通">五、繳交初稿時要做的溝通<a class="h-anchor" href="#%e4%ba%94%e7%b9%b3%e4%ba%a4%e5%88%9d%e7%a8%bf%e6%99%82%e8%a6%81%e5%81%9a%e7%9a%84%e6%ba%9d%e9%80%9a" aria-label="這個段落的連結"></a></h2>
<p><strong>如果排版上你有自己的想法，請一定要在前期提出！</strong> 越到後面調整起來越困難，也容易有遺漏的部分。</p>
<h3 id="-封面建議等書名確定後再來決定">封面建議等書名確定後再來決定<a class="h-anchor" href="#-%e5%b0%81%e9%9d%a2%e5%bb%ba%e8%ad%b0%e7%ad%89%e6%9b%b8%e5%90%8d%e7%a2%ba%e5%ae%9a%e5%be%8c%e5%86%8d%e4%be%86%e6%b1%ba%e5%ae%9a" aria-label="這個段落的連結"></a></h3>
<p>封面跟書名最好有直接關連性，但在初稿剛交出去時，書名的變動性其實還蠻大的，就像筆者原本腦洞大開想用「修煉」做為封面的主視覺。</p>
<figure class="figure"><img src="https://deanlin.net/blog/202302/before-writing-a-book/images/before-writing-a-book-03.webp" width="1280" height="853" alt="圖片來源：https://www.peekme.cc/post/702785" loading="lazy" decoding="async">
  <figcaption>圖片來源：https://www.peekme.cc/post/702785</figcaption>
</figure>

<blockquote>
<p>當初書名大概列了快 20 個版本吧，現在各位看到的最終版本，是筆者在瘋狂騷擾周圍朋友反覆投票後所決定的。</p>
</blockquote>
<h3 id="-給編輯格式最齊全的章節做排版">給編輯格式最齊全的章節做排版<a class="h-anchor" href="#-%e7%b5%a6%e7%b7%a8%e8%bc%af%e6%a0%bc%e5%bc%8f%e6%9c%80%e9%bd%8a%e5%85%a8%e7%9a%84%e7%ab%a0%e7%af%80%e5%81%9a%e6%8e%92%e7%89%88" aria-label="這個段落的連結"></a></h3>
<p>在正式排版前，編輯會先產出一個排版的「樣章」供你確認格式。</p>
<p>筆者建議<strong>挑選一個格式相對來說最複雜的章節，如果一個章節無法包含所有格式，就把特殊格式都挑出來，請編輯幫你試排</strong>。</p>
<p>以筆者舉例，在我的書中除了「目錄、大標、小標、表格、內文…」等常規格式外，還有「流程圖、提示框、段落階層…」等特殊格式。</p>
<p>樣章的作用就是統一這些格式，讓作者與編輯在校稿的過程更加順利！</p>
<h3 id="-如果想調整樣章的排版">如果想調整樣章的排版<a class="h-anchor" href="#-%e5%a6%82%e6%9e%9c%e6%83%b3%e8%aa%bf%e6%95%b4%e6%a8%a3%e7%ab%a0%e7%9a%84%e6%8e%92%e7%89%88" aria-label="這個段落的連結"></a></h3>
<p>收到第一版樣章時，你可能會想做一些調整；但建議先跟編輯開個會，了解為何會這樣設計，畢竟他們是這個領域的專業人士。</p>
<p>如果會後還是想調整排版，千萬別對編輯說：「我不知道自己要什麼，我只知道這不是我想呈現的。」</p>
<p>編輯大大們已經夠辛苦了，別要求他們連通靈都要會啊！</p>
<p>筆者建議請編輯修改排版時，要提供明確的「排版範例」；如果你只是感覺不對又無法用文字表達，<strong>直接跑實體書局去找答案吧，這會比你在網路上模索來得更有效率</strong>。</p>
<hr>
<h2 id="六校稿時要有的心理建設">六、校稿時要有的心理建設<a class="h-anchor" href="#%e5%85%ad%e6%a0%a1%e7%a8%bf%e6%99%82%e8%a6%81%e6%9c%89%e7%9a%84%e5%bf%83%e7%90%86%e5%bb%ba%e8%a8%ad" aria-label="這個段落的連結"></a></h2>
<p>在校稿的過程，許多作者都會萌生出：「現在放棄，明天就可以送印嚕～」的想法。</p>
<p>但都已經走到最後一步了，會把這個想法付諸於行動的作者應該很少；大多數的人在校稿階段，更容易產生跟自己過不去的心理。</p>
<h3 id="-這本書是專業書籍不是文學作品">這本書是專業書籍，不是文學作品<a class="h-anchor" href="#-%e9%80%99%e6%9c%ac%e6%9b%b8%e6%98%af%e5%b0%88%e6%a5%ad%e6%9b%b8%e7%b1%8d%e4%b8%8d%e6%98%af%e6%96%87%e5%ad%b8%e4%bd%9c%e5%93%81" aria-label="這個段落的連結"></a></h3>
<p>有時原本看起來很順的文字，在校稿時會變得怎麼看都不順眼，因為我們想把最好的一面傳達給讀者。</p>
<p>但這種字字斟酌會使校稿進度變得非常緩慢，可能花了大量時間，但產出的內容對讀者來說卻沒有多大幫助。</p>
<p>站在專業書籍的角度，比起文字行雲流水，更重要的是知識的正確性；<strong>文字有點卡頂多閱讀起來不順，但如果知識面有錯誤，就可能導致讀者在日後做出錯誤的判斷。</strong></p>
<blockquote>
<p>我們的目標讀者是工程師，他們需要的是明確的語言，簡單的邏輯。</p>
</blockquote>
<h3 id="-在校稿過程你將經歷的心境轉換">在校稿過程你將經歷的心境轉換<a class="h-anchor" href="#-%e5%9c%a8%e6%a0%a1%e7%a8%bf%e9%81%8e%e7%a8%8b%e4%bd%a0%e5%b0%87%e7%b6%93%e6%ad%b7%e7%9a%84%e5%bf%83%e5%a2%83%e8%bd%89%e6%8f%9b" aria-label="這個段落的連結"></a></h3>
<p>隨著校稿次數增加，你的注意力會逐漸渙散：</p>
<ul>
<li><strong>一校</strong>：不放過每個錯漏字！想交出最完美的作品。</li>
<li><strong>二校</strong>：恩…這句話會不會讓人誤解？來調整一下好了。</li>
<li><strong>三校</strong>：大體上看得懂就好了。</li>
<li><strong>N校</strong>：%&amp;$&amp;%^&amp;%^@。</li>
</ul>
<p>通常到第三校的時候，大部分作者的精神力已經逼近崩潰邊緣；因此<strong>我們寧可花多一點的時間、精力在第一次校稿上，也不要分成太多次校稿</strong>，同樣的內容看太多次會麻痺，就算有問題也很難察覺。</p>
<hr>
<h2 id="七校稿的注意事項">七、校稿的注意事項<a class="h-anchor" href="#%e4%b8%83%e6%a0%a1%e7%a8%bf%e7%9a%84%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a0%85" aria-label="這個段落的連結"></a></h2>
<p>一本好書的誕生，需要作者與編輯的齊心合作；不要把挑錯誤這件事都丟給編輯，身為作者我們要對自己的書負起責任。</p>
<h3 id="-編輯修改後的稿件自己要再仔細看過一遍">編輯修改後的稿件，自己要再仔細看過一遍<a class="h-anchor" href="#-%e7%b7%a8%e8%bc%af%e4%bf%ae%e6%94%b9%e5%be%8c%e7%9a%84%e7%a8%bf%e4%bb%b6%e8%87%aa%e5%b7%b1%e8%a6%81%e5%86%8d%e4%bb%94%e7%b4%b0%e7%9c%8b%e9%81%8e%e4%b8%80%e9%81%8d" aria-label="這個段落的連結"></a></h3>
<p>在一校的時候除了看文字是否通順、邏輯是否有誤外，還要注意以下問題：</p>
<ol>
<li><strong>是否有「錯誤的內容、缺漏字」</strong>：在稿件重新排版成 PDF 後，因為每頁能容納的內容不同，所以我們會獲得全新的視覺體驗，此時更容易察覺過去沒發現的問題。</li>
<li><strong>文章的特殊符號「➔➠✔」、emoji「😃😅😇」是否如預期呈現</strong>：這些符號在不同作業系統（ex：android/windows/macOS）會有各自呈現的樣式，如果 PDF 上的內容跟你預期不同，建議提供「截圖」。</li>
<li><strong>每頁的內容編排</strong>：因為書籍是左右翻頁，不像網頁一直往下滾就好；有些資訊放在「上一頁」或「下一頁」可能會有更好的閱讀體驗。</li>
</ol>
<h3 id="-正式送印前筆者建議要做的檢查">正式送印前，筆者建議要做的檢查<a class="h-anchor" href="#-%e6%ad%a3%e5%bc%8f%e9%80%81%e5%8d%b0%e5%89%8d%e7%ad%86%e8%80%85%e5%bb%ba%e8%ad%b0%e8%a6%81%e5%81%9a%e7%9a%84%e6%aa%a2%e6%9f%a5" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>再檢查一次封面、封底</strong>：確認文字通順、排版合理、內容無誤。</li>
<li><strong>快速瀏覽內文的每個頁面</strong>：確認要調整的內容皆如預期校訂完畢。</li>
<li><strong>重要的資訊是否無誤</strong>：像是書籍範例程式的下載網址、表單連結。</li>
</ul>
<hr>
<h2 id="後記">後記<a class="h-anchor" href="#%e5%be%8c%e8%a8%98" aria-label="這個段落的連結"></a></h2>
<p>筆者的經驗分享到這裡就告一個段落了，希望每位大神都能出版一本自己理想中的書籍。</p>
<p>文章的最後為自己的新書打個廣告，如果你最近有轉職的需求，或是對現在的職涯感到迷茫，可以參考看看喔（歡迎路過按讚）～</p>
<a class="linkcard" href="https://www.tenlong.com.tw/products/9786263334571" target="_blank" rel="noopener">
  <span class="lc-body">
    <span class="lc-title">天瓏網路書店 | 給全端工程師的職涯生存筆記：從履歷×面試×職場打造無可取代的軟實力 (ChatGPT加強版) (iThome鐵人賽系列書)</span>
    <span class="lc-desc">出版商: 博碩文化 出版日期: 2023-05-04 定價: $650 售價: 7.8 折 $507 語言: 繁體中文 頁數: 416 裝訂: 平裝 ISBN: 6263334576 ISBN-13: 9786263334571…</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="M7 17 17 7M8.5 7H17v8.5"/></svg>www.tenlong.com.tw</span>
  </span>
  <span class="lc-thumb">
    <img src="https://deanlin.net/blog/202302/before-writing-a-book/images/before-writing-a-book-card-01_hu_a93435c81910d787.webp" width="320" height="320" alt="" loading="lazy" decoding="async">
  </span>
</a>

<pre tabindex="0"><code>寫作＆比賽經驗分享
▋ 文章倍化術！解決字數不足、篇幅不夠的 9個實用秘訣
▋ 我希望在參加 [iT邦幫忙鐵人賽] 前就知道的事
▋ 出版書籍經驗分享
▋ 我希望在寫書前就知道的 N 件事 ── 出版之後（本篇）
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>[職場]掌握薪水談判的秘訣，取得自己應有的報酬 &amp; 完賽感言</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-30/</link>
      <pubDate>Fri, 15 Oct 2021 09:20:00 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-30/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>本篇文章想跟讀者探討內部加薪這個議題，如果有辦法在熟悉的環境中持續成長，應該沒有多少人想在陌生的環境中從零開始吧？</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-30/images/cover_hu_4ae13f54698508a1.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>如果你是一個有野心且不安於現狀的人，薪水談判是你一定要去做的事情。</p>
</blockquote>
<p>在閱讀這篇文章前，你可以先思考幾個問題：</p>
<ul>
<li>你入職多久了？</li>
<li>入職後的薪水有調整過嗎？</li>
<li>你滿意現在的薪水嗎？</li>
<li>你想跳槽是因為工作環境問題，還是薪水不夠？</li>
<li>你對公司有什麼具體貢獻？</li>
<li>如果公司願意給你應得的待遇，你想要多少？</li>
</ul>
<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 千萬不要使用紙本或是 Email 要求加薪</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>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-薪水談判前的準備">1. 薪水談判前的準備<a class="h-anchor" href="#1-%e8%96%aa%e6%b0%b4%e8%ab%87%e5%88%a4%e5%89%8d%e7%9a%84%e6%ba%96%e5%82%99" aria-label="這個段落的連結"></a></h2>
<p>不打沒把握的仗，你為了這一天已經努力了這麼久，<mark>千萬不要因為一時的情緒，就在毫無準備的狀態下去敲長官的門</mark>。</p>
<h3 class="h-nomark" id="11-準備自己的成果">1.1 準備自己的成果<a class="h-anchor" href="#11-%e6%ba%96%e5%82%99%e8%87%aa%e5%b7%b1%e7%9a%84%e6%88%90%e6%9e%9c" aria-label="這個段落的連結"></a></h3>
<p>只有初階的工作才會因為你花了更多時間使公司賺更多錢，高階的工作只關心你有多少產出、為公司帶來什麼利益。</p>
<ul>
<li><strong>最容易被無視的加薪理由</strong><br>如果你加薪的理由是：「我感覺自己很努力」、「我已經在這間公司待很久了」、「我覺得自己很認真工作」。<br>相信大部分公司<mark>不會因為上面的理由給你加薪</mark>，且通常會給你一些罐頭回覆：「繼續努力，公司不會虧待你的！」</li>
<li><strong>準備實際證據</strong><br>筆者在 <a href="https://deanlin.net/blog/202109/fullstack-career-notes-day-02/">Day 2</a> 的文章中有提到<mark>履歷要定時整理，因為許多證據是有時效性的</mark>，時間過了你就很難取得，<mark>如果你要公司為你加薪，要有實際的證據他才會買單</mark>。<br>而且這些證據的重點要放在<mark>你做的事為公司帶來多少利益，而不是你做了多少事</mark>，比如幫公司賺了多少錢或省了多少錢。公司的人很多，如果沒有主動說出來，上級根本不知道你有這些貢獻。</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-撰寫有說服力的證據">1.2 撰寫有說服力的證據<a class="h-anchor" href="#12-%e6%92%b0%e5%af%ab%e6%9c%89%e8%aa%aa%e6%9c%8d%e5%8a%9b%e7%9a%84%e8%ad%89%e6%93%9a" aria-label="這個段落的連結"></a></h3>
<p>筆者在這邊提供幾個不同面向的範例，內容的結構為：「<mark>過去遇到的問題 → 我用了什麼方法解決 → 具體有什麼成效</mark>。」</p>
<ul>
<li><strong>減少部署難度、提升系統穩定性、節省公司開銷</strong><br>過去專案在新版本部署時都需要小心翼翼，害怕有什麼參數遺漏導致部署失敗；但在導入 <code>k8s</code> 後，我們就能確保<mark>專案在測試區跟跟正式區的環境都一樣</mark>；並且透過它自動擴展的特性，我們就<mark>不用給伺服器預留太高的硬體配置</mark>，從近幾期 GCP 的帳單來看，這個工具的導入<mark>平均為公司省下了 30% 的費用</mark>。</li>
<li><strong>推廣工具、強化跨部門溝通、提升公司整體工作效率</strong><br>過去各部門在專案溝通上都以郵件為主，如果郵件數量一多就容易有資訊上的遺漏；為了讓部門之間溝通更為順利，<mark>我導入了專案管理系統</mark>，並負責向各部門做系統的<mark>教育訓練</mark>、請 Key man 協助推廣；有了它之後，執行專案時可以更有效地掌握<mark>任務時程、歷史紀錄、負責人員</mark>，從 Issue Ticket 完成的數量來看，<mark>公司整體工作效率提升了 40%</mark>。</li>
<li><strong>學習新技術、解決公司問題、取得良好的數據回饋</strong><br>過去系統首頁的載入速度太慢、且導航功能不夠明確，導致<mark>使用者的跳出率一直很高</mark>；針對這個部分我把首頁改成 <mark>SSR 渲染提升載入速度</mark>，並且與 UI/UX 設計師一起討論系統流程，透過<mark>審視系統的 SiteMap 來重新規劃導航功能的權重</mark>；透過這些更新，從 Google Analytics 的報表我們可以得知使用者平均在系統的<mark>停留時間上升了 200%，且瀏覽的頁數增加了 320%</mark>。</li>
<li><strong>讓公司業務理解產品，提升部門產品的銷售業績</strong><br>我們部門完成了很多產品，但幾個月前的銷售業績並不理想；在跟業務聊天的過程中發現他們<mark>並不了解我們的產品</mark>。所以我便舉辦了一場產品說明會，向業務說明<mark>我們的產品能幫客戶解決哪些痛點</mark>，讓他們<mark>了解並對產品有信心</mark>；以這個月來說，<mark>我們部門產品的銷售業績提升了 300%</mark>。</li>
</ul>
<p>如果你的上級是個有遠見的人，當你說出自己具體貢獻時，他會毫不猶豫地為你加薪，<mark>內部調薪 30% 甚至 50% 以上都是有可能的</mark>，因為好的人才真的很難找。</p>
<hr>
<h3 class="h-nomark" id="13-安排談判時間">1.3 安排談判時間<a class="h-anchor" href="#13-%e5%ae%89%e6%8e%92%e8%ab%87%e5%88%a4%e6%99%82%e9%96%93" aria-label="這個段落的連結"></a></h3>
<p>就算你已經做好了事前準備，也<mark>千萬不要臨時起意找上級談加薪</mark>，因為你無法確認他晚點是否有會議要開；而且這種做法也不夠正式，會給對方一種<mark>我只是提提看，如果不行就算了</mark>的感覺。</p>
<p>談判的時間很重要，相同的要求在不同的時間點提出，可能會有完全不一樣的結果。</p>
<ul>
<li><strong>要避免的談判時間點</strong>
<ul>
<li><strong>禮拜一</strong><br>通常休假後的隔天<mark>大家的心情都不太好</mark>，如果在此時進行談判，上級很可能會帶有較多個人情緒。</li>
<li><strong>禮拜五</strong><br>薪水談判未必可以在一天之內結束，因為有些公司有<mark>複雜的簽核流程要跑</mark>；如果選在禮拜五，你的上級可能會因為要跑的流程太多無法當下給你保證，而你還要捱過一個得不到結果的假日。</li>
<li><strong>下班前</strong><br><mark>你喜歡在下班前開會嗎？</mark>如果你不喜歡，就不要在這個時機點說，因為上級的臉絕對很臭。</li>
</ul>
</li>
<li><strong>最佳的談判時間點</strong><br>儘管避開上面的時段都可以談判，但下面的時機更為恰當。
<ul>
<li><strong>專案獲得成功的時候</strong><br>此時你已經證明了自己的能耐，這是<mark>上級是無法忽視的實際證據</mark>。</li>
<li><strong>上級心情好的時候</strong><br>只要你給的證據足夠、加薪幅度合理，<mark>挑對方心情好的時候談判，成功率會大幅上升</mark>。</li>
<li><strong>知道公司營收成長的時候</strong><br>如果公司沒賺錢，或是你待的產業走下坡；即便你在努力也很難加薪。<br>相反的，<mark>如果公司的營收很好，薪水談判的空間就會很大</mark>。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="2-薪水談判時的注意事項">2. 薪水談判時的注意事項<a class="h-anchor" href="#2-%e8%96%aa%e6%b0%b4%e8%ab%87%e5%88%a4%e6%99%82%e7%9a%84%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a0%85" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-千萬不要使用紙本或是-email-要求加薪">2.1 千萬不要使用紙本或是 Email 要求加薪<a class="h-anchor" href="#21-%e5%8d%83%e8%90%ac%e4%b8%8d%e8%a6%81%e4%bd%bf%e7%94%a8%e7%b4%99%e6%9c%ac%e6%88%96%e6%98%af-email-%e8%a6%81%e6%b1%82%e5%8a%a0%e8%96%aa" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>為什麼不要用文字溝通？</strong><br>很多人會因為害怕和公司高層或是上級面對面談判而使用文字溝通，但這個方式真的非常愚蠢。<br><mark>因為每個人對文字的理解是非常主觀的</mark>；所以請你面對面（或至少讓對方聽得到你聲音）的狀態下去談判，這樣對方才能從你的情緒、語調、肢體語言、眼神的溝通中感受到你的自信。</li>
<li><strong>如果你感到害怕，那請準備好劇本，並練習到滾瓜爛熟</strong><br>有些人口才很好，只要準備好實際證據，就能信心十足地與上級談判。<br>如果你沒有這個天賦，那請你<mark>為這場談判準備好劇本</mark>；在談判前先跟你的朋友、家人、合作伙伴們<mark>多練習幾次，模擬一切可能發生的狀況</mark>。儘管實際談判時，恐懼不會消失，劇本也不會完全按造你的想像發展，但你的表現肯定比沒準備時從容許多。</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-引導談判流程">2.2 引導談判流程<a class="h-anchor" href="#22-%e5%bc%95%e5%b0%8e%e8%ab%87%e5%88%a4%e6%b5%81%e7%a8%8b" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>講清楚這次談判目的，直球對決！</strong><br><mark>談判需要有明確的目標以及結果，否則就只是一場毫無意義的面談。</mark><br>筆者建議一開始就正大光明地說出<mark>此時此刻就是要討論薪水的議題</mark>；如果你只是暗示，有些上級會假裝聽不懂或是轉移話題。</li>
<li><strong>讓對方認同自己的貢獻</strong><br>你可以先說說自己過去在公司的貢獻，並在陳述到一個段落時<mark>向上級詢問這些貢獻是否對公司有幫助</mark>，透過這樣一來一往的過程，對方更能吸收並認同你所提供的訊息。</li>
<li><strong>主動提問題，不要只顧著講自己的豐功偉業</strong><br>除了表明自己的貢獻外，你同時也要提出問題，請記住<mark>提出問題的人才是主導這個會議的人</mark>。<br>提問是為了獲取更多情報，並試探對方的底線；但<mark>請以合作者的角度去做詢問</mark>，讓他知道我們是在同一個陣營的，我們要一起讓公司變得更好。</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-不要讓對方覺得是你有求於他">2.3 不要讓對方覺得是你有求於他<a class="h-anchor" href="#23-%e4%b8%8d%e8%a6%81%e8%ae%93%e5%b0%8d%e6%96%b9%e8%a6%ba%e5%be%97%e6%98%af%e4%bd%a0%e6%9c%89%e6%b1%82%e6%96%bc%e4%bb%96" aria-label="這個段落的連結"></a></h3>
<p>不要在對話中出現：「我想要…」、「我需要…」這類的開頭，<mark>一但出現有求於人的詞彙，那談判的主動權就可能轉移</mark>。</p>
<p>你可以嘗試改用：「<mark>這份薪水有彈性嗎？如果我的年薪有 XX 會更合適，你覺得呢？</mark>」這類中性的語句，不要參雜多餘的事情來做詢問。</p>
<hr>
<h3 class="h-nomark" id="24-被拒絕時的應對進退">2.4 被拒絕時的應對進退<a class="h-anchor" href="#24-%e8%a2%ab%e6%8b%92%e7%b5%95%e6%99%82%e7%9a%84%e6%87%89%e5%b0%8d%e9%80%b2%e9%80%80" aria-label="這個段落的連結"></a></h3>
<p>並不是每次提加薪都會被接受，<mark>但如果被婉拒就直接離場，那你在這次的談判中毫無收穫</mark>；筆者在這邊提出另一條思路，讓你確定是否要繼續留在公司打拼。</p>
<ul>
<li><strong>如果現在不符資格，那我要怎麼做？</strong><br>在提出加薪需求時，上級可能會跟你說：「現在還不是時候」、「你知道現在公司營運狀況」、「共體時艱」&hellip;<br>遇到這些問題時你可以考慮這樣回答：「我來這間公司的目標是為了成為一個最有價值的成員，而不是成為公司的負擔；我想要為公司創造收入，並讓您理解當公司支付這個薪水時，我會 10 倍、20 倍的回饋給公司，我是為了讓公司成長而進來的，我不是把這裡當成一份工作而是把它當成我的事業。」先說讓上級聽起來舒服的場面話，然後在最後詢問他：「<mark>現在我的年薪是 XX，而我的目標是 YY，我知道不可能馬上調整成這個數字，但是可以請您告訴我，要怎麼做才能賺到這個薪水嗎？</mark>」</li>
<li><strong>直接向上級確認，完成目標後是否就會調整成期待薪資</strong><br>相信上面這些話會讓上級對你印象深刻，<mark>至少他會知道公司有你這名充滿野心的員工</mark>，此時他就會去思考要如何幫助你成為有資格賺到這個薪水的人，並提出他期望你能夠完成的 KPI、milestones…<br>接下來就到最後一步了，<mark>你直接向上級確認：「如果我真的完成了您所說的這些目標，公司是否願意支付我所提出的年薪？」</mark>在得到肯定的答覆後，可以直接跟上級約下一次會面的時間（ex：3 個月後）。</li>
<li><strong>完成目標後，向更高的目標發起挑戰！</strong><br>等到下一次的會議，<mark>再重複一次你今天所做的事情</mark>，挑戰更高的目標。<br>如果你是待在一間高速成長的公司，那麼你的機會是無限的，能成長多快完全取決於你的意願、野心、能力。</li>
</ul>
<p>如果在你達成目標後，公司願意給你該有的報酬，那其實留在原本的公司也不錯；假設公司沒有履行承諾，那你也能非常果決的離職，尋找下一間適合自己的公司。</p>
<hr>
<h2 class="h-nomark" id="3-完賽感言">3. 完賽感言<a class="h-anchor" href="#3-%e5%ae%8c%e8%b3%bd%e6%84%9f%e8%a8%80" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>每個人的人生，都是自己選擇而來的。</p>
</blockquote>
<h3 class="h-nomark" id="31-趨吉避凶真的很重要">3.1 趨吉避凶真的很重要<a class="h-anchor" href="#31-%e8%b6%a8%e5%90%89%e9%81%bf%e5%87%b6%e7%9c%9f%e7%9a%84%e5%be%88%e9%87%8d%e8%a6%81" aria-label="這個段落的連結"></a></h3>
<p>有些話年輕的時候聽不進去，直到經歷了某些事，才會了解它的道理。</p>
<ul>
<li><strong>做人要圓融</strong><br>以前覺得圓融就像是示弱，但社會化越深，越能體會圓融的重要性；<mark>如果讓對方講話大聲一點就能取得雙贏，為何一定要鬥到其中一方慘勝？</mark></li>
<li><strong>做好本分，不要意氣用事</strong><br>如果今天領著公司的薪水，就做好自己的本分；筆者看過太多因為對公司制度不爽或是被 PIP 的同事，在離職前努力的給同事製造麻煩，甚至遇過離職當天把公司電腦給格式化的。<br>公司對不起你，你有情緒完全合理；但請注意，<mark>如果你損壞公司財產，你可能會背上法律責任</mark>，真的不要意氣用事。<br>如果公司沒犯法，盡可能不要把事情做絕；至少保留檯面上雙方的面子；圈子說小不小、說大也不大，誰知道有沒有再次相會，也不知道是以什麼方式相遇。</li>
<li><strong>給人留一條活路</strong><br>以前，在局面對自己有利的時候，我會想要獲得全面性的勝利；但現在，我會選擇給對方一個台階下。<br><mark>一無所有的人才是最可怕的</mark>，人生中有勁敵會讓自己成長；但如果周圍都是仇敵，你連睡覺都不安穩。</li>
</ul>
<hr>
<h3 class="h-nomark" id="32-致謝">3.2 致謝<a class="h-anchor" href="#32-%e8%87%b4%e8%ac%9d" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>令人感動的朋友群</strong><br>這個系列文能完成，<mark>首先要感謝我身邊受苦受難的朋友們；沒有你們，我真的沒有這麼多題材可以寫，希望你們早日脫離苦海</mark>。</li>
<li><strong>給工程師表演舞台的比賽</strong><br>感謝 iT 邦幫忙舉辦鐵人賽，讓我趁這個比賽逼自己去完成一件有意義的事情；並且很幸運的認識一群對技術充滿熱誠的<del>自虐狂</del><a href="https://ithelp.ithome.com.tw/2021ironman/signup/team/154">戰友們</a>，並成功在各自的主題中完成了連續 30 天發表技術文章的里程碑。</li>
<li><strong>使我有勇氣繼續奮戰的後盾</strong><br>家人與女友在比賽期間一直給我加油打氣，有你們當我的後盾真的很安心。</li>
</ul>
<hr>
<h3 id="後記">後記<a class="h-anchor" href="#%e5%be%8c%e8%a8%98" aria-label="這個段落的連結"></a></h3>
<p>這是一系列個人風格強烈的筆記，筆者將自己這些年累積的技術與經驗用最短的篇幅呈現出來；但每個人都有自己的人生要走，文中的應對進退未必適合每一個人。</p>
<p>即便你覺得哪句話說得再有道理，也要經過自己的思考再去實踐。</p>
<p>如果這個系列文對你們有幫助，那是我最大的榮幸。</p>
<p>最後想跟讀者們分享「熔爐」這部電影中，一句讓我印象深刻的台詞：「<mark>我們一路奮戰，不是為了改變世界，而是為了不讓世界改變我們。</mark>」</p>
<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/fc3abd5fce88">薪水談判-Salary Negotiation(筆者部落格)</a></li>
<li><a href="https://www.youtube.com/watch?v=_PtKv7IXrBI">Salary Negotiation: 7 Tips On How To Negotiate A Higher Salary</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-30/images/fullstack-career-notes-day-30-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-29/</link>
      <pubDate>Thu, 14 Oct 2021 08:35:07 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-29/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>同一個部門，類似的職缺；為什麼有些人做的輕鬆愜意，而有些人卻累的跟狗一樣？</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-29/images/cover_hu_e3f216d4ed4ce695.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>同一個部門，類似的職缺；<mark>為什麼有些人做的輕鬆愜意，而有些人卻累的跟狗一樣？</mark></p>
<p>如果在過往的工作中只遇過 1、2 次不愉快的經驗，那應該是主管偏心、公司制度有問題；但如果你<mark>過去的每間公司都遇到類似的狀況，那問題可能出在自己身上</mark>。</p>
<p>如果你覺得現在過得不好，不妨參考文章中的方法，也許裡面有些觀點能給你的職涯帶來不一樣的啟發。</p>
<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>
</ul>
</li>
<li><strong>職場禮儀</strong>
<ul>
<li>3.1 避免當個讓人討厭的主管</li>
<li>3.2 詢問別人問題時不要犯的禁忌</li>
<li>3.3 詢問別人問題時該有的禮儀</li>
<li>3.4 給出具體時間，避免使用模糊用語</li>
</ul>
</li>
<li><strong>建立友善的溝通環境</strong>
<ul>
<li>4.1 面對想法意見不同的人，不要先入為主</li>
<li>4.2 不是所有人都跟你有相同的知識背景</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-做好份內的事">1. 做好份內的事<a class="h-anchor" href="#1-%e5%81%9a%e5%a5%bd%e4%bb%bd%e5%85%a7%e7%9a%84%e4%ba%8b" aria-label="這個段落的連結"></a></h2>
<p>筆者過去非常認真工作，<mark>面對主管交代的事從不拒絕，同事需要幫忙時也傾力協助</mark>；就算下班時間同事都走光了，我還是獨自加班完成工作。</p>
<p>當時我覺得應該是自己的能力受到肯定，才會被分配到這麼多工作；但過了一段時間後，身體漸漸無法承受這麼高強度的工作，對公司不滿的情緒也越來越多：「<mark>為什麼主管只把困難的任務丟給我做？為什麼其他同事都能準時下班？為什麼我要一個人處理這麼多的事情？</mark>」然後不久後我就離職了。</p>
<p>直到現在回頭看當年的這段故事，才了解<mark>原來「能者過勞」最大的元兇是自己</mark>。</p>
<h3 class="h-nomark" id="11-勇敢拒絕非本職工作持續加強自己的專業">1.1 勇敢拒絕非本職工作，持續加強自己的專業<a class="h-anchor" href="#11-%e5%8b%87%e6%95%a2%e6%8b%92%e7%b5%95%e9%9d%9e%e6%9c%ac%e8%81%b7%e5%b7%a5%e4%bd%9c%e6%8c%81%e7%ba%8c%e5%8a%a0%e5%bc%b7%e8%87%aa%e5%b7%b1%e7%9a%84%e5%b0%88%e6%a5%ad" aria-label="這個段落的連結"></a></h3>
<p>如果你是後端工程師，那就請<mark>把自己後端的能力鑽研到頂尖的水準</mark>；如果指派給你非本職工作的任務時，盡可能地拒絕。</p>
<p><mark>如果你不懂得拒絕，你就會變成「全包」工程師</mark>，同事有事情第一個都先想到你；這容易導致你原本的專業技能停止成長，甚至每天都有做不完的事。</p>
<p>就算你擔任的是全端工程師的職位，我相信一定還是有偏重前端或是後端；筆者建議要認真鑽研偏重的部分，因為<mark>絕大多數人都做不到全能</mark>，謹記<mark>主武器負責殺敵，副武器用來輔助；如果你的主武器已經生鏽，那副武器再多也沒有任何意義。</mark></p>
<blockquote>
<p><strong>從高手變成庸才的故事</strong></p>
<p>筆者有個朋友曾經是業界非常優秀的後端工程師，但他在入職新公司兩年後卻變得很平庸；因為除了後端開發外，他還要負責前端網頁開發、公司內部 Server 管理、專案管理系統架設、程式碼版控系統架設、雲端 Server 部署、專案管理、內部教育訓練、Android APP 撰寫&hellip;；<mark>這些瑣事不僅填滿了上班時間，也讓他下班後沒有體力繼續提升自己，導致技術停留在兩年前毫無進步</mark>，而當年落後的人已經可以跟他並駕齊驅，甚至比他更優秀了。</p>
</blockquote>
<blockquote>
<p><strong>備註</strong>：如果在自己手上有事情忙的狀態下，還被強制分配非本職工作的任務；可以趁這個時候跟上級交易，畢竟大家上班都是來賺錢的，<mark>如果要我多負擔一份壓力，那也要付出相應的代價</mark>。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="12-不要什麼事情都攬下來學習當溝通的橋樑">1.2 不要什麼事情都攬下來，學習當溝通的橋樑<a class="h-anchor" href="#12-%e4%b8%8d%e8%a6%81%e4%bb%80%e9%ba%bc%e4%ba%8b%e6%83%85%e9%83%bd%e6%94%ac%e4%b8%8b%e4%be%86%e5%ad%b8%e7%bf%92%e7%95%b6%e6%ba%9d%e9%80%9a%e7%9a%84%e6%a9%8b%e6%a8%91" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>將對話窗口轉移</strong><br>在非純軟的公司裡面，大部分的部門都搞不清楚前端、後端的差別，因此<mark>遇到問題他們不是找主管就是找「全端」工程師</mark>；就算你真的能幫他們解決問題，但<mark>只要你當了一次的窗口，他們之後遇到問題都會來找你</mark>；為了避免日後工作時被其他部門頻繁打擾，筆者的處理方式為：</p>
<ul>
<li>如果不是本職工作，我會用 Email 告訴其他部門的同事，遇到問題時可以找哪些對應的窗口（ex：如果要提需求可以跟產品經理討論、想知道專案進度請去找專案經理）。</li>
<li>如果是本職工作，但手上工作滿檔；我會將 Email 轉發給其他工程師並描述事情經過，請他協助處理。</li>
</ul>
<p><mark>如果你接到問題就處理，那只會是「能者過勞」</mark>；你要學習將任務分給同事。</p>
</li>
<li>
<p><strong>不要把別人的問題變成自己的問題</strong><br>能力越強，責任越大；當你擔任 Senior Engineer 或是 Tech Lead 時，時常會有同事請你幫忙解決 Bug；如果時間允許，忙當然是要幫的，但請謹記<mark>你是幫人，不是攬過來做</mark>。<br>有些工程師問完問題看到有人幫忙後，就會回到座位一副事不關己的樣子；所以<mark>幫忙時請注意界線，以引導為主</mark>，不然最後出包，對方可能會把責任直接推到你的頭上。</p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-拒絕承擔與職位不符的責任">1.3 拒絕承擔與職位不符的責任<a class="h-anchor" href="#13-%e6%8b%92%e7%b5%95%e6%89%bf%e6%93%94%e8%88%87%e8%81%b7%e4%bd%8d%e4%b8%8d%e7%ac%a6%e7%9a%84%e8%b2%ac%e4%bb%bb" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>背高價、沒看過的資產</strong><br>個人資產應該是公司配給你的電腦、螢幕；但有些爛主管會把一些自己不敢背的高價資產掛到下屬身上，<mark>如果主管要你在資產轉移單上面簽名，一定要堅定的拒絕</mark>。<br>大部分的高價資產，只有該領域的工程師才知道如何檢測；所以資產轉移這件事通常有鬼，如果你沒搞清楚就簽下去，很可能成為主管的替死鬼。</p>
</li>
<li>
<p><strong>要你幫忙做不符職權的重要決策</strong><br>有些爛主管會將做決策的任務推給下屬，如果你發現這個<mark>決策的影響範圍已經超過了你的職權，甚至涉及到多個部門、公司預算；筆者強烈建議你要裝傻推掉</mark>。<br>他把決策的任務交給你，通常不是為了重用你；只是單純的想要逃避責任跟栽贓嫁禍而已。</p>
<blockquote>
<p><strong>小提醒</strong>：假設 1 個決策的失敗率是 20%，你幫忙做了 5 次決策；主管會獲得那 4 次決策正確的獎勵，而你則會承受那 1 次決策失敗的後果。</p>
</blockquote>
</li>
</ul>
<p>當然也是有善良的主管，但社會上陰險的人太多，遇到不合理的事你要想辦法拒絕才能保護自己；<mark>有的時候不是你比較倒霉，而是因為你沒有拒絕不該由你承擔的責任</mark>。</p>
<hr>
<h2 class="h-nomark" id="2-讓自己獲得好的工作狀態">2. 讓自己獲得好的工作狀態<a class="h-anchor" href="#2-%e8%ae%93%e8%87%aa%e5%b7%b1%e7%8d%b2%e5%be%97%e5%a5%bd%e7%9a%84%e5%b7%a5%e4%bd%9c%e7%8b%80%e6%85%8b" aria-label="這個段落的連結"></a></h2>
<p>讓我們還回想一下，同樣是工作一整天；有時你回到家時感到沮喪，而有時卻覺得非常充實。</p>
<p>如果你仔細觀察，會發現會造成這兩者差異的主要原因，是因為<mark>前者做了太多的雜事，而後者則是去完成有挑戰性的事</mark>。</p>
<p>在上個小節已經分享了許多避免雜事的技巧，這個小節就讓我們來了解，如何讓自己<mark>有能量去完成有挑戰性的事情</mark>。</p>
<h3 class="h-nomark" id="21-提升工作品質的小技巧">2.1 提升工作品質的小技巧<a class="h-anchor" href="#21-%e6%8f%90%e5%8d%87%e5%b7%a5%e4%bd%9c%e5%93%81%e8%b3%aa%e7%9a%84%e5%b0%8f%e6%8a%80%e5%b7%a7" aria-label="這個段落的連結"></a></h3>
<p>你是否曾經工作到一半<mark>突然覺得所有事情做起來都非常流暢</mark>，在這個狀態下遇到的難題都能迎刃而解、而且能在短時間完成非常多的事，更神奇的是這段時間你的注意力完全不會渙散，甚至會忘記時間的流逝。</p>
<p>筆者周圍有蠻多朋友都體會過這個狀態，它有一個專有名詞 ──「<code>心流</code>」；雖然可遇不可求，但能透過一些小技巧增加它出現的頻率，這些技巧就算無法讓你進入心流，也能提升不少工作品質：</p>
<ul>
<li>
<p>給自己一個<mark>完整的工作區間</mark>，在這個區間內拒絕一切外界聲音；這樣做可以避免你的注意力被打來的電話、訊息的提示音所干擾。</p>
</li>
<li>
<p><mark>給任務設定一個期限</mark>，必須在時間內完成某件事；讓大腦把所有精力只投入到一件事情上面。</p>
</li>
<li>
<p><mark>在開始前用一個儀式</mark>，暗示自己就要進入無敵模式；透過這個儀式把自己從混亂的思緒中抽離，切斷大腦之前的思緒。</p>
<blockquote>
<p>筆者的儀式是在開始做事前，聽 <a href="https://www.youtube.com/watch?v=hKRUPYrAQoE">Victory</a> 這個激昂的音樂。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-下班後好好休息">2.2 下班後好好休息<a class="h-anchor" href="#22-%e4%b8%8b%e7%8f%ad%e5%be%8c%e5%a5%bd%e5%a5%bd%e4%bc%91%e6%81%af" aria-label="這個段落的連結"></a></h3>
<p>很多人的下班不是下班，讀者可以回想一下，自己是不是下班後大腦還在思考著<mark>剛剛會議的事項，今天尚未完成的工作</mark>？</p>
<p>這樣的過度努力，有時會導致你<mark>隔天早上到公司的時候昏昏沈沈，做事沒有動力</mark>；過去筆者一直以為會這樣是因為工作倦怠、前一晚沒睡好；但後來發現<mark>主要是因為在下班後並未將大腦抽離工作環境</mark>。</p>
<p>筆者建議在下班前<mark>把尚未完成的工作記錄下來</mark>，現在有許多筆記軟體都可以幫助你完成這件事，<mark>完成後就讓大腦關機，給它喘息的時間</mark>；如果讀者願意嘗試看看這個方法，在幾天後你會發現這個細微的調整，竟然能讓隔天上班的時候更有精神、做事效率更高。</p>
<hr>
<h2 class="h-nomark" id="3-職場禮儀">3. 職場禮儀<a class="h-anchor" href="#3-%e8%81%b7%e5%a0%b4%e7%a6%ae%e5%84%80" aria-label="這個段落的連結"></a></h2>
<p>與其說是禮儀，不如說是同事間的互相尊重；如果你有給長官、同事、下屬該有的尊重，彼此的合作會輕鬆許多。</p>
<p>這個小節筆者主要分享<mark>不要去踩的地雷</mark>，讀者可以從範例中看看自己是否有需要調整的地方；<mark>很多時候我們一直在犯錯卻又毫不自覺</mark>。</p>
<h3 class="h-nomark" id="31-避免當個讓人討厭的主管">3.1 避免當個讓人討厭的主管<a class="h-anchor" href="#31-%e9%81%bf%e5%85%8d%e7%95%b6%e5%80%8b%e8%ae%93%e4%ba%ba%e8%a8%8e%e5%8e%ad%e7%9a%84%e4%b8%bb%e7%ae%a1" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>朋友的故事很經典，又符合這個主題，所以就直接拿他來當範例了。</p>
</blockquote>
<p>朋友公司的 MIS 部門<mark>離職率很高</mark>，有多高呢？10 個人的 MIS 部門，一個月可以走 2 個！招募的速度都趕不上離職的速度，<mark>部門內除了主管外，最資深的員工還沒待滿一年</mark>。</p>
<p>所以時常會找其他 IT 部門來支援業務，而我朋友很不幸的被抓去支援，進去後他瞬間理解為何會有如此高的離職率，簡單幾個例子：</p>
<ul>
<li>
<p><strong>在錯誤的時間點發布任務</strong><br>除了上班時間會指派任務外，他在<mark>禮拜五下午、下班前一小時、下班後</mark>還會指派任務給下屬；讓部門每個人的<mark>工時超長</mark>。</p>
</li>
<li>
<p><strong>光說不練</strong><br>一直說自己超級強，<mark>但從來沒有看過他自己動手操作</mark>，連機房都不太敢走進去；讓下屬對他的能力充滿質疑。</p>
</li>
<li>
<p><strong>推卸責任，陷害同仁</strong><br>朋友進去支援，在做交接時身上還要背一堆公司資產；但交接時間只有半小時，資產根本不可能在這麼短的時間內盤點完（當時朋友菜逼巴，不知道一個人背這麼多資產根本不合理），當時他就非常不安；果然隔天就出事了，公司在做資產盤點時發現有些東西不見了，這個<mark>主管立馬把鍋甩到我朋友身上要他賠償</mark>；一台伺服器就要 20 萬，我朋友現場崩潰大哭。</p>
<blockquote>
<p>如果你只是支援，<mark>千萬不要在身上掛賬</mark>；就算對方階級比你高，<mark>直接請主管幫你擋掉，這是上司應該對下屬的保護</mark>。</p>
</blockquote>
</li>
<li>
<p><strong>分不清輕重緩急</strong><br>凌晨 12 點突然打電話給我朋友，我朋友當時還以為發生了什麼重大事故；結果<mark>只是要我朋友幫忙把防火牆重新啟動</mark>。因為主管怕自己做出事，所以連小事都要在凌晨打擾下屬。</p>
</li>
<li>
<p><strong>用考績來威脅做事</strong><br>在打考績的時期，這名主管在下班時間是這麼交代事情的：「<mark>XXX 你幫忙處理一下這個問題，我現在要打考績沒時間處理。</mark>」</p>
</li>
<li>
<p><strong>不管團隊承受能力狂接需求，甚至未經討論直接壓任務死線</strong><br>這位主管好大喜功，<mark>為了討好各個部門，只要有需求就先接再說</mark>，完全不考慮團隊的承受能力；為了達成自己的績效，他甚至直接壓死線，要求做不完就全部留下來加班。</p>
</li>
</ul>
<p>看完上面的敘述後，有沒有覺得這個部門根本是地獄？每個人在未來都有可能擔任主管，當你覺得這個故事荒謬的同時，請謹記：「<mark>不要變成自己曾經討厭的樣子。</mark>」</p>
<hr>
<h3 class="h-nomark" id="32-詢問別人問題時不要犯的禁忌">3.2 詢問別人問題時不要犯的禁忌<a class="h-anchor" href="#32-%e8%a9%a2%e5%95%8f%e5%88%a5%e4%ba%ba%e5%95%8f%e9%a1%8c%e6%99%82%e4%b8%8d%e8%a6%81%e7%8a%af%e7%9a%84%e7%a6%81%e5%bf%8c" aria-label="這個段落的連結"></a></h3>
<p>如果你問問題的方式讓同事感到不舒服，他們可能之後就不想再幫你了。</p>
<ul>
<li>
<p><strong>訊息太過零散</strong><br>現在普遍是用通訊軟體來問問題的，如果你<mark>沒有把問題整理好再發送</mark>，會導致對方電腦一直跳彈窗，非常干擾工作。筆者建議詢問別人問題前，要先整理好訊息且內容要包含：</p>
<ul>
<li>在哪個地方遇到問題</li>
<li>我搜尋了哪些相關資源</li>
<li>過去有沒有可以參考的範例</li>
</ul>
</li>
<li>
<p><strong>對方沒馬上回，就一直發訊息</strong><br>有些人認為在上班時，同事的訊息應該要秒回，如果超過 5 分鐘沒回就一直敲他。<br>我相信會這樣做是因為事情很緊急，但這個做法非常討人厭；<mark>對方沒有立刻回訊息，可能是手上也有緊急的事情在處理</mark>，如果真的有時間壓力，筆者會建議直接打電話。</p>
</li>
<li>
<p><strong>在下班時間或假日打擾對方</strong><br>如果是你個人在專案遇到的問題，建議上班時間處理。</p>
</li>
<li>
<p><strong>訊息只有：「請問你現在有時間嗎？」</strong><br>很多人的訊息只有：「請問你現在有時間嗎？」這樣的詢問方式讓<mark>收到問題的人沒有辦法預估這個問題將會佔用他多少時間</mark>，所以有些人就當成沒看到這則訊息。</p>
</li>
<li>
<p><strong>在開頭說打擾 3 分鐘，結果打擾了 30 分鐘</strong><br>如果你的問題其實不小，<mark>千萬不要騙對方說只打擾 3 分鐘</mark>；這樣很容易打亂他這一天的計畫。</p>
<blockquote>
<p>很多業務開頭的話術就是：「請問可以打擾您 3 分鐘的時間嗎？」如果你有停下來聽他們講解過，就會知道 3 分鐘都是騙人的。</p>
</blockquote>
</li>
<li>
<p><strong>拖到最後一刻才問人</strong><br>如果最後一刻才求救，不但你緊張，被你求救的人也很緊張；<mark>在雙方壓力都很大的狀態下，工作效率實際上是更低的</mark>。</p>
</li>
<li>
<p><strong>問重複的問題</strong><br>如果詢問重複的問題，除了造成同事的困擾外，還<mark>會被質疑你的能力是不是有問題</mark>。</p>
</li>
<li>
<p><strong>對方不知道你什麼時候會過來詢問</strong><br>如果問問題時對方正好在忙，千萬別說：「我晚點來問你。」因為<mark>不明確的時間會給對方帶來更多壓力</mark>，應該問：「我什麼時候方便來請教？或是等您有空的時候跟我說一聲？我再過來這邊。」這樣會讓對方比較舒服。</p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="33-詢問別人問題時該有的禮儀">3.3 詢問別人問題時該有的禮儀<a class="h-anchor" href="#33-%e8%a9%a2%e5%95%8f%e5%88%a5%e4%ba%ba%e5%95%8f%e9%a1%8c%e6%99%82%e8%a9%b2%e6%9c%89%e7%9a%84%e7%a6%ae%e5%84%80" aria-label="這個段落的連結"></a></h3>
<p>有時同事會說：「你幹嘛這麼客氣，直接問就好了啊！」筆者建議你就算聽到這段話，還是要保持該有禮貌，畢竟禮多人不怪。</p>
<ul>
<li><strong>無論是否得到你想要的解答，記得感謝對方</strong><br>對方願意幫你，是他人很好而不是義務，就算沒有得到解答也要記得感謝對方，<mark>有禮貌對方下一次才會幫你</mark>。</li>
<li><strong>在打電話給對方前，請先用文字簡述要討論的問題，並確認方便詢問的時間</strong><br>如果你直接打電話過去，對方可能在洗手間、會議中&hellip;無法及時接電話，並且也<mark>不知道接了這通電話要處理什麼事情</mark>，而且<mark>未經詢問直接打電話有點沒禮貌</mark>。<br>筆者建議先用文字說明問題，讓對方有思考的時間，這樣可以有效提升討論品質。</li>
<li><strong>如果真的有緊急事件需要用通話</strong><br>有時會開到一半發現需要某個成員的參與，所以臨時把他拉到會議室。<br>若有這種突發狀況，請記得<mark>開頭要先說一聲抱歉</mark>，並說明要找他討論的主旨，讓他知道突然被加入的會議要討論哪些內容。</li>
<li><strong>不要問：「這個要怎麼做？」而是詢問：「這樣做的方向對嗎？」</strong><br>第一種詢問方式就像是自己沒有做過功課，第二種詢問方式則是讓對方做修正；<mark>大家都很討厭沒做功課就來問問題的人</mark>。</li>
</ul>
<hr>
<h3 class="h-nomark" id="34-給出具體時間避免使用模糊用語">3.4 給出具體時間，避免使用模糊用語<a class="h-anchor" href="#34-%e7%b5%a6%e5%87%ba%e5%85%b7%e9%ab%94%e6%99%82%e9%96%93%e9%81%bf%e5%85%8d%e4%bd%bf%e7%94%a8%e6%a8%a1%e7%b3%8a%e7%94%a8%e8%aa%9e" aria-label="這個段落的連結"></a></h3>
<p>當同事問你：「什麼時候東西才會完成？」請不要回答：「等一下就好！」<mark>而是說再等 30 分鐘這類具體的時間</mark>，因為每個人對「一下」是多少時間的認定是不同的；如果回答的人認為是 1 小時，而發問的人認為是 5 分鐘，<mark>就會造成不必要的摩擦</mark>。</p>
<p>有些主管在找下屬討論問題時會說：「下午幫我空出一個時間，要找你討論前端設計的問題。」此時千萬不要回答：「好！」<mark>你應該要直接提出一個具體時間詢問主管是否方便</mark>，如果沒有明確的時間，會導致你<mark>無法安排需要高度專注力或是長時間作業的任務</mark>。</p>
<hr>
<h2 class="h-nomark" id="4-建立友善的溝通環境">4. 建立友善的溝通環境<a class="h-anchor" href="#4-%e5%bb%ba%e7%ab%8b%e5%8f%8b%e5%96%84%e7%9a%84%e6%ba%9d%e9%80%9a%e7%92%b0%e5%a2%83" aria-label="這個段落的連結"></a></h2>
<p>不要說新手，就算是職場上的老鳥，在與同事溝通這塊也經常碰壁；<mark>這種情形在跨團隊溝通時遇到的頻率更高，總覺得其他團隊的人在針對我們</mark>。</p>
<p>這邊先排除辦公室政治那種拒絕溝通的情境，而是去思考：「<mark>如果我們的共同目標是希望讓公司更好，有什麼方法能創造出共贏的局面呢？</mark>」</p>
<h3 class="h-nomark" id="41-面對想法意見不同的人不要先入為主">4.1 面對想法意見不同的人，不要先入為主<a class="h-anchor" href="#41-%e9%9d%a2%e5%b0%8d%e6%83%b3%e6%b3%95%e6%84%8f%e8%a6%8b%e4%b8%8d%e5%90%8c%e7%9a%84%e4%ba%ba%e4%b8%8d%e8%a6%81%e5%85%88%e5%85%a5%e7%82%ba%e4%b8%bb" aria-label="這個段落的連結"></a></h3>
<p>如果想透過說服對方達成共識，那很容易就把彼此拉到了對立面；即使你成功說服對方，他也未必會真心一起共事。</p>
<p>很多時候在談判前，我們會很自以為是的<mark>覺得自己已經了解對方在想什麼了</mark>，然後很急迫地把自己的想法加諸到對方身上，不給對方開口的機會。</p>
<p>也許你曾經透過這個方法獲得談判的勝利，但你勝利的原因可能只是因為你的口才比較好；過去的經驗告訴我，這個方法是在<mark>封閉彼此溝通的橋樑</mark>。</p>
<p>與其一直表達自己的想法，不如嘗試另一條路，<mark>詢問對方為何會這麼想</mark>，讓對方描述更多想法的細節；在了解對方完整的想法後，用<mark>討論的方式詢問對方是否有思考到完整的情境</mark>；也許深入探討後你會發現自己的想法是有漏洞的，討論的目的是做出更好的決定，在討論後選擇對方的意見不是認輸，而是達成彼此的共識。</p>
<hr>
<h3 class="h-nomark" id="42-不是所有人都跟你有相同的知識背景">4.2 不是所有人都跟你有相同的知識背景<a class="h-anchor" href="#42-%e4%b8%8d%e6%98%af%e6%89%80%e6%9c%89%e4%ba%ba%e9%83%bd%e8%b7%9f%e4%bd%a0%e6%9c%89%e7%9b%b8%e5%90%8c%e7%9a%84%e7%9f%a5%e8%ad%98%e8%83%8c%e6%99%af" aria-label="這個段落的連結"></a></h3>
<p>有時你會在心裡 OS：「這東西這麼簡單，為什麼跟其他部門解釋了好幾次他們還是聽不懂？」會這樣是因為<mark>你認為簡單的東西，對其他領域的人而言可能是天書</mark>。</p>
<p>每個行業都有自己的專業術語、不同的部門對同一個名詞也會有不同的解釋；如果你今天擔任的角色需要協調各部門的資源，你就要像是個翻譯員，把資訊轉換成他們可以迅速理解與吸收的語言；<mark>有時不是其他部門不想配合，而是因為他們真的聽不懂，所以無從配合</mark>，資訊轉換也絕對不是一門簡單的功夫，因為<mark>你需要對合作部門的業務有一定程度的理解</mark>。</p>
<blockquote>
<p><strong>備註</strong>：筆者認為資深員工最大的價值並不是他擁有的技術，而是他在這間公司的影響力、人脈，以及對各部門業務的熟悉與理解，<mark>有能力串連各部門共同完成任務的員工，是公司寶貴的資產</mark>。</p>
</blockquote>
<hr>
<h3 id="後記">後記<a class="h-anchor" href="#%e5%be%8c%e8%a8%98" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>不會永遠都會有人給你合適的職場建議。</p>
</blockquote>
<p>工程師剛進入職場時，能夠在帶你的前輩身上學到很多的技術與經驗。</p>
<p>在你成為老鳥後，你可能要獨自負責一個專案，此時主管能給你一些戰略上面的建議。</p>
<p>但當你成為主管後，你需要同時處理多個專案；到了這個階段，比你更高階的長官因為不了解這些專案的細節，所以很難給出有效的建議。</p>
<p><mark>職涯的前期你會獲得解決問題的標準答案；但隨著你的成長，在未來無論是專案的決策、還是做事的方法，都不會有一個標準答案。</mark></p>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-29/images/fullstack-career-notes-day-29-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-28/</link>
      <pubDate>Wed, 13 Oct 2021 08:26:43 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-28/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>每間公司在新人教育訓練上，願意投資的資源落差很大；有些有完整的制度與流程，讓新人一步步走上軌道；而有些則是放牛吃草，讓他自行摸索。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-28/images/cover_hu_81044f50bfe60b5d.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 申請 VPN 連線</li>
<li>1.2 註冊公司內部協作平台</li>
<li>1.3 設定內部 Server 登入權限</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>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-新人報到前的準備工作">1. 新人報到前的準備工作<a class="h-anchor" href="#1-%e6%96%b0%e4%ba%ba%e5%a0%b1%e5%88%b0%e5%89%8d%e7%9a%84%e6%ba%96%e5%82%99%e5%b7%a5%e4%bd%9c" aria-label="這個段落的連結"></a></h2>
<p>隨著公司的擴張，會有越來越多人加入團隊來分擔你的工作，此時你需要<mark>準備交接文件</mark>，以保證他們會成為團隊中的神隊友；而不是增加你工作份量的豬隊友，這篇文章我會列出自己在交接時準備的東西。</p>
<h3 class="h-nomark" id="11-申請-vpn-連線">1.1 申請 VPN 連線<a class="h-anchor" href="#11-%e7%94%b3%e8%ab%8b-vpn-%e9%80%a3%e7%b7%9a" aria-label="這個段落的連結"></a></h3>
<p>因為疫情關係，越來越多公司的 IT 部門採取遠端工作，但許多<mark>工作上的資料以及開發環境都在公司內網</mark>。</p>
<p>所以在新人報到前，你要先幫忙申請 VPN 帳號，常見的 VPN 連線方式有 PPTP、L2TP。</p>
<p>這邊筆者要特別提醒一下，因為安全性問題，<mark>Mac 從 Catalina 這個版本開始就不支援 PPTP 的連線</mark>；建議還在使用 PPTP 來做 VPN 連線的公司，可以考慮換一個更安全的連線方式。</p>
<hr>
<h3 class="h-nomark" id="12-註冊公司內部協作平台">1.2 註冊公司內部協作平台<a class="h-anchor" href="#12-%e8%a8%bb%e5%86%8a%e5%85%ac%e5%8f%b8%e5%85%a7%e9%83%a8%e5%8d%94%e4%bd%9c%e5%b9%b3%e5%8f%b0" aria-label="這個段落的連結"></a></h3>
<p>如果公司內部有<mark>專案管理系統</mark>（ex：Jira、Zentao、Redmine）或是<mark>軟體開發平台</mark>（ex：GitLab），請先幫新人註冊好帳號，並列成清單讓他們知道不同協作平台的功能。</p>
<hr>
<h3 class="h-nomark" id="13-設定內部-server-登入權限">1.3 設定內部 Server 登入權限<a class="h-anchor" href="#13-%e8%a8%ad%e5%ae%9a%e5%85%a7%e9%83%a8-server-%e7%99%bb%e5%85%a5%e6%ac%8a%e9%99%90" aria-label="這個段落的連結"></a></h3>
<p>通常會根據專案的測試進度部署到不同環境：</p>
<ul>
<li><strong>DEV 開發環境</strong><br>工程師內部開發與測試使用，功能穩定性不高，但版本都是最新。</li>
<li><strong>QA 測試環境</strong><br>工程師開發完成，並通過自己 Unit Test 的驗證後，會交給公司的測試團隊在這個環境做各式各樣的測試。</li>
<li><strong>PROD 正式環境</strong><br>測試部門確認功能穩定且通過壓力測試後，就能部署到正式環境。</li>
</ul>
<p>這三個情境會對應到不同的 Server，筆者建議一開始只給新人登入 DEV Server 的權限，等到熟悉環境後再慢慢把 QA、PROD 的操作權限給他；<mark>避免新人在不熟悉環境的狀態下操作，導致不可挽回的後果</mark>。</p>
<blockquote>
<p><strong>備註 1</strong>：建議<code>設定 alias name</code> 來登入 Server，用 ip 登入只要一時眼花手滑就可能導致悲劇。</p>
</blockquote>
<blockquote>
<p><strong>備註 2</strong>：如果公司有<code>導入 CI/CD來做部署，或是由專人負責部署作業</code>，就能大幅降低錯誤操作的可能性（通常 QA、PROD 除了環境變數外，其他設定都是一樣的，以此保證部署的穩定性）。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="14-整理技術相關文件">1.4 整理技術相關文件<a class="h-anchor" href="#14-%e6%95%b4%e7%90%86%e6%8a%80%e8%a1%93%e7%9b%b8%e9%97%9c%e6%96%87%e4%bb%b6" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>Coding Style（程式碼風格）</strong><br>一樣米養百樣人，工程師來自五湖四海自然風格迥異，公司可以使用 ESLint 這類工具來約束風格，但程式碼的效能、長度、分類…很多事情還是需要有前輩跟新人說明。</p>
</li>
<li>
<p><strong>Deploy 的方式</strong><br>如果團隊有導入 CI/CD，你就要跟對方解釋清楚<mark>哪些 branch 在 merge 時會自動 Deploy 到對應的 Server</mark>。</p>
<blockquote>
<p><strong>警告</strong>：如果有設計這種依照 branch 更新做自動部署的功能，建議<mark>將新人的權限設定為 Developer</mark>；避免他推送更新到受保護的 branch。</p>
</blockquote>
</li>
<li>
<p><strong>專案文件</strong><br>大概可以分成：<mark>專案架構文件、API 功能說明文件、過去開發的需求規格</mark>這 3 種；基本上這些文件是為了讓對方快速了解專案的結構，而不是讓他了解每一行程式碼的意義。</p>
</li>
<li>
<p><strong>會用到的相關技能</strong><br>假設公司今天來了一個後端工程師，他過去的工作經驗是用 PHP 開發，但現在公司的專案是用 Node.js 開發；<mark>儘管都是後端工程師，但兩者的技能樹存在一定的差距</mark>。<br>如果知道新人有這種狀況，除了「相關技能」外，我還會準備「基礎知識」的學習連結，讓他可以快速上手，舉例來說：</p>
<ul>
<li><strong>基礎知識</strong>
<ul>
<li>
<p>Node.js 的 iT 邦幫忙鐵人賽系列文。</p>
<blockquote>
<p>由有經驗的人來挑選學習素材，較能分辨誰寫的文章更適合新手入門。</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>相關技能</strong>
<ul>
<li>如何用 POSTMAN 測試 API、切換執行環境、建立 Mock Server，<a href="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/">連結</a>。</li>
<li>API 文件的撰寫格式，以及產生的方式。</li>
<li>團隊 Git Flow 規則。</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Debug 的基礎知識</strong><br>筆者發現有些新人缺乏 Debug 的基礎能力，講解了好幾次他還是跑來問你類似的問題；我相信這個問題不只發生在筆者身上，所以建議大家可以寫了一份文件<mark>說明 Debug 的基礎知識</mark>，內容包含：</p>
<ul>
<li>如何印出錯誤訊息。</li>
<li>設計程式斷點，縮小錯誤範圍。</li>
<li>理解錯誤訊息。</li>
<li>判斷是因為程式邏輯造成的錯誤，還是運行環境導致的問題。</li>
<li>解決問題的方向，可以參考的資源。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="15-整理工作相關文件">1.5 整理工作相關文件<a class="h-anchor" href="#15-%e6%95%b4%e7%90%86%e5%b7%a5%e4%bd%9c%e7%9b%b8%e9%97%9c%e6%96%87%e4%bb%b6" aria-label="這個段落的連結"></a></h2>
<ul>
<li><strong>列出例行公事清單</strong><br>列出團隊、部門、公司的<mark>例會時間點</mark>，並簡述參與人員、討論內容；讓新人日後參與會議時能搞清楚狀況。<br>如果有要繳交週報、月報，會說明<mark>繳交的時間點</mark>並提供撰寫格式。</li>
<li><strong>列出待辦事項</strong><br>新人會協助或接替你的工作，因此你可以先將手中待完成的任務做一個難易度排序，<mark>先將簡單的任務交給新人，觀察他的完成度及適應性</mark>。</li>
<li><strong>提點行政庶務</strong><br>新人剛入職時誰都不認識，印表機不知道怎麼操作、專案管理系統不會用、電腦不會設定開發環境；你可以撰寫「秘笈」讓他了解<mark>遇到問題時可以向哪個窗口提問</mark>。</li>
<li><strong>了解團隊中每個人的職權</strong><br>以一個雲端開發團隊來說，你需要讓新人知道遇到前端、後端、資料庫、伺服器&hellip;的問題時要找誰，<mark>讓他了解團隊每個人的職權</mark>，才能讓新人更快的融入團隊。</li>
<li><strong>保密協議</strong><br>如果公司開發的專案有其機密性，記得提醒新進人員不可洩露專案內容。</li>
</ul>
<hr>
<h2 class="h-nomark" id="2-新人入職後的協助">2. 新人入職後的協助<a class="h-anchor" href="#2-%e6%96%b0%e4%ba%ba%e5%85%a5%e8%81%b7%e5%be%8c%e7%9a%84%e5%8d%94%e5%8a%a9" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>如果沒辦法讓新人成為戰力，那當初根本沒有招募的必要。</p>
</blockquote>
<p>很多人會以<mark>手上工作太多為藉口，不幫忙帶新人以及做教育訓練</mark>，但新人來公司就是為了分擔你過多的工作，<mark>如果不教他怎麼做事，你不是活該自己做到死？</mark></p>
<h3 class="h-nomark" id="21-讓新人對公司有好感">2.1 讓新人對公司有好感<a class="h-anchor" href="#21-%e8%ae%93%e6%96%b0%e4%ba%ba%e5%b0%8d%e5%85%ac%e5%8f%b8%e6%9c%89%e5%a5%bd%e6%84%9f" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>不要讓新人一入職，就感受到沈重的壓力</strong><br>有些公司會準備好一系列的新人教育課程，期待他可以快速上工；但如果這些<mark>課程排得太過密集、要記住的事項太多，坐在台下的新人可能聽到一半就魂遊太虛了</mark>，不要期待新人可以在短短的一天、一週就學會所有事情。</p>
</li>
<li>
<p><strong>與新人建立連結，帶他熟悉周圍還境</strong><br>最輕鬆簡單的方式就是<mark>中午帶新人一起吃飯</mark>，在前往餐廳的路上，可以<mark>向新人介紹周圍的環境</mark>，像是餐廳、停車場、大眾交通工具等。<br>到了餐廳坐下來後，大家可以在較為輕鬆的環境下<mark>再次自我介紹</mark>，可以多聊聊興趣、休閒活動，如果有共同興趣愛好可以更快建立連結。</p>
<blockquote>
<p><strong>備註</strong>：有些人會自己帶便當，或是有午休的習慣；千萬不要把帶新人吃午餐當成 SOP，請先詢問對方的意願，搞不好新人更想自己一個人靜靜的午休，一起吃飯反而給他帶來更大的壓力。</p>
</blockquote>
</li>
<li>
<p><strong>安排資深員工協助新人</strong><br>無論有在完善的教育訓練、交接文件；新人總是會遇到一些「基礎、非專業」的問題，比如：「看不懂群組的特殊用詞、不知道適合向主管匯報的時間、不清楚辦公室的特殊禮節&hellip;」<mark>與其讓新人與公司的文化碰撞，不如安排一個資深員工在初期帶領他</mark>。</p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-了解新人的能力用合適的方式做教育訓練">2.2 了解新人的能力，用合適的方式做教育訓練<a class="h-anchor" href="#22-%e4%ba%86%e8%a7%a3%e6%96%b0%e4%ba%ba%e7%9a%84%e8%83%bd%e5%8a%9b%e7%94%a8%e5%90%88%e9%81%a9%e7%9a%84%e6%96%b9%e5%bc%8f%e5%81%9a%e6%95%99%e8%82%b2%e8%a8%93%e7%b7%b4" aria-label="這個段落的連結"></a></h3>
<p>請依據每個新人的狀況去調整教育訓練的內容，就像應屆畢業生跟有 5 年經驗的工程師；就算同為新人，但你肯定<mark>對這兩個人的能力期待有所不同</mark>，下面是筆者公司過去安排教育訓練的步驟：</p>
<ul>
<li><strong>應屆畢業生</strong>
<ul>
<li><strong>SETP 1：熟悉工作會用到的技術</strong><br>應屆畢業生的技術通常跟工作會用到的技術有一定的差距，因此在入職後的 1~2 個禮拜，我會先讓他熟悉工作會用到的技術。</li>
<li><strong>SETP 2：能使用工作的技術做出簡單的 Side Project</strong><br>在掌握基礎技術後，我會請他們花 2 週的時間寫一個 Side Project，主題不限。<br>主要是藉由這個機會了解他們<mark>程式的撰寫風格，對技術理解的深度</mark>，這樣比較方便評估日後要安排的工作。</li>
<li><strong>SETP 3：實作 Senior 工程師分配的小 Feature</strong><br>大約入職一個月後，才會請 Senior 工程師分配小 Feature 給新人，讓他們真正參與開發。</li>
</ul>
</li>
<li><strong>5 年工作經驗的工程師</strong>
<ul>
<li><strong>SETP 1：有專人一對一帶他了解專案架構</strong><br>我們期待有經驗的工程師可以快速上手成為團隊即戰力，所以會帶他了解整個專案的架構；並期許他可以在 2 週的時間內<mark>對專案有基礎的認知，到達可以開發的地步</mark>。（熟悉的時間會依照專案大小而有所不同）</li>
<li><strong>SETP 2：指派一些簡單的 Feature 觀察實作能力</strong><br>在對專案有基礎的掌握後，就會開始指派一些<mark>不影響系統主功能</mark>的簡單 Feature （ex：開發新功能、維護舊程式）。<br>以此觀察新人對<mark>開發時程的掌控度、程式碼的自我要求、團隊的溝通能力</mark>，確認他的能力是否符合職位要求。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-最終目標是讓新人有獨立作業的能力">2.3 最終目標是讓新人有獨立作業的能力<a class="h-anchor" href="#23-%e6%9c%80%e7%b5%82%e7%9b%ae%e6%a8%99%e6%98%af%e8%ae%93%e6%96%b0%e4%ba%ba%e6%9c%89%e7%8d%a8%e7%ab%8b%e4%bd%9c%e6%a5%ad%e7%9a%84%e8%83%bd%e5%8a%9b" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>用 Code Review 讓新人的程式能力提升</strong><br>在新人完成 Feature 發出 merge request 後，由團隊的資深工程師做 Code Review，告訴新人哪裡可以再做改善；這個步驟的好處有：
<ul>
<li>新人日後寫程式時會特別注意這些問題。</li>
<li>維護專案程式碼的水平。</li>
</ul>
</li>
<li><strong>透過 Scrum 機制讓新人融入團隊</strong><br>儘管新人主要是做被指派的任務，但透過 Scrum 的機制他可以：
<ul>
<li>更清楚專案全貌，知道自己負責的 Feature 能幫上什麼忙。</li>
<li>了解同事各自負責的領域、實作進度。</li>
<li>透過報告工作進度，讓其他同事對自己的能力有更清晰的認知。</li>
</ul>
</li>
<li><strong>讓新人能自己解決問題，不要養成依賴的習慣</strong><br>在前期，有人協助新人可以讓他更快的適應環境；但這個幫助是有限度的，<mark>過度的幫助只會讓人養成依賴的壞習慣</mark>。<br>如果遇到問題找前輩就能解決，那長期下來他就會<mark>放棄思考</mark>；因此在新人結束適應期後（大約 2~3 個月），筆者會有意識的讓新人知道：
<ul>
<li>每個人的工作都很忙，遇到問題要先想辦法靠自己解決。</li>
<li>在我有空的時候，你還是可以問我問題；但我只會給你解題的方向，不會直接告訴你答案。</li>
</ul>
</li>
</ul>
<hr>
<h3 id="後記">後記<a class="h-anchor" href="#%e5%be%8c%e8%a8%98" aria-label="這個段落的連結"></a></h3>
<p>有些人喜歡在新人面前留一手，在教育訓練時故意漏講一些技術重點，害怕新人成長太快超越自己。</p>
<p>如果在 20 年前，這樣做或許能保留身為前輩的尊嚴；但現在時代不同了，即便你不教，網路上也有一堆免費資源供他學習；所以筆者建議不如做個順水人情，在教育訓練時盡心盡力，我相信新人也會對你更加尊敬。</p>
<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/9624a718d3aa">新工程師報到，我該準備哪些東西來加快交接進度呢？(筆者部落格)</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-28/images/fullstack-career-notes-day-28-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>[職場]不放過每個細節，完成一場 0 失誤的專案 Demo！</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-27/</link>
      <pubDate>Tue, 12 Oct 2021 08:20:07 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-27/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>每份專案都是團隊盡心竭力的成果，而 Demo 就是向長官及其他部門展示團隊實力的重要時刻！</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-27/images/cover_hu_ff80f4214fd4881b.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>每份專案都是團隊盡心竭力的成果，而 Demo 就是<mark>向長官及其他部門展示團隊實力的重要時刻！</mark></p>
<p>但如果在 Demo 時發生意外，不但會影響發表的成效，<mark>嚴重點甚至會影響到整個團隊的考績與年終</mark>。</p>
<p>為了避免憾事發生，筆者將在這篇文章講解專案 Demo 時的種種細節與技巧，並分享過往遇到的問題及解決方案，希望大家日後在 Demo 時都可以展現專案最好的一面。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>保證現場 Demo 穩定度，不打沒把握的仗</strong>
<ul>
<li>1.1 Demo 前要盡可能的測試</li>
<li>1.2 同事間的預演</li>
<li>1.3 只用一台電腦做 Demo</li>
<li>1.4 穿著得體、統一</li>
</ul>
</li>
<li><strong>邀請「對」的人，不是「多」的人</strong></li>
<li><strong>選擇合適的會議室</strong>
<ul>
<li>3.1 會議室的座位只能多不能少</li>
<li>3.2 將觀看 Demo 的最佳座位安排給 Key man</li>
<li>3.3 測試現場投影機，並確認網路環境</li>
<li>3.4 讓每個要上台 Demo 的人在講台試講一小段</li>
<li>3.5 預定會議室</li>
</ul>
</li>
<li><strong>在 Demo 過程中要注意的事項</strong>
<ul>
<li>4.1 記得關閉會干擾 Demo 的軟體、網頁</li>
<li>4.2 現場燈光控制</li>
<li>4.3 讓每段 Demo 的時間都在掌控之內</li>
<li>4.4 記得帶筆記本上台</li>
<li>4.5 安排中場休息時間</li>
</ul>
</li>
<li><strong>遠端 Demo 前要確認的清單</strong>
<ul>
<li>5.1 確認雙方的畫面與聲音穩定性</li>
<li>5.2 確認自己的網速是否足夠</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-保證現場-demo-穩定度不打沒把握的仗">1. 保證現場 Demo 穩定度，不打沒把握的仗<a class="h-anchor" href="#1-%e4%bf%9d%e8%ad%89%e7%8f%be%e5%a0%b4-demo-%e7%a9%a9%e5%ae%9a%e5%ba%a6%e4%b8%8d%e6%89%93%e6%b2%92%e6%8a%8a%e6%8f%a1%e7%9a%84%e4%bb%97" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>穩定比表現的突出更重要。</p>
</blockquote>
<h3 class="h-nomark" id="11-demo-前要盡可能的測試">1.1 Demo 前要盡可能的測試<a class="h-anchor" href="#11-demo-%e5%89%8d%e8%a6%81%e7%9b%a1%e5%8f%af%e8%83%bd%e7%9a%84%e6%b8%ac%e8%a9%a6" aria-label="這個段落的連結"></a></h3>
<p>如果在 Demo 現場專案出包，你講的內容再好都無力回天了。</p>
<ul>
<li><strong>尋找可能存在的錯誤</strong><br>就算專案已經通過測試部門的審核，我還是強烈建議<mark>正式 Demo 前要窮盡各種可能來測試專案</mark>；萬一你真的發現錯誤，就算無法及時修正，至少可以避免 Demo 時操作到這個部分。</li>
<li><strong>只 Demo 絕對不會出錯的內容</strong><br><mark>重點：全部都要按造 SOP，絕對不要即興發揮！</mark><br>功能越多的專案，在 Demo 時就越要謹慎；如果你每次操作的路線都不一樣，<mark>萬一你在 Demo 現場把 Bug 測出來就真的完蛋了</mark>。<br>不要認為自己沒有這麼衰，筆者已經親眼見證過好幾次地獄倒霉鬼的誕生了；所以建議：「<mark>乖乖的按照 SOP 來做 Demo，並且至少跑 3 輪，確認專案在這個流程絕對不會發生意外。</mark>」</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-同事間的預演">1.2 同事間的預演<a class="h-anchor" href="#12-%e5%90%8c%e4%ba%8b%e9%96%93%e7%9a%84%e9%a0%90%e6%bc%94" aria-label="這個段落的連結"></a></h3>
<p>有時專案 Demo 會讓同事輪流上台演示自己負責的 Feature；這邊說一下<mark>預演的重要性</mark>：</p>
<ul>
<li>
<p><strong>預估每個人 Demo 所需的時間</strong><br>Demo 都是有時間限制的，如果某個人 Demo 的時間太長，會壓縮到其他人的時間。</p>
<blockquote>
<p>在大約了解每個人 Demo 需要的時間後，也比較好制定議程。</p>
</blockquote>
</li>
<li>
<p><strong>知道彼此會講的內容</strong><br>預演能讓其他同事知道你 Demo 時要講的內容，如果當天不幸發生了什麼變故還能協助支援。</p>
</li>
<li>
<p><strong>微調內容</strong><br>基本上第一次預演會發現很多問題（ex：口條不流暢、內容不連貫&hellip;），需要彼此互相提醒才能在當天有好的表現。</p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-只用一台電腦做-demo">1.3 只用一台電腦做 Demo<a class="h-anchor" href="#13-%e5%8f%aa%e7%94%a8%e4%b8%80%e5%8f%b0%e9%9b%bb%e8%85%a6%e5%81%9a-demo" aria-label="這個段落的連結"></a></h3>
<p><mark>如果需要輪流上台請將資料都存到一台電腦上面</mark>，下面列舉在 Demo 過程中換電腦的缺點：</p>
<ul>
<li><strong>壓縮 Demo 的時間長度</strong><br>換一次電腦在快也要 30 秒，如果有 5 個人要上台就浪費了 2 分 30 秒。</li>
<li><strong>打斷會議流暢程度</strong><br>每換一次電腦，投影幕畫面就會跳掉一次，這容易導致台下觀眾思緒抽離。</li>
<li><strong>未知的風險</strong><br>換電腦可能導致投影幕有色偏、斷訊、閃爍的問題，雖然這不是專案本身的問題；但也容易遭到台下觀眾質疑團隊的專業能力。</li>
</ul>
<hr>
<h3 class="h-nomark" id="14-穿著得體統一">1.4 穿著得體、統一<a class="h-anchor" href="#14-%e7%a9%bf%e8%91%97%e5%be%97%e9%ab%94%e7%b5%b1%e4%b8%80" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>第一印象很重要</strong><br>建議男生襯衫西褲、女生職業套裝；千萬不要因為邋遢的打扮讓自己的專業被扣分。</p>
</li>
<li>
<p><strong>穿著盡量統一</strong><br>如果每個人上台的穿著差異太大，可能<mark>導致觀眾把焦點轉移到人，而非在 Demo 的專案</mark>；所以這塊請事先溝通，最常見的是黑白搭配。</p>
</li>
<li>
<p><strong>不要有異味</strong><br>有些人的汗腺較為發達，在夏天及緊張的環境更容易出汗；筆者建議隨身攜帶香水，讓觀眾在舒服的狀態下參與專案 Demo。</p>
<blockquote>
<p>在空氣不流通的會議室，這個問題更加明顯。</p>
</blockquote>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="2-邀請對的人不是多的人">2. 邀請「對」的人，不是「多」的人<a class="h-anchor" href="#2-%e9%82%80%e8%ab%8b%e5%b0%8d%e7%9a%84%e4%ba%ba%e4%b8%8d%e6%98%af%e5%a4%9a%e7%9a%84%e4%ba%ba" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>事半功倍 VS 事倍功半</p>
</blockquote>
<ul>
<li><strong>重要的長官一定要出席</strong><br><mark>Demo 的日期以 Key man 能參與的時間為主</mark>，如果最關鍵的 Key man 無法出席，你這次的 Demo 可以說有一半以上的意義都沒有了；因為就算有代理人出席，他也只能做到轉述，而轉述的效果就跟你分享電影情節給朋友的感覺差不多。</li>
<li><strong>不要邀請一堆無關的閒雜人等</strong><br>有些會議為了要做排場給長官看，就邀請了一堆跟專案無關的人參與；這樣的做法除了會引起他人的反感外，甚至<mark>會造成台下一堆人在滑手機、做自己的事</mark>。</li>
</ul>
<hr>
<h2 class="h-nomark" id="3-選擇合適的會議室">3. 選擇合適的會議室<a class="h-anchor" href="#3-%e9%81%b8%e6%93%87%e5%90%88%e9%81%a9%e7%9a%84%e6%9c%83%e8%ad%b0%e5%ae%a4" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>如果可以絕對要提前場勘，不然很容易死得不明不白。</p>
</blockquote>
<h3 class="h-nomark" id="31-會議室的座位只能多不能少">3.1 會議室的座位只能多不能少<a class="h-anchor" href="#31-%e6%9c%83%e8%ad%b0%e5%ae%a4%e7%9a%84%e5%ba%a7%e4%bd%8d%e5%8f%aa%e8%83%bd%e5%a4%9a%e4%b8%8d%e8%83%bd%e5%b0%91" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>確認參與會議的實際人數</strong><br>確認 Key man 可以參與的日期與時間後，再邀請與專案相關的人員一起參與會議；<mark>切記會議通知並不是發出去就沒事了，在會議開始的前 2 天，你要再次確認實際參與人數</mark>。</li>
<li><strong>到現場確認有效座位</strong><br>有些會議室的結構比較特殊，<mark>部分的位置是看不到講台的</mark>，這塊在場勘的時候要特別注意。</li>
<li><strong>保留 20% 的座位</strong><br>除了收到會議通知的人會來參加外，有時會有不在你邀請名單的長官或是同事出席；因此建議預留 20% 的空位給臨時參與的人。</li>
</ul>
<hr>
<h3 class="h-nomark" id="32-將觀看-demo-的最佳座位安排給-key-man">3.2 將觀看 Demo 的最佳座位安排給 Key man<a class="h-anchor" href="#32-%e5%b0%87%e8%a7%80%e7%9c%8b-demo-%e7%9a%84%e6%9c%80%e4%bd%b3%e5%ba%a7%e4%bd%8d%e5%ae%89%e6%8e%92%e7%b5%a6-key-man" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>了解哪些座位擁有黃金視角</strong><br>因為大部分的會議室都是平面的，基本上只有前幾排可以看得清楚投影幕與 Demo 成員；你需要在<mark>場勘時就掌握會議室的座位圖，並了解哪些座位擁有黃金視角</mark>。</p>
</li>
<li>
<p><strong>在入口處有專人引導</strong><br>為了保證 Key man 是坐在觀看 Demo 的黃金視角，筆者會建議安排<mark>專人負責引導 Key man 到座位上</mark>。</p>
<blockquote>
<p>也可以放座位名牌避免同仁誤坐。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="33-測試現場投影機並確認網路環境">3.3 測試現場投影機，並確認網路環境<a class="h-anchor" href="#33-%e6%b8%ac%e8%a9%a6%e7%8f%be%e5%a0%b4%e6%8a%95%e5%bd%b1%e6%a9%9f%e4%b8%a6%e7%a2%ba%e8%aa%8d%e7%b6%b2%e8%b7%af%e7%92%b0%e5%a2%83" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>測試投影機注意事項</strong>
<ul>
<li><mark>一定要用當天 Demo 的電腦測試</mark>，確認是否可以順利投影，以及投影的畫面是否有模糊、字體太小、色偏…等問題。</li>
<li>同時記得<mark>確認投影機轉接頭</mark>是 HDMI 還是 RS232，現場是否有提供轉接頭還是要自己準備。</li>
</ul>
</li>
<li><strong>網路環境注意事項</strong>
<ul>
<li>
<p>是否<mark>有穩定的 Wi-Fi 或是網路線連接口</mark>，建議拿當天會用的電腦直接<code>測速</code>會比較安全。</p>
<blockquote>
<p>有些公司的網路只能連接內網，如果你 Demo 會用到外網也須特別注意。</p>
</blockquote>
</li>
<li>
<p>有些會議室的收訊不佳，建議也要<mark>測試自己手機熱點的網速</mark>是否穩定。</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="34-讓每個要上台-demo-的人在講台試講一小段">3.4 讓每個要上台 Demo 的人在講台試講一小段<a class="h-anchor" href="#34-%e8%ae%93%e6%af%8f%e5%80%8b%e8%a6%81%e4%b8%8a%e5%8f%b0-demo-%e7%9a%84%e4%ba%ba%e5%9c%a8%e8%ac%9b%e5%8f%b0%e8%a9%a6%e8%ac%9b%e4%b8%80%e5%b0%8f%e6%ae%b5" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>確認聲音大小</strong><br>每個人說話的聲音大小不同，同一個場地有人需要麥克風有人不需要，所以可以藉由試講來確認。<br>另外<mark>如果場地較大，就算你的音量足夠也建議使用麥克風</mark>，因為很多場地有回音的問題。</li>
<li><strong>熟悉講台的視角與站位</strong><br>如果有機會，建議要先上去熟悉講台的視角，這個動作可以<mark>消除部分恐慌的情緒</mark>。<br>有些 Demo 會需要多人站在講台上，這部分一定要在現場走位過，<mark>不然很容易擋到投影幕以及要 Demo 的產品</mark>。</li>
</ul>
<hr>
<h3 class="h-nomark" id="35-預定會議室">3.5 預定會議室<a class="h-anchor" href="#35-%e9%a0%90%e5%ae%9a%e6%9c%83%e8%ad%b0%e5%ae%a4" aria-label="這個段落的連結"></a></h3>
<p>這邊筆者分享一些定會議室的注意事項：</p>
<ul>
<li><strong>一定要提早訂</strong><br>這種重要會議筆者一般會<mark>提前兩個禮拜預定會議室</mark>，因為公司通常沒有太多合適的大會議室。</li>
<li><strong>確認會議時間沒有與公司例會衝突</strong><br>如果你專案的 Demo 時間與公司例會衝突，有可能<mark>導致部分原本會來的長官缺席</mark>，所以記得把時間排開。</li>
<li><strong>保留緩衝時間</strong><br>除了 Demo 的時間外，<mark>前後都至少要留 1 小時</mark>來做緩衝。
<ul>
<li>如果會議室上一批人剛使用完，可能需要進行場復作業。</li>
<li>提早到可以再次確認與熟悉場地環境。</li>
<li>後面多留一小時，是因為通常專案 Demo 都會 Delay 一點；並且讓會後交流的時間更充裕。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="4-在-demo-過程中要注意的事項">4. 在 Demo 過程中要注意的事項<a class="h-anchor" href="#4-%e5%9c%a8-demo-%e9%81%8e%e7%a8%8b%e4%b8%ad%e8%a6%81%e6%b3%a8%e6%84%8f%e7%9a%84%e4%ba%8b%e9%a0%85" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>這塊 PM 需要適時的去協助</p>
</blockquote>
<h3 class="h-nomark" id="41-記得關閉會干擾-demo-的軟體網頁">4.1 記得關閉會干擾 Demo 的軟體、網頁<a class="h-anchor" href="#41-%e8%a8%98%e5%be%97%e9%97%9c%e9%96%89%e6%9c%83%e5%b9%b2%e6%93%be-demo-%e7%9a%84%e8%bb%9f%e9%ab%94%e7%b6%b2%e9%a0%81" aria-label="這個段落的連結"></a></h3>
<p>如果在 Demo 過程中跳出奇怪的訊息是非常尷尬的，而且訊息不斷跳出的提示音也很吵；<mark>Demo 時千萬不要犯下如此低級的錯誤</mark>。</p>
<p>下面列出幾個常用的的軟體、網頁：</p>
<ul>
<li><strong>通訊軟體</strong><br>LINE、Messager、WeChat&hellip;<br>只要你的好朋友剛好在這個時間點傳送奇怪的訊息，你的個人隱私就會直接顯示在大螢幕上。</li>
<li><strong>郵件</strong><br>我發現很多人都會忘記關閉郵件，筆者之前就曾看過同事 Demo 到一半，螢幕右上角跳出信件通知，<mark>主旨是：「XXX 公司錄取通知」</mark>；當時台下還有總經理，畫面有多尷尬我就不說了。</li>
<li><strong>社群網頁</strong><br>Facebook、LinkedIn、Twitter&hellip;<br>千萬不要忘記關閉社群網頁，他們的提示音也是很吵的。</li>
</ul>
<hr>
<h3 class="h-nomark" id="42-現場燈光控制">4.2 現場燈光控制<a class="h-anchor" href="#42-%e7%8f%be%e5%a0%b4%e7%87%88%e5%85%89%e6%8e%a7%e5%88%b6" aria-label="這個段落的連結"></a></h3>
<p>在 Demo 過程中，你可以透過燈控引導觀眾的注意力，筆者大概分成 3 種：</p>
<ul>
<li><strong>只開講台燈（舞台燈）</strong><br>需要觀眾將<mark>注意力放在講者或是產品身上的時候</mark>，通常是講者剛上台做自我介紹，或是要展示產品的時候。</li>
<li><strong>只開最後一排的燈</strong><br>希望觀眾<mark>仔細聆聽簡報的時候</mark>，在這樣的燈光下，有需要做筆記的人還是可以記錄。<br>筆者不選擇將燈全關是因為：
<ul>
<li>有些人的眼睛在全暗的狀態下看發光物體會有不適感。</li>
<li>如果講者的表達能力不佳，在全暗的環境中台下會睡成一片。</li>
</ul>
</li>
<li><strong>全亮</strong><br>簡報結束<mark>與觀眾互動的環節</mark>，這樣講者與觀眾的互動比較友善。</li>
</ul>
<hr>
<h3 class="h-nomark" id="43-讓每段-demo-的時間都在掌控之內">4.3 讓每段 Demo 的時間都在掌控之內<a class="h-anchor" href="#43-%e8%ae%93%e6%af%8f%e6%ae%b5-demo-%e7%9a%84%e6%99%82%e9%96%93%e9%83%bd%e5%9c%a8%e6%8e%8c%e6%8e%a7%e4%b9%8b%e5%85%a7" aria-label="這個段落的連結"></a></h3>
<p>有些與會者會不停拋出問題或意見來打斷 Demo 的過程，如果沒有阻止，<mark>會導致 Demo 時間脫離掌控</mark>。</p>
<p>這邊筆者建議<mark>引導他們等 Demo 結束後再發問</mark>，比如：「不好意思，我這邊先完成專案的 Demo，讓大家對專案有一個整體的概念；也許接下來的操作就能解釋您心中的疑惑，如果沒有，在 Demo 結束後我再回答您的問題。」</p>
<hr>
<h3 class="h-nomark" id="44-記得帶筆記本上台">4.4 記得帶筆記本上台<a class="h-anchor" href="#44-%e8%a8%98%e5%be%97%e5%b8%b6%e7%ad%86%e8%a8%98%e6%9c%ac%e4%b8%8a%e5%8f%b0" aria-label="這個段落的連結"></a></h3>
<p>通常在 Demo 的過程都是<mark>使用鏡像螢幕</mark>，因此在遇到發問時，<mark>你不方便使用電腦來記錄問題</mark>。</p>
<p>如果你<mark>在講台上用手機來紀錄問題，畫面也不太好看</mark>；所以筆者建議帶一本筆記本上台，好處有：</p>
<ul>
<li>
<p><strong>快速筆記</strong><br>有些人一次會拋出 4、5 個問題，如果沒有筆記本輔助，容易有問題遺漏沒回答到。</p>
</li>
<li>
<p><strong>記錄需求</strong><br>Demo 結束後通常會得到很多反饋，如果沒有筆記本，光憑記憶是很難把這些全部記下來的。</p>
<blockquote>
<p>通常會安排一個同事來做會議記錄，雙重保險。</p>
</blockquote>
</li>
<li>
<p><strong>忘詞提示</strong><br>如果你對今天 Demo 的內容不夠熟悉，或是擔心自己緊張忘詞，你可以在上台前將重點記錄在筆記本上。</p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="45-安排中場休息時間">4.5 安排中場休息時間<a class="h-anchor" href="#45-%e5%ae%89%e6%8e%92%e4%b8%ad%e5%a0%b4%e4%bc%91%e6%81%af%e6%99%82%e9%96%93" aria-label="這個段落的連結"></a></h3>
<p><code>如果議程超過 1.5 小時，絕對要安排休息時間！</code></p>
<p>無論在精彩的 Demo，觀眾的注意力還是有限的；建議 40~50 分鐘就需要休息一下，先讓大家喝杯水，5~10 分鐘後繼續。</p>
<p>這樣可以<mark>讓觀眾整理大腦新接收到的資訊</mark>，而會議主辦方也能利用這個空檔討論下面的<mark>議程是否需要調整</mark>。</p>
<p>如果主辦方沒有安排休息時間，一口氣給他講 2 個小時；相信我，<mark>這絕對是一場失敗的專案 Demo</mark>，因為幾乎沒有人可以維持長時間的注意力，通常這麼做的後果就是觀眾後半段的記憶接近空白。</p>
<hr>
<h2 class="h-nomark" id="5-遠端-demo-前要確認的清單">5. 遠端 Demo 前要確認的清單<a class="h-anchor" href="#5-%e9%81%a0%e7%ab%af-demo-%e5%89%8d%e8%a6%81%e7%a2%ba%e8%aa%8d%e7%9a%84%e6%b8%85%e5%96%ae" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>如果要向國外的客戶 Demo，又或是因為疫情影響需要在家 Demo。</p>
</blockquote>
<h3 class="h-nomark" id="51-確認雙方的畫面與聲音穩定性">5.1 確認雙方的畫面與聲音穩定性<a class="h-anchor" href="#51-%e7%a2%ba%e8%aa%8d%e9%9b%99%e6%96%b9%e7%9a%84%e7%95%ab%e9%9d%a2%e8%88%87%e8%81%b2%e9%9f%b3%e7%a9%a9%e5%ae%9a%e6%80%a7" aria-label="這個段落的連結"></a></h3>
<p>因為是遠端，所以非常吃雙方的網路速度；有時畫面及聲音的延遲會造成溝通上的問題，筆者在正式 Demo 前會用以下方法維護會議品質。</p>
<ul>
<li><strong>主講者自我介紹</strong><br>讓每位主講者先簡單自我介紹，<mark>確認聲音的大小</mark>，避免有些人超大聲有些人超小聲。</li>
<li><strong>只有主講者開麥克風</strong><br>遠端開會時<mark>除了主講者外其他人都要靜音</mark>，不然很容易會有雜訊。</li>
<li><strong>建議購買麥克風，不要靠電腦收音</strong><br>如果有遠端 Demo 的需求，<mark>建議購買專業的麥克風收音</mark>，這樣可以大幅降低周遭環境音（ex：咖啡廳背景音樂、小孩哭鬧聲、鄰居吵架聲…）的干擾。</li>
</ul>
<hr>
<h3 class="h-nomark" id="52-確認自己的網速是否足夠">5.2 確認自己的網速是否足夠<a class="h-anchor" href="#52-%e7%a2%ba%e8%aa%8d%e8%87%aa%e5%b7%b1%e7%9a%84%e7%b6%b2%e9%80%9f%e6%98%af%e5%90%a6%e8%b6%b3%e5%a4%a0" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>使用 Wi-Fi 時注意不要互搶流量</strong><br>在家中通常是連接 Wi-Fi，但如果很多人共用這個 Wi-Fi 可能會導致<mark>Demo 品質不穩定</mark>；如果可以，建議大家在 Demo 時請其他人暫停使用 Wi-Fi。</li>
<li><strong>確認自己的頻寬能否 Demo 需要高網速的頁面</strong><br>在載入圖片、影片資源時，會消耗很多的網路流量，這很可能<mark>造成你畫面卡頓甚至靜止</mark>，因此建議你先找朋友測試看看，以保證當天勤務萬全。</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><mark>Demo 過程發生的意外，對台下的觀眾而言是一種必然，因為他們只會聽一次而已</mark>；請不要讓這些可以控管的風險毀掉團隊努力的成果，每次 Demo 前都按照這份清單檢核一次吧！</p>
<p>如果大家還有遇到什麼狀況想補充的，請在底下留言告訴我，你無私的分享可以減少 Demo 時悲劇的發生～</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/d202f73d485d">專案 Demo 前的準備、細節、技巧(筆者部落格)</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-27/images/fullstack-career-notes-day-27-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-26/</link>
      <pubDate>Mon, 11 Oct 2021 08:51:20 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-26/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>我想很多人跟筆者一樣，從小就被教導謙虛是美德、不要去邀功；相信皇天不負苦心人，默默地做總有一天會被看見的。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-26/images/cover_hu_e1bbfaadd819c209.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>如果不會表達，那麼你的功勞不是你的功勞。</p>
</blockquote>
<p>我想很多人跟筆者一樣，從小就被教導謙虛是美德、不要去邀功；相信皇天不負苦心人，默默地做總有一天會被看見的。</p>
<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 你完成的 Feature 重要嗎？</li>
<li>1.3 主管分配任務給你，跟你主動去要任務是不同的</li>
<li>1.4 如果公司願意把一個高難度的專案交給你</li>
</ul>
</li>
<li><strong>讓自己的努力被看到</strong>
<ul>
<li>2.1 即時匯報進度</li>
<li>2.2 不只完成專案，還要宣傳專案</li>
<li>2.3 專案結束後，要主動去拿獎勵</li>
</ul>
</li>
<li><strong>提升在團隊內的影響力</strong>
<ul>
<li>3.1 主動幫助同事</li>
<li>3.2 讓團隊成員有更好的獨立作業能力</li>
<li>3.3 不要只想著表現自己，分功勞未必是個零和遊戲</li>
</ul>
</li>
<li><strong>增加在公司的影響力</strong>
<ul>
<li>4.1 開會時，你代表的是部門</li>
<li>4.2 如何在沒有權力的狀態下創造影響力</li>
<li>4.3 把握上台機會</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-做重要的事">1. 做重要的事<a class="h-anchor" href="#1-%e5%81%9a%e9%87%8d%e8%a6%81%e7%9a%84%e4%ba%8b" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>花一樣的力氣做事，當然選效益最高的。</p>
</blockquote>
<p>很多人天天加班，覺得自己為公司賣命應該要獲得對應的報酬；但這一切往往事與願違。</p>
<p>很多人都會把矛頭指向「個人能力」或是「慣老闆」身上；儘管這兩點也是原因之一，但筆者想從「做事」的角度跟讀者分享，<mark>也許我們過去所認為的努力，對公司來說毫無意義</mark>。</p>
<h3 class="h-nomark" id="11-了解公司目標">1.1 了解公司目標<a class="h-anchor" href="#11-%e4%ba%86%e8%a7%a3%e5%85%ac%e5%8f%b8%e7%9b%ae%e6%a8%99" aria-label="這個段落的連結"></a></h3>
<p>有些人埋頭苦幹做了一堆專案，認為自己績效斐然值得嘉獎；但<mark>是否值得嘉獎是由專案帶來的效益所決定的</mark>。</p>
<p>你的<mark>專案要跟公司的商業目標一致</mark>，不然你做的專案再多、再好，公司裡面沒有業務幫你推，你再怎麼加班也是白忙。</p>
<p>相反的，如果你做的專案<mark>可以讓業務更輕鬆的銷售產品，提升公司業績</mark>；就算你只做一個專案，也能讓你在公司有更高的地位。</p>
<hr>
<h3 class="h-nomark" id="12-你完成的-feature-重要嗎">1.2 你完成的 Feature 重要嗎？<a class="h-anchor" href="#12-%e4%bd%a0%e5%ae%8c%e6%88%90%e7%9a%84-feature-%e9%87%8d%e8%a6%81%e5%97%8e" aria-label="這個段落的連結"></a></h3>
<p>也許你的技術很頂尖，特別喜歡攻克專案裡面困難的 Feature；但幾年之後卻發現<mark>周圍技術能力普普的同事都升官加薪，而自己卻還在原地打轉</mark>。</p>
<p>此時請不要抱怨職場不公平，你應該要先審視<mark>自己完成的 Feature 為公司或是團隊帶來多少利益</mark>；無論你攻克的 Feature 有多難，如果它只有 1% 的使用者會用到，從公司的角度而言你是沒有多少影響力的。</p>
<p>反觀你周圍技術普普的同事，他們用框架快速完成系統的主功能，又用簡單的技術完成了很多實用的 Feature；<mark>即便難度不高，但從公司的角度來看，是這些人撐起了系統的運轉</mark>。</p>
<p>如果你是老闆，在升職加薪的時候，你會選擇誰？<mark>這是一個資本主義的社會，並不是你選擇困難的道路就會獲得更多回報</mark>。</p>
<hr>
<h3 class="h-nomark" id="13-主管分配任務給你跟你主動要到任務是不同的">1.3 主管分配任務給你，跟你主動要到任務是不同的<a class="h-anchor" href="#13-%e4%b8%bb%e7%ae%a1%e5%88%86%e9%85%8d%e4%bb%bb%e5%8b%99%e7%b5%a6%e4%bd%a0%e8%b7%9f%e4%bd%a0%e4%b8%bb%e5%8b%95%e8%a6%81%e5%88%b0%e4%bb%bb%e5%8b%99%e6%98%af%e4%b8%8d%e5%90%8c%e7%9a%84" aria-label="這個段落的連結"></a></h3>
<p>如果你不主動爭取，通常<mark>最後被分到的都是低價值任務</mark>；要嘛難度很高，要嘛毫無價值。</p>
<ul>
<li><strong>為什麼要主動爭取任務？</strong><br>每個人的時間都是有限的，如果你長期接手低價值任務，你與周圍同事的差距會越來越大；<mark>大部分的人上班都是為了錢，為什麼不去主動爭取高價值任務？</mark><br>就算任務的內容完全一樣，但你完成被分配任務只能說是<code>合格</code>；而你完成主動要到任務卻是<mark>達成目標</mark>，被動跟主動給人<mark>完全不同的觀感</mark>。</li>
<li><strong>如果始終無法獲得高價值任務</strong><br>如果主管明顯特別照顧某個同事，即使你主動要求也無法獲得高價值任務；筆者建議<mark>直接跟主管敞開了聊，不要在那邊用猜測的方式揣摩上意</mark>。<br>假使聊完後你還是被分配低價值任務，那你也可以心無罣礙的離開；<mark>人生很短，不需要堅持在不合適的環境中成長</mark>。</li>
</ul>
<hr>
<h3 class="h-nomark" id="14-如果公司把一個高難度的專案交給你">1.4 如果公司把一個高難度的專案交給你<a class="h-anchor" href="#14-%e5%a6%82%e6%9e%9c%e5%85%ac%e5%8f%b8%e6%8a%8a%e4%b8%80%e5%80%8b%e9%ab%98%e9%9b%a3%e5%ba%a6%e7%9a%84%e5%b0%88%e6%a1%88%e4%ba%a4%e7%b5%a6%e4%bd%a0" aria-label="這個段落的連結"></a></h3>
<p>先判斷這個高難度的專案能否<mark>給你帶來成長</mark>，再去思考這個專案<mark>對公司而言的重要程度</mark>。</p>
<p>如果評估後是有價值的，請勇敢的接下來；<mark>有價值的任務，你不接還是會有人接的</mark>。</p>
<p>也不要過度擔心自己做不來失敗的後果，<mark>老實說就算真的失敗了，損失最大的是公司</mark>，你最慘就是換一間公司；既然公司願意冒這個風險把任務交給你，那就放手一搏吧。</p>
<p>如果你不打算庸庸碌碌過一生，機會來的時候就必須冒險；如果你把握住了，<mark>它們是升職加薪非常重要的談判籌碼</mark>。</p>
<blockquote>
<p>突破舒適圈未必會獲得物質上的獎勵，但肯定能讓你的視野上升一個台階。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="2-讓自己的努力被看到">2. 讓自己的努力被看到<a class="h-anchor" href="#2-%e8%ae%93%e8%87%aa%e5%b7%b1%e7%9a%84%e5%8a%aa%e5%8a%9b%e8%a2%ab%e7%9c%8b%e5%88%b0" aria-label="這個段落的連結"></a></h2>
<p>大部分工程師的思考都是線性的，就像是遊戲關卡一樣，我們覺得完成了手上的專案就會獲得回報；<mark>但現實生活不是遊戲</mark>，絕大多數的時候你完成專案只會被當作理所當然；<mark>你不說，沒人會知道你付出了多少心血</mark>。</p>
<h3 class="h-nomark" id="21-即時匯報進度">2.1 即時匯報進度<a class="h-anchor" href="#21-%e5%8d%b3%e6%99%82%e5%8c%af%e5%a0%b1%e9%80%b2%e5%ba%a6" aria-label="這個段落的連結"></a></h3>
<p>每做到一個里程碑，你一定要即時匯報進度，不要總是等別人來問你。</p>
<ul>
<li><strong>為什麼要匯報進度？</strong>
<ul>
<li><strong>讓主管方便評估時程</strong><br>職場與學校不同，學校有交功課就完事了；但職場會有 30% 進度、70％ 進度，<mark>你要讓主管掌握你的進度，他才能評估要安排多少任務給你做</mark>。</li>
<li><strong>讓同事掌握進度</strong><br>有些合作的任務是有順序性的，<mark>你完成了如果不吭聲，很可能壓縮到後面接手同事的工作時間</mark>。<br>像是做網頁時，如果後端完成後拖到最後一刻才跟前端說，就會壓縮到前端串接後端 API 及確認功能穩定性的時間。</li>
</ul>
</li>
<li><strong>匯報的內容要有哪些？</strong>
<ul>
<li><strong>目前進度</strong><br>說明你的進度是按照預想的、超前的，還是落後的。</li>
<li><strong>這次你做了什麼</strong><br>要<mark>強調最終的結果</mark>，而不是努力的過程；大家時間有限，不要模糊焦點。</li>
<li><strong>接下來的任務</strong><br>說明接下來任務的時程規劃。</li>
</ul>
</li>
<li><strong>主動匯報進度的好處</strong>
<ul>
<li>
<p><strong>讓你擁有安排進度的主控權</strong><br>如果你每次都能按時完成任務，那主管在問你需要多少時間完成任務時，<mark>你報出來的工作天數通常都會被接受</mark>。</p>
<blockquote>
<p>假使你常常無法準時完成，或是浮報太多工作天數；這些破壞合作信用的行為，會換來主管每天緊迫盯人。</p>
</blockquote>
</li>
<li>
<p><strong>讓自己的存在感強烈</strong><br>公司人太多，就算是直屬主管也未必知道你所做的事情；不要等到加薪前才刷存在感，只要有成果便要匯報，<mark>讓主管覺得你持續有產出</mark>。</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-不只完成專案還要宣傳專案">2.2 不只完成專案，還要宣傳專案<a class="h-anchor" href="#22-%e4%b8%8d%e5%8f%aa%e5%ae%8c%e6%88%90%e5%b0%88%e6%a1%88%e9%82%84%e8%a6%81%e5%ae%a3%e5%82%b3%e5%b0%88%e6%a1%88" aria-label="這個段落的連結"></a></h3>
<p>筆者身旁有朋友明明是<mark>專案的主力開發工程師</mark>，但在專案取得成功後<mark>升官拿獎金的都不是他</mark>；一開始他在跟我抱怨的時候，我覺得這應該是公司制度的問題，但每隔幾個月他就會約我出來借酒消愁痛罵公司。</p>
<p>幾次之後我也感到很納悶，以我對朋友的了解，他的技術能力肯定沒問題，不然不會每次都擔任專案的主力開發工程師；<mark>但為什麼他都沒有得到應得的報酬</mark>？</p>
<p>後來跟朋友細聊專案流程才發現，<mark>儘管程式大部分都是他寫的；但在專案完成後都是由主管或同事向高層匯報</mark>，向業務做 Demo 的時候他也只是坐在台下讓同事講，因為他認為自己負責開發已經夠辛苦了，這些雜事交給其他同事處理就好。</p>
<p>原本這種各司其職的想法也沒錯，但是有些公司<mark>只看得到在做宣傳的人，看不到在做事的人</mark>；如果你主管匯報時沒有向高層提及你的功勞、同事在 Demo 時沒有放上你的名字，那你所有的努力都是在做白工。</p>
<p>寫專案固然辛苦，但這個專案是否能帶來收益，要看公司是否採納，業務是否願意推廣；<mark>公司的專案那麼多，如果不主動宣傳自己的專案，就算做得再好也沒有意義</mark>，因此下次完成專案後，請爭取宣傳專案的機會。</p>
<hr>
<h3 class="h-nomark" id="23-專案結束後要主動去拿獎勵">2.3 專案結束後，要主動去拿獎勵<a class="h-anchor" href="#23-%e5%b0%88%e6%a1%88%e7%b5%90%e6%9d%9f%e5%be%8c%e8%a6%81%e4%b8%bb%e5%8b%95%e5%8e%bb%e6%8b%bf%e7%8d%8e%e5%8b%b5" aria-label="這個段落的連結"></a></h3>
<p>不管你的程式寫得再好、功能多麼穩定；你在主管眼中只是一個合格的工程師而已，況且主管未必會去看你寫的 Code。</p>
<ul>
<li><strong>把自己跟專案的成功綁在一起</strong><br>一個人的成功，是需要其他同事的肯定、主管的提拔；如果你<mark>沒有適時向他們展示你的成果，那你的努力也會隨著專案結束一起消失</mark>；至於要如何表現自己，可以參考以下方案：
<ul>
<li><strong>技術分享會</strong><br>如果你在這個專案中用了新的技術，你可以舉辦一個技術分享會向同事以及長官分享。
<ul>
<li>這個技術的優點</li>
<li>它解決了哪些問題</li>
<li>你遇到問題有多難，如何克服</li>
<li>它幫公司賺了/節省多少錢</li>
</ul>
</li>
<li><strong>提醒主管不要忘記你的好處</strong><br>跟主管聊天時，主動提起自己自己的貢獻與影響力；<mark>千萬不要怕邀功，你不說，就算別人都看在眼裡也不會給你應得的獎勵</mark>。</li>
<li><strong>爭取向高層匯報、向業務簡報的機會</strong><br>如同上個小節所說，專案做完後是需要宣傳的，<mark>如果你獲得宣傳者的角色，並成功讓高層與業務採納，你將是這個專案最大的受益者</mark>。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="3-提升在團隊內的影響力">3. 提升在團隊內的影響力<a class="h-anchor" href="#3-%e6%8f%90%e5%8d%87%e5%9c%a8%e5%9c%98%e9%9a%8a%e5%85%a7%e7%9a%84%e5%bd%b1%e9%9f%bf%e5%8a%9b" aria-label="這個段落的連結"></a></h2>
<p>你有可能因為工作績效而獲得加薪；但如果想要升官，跟同事打好關係、提升團隊績效也是重要的條件之一。</p>
<blockquote>
<p>拍馬屁、玩政治、當聽話的狗也是有效的升官的方式；但這些實際操作起來非常依賴個人特質，並不適用於每個人，因此本文不討論這塊。</p>
</blockquote>
<h3 class="h-nomark" id="31-主動幫助同事">3.1 主動幫助同事<a class="h-anchor" href="#31-%e4%b8%bb%e5%8b%95%e5%b9%ab%e5%8a%a9%e5%90%8c%e4%ba%8b" aria-label="這個段落的連結"></a></h3>
<p>公司要提拔一個人，除了專業能力的評估外，也會看他跟同事相處如何；<mark>你總不會想提拔一個經常跟周圍同事鬧不合的獨行俠吧？</mark></p>
<p>如果你所處的組織比較龐大，主管並不會知道你每天在做什麼，但周圍跟你合作的同事都很清楚；當有合適的缺空出來，而<mark>主管無法準確判斷要將誰升官時，就會詢問同事日常跟誰合作較為愉快</mark>；所以是否能被提拔，周圍同事的評分也是重要的參考意見。</p>
<hr>
<h3 class="h-nomark" id="32-讓團隊成員有更好的獨立作業能力">3.2 讓團隊成員有更好的獨立作業能力<a class="h-anchor" href="#32-%e8%ae%93%e5%9c%98%e9%9a%8a%e6%88%90%e5%93%a1%e6%9c%89%e6%9b%b4%e5%a5%bd%e7%9a%84%e7%8d%a8%e7%ab%8b%e4%bd%9c%e6%a5%ad%e8%83%bd%e5%8a%9b" aria-label="這個段落的連結"></a></h3>
<p>應該很多由技術轉管理職的新手 Leader 都面臨過以下問題：</p>
<ul>
<li>把任務指派下去後，成員不停再追問細節。</li>
<li>成員把任務做到一半就停下來了，他們不知道接下來要做什麼。</li>
</ul>
<p>遇到這類狀況時，有時不禁會想：「這些問題這麼簡單，為什麼這點小事都要我來做決定？你們沒有基礎的判斷能力嗎？」</p>
<p>除了成員經驗不足容易有這些問題外，也有可能是你在分配任務時<mark>只說有哪些任務要做，而沒說明為什麼這麼做、這個任務的目標是什麼</mark>；如果願意花多一點的時間說明與撰寫完善的需求規格，<mark>成員在了解來龍去脈後，很多細節與流程他們就能夠自己決定了</mark>。</p>
<hr>
<h3 class="h-nomark" id="33-不要只想著表現自己分功勞未必是個零和遊戲">3.3 不要只想著表現自己，分功勞未必是個零和遊戲<a class="h-anchor" href="#33-%e4%b8%8d%e8%a6%81%e5%8f%aa%e6%83%b3%e8%91%97%e8%a1%a8%e7%8f%be%e8%87%aa%e5%b7%b1%e5%88%86%e5%8a%9f%e5%8b%9e%e6%9c%aa%e5%bf%85%e6%98%af%e5%80%8b%e9%9b%b6%e5%92%8c%e9%81%8a%e6%88%b2" aria-label="這個段落的連結"></a></h3>
<p>從短期來看，在團隊中搶功勞的人可以快速升職加薪；但同事也不是傻子，<mark>有誰願意跟愛搶功勞的人合作？</mark></p>
<p>如果你願意在<mark>彰顯自己功勞的同時也把其他同事帶上</mark>，你在同事裡面會獲得更多的聲望；功勞就像是一塊蛋糕，但分功勞也未必只是一個零和遊戲，<mark>把蛋糕做大，所有人都能獲得回報</mark>。</p>
<p>每個人的性格都不同，有些人在做事上面是一把手，但如果你要他上台跟要了他的命一樣；因此這類有生產力的人容易被組織所忽略，假使你願意在<mark>專案宣傳的時候，於簡報的最後一頁放上團隊名單</mark>，並說出感謝的話，也許你會獲得意想不到的回報。</p>
<p>筆者感謝結語範例：「很榮幸今天可以跟各位長官分享我們團隊完成的專案，在這裡要先感謝 XXX 經理在這段時間四處奔波，在他的領導下專案才能進行的如此順利；在開發的過程中，XXX 資深工程師也花了很多心力在攻克幾項技術難題，以保證我們系統的穩定性&hellip;謝謝大家，我是 XXX，再次感謝各位長官的蒞臨。」</p>
<ul>
<li><strong>感謝結語建議結構</strong>
<ul>
<li>先感謝主管，稱讚領導有方。</li>
<li>接著提到專案中的靈魂人物。</li>
<li>最後再說一次自己的名字，增加長官們對你的印象。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="4-增加在公司的影響力">4. 增加在公司的影響力<a class="h-anchor" href="#4-%e5%a2%9e%e5%8a%a0%e5%9c%a8%e5%85%ac%e5%8f%b8%e7%9a%84%e5%bd%b1%e9%9f%bf%e5%8a%9b" aria-label="這個段落的連結"></a></h2>
<p>讓公司更多人記住你的名字、知道你在做的專案；之後在跨團隊合作的時候會輕鬆許多。</p>
<h3 class="h-nomark" id="41-開會時你代表的是部門">4.1 開會時，你代表的是部門<a class="h-anchor" href="#41-%e9%96%8b%e6%9c%83%e6%99%82%e4%bd%a0%e4%bb%a3%e8%a1%a8%e7%9a%84%e6%98%af%e9%83%a8%e9%96%80" aria-label="這個段落的連結"></a></h3>
<p>組織一大，辦公室政治幾乎無法避免；<mark>每個部門都想要爭取話語權與資源</mark>。</p>
<p>如果你只需要面對工程師團隊，大部分的時候還可以就事論事；但如果你的位置會需要<mark>接觸業務單位</mark>，面對這群非常擅長<del>嘴砲</del>溝通的人，<mark>工程師在開會的時候非常容易處於被動狀態</mark>。畢竟業務單位每天的工作就是溝通與談判，雙方的經驗值差距太大。</p>
<p>但請不要忘記，<mark>出來開會你代表的是部門</mark>；假設今天這場會議其中一個環節是由你來做專案的 Demo，你就要有被業務單位 Challenge 的心理預期，對方可能在你 Demo 到一半的時候就直接打斷你、對專案指手畫腳。此時筆者建議你要<mark>掌握會議的主導權</mark>，如果你在 Demo 時頻繁回答對方的提問，會造成其他與會者無法連貫吸收專案內容。因此筆者會說：「不好意思，我這邊先完成專案的 Demo，讓大家對專案有一個整體的概念；也許接下來的操作就能解釋您心中的疑惑，如果沒有，在 Demo 結束後我再回答您的問題。」</p>
<p>在 Demo 結束時，往往會收到很多的回饋；<mark>你可以記錄需求，但千萬不要隨便承諾</mark>，你在開會所說的話都是代表部門，<mark>如果沒有經過內部的討論就亂給承諾，你在團隊內部會被排擠，在其他團隊也會失去信任</mark>。</p>
<p>話語的主導權並非看一兩本書就能夠學會，需要經過多場實戰才能有所提升；筆者認為開會時的心態最為重要，不要一被質疑氣勢就落入下風，謹記<mark>出來開會，自己是代表部門！</mark>你希望別人怎麼看待自己的部門，你就要表現出什麼態度。</p>
<blockquote>
<p><strong>備註</strong>：你要搞清楚自己代表的角色，是公司、部門、團隊還是自己；代表不同角色時，說話的份量差異會很大，因為對方還需要顧及你身後的人。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="42-如何在沒有權力的狀態下創造影響力">4.2 如何在沒有權力的狀態下創造影響力<a class="h-anchor" href="#42-%e5%a6%82%e4%bd%95%e5%9c%a8%e6%b2%92%e6%9c%89%e6%ac%8a%e5%8a%9b%e7%9a%84%e7%8b%80%e6%85%8b%e4%b8%8b%e5%89%b5%e9%80%a0%e5%bd%b1%e9%9f%bf%e5%8a%9b" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>每個人都曾在沒有權力的狀態下影響過別人的決定</strong><br>回想進入公司前的<code>面試</code>，你要在短短 1 小時的時間內取得陌生人的肯定，最終獲得 Offer；<mark>此時你在這間公司是毫無權力的，卻能夠影響長官的決定</mark>。</p>
</li>
<li>
<p><strong>你成為了主管，但並沒有對其他部門發布命令的權力</strong><br>在成為主管後，你能夠組織團隊成員做事；但很多時候<mark>一個專案需要跟多個部門合作</mark>，你可能會接觸到：「市場部門、行銷部門、品管部門&hellip;」而這些部門你都是沒有權力下達命令的。<br>對你來說是主任務，但對其他部門可能而言可能只是次要任務；<mark>你需要創造一個讓對方獲利的理由</mark>，他才有可能配合你，共贏的完成這個專案。</p>
</li>
<li>
<p><strong>有影響力的是人，不是強而有力的數據</strong><br>假設你收到公司指令，要負責推行一個專案管理系統，通常情況下，<mark>除了自己的部門外，其他部門在推的時候會遇到非常大的阻礙</mark>。</p>
<blockquote>
<p>很多人會覺得這是公司高層下的指令，各部門應該會配合；但以筆者的經驗來說，願意在初期配合的幾乎不存在&hellip;</p>
</blockquote>
<ul>
<li>
<p><strong>不要用產品說服對方</strong><br>為了要增加使用人數，筆者曾像推銷員一樣到各個部門說這個產品有多好用、幫自己部門提升了多少工作效率；但成效很差，因為大家現在事情都做得好好的，根本<mark>沒理由去學一個要重新熟悉的產品</mark>。</p>
</li>
<li>
<p><strong>由對方創造需求</strong><br>後來筆者在推這個專案管理系統前，改成先用聊天的方式跟其他的部門的 Leader 了解：</p>
<ul>
<li>部門現在面對的挑戰是什麼？</li>
<li>未來 1~3 年的戰略目標</li>
</ul>
<p>先用同理心去傾聽對方的回答，適時地引導對方來詢問你有什麼好的建議；此時你再說出自己部門目前有在用這套專案管理系統，且透過它提升了不少工作效率，如果對方想更近一步了解再跟他做介紹；談話重點就是<mark>先用問題引導對方去思考，而不是直接告訴對方你覺得這樣做更有效</mark>。</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="43-爭取上台機會">4.3 爭取上台機會<a class="h-anchor" href="#43-%e7%88%ad%e5%8f%96%e4%b8%8a%e5%8f%b0%e6%a9%9f%e6%9c%83" aria-label="這個段落的連結"></a></h3>
<p>害怕上台是人性，但當你走上台從聽眾變成講者時，你所擁有的是更多的機會。</p>
<ul>
<li>
<p><strong>增加自己在公司的存在感</strong><br>很多工程師對上台報告很抗拒，害怕自己報告的不好會拖團隊的後腿；但這樣長久下來，你在組織中會變成一個<code>隱形人</code>，幾乎沒有影響力；不過如果你願意上台報告，<mark>就算你表現得不夠好，至少也讓公司更多的人記住你</mark>。</p>
<blockquote>
<p>很多時候，你比自己想像的還要更厲害。</p>
</blockquote>
</li>
<li>
<p><strong>從觀眾的角度出發，準備對方感興趣的內容</strong><br>有些人站到台上後，會瘋狂的向台下輸出自己的專案有多好、產品的功能有多強；<mark>台上講的激情四射，但台下睡眼朦朧</mark>，會發生這種狀況是因為你一直在講你想講的，而不是台下觀眾想聽的。<br>以筆者要跟業務單位宣傳團隊的專案來做舉例，我會重點描述下面 3 點：</p>
<ul>
<li>專案能幫客戶解決哪些問題</li>
<li>這個專案預計的定價與目標市場</li>
<li>業務要如何向客戶介紹我們的專案</li>
</ul>
<p>想要讓業務願意推銷團隊的專案，筆者認為重點是<mark>產品有用、利潤足夠、介紹輕鬆</mark>；把一條龍的服務做好，他們要做的事越少，專案推得越好。</p>
</li>
</ul>
<hr>
<h3 id="後記">後記<a class="h-anchor" href="#%e5%be%8c%e8%a8%98" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>你想要當參賽者還是觀眾？</p>
</blockquote>
<p>如果你想要在一間公司往上爬，無論人際關係處理得多好，都肯定會受到其他人的嫉妒；公司中<mark>想升官的絕對不只你一個</mark>，如果決定進入這場鬥爭，你就要有受傷的心理準備。</p>
<p>當參賽者還是觀眾都是自己的選擇，每個人在不同人生階段所能承受的壓力不同、職涯規劃也不同；並<mark>不存在選擇哪一個一定比較好</mark>，筆者只是建議你選一個 10 年之後自己不會後悔的選擇。</p>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-26/images/fullstack-career-notes-day-26-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-25/</link>
      <pubDate>Sun, 10 Oct 2021 13:35:59 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-25/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>這邊筆者依照類型統整了經常遇到的經典面試題，並附上筆者認為回答時要注意的重點與忌諱。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-25/images/cover_hu_d659632717e52c62.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>每個人的性格不同，不要去追求所謂的完美解答；而是去尋找適合自己的環境。</p>
</blockquote>
<p>這邊筆者依照類型統整了經常遇到的經典面試題，並附上筆者認為回答時要注意的<code>重點</code>與<code>忌諱</code>。</p>
<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>
<li>2.5 你有無法合作的人嗎？</li>
</ul>
</li>
<li><strong>薪資</strong>
<ul>
<li>3.1 你的期待薪資是？</li>
<li>3.2 為什麼你覺得自己值得這份薪水？</li>
<li>3.3 你的期待薪資與現在的薪水相比漲幅有點太大</li>
</ul>
</li>
<li><strong>自我成長</strong>
<ul>
<li>4.1 平常會怎麼精進自己的能力？</li>
<li>4.2 會不會覺得自己學得太雜了？</li>
</ul>
</li>
<li><strong>未來規劃</strong>
<ul>
<li>5.1 你在未來 3~5 年有什麼樣的職涯規劃</li>
<li>5.2 你進入公司後會想得到或學到什麼？</li>
</ul>
</li>
<li><strong>其他</strong>
<ul>
<li>6.1 有沒有應徵其他公司？</li>
<li>6.2 說說看自己的優點</li>
<li>6.3 談談自己的缺點</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-離職原因">1. 離職原因<a class="h-anchor" href="#1-%e9%9b%a2%e8%81%b7%e5%8e%9f%e5%9b%a0" aria-label="這個段落的連結"></a></h2>
<p>不管對前公司抱有多少不滿，請不要在面試時由你口中表達出來。</p>
<p>就算遇到面試官在批評你的前公司：「聽說你前公司高層的管理作風很有問題，朝令夕改，決策錯誤也不願意承認。」</p>
<p>筆者也<mark>強烈建議你不要附和</mark>，婉轉的帶過就好：「每個人都有自己的管理風格，我也是從前公司學到很多東西。」</p>
<h3 class="h-nomark" id="11-為什麼想換工作">1.1 為什麼想換工作？<a class="h-anchor" href="#11-%e7%82%ba%e4%bb%80%e9%ba%bc%e6%83%b3%e6%8f%9b%e5%b7%a5%e4%bd%9c" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：要說出具體的原因，跟面試的公司有關聯性更好</mark></p>
<ul>
<li><strong>單打獨鬥轉為團隊合作</strong><br>我現在這份工作是專案外包公司，大部分的專案都是單打獨鬥，這讓我累積了非常多的實戰經驗；但也讓我意識到儘管自己可以完成每項任務，但我對後端開發這塊更有心得，我希望可以在自己擅長的領域做的更加出色，並期待與<mark>優秀的團隊</mark>一同打造出<mark>受到市場認可的產品</mark>。</li>
<li><strong>完成階段性任務，想突破舒適圈</strong><br>在 3 個月前，我已經完成公司期待的產品；現在產品的結構趨於穩定，只剩下一些細節需要擴充與維護。<br>我希望趁自己年輕的時候多嘗試不同的領域，來這裡面試前我有<mark>先做功課</mark>，了解貴公司是一個<mark>年輕有衝勁的團隊</mark>，是我所嚮往的工作環境。</li>
</ul>
<blockquote>
<p>筆者會用「生涯規劃」做開頭，然後再把話題轉移到面試公司身上。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="12-什麼情況下會想離職">1.2 什麼情況下會想離職？<a class="h-anchor" href="#12-%e4%bb%80%e9%ba%bc%e6%83%85%e6%b3%81%e4%b8%8b%e6%9c%83%e6%83%b3%e9%9b%a2%e8%81%b7" aria-label="這個段落的連結"></a></h3>
<p><mark>忌諱：盡量避免批評前公司</mark></p>
<p>請用委婉的方式<mark>說出真實理由</mark>，也許會失去一個 Offer；但至少避開了自己的雷區。</p>
<ul>
<li><strong>辦公室政治</strong><br>我希望公司的成員都是團結一心，努力想把產品做好；我無法接受好的產品因為派系鬥爭而被犧牲。</li>
<li><strong>常態加班</strong><br>如果有緊急狀況我可以配合公司加班，但我無法接受常態加班；我希望自己可以在工作與生活間取得平衡。</li>
<li><strong>加班沒有加班費</strong><br>員工加班是為了幫公司解決問題，如果公司沒有支付這塊的酬勞；除了違反勞基法外，辦公室的氣氛也會非常低迷，我無法在這樣的環境下工作。</li>
</ul>
<p>因為<mark>說真話失去的工作機會不需要太留戀</mark>，假設你就是因為無法接受每天加班 3 小時才離職；你在面試時又不提出自己無法接受常態加班，搞不好這間公司每天要加班 6 小時。你不說出自己無法接受的點，進去後崩潰能怪誰？</p>
<blockquote>
<p><strong>備註</strong>：勞方與資方的思維模式差異極大，如果你今天直接說是因為薪水凍漲、加班沒錢、升遷遇阻而想離職；面試官就會認為是你能力不足、團隊溝通有問題、沒做好向上管理；<mark>會將原因歸咎在求職者而非公司，所以請模仿上面的方式換句話說。</mark></p>
</blockquote>
<hr>
<h2 class="h-nomark" id="2-工作環境">2. 工作環境<a class="h-anchor" href="#2-%e5%b7%a5%e4%bd%9c%e7%92%b0%e5%a2%83" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-聊聊自己期待的工作地點內容氛圍並做個排序">2.1 聊聊自己期待的工作地點、內容、氛圍，並做個排序<a class="h-anchor" href="#21-%e8%81%8a%e8%81%8a%e8%87%aa%e5%b7%b1%e6%9c%9f%e5%be%85%e7%9a%84%e5%b7%a5%e4%bd%9c%e5%9c%b0%e9%bb%9e%e5%85%a7%e5%ae%b9%e6%b0%9b%e5%9c%8d%e4%b8%a6%e5%81%9a%e5%80%8b%e6%8e%92%e5%ba%8f" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>工作地點</strong><br>我希望工作地點的通勤時間不超過 30 分鐘，因為通勤時間太長會壓縮到睡眠時間；進而導致上班的精神狀況不佳。</li>
<li><strong>工作內容</strong><br>在工作內容方面，我期待有挑戰性的專案，即使是沒接觸過的領域我也很樂意學習。</li>
<li><strong>工作氛圍</strong><br>在工作氛圍方面，我希望同事間可以互相幫助，在開發遇到難題時能夠討論並解決。</li>
</ul>
<p>筆者個人排序：「工作地點 &gt; 內容 &gt; 氛圍」</p>
<hr>
<h3 class="h-nomark" id="22-你了解我們公司的產品嗎">2.2 你了解我們公司的產品嗎？<a class="h-anchor" href="#22-%e4%bd%a0%e4%ba%86%e8%a7%a3%e6%88%91%e5%80%91%e5%85%ac%e5%8f%b8%e7%9a%84%e7%94%a2%e5%93%81%e5%97%8e" aria-label="這個段落的連結"></a></h3>
<p><code>重點：一定要事先了解/試用過面試公司的產品</code></p>
<ul>
<li><strong>用稱讚作為開頭</strong><br>我覺得貴公司不僅是有願景的公司，更是一群具有實力實現願景的團隊。</li>
<li><strong>簡單敘述產品</strong><br>在來面試前，我透過一些報導了解貴公司近幾年在大力推動「數位轉型」。<br>而貴團隊目前開發的這款 App 我也有下載；在使用的過程中我感受到開發人員對使用者的用心：
<ul>
<li>功能貼合使用者的日常生活需求。</li>
<li>打破傳統思維，可以更靈活的使用點數。</li>
<li>結合地圖讓抵達新生活圈的人，可以快速知道周圍有提供哪些好康活動。</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>備註</strong>：有些公司除了要你敘述他們的業務之外，甚至會請你提出<mark>哪裡可以改善</mark>；這個問題可以事先搜尋網路的評論，<mark>但請用優化而非批評的角度來敘述</mark>。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="23-你覺得自己適合這個產業嗎">2.3 你覺得自己適合這個產業嗎？<a class="h-anchor" href="#23-%e4%bd%a0%e8%a6%ba%e5%be%97%e8%87%aa%e5%b7%b1%e9%81%a9%e5%90%88%e9%80%99%e5%80%8b%e7%94%a2%e6%a5%ad%e5%97%8e" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：要對產業有基礎的認知，說出自己與產業相符的特質</mark></p>
<p>我認為這個產業有以下特點</p>
<ul>
<li><strong>技術快速輪動</strong><br>我本身是一個勇於嘗試新技術的人，我相信唯有不斷增強自己的技術才能走在時代的前段班。</li>
<li><strong>產品服務第一線客戶</strong><br>不怕客戶提需求，只怕沒有客戶；我對待產品的態度就像是自己的孩子，我希望它可以越來越好。</li>
<li><strong>未來發展潛力大</strong><br>這個產業目前還是藍海市場，公司也在快速發展的階段；而我對環境變動的適應力很強，相信自己能跟著團隊一起成長。</li>
</ul>
<hr>
<h3 class="h-nomark" id="24-你期待加入什麼樣的團隊">2.4 你期待加入什麼樣的團隊？<a class="h-anchor" href="#24-%e4%bd%a0%e6%9c%9f%e5%be%85%e5%8a%a0%e5%85%a5%e4%bb%80%e9%ba%bc%e6%a8%a3%e7%9a%84%e5%9c%98%e9%9a%8a" aria-label="這個段落的連結"></a></h3>
<p>我希望加入一個願意溝通的團隊；主管在這個團隊中有自己的主見，但也願意聽取成員的建議；而同事都有自己專精的領域且願意互相交流成長；在面對困難時，大家會齊心協力設法解決。</p>
<hr>
<h3 class="h-nomark" id="25-你有無法合作的人嗎">2.5 你有無法合作的人嗎？<a class="h-anchor" href="#25-%e4%bd%a0%e6%9c%89%e7%84%a1%e6%b3%95%e5%90%88%e4%bd%9c%e7%9a%84%e4%ba%ba%e5%97%8e" aria-label="這個段落的連結"></a></h3>
<p><mark>忌諱：避免用前公司的人來做舉例</mark></p>
<ul>
<li><strong>拒絕溝通的人</strong><br>如果合作對象拒絕溝通，那專案很容易因為這個人而卡在某個功能無法 Release，嚴重影響開發進度。</li>
<li><strong>情緒管理有問題的人</strong><br>如果合作對象完全看當天的情緒來決定如何做事，那產出的品質與效率將完全無法預估；總不能把事情交給一個未爆彈做吧？</li>
</ul>
<hr>
<h2 class="h-nomark" id="3-薪資">3. 薪資<a class="h-anchor" href="#3-%e8%96%aa%e8%b3%87" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-你的期待薪資是">3.1 你的期待薪資是？<a class="h-anchor" href="#31-%e4%bd%a0%e7%9a%84%e6%9c%9f%e5%be%85%e8%96%aa%e8%b3%87%e6%98%af" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：請說出一個明確的數字或是區間，不要再說出：「依公司規定了。」</mark></p>
<p>如果你說的是一個區間，那區間的最低值要在自己能接受的範圍之上，因為<mark>公司通常都是採數字區間裡最低值來核薪</mark>。</p>
<p>面試官或是人事會<mark>依照你說出的數字</mark>決定後續走向：</p>
<ul>
<li>
<p><strong>符合行情</strong><br>如果學經歷都符合需求，通常就不會繼續往下問了。</p>
</li>
<li>
<p><strong>略高於行情</strong><br>你要能說出自己跟其他工程師的差異，公司才可能願意用高於行情的價格僱傭你。</p>
<blockquote>
<p>通常會往「3.2 為什麼你覺得自己值得這份薪水？」發展。</p>
</blockquote>
</li>
<li>
<p><strong>符合行情，但想要壓你薪水</strong><br>有不少公司會詢問求職者上一份工作的薪水，甚至在核薪階段要求提供薪資證明文件；這部分就由讀者自行斟酌，因為提供與否各有利弊。<br>這邊來說一下<mark>弊的部分</mark>，如果你在沒良心公司待太久，就算功績卓越，薪水也無法跟上市場價格；因此部分面試官會用這個理由來壓你薪水。</p>
<blockquote>
<p>通常會往「3.3 你的期待薪資與現在的薪水相比漲幅有點太大」發展。</p>
</blockquote>
</li>
</ul>
<blockquote>
<p><strong>備註</strong>：筆者在接受面試前會先了解這個職位的預算範圍，故此階段會直球對決；若讀者<mark>尚未確認公司預算，請先試探公司的底線</mark>。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="32-為什麼你覺得自己值得這份薪水">3.2 為什麼你覺得自己值得這份薪水？<a class="h-anchor" href="#32-%e7%82%ba%e4%bb%80%e9%ba%bc%e4%bd%a0%e8%a6%ba%e5%be%97%e8%87%aa%e5%b7%b1%e5%80%bc%e5%be%97%e9%80%99%e4%bb%bd%e8%96%aa%e6%b0%b4" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：說出自己與其他工程師的差異</mark></p>
<ul>
<li><strong>硬實力</strong>
<ul>
<li>我擁有的技術與職缺相符，可以快速上手成為即戰力。</li>
</ul>
</li>
<li><strong>軟實力</strong>
<ul>
<li>過去跨團隊溝通、資源協調的經驗讓我知道如何保護團隊並且爭取資源。</li>
</ul>
</li>
<li><strong>其他</strong>
<ul>
<li>寫文章、寫書的經驗讓我擁有極佳的資訊統整能力。</li>
<li>下班後的學習除了開拓自己的視野外，我還會將新的知識與技術帶到公司，讓大家一起成長。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="33-你的期待薪資與現在的薪水相比漲幅有點太大">3.3 你的期待薪資與現在的薪水相比漲幅有點太大<a class="h-anchor" href="#33-%e4%bd%a0%e7%9a%84%e6%9c%9f%e5%be%85%e8%96%aa%e8%b3%87%e8%88%87%e7%8f%be%e5%9c%a8%e7%9a%84%e8%96%aa%e6%b0%b4%e7%9b%b8%e6%af%94%e6%bc%b2%e5%b9%85%e6%9c%89%e9%bb%9e%e5%a4%aa%e5%a4%a7" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：用能力談薪水，而不是用年資</mark></p>
<p>我相信透過這幾次面試，貴公司對我的能力與經歷是有所了解的；我認為自己開出的期待薪資是<mark>符合市場價格</mark>的，也相信貴公司在核薪上，是依照<mark>人才的能力</mark>，而<mark>不是用過去的薪資</mark>來做衡量。</p>
<blockquote>
<p><strong>備註</strong>：此問題在期待薪資漲幅超過 20% 時較容易出現。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="4-自我成長">4. 自我成長<a class="h-anchor" href="#4-%e8%87%aa%e6%88%91%e6%88%90%e9%95%b7" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="41-平常會怎麼精進自己的能力">4.1 平常會怎麼精進自己的能力？<a class="h-anchor" href="#41-%e5%b9%b3%e5%b8%b8%e6%9c%83%e6%80%8e%e9%ba%bc%e7%b2%be%e9%80%b2%e8%87%aa%e5%b7%b1%e7%9a%84%e8%83%bd%e5%8a%9b" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：列出具體事項，最好有陳列在履歷上</mark></p>
<ul>
<li><strong>技術文章</strong><br>在下班後我會持續精進自己的技術，並透過撰寫技術文章將知識內化；為了要給讀者呈現最正確的知識，我也需要看大量的資料做為輔助，這個過程讓我學習到更完整的知識。</li>
<li><strong>Side Project</strong><br>常常有朋友跟我分享他們的靈感或是工作遇到的困難，如果剛好這些問題是能夠用技術解決的，我就會動手把它實踐出來，像是我之前曾用爬蟲技術解決小編朋友搜集資料的需求。</li>
</ul>
<hr>
<h3 class="h-nomark" id="42-會不會覺得自己學得太雜了">4.2 會不會覺得自己學得太雜了？<a class="h-anchor" href="#42-%e6%9c%83%e4%b8%8d%e6%9c%83%e8%a6%ba%e5%be%97%e8%87%aa%e5%b7%b1%e5%ad%b8%e5%be%97%e5%a4%aa%e9%9b%9c%e4%ba%86" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：證明這些學習對工作帶來的幫助</mark></p>
<p>我學習的技術會分成兩種，一種要到達<mark>專業的深度</mark>，另一種則是<mark>拓展我的眼界</mark>。</p>
<p>像是我具備伺服器端的知識，在開發前端、後端的時候就會比其他工程師注意更多細節，對資源的管控也有更深刻的體悟。</p>
<p>撰寫技術文章在訓練我溝通表達與教學的能力，你要對技術有深刻的理解，才有可能用自己的語言讓其他人理解。</p>
<p>而寫 App 讓我從不同的角度去思考後端 API 的設計，更深刻的體會到回傳的格式、HTTP Status Code 對開發者的重要性，這些經驗讓我之後跟 App 工程師合作時更加順利。</p>
<blockquote>
<p>我相信沒有白走的路，過去的經歷都會成為未來的養分。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="5-未來規劃">5. 未來規劃<a class="h-anchor" href="#5-%e6%9c%aa%e4%be%86%e8%a6%8f%e5%8a%83" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>「人生就像射箭，夢想就像箭靶，如果沒有箭靶，你每天拉弓有什麼意義？」── 銀河補習班</p>
</blockquote>
<h3 class="h-nomark" id="51-你在未來-35-年有什麼樣的職涯規劃">5.1 你在未來 3~5 年有什麼樣的職涯規劃<a class="h-anchor" href="#51-%e4%bd%a0%e5%9c%a8%e6%9c%aa%e4%be%86-35-%e5%b9%b4%e6%9c%89%e4%bb%80%e9%ba%bc%e6%a8%a3%e7%9a%84%e8%81%b7%e6%b6%af%e8%a6%8f%e5%8a%83" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：說出自己的具體規劃</mark></p>
<p>如果有榮幸加入貴公司</p>
<ul>
<li><strong>短期</strong><br>我會先熟悉工作所需技能、了解專案；並學習產業知識，讓自己的能力能夠完全符合職位所需。</li>
<li><strong>中期</strong><br>期待自己能擔任跨部門溝通的角色，為團隊帶來更大利益，並朝管理職的方向發展。</li>
<li><strong>長期</strong><br>希望自己可以擔任團隊的 Leader，帶領團隊為公司創造更多價值。</li>
</ul>
<blockquote>
<p><strong>備註</strong>：依照自己的職涯規劃敘述就好，並不是每個人都想往管理職發展，公司需要多元的人才。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="52-你進入公司後會想學到什麼">5.2 你進入公司後會想學到什麼？<a class="h-anchor" href="#52-%e4%bd%a0%e9%80%b2%e5%85%a5%e5%85%ac%e5%8f%b8%e5%be%8c%e6%9c%83%e6%83%b3%e5%ad%b8%e5%88%b0%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：想學的東西最好要跟產業以及你現在的技術有關聯性</mark></p>
<blockquote>
<p>以電商產業舉例</p>
</blockquote>
<p>電商這個產業的 Know-how 對我來說新的領域，儘管我有自學一些高併發高流量以及 NoSQL 的知識；但面對真實的 Big Data 數據處理、NoSQL 與 Relation Database 結合的應用，都還是需要再學習並與前輩請教的。</p>
<hr>
<h2 class="h-nomark" id="6-其他">6. 其他<a class="h-anchor" href="#6-%e5%85%b6%e4%bb%96" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="61-有沒有應徵其他公司">6.1 有沒有應徵其他公司？<a class="h-anchor" href="#61-%e6%9c%89%e6%b2%92%e6%9c%89%e6%87%89%e5%be%b5%e5%85%b6%e4%bb%96%e5%85%ac%e5%8f%b8" aria-label="這個段落的連結"></a></h3>
<p>筆者建議照實回答：「<code>有</code>。」有些公司問這個問題是因為他對你感興趣，想了解有沒有潛在競爭者。</p>
<blockquote>
<p>應該很少人準備換工作，卻只應徵一間吧？</p>
</blockquote>
<p>如果公司對你非常感興趣，他們甚至會<mark>詢問你是否有拿到其他公司的 Offer、具體的待遇是多少、你什麼時候要做決定</mark>。</p>
<p>如果遇到上面的情境，恭喜你獲得薪水談判的權利；但這並不代表你可以獅子大開口，只是談判空間更大而已。</p>
<hr>
<h3 class="h-nomark" id="62-說說看自己的優點">6.2 說說看自己的優點<a class="h-anchor" href="#62-%e8%aa%aa%e8%aa%aa%e7%9c%8b%e8%87%aa%e5%b7%b1%e7%9a%84%e5%84%aa%e9%bb%9e" aria-label="這個段落的連結"></a></h3>
<p><mark>重點：說出的優點必須要有實際案例作證</mark></p>
<p>當面試官問到這個問題時，很多人都會反射性的用形容詞來回應，比如：「積極進取、樂於助人、懂得應對進退&hellip;」如果你只用形容詞回答，面試官是不會接受的，<mark>在沒有實際例子包裝前，一切的形容都毫無意義。</mark></p>
<p>筆者會建議改成這樣表達：</p>
<ul>
<li><strong>積極進取</strong><br>下班後繼續充實自我，目前已經在 Medium 平台上發表了 60 餘篇技術文章；並且在 GitHub 上面分享自己的 Side Project。</li>
<li><strong>樂於助人</strong><br>我會為團隊的新進成員準備 OnBoard 文件，帶他們了解工作所需的技術與知識，使新人快速融入這個環境成為有效戰力。</li>
<li><strong>懂得應對進退</strong><br>我會注意職場上的禮節，像是主持會議時會穿著得體的服裝、與長官交流時不會因為私下交情而沒大沒小、如果請假出去玩會帶當地的特產與同事分享。</li>
</ul>
<hr>
<h3 class="h-nomark" id="63-談談自己的缺點">6.3 談談自己的缺點<a class="h-anchor" href="#63-%e8%ab%87%e8%ab%87%e8%87%aa%e5%b7%b1%e7%9a%84%e7%bc%ba%e9%bb%9e" aria-label="這個段落的連結"></a></h3>
<p><mark>忌諱：說自己沒有缺點，或是說出來的缺點不是缺點</mark></p>
<p>很多求職者會回答一些根本是優點的缺點，比如：「做事太過積極造成同事壓力、擇善固執不懂得妥協&hellip;」聽到這類回答時，面試官會覺得有說跟沒說一樣，<mark>就像是從參考書裡面抄來的，毫無靈魂</mark>。</p>
<p>筆者建議關於缺點的回答可以從缺乏的技能出發，說出缺點的同時也要提出你打算如何改善它；盡量避免說性格上的缺點，因為面試官<mark>無法確定你的性格是否會改善，但技能是可以培養的</mark>。</p>
<p>筆者建議的回答結構：</p>
<ul>
<li><strong>缺乏的技能</strong><br>我在一開始兼任專案經理的時候<mark>缺乏時間管理的概念、也無法分辨任務的輕重緩急</mark>；導致上班時間幾乎都在開會、整理需求，下班後才有時間著手規劃與執行任務。</li>
<li><strong>如何改善</strong><br>但我覺得用加班解決問題不是一個好方法，所以我開始<mark>閱讀時間管理的書籍</mark>，像是理查．柯克的 80/20 法則。<br>這本書讓我知道如何過濾掉不必要的會議、需求，學會分清楚每個時間點該做的事情。</li>
<li><strong>接受建議</strong><br>在專案管理方面我還有很多需要學習的，我也非常樂意接受您的指導，希望在未來我可以透過這些技能，幫助公司提高更多的生產力。</li>
</ul>
<hr>
<h3 id="後記">後記<a class="h-anchor" href="#%e5%be%8c%e8%a8%98" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>讓你變強的不是面試，而是面試後的每一次整理；若缺少這個步驟，你下次還是會犯同樣的錯誤。</p>
</blockquote>
<p><mark>你未必認同我的回答方式，我的回答也不可能適合每一位讀者；請依據自己的人生閱歷給出最合適的回答。</mark></p>
<p>本系列「面試」的主題到今天告一個段落；接下來會分享「職場」上實用的技能與建議。</p>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-25/images/fullstack-career-notes-day-25-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-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>[面試][後端]設計 API 時會考慮哪些點？</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-16/</link>
      <pubDate>Fri, 01 Oct 2021 08:46:39 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-16/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>既然 Junoir 跟 Senior 的後端工程師都能寫出可以運作的 API；那公司為什麼要多花錢請一個比較貴的工程師呢？</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-16/images/cover_hu_7e037945b929f204.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>你設計的 API 除了跑的動以外，它安全穩定嗎？</p>
</blockquote>
<p>既然 Junoir 跟 Senior 的後端工程師都能寫出可以運作的 API；那公司為什麼要多花錢請一個比較貴的工程師呢？</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>設計 API 時會考慮哪些點？</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 驗證 Client 端身份</li>
<li>2.2 了解後端驗證的重要性，並認識常見的 Validation Rule</li>
<li>2.3 優化後端的常見方法</li>
<li>2.4 淺談 Unit Test（單元測試）</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 你知道 PUT &amp; PATCH 的差異嗎？</li>
<li>3.2 請舉例 RESTful api 的命名原則</li>
<li>3.3 GraphQL 跟 RESTful API 的差異在哪裡？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-設計-api-時會考慮哪些點">1. 設計 API 時會考慮哪些點？<a class="h-anchor" href="#1-%e8%a8%ad%e8%a8%88-api-%e6%99%82%e6%9c%83%e8%80%83%e6%85%ae%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>
<p>能依照需求規格書寫好 API 只是對後端工程師的基礎要求；但這個 API 考慮的是否周全就看每個工程師的經驗了。</p>
<p>這算是<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>你設計的 API 是否有防護的機制</li>
<li>當 Client 端傳入不符規範的參數及型態時，你的 API 是否有成為防線</li>
<li>你有用什麼方法保證 API 的穩定性</li>
<li>是否有思考過 API 的優化</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>以過去設計的內部管理系統來說，Client 端在登入後會取得 JWT，之後在存取 API 前會用這個 <mark>JWT 來驗證是否有存取的權限</mark>；為了避免 Client 端使用不符規範的參數，我會先<mark>驗證每個傳入參數的格式</mark>，並撰寫 <code>Unit Test</code> 確保這隻 API 在不同情境下的 Response 都符合期待；再確認 API 的穩定性後，我會去思考還有什麼地方可以優化，讓 Client 端有更好的體驗。</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-驗證-client-端身份">2.1 驗證 Client 端身份<a class="h-anchor" href="#21-%e9%a9%97%e8%ad%89-client-%e7%ab%af%e8%ba%ab%e4%bb%bd" aria-label="這個段落的連結"></a></h3>
<p>這邊帶讀者瞭解兩種主流驗證機制，大部分的框架都有提供對應的方案供開發人員使用。</p>
<ul>
<li><strong>Session 與 Cookie</strong><br>當 Client 端登入後，Server 會產生一筆 Session 並記錄（可能存在資料庫或記憶體），同時將可以識別身份的 SessionID 送回 Client 端儲存於 Cookie；此後 Client 端的請求，Server 只需要根據 Cookie 中的 SessionID 尋找 Session 來驗證使用者即可。
<ul>
<li><strong>使用 Session 要考量的問題</strong>
<ol>
<li>因為 Session 的資料存在 Server 中，需考慮同時有<mark>很多使用者上線時要面對的效能或是記憶體問題</mark>。
<ul>
<li><strong>解決方案</strong>：定期清理過期 Session。</li>
</ul>
</li>
<li>如果採取 Cluster 部署，就要考慮<mark>不同 Server 的 Session 共享問題</mark>。
<ul>
<li><strong>解決方案</strong>：Server 需設計 Session 同步機制（Replication）。</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li><strong>JSON Web Token（JWT）</strong><br>當 Client 端登入成功後，Server 會回傳一個 JWT，Client 端通常會將 JWT 儲存於 localstorage；當 Client 端打算訪問受保護的資源時，需要在 Header 的 Authorization 使用 Bearer 模式添加 JWT，如果驗證通過即可存取相關資源。
<ul>
<li><strong>使用 JWT 要考量的問題</strong>
<ol>
<li>在 Client 端取得 JWT 後，在 JWT 有效期內他都能夠使用；即使我們直接把使用者帳號刪除，這個 JWT 依舊可以訪問受保護的資源。
<ul>
<li><strong>解決方案</strong>：設計黑名單機制，使用記憶體資料庫維護一份黑名單，如果想讓某個 JWT 失效，把它加入黑名單就好了。</li>
</ul>
</li>
<li><mark>不要為了方便把 JWT 的有效期限設定太長</mark>，不然這個 JWT 洩露出去後，任何人都可以拿它來幹大事。
<ul>
<li><strong>解決方案</strong>：JWT 有效期限不要太長，同時使用 HTTPS 來減少盜用。</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-了解後端驗證的重要性並認識常見的-validation-rule">2.2 了解後端驗證的重要性，並認識常見的 Validation Rule<a class="h-anchor" href="#22-%e4%ba%86%e8%a7%a3%e5%be%8c%e7%ab%af%e9%a9%97%e8%ad%89%e7%9a%84%e9%87%8d%e8%a6%81%e6%80%a7%e4%b8%a6%e8%aa%8d%e8%ad%98%e5%b8%b8%e8%a6%8b%e7%9a%84-validation-rule" aria-label="這個段落的連結"></a></h3>
<p><mark>無論前端驗證做得多完美，後端驗證都是必須存在的</mark>；因為想攻擊系統的人不一定會從前端執行，直接 Call API 嘗試所有可能性的效率快多了；<mark>前端驗證可以大幅減少不必要的 Request 產生，後端驗證則是保護資料的最終防線</mark>。</p>
<ul>
<li><strong>為什麼要了解 Validation Rule？</strong>
<ul>
<li>你可以節省非常多親手撰寫 Function 的時間（自己寫的效能跟穩定度未必好，還很佔版面）。</li>
<li>這些不符規範的資料並不會進入後端運算的邏輯層面。</li>
<li>驗證發現錯誤就 Response Error，減少 Client 端等待時間。</li>
</ul>
</li>
</ul>
<p>接下來用情境來介紹幾個常見的 Validation Rule，不同框架的名字可能略有不同，我這邊以 <a href="https://laravel.tw/docs/5.0/validation#available-validation-rules">Laravel</a> 的為主。</p>
<ul>
<li><strong>驗證使用者註冊系統時填寫的參數</strong>
<ul>
<li>
<p>此為 Laravel 驗證傳入參數的程式，下方依據參數進行解說。</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-php" data-lang="php"><span style="display:flex;"><span><span style="color:#79c0ff">$validator</span> <span style="color:#ff7b72;font-weight:bold">=</span> Validator<span style="color:#ff7b72;font-weight:bold">::</span>make(<span style="color:#79c0ff">$request</span><span style="color:#ff7b72;font-weight:bold">-&gt;</span>all(), [
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#39;email&#39;</span> <span style="color:#ff7b72;font-weight:bold">=&gt;</span>  [<span style="color:#a5d6ff">&#39;required&#39;</span>, <span style="color:#a5d6ff">&#39;email&#39;</span>, <span style="color:#a5d6ff">&#39;string&#39;</span>, <span style="color:#a5d6ff">&#39;max:255&#39;</span>, <span style="color:#a5d6ff">&#39;unique:users&#39;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#39;name&#39;</span> <span style="color:#ff7b72;font-weight:bold">=&gt;</span>  [<span style="color:#a5d6ff">&#39;required&#39;</span>, <span style="color:#a5d6ff">&#39;string&#39;</span>, <span style="color:#a5d6ff">&#39;max:50&#39;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#39;password&#39;</span> <span style="color:#ff7b72;font-weight:bold">=&gt;</span> [<span style="color:#a5d6ff">&#39;required&#39;</span>, <span style="color:#a5d6ff">&#39;string&#39;</span>, <span style="color:#a5d6ff">&#39;max:255&#39;</span>, <span style="color:#a5d6ff">&#39;min:8&#39;</span>, <span style="color:#a5d6ff">&#39;regex:/^.*(?=.{8,})(?=.*?[a-zA-Z])(?=.*?[0-9]).*$/&#39;</span>, <span style="color:#a5d6ff">&#39;confirmed&#39;</span>]
</span></span><span style="display:flex;"><span>]);
</span></span></code></pre></div><ul>
<li><strong>email</strong><br>為必填 <code>required</code> 欄位，同時檢查傳入的字串是否符合 <code>email</code> 格式以及長度 <code>max</code> 是否超過限制，並確認這個 email 並沒有被註冊過 <code>unique</code>。</li>
<li><strong>name</strong><br>為必填 <code>required</code> 欄位，同時檢查傳入的字串長度 <code>max</code> 是否超過限制。</li>
<li><strong>password</strong><br>為必填 <code>required</code> 欄位，除了檢查傳入的字串長度 <code>max</code> 是否超過限制外，通常會限制最小長度 <code>min</code>；再嚴謹一點會要求 password 複雜度需包含英數字 <code>regex</code>，且內容與 password_confirmation 欄位相同 <code>confirmed</code>。</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-優化後端的常見方法">2.3 優化後端的常見方法<a class="h-anchor" href="#23-%e5%84%aa%e5%8c%96%e5%be%8c%e7%ab%af%e7%9a%84%e5%b8%b8%e8%a6%8b%e6%96%b9%e6%b3%95" aria-label="這個段落的連結"></a></h3>
<p>這邊先不討論程式語言和資料庫選擇帶來的影響，專注在共同可行性方案上。</p>
<ul>
<li><strong>操作資料庫時加入限制條件</strong><br>就算前端 Request 的參數沒有限制條件（ex：分頁、筆數、種類&hellip;），後端也要有意識的<mark>設定預設值</mark>，不然當資料量大的時候除了會跑很久浪費效能外，前端也不適合接收/呈現這麼多資訊。</li>
<li><strong>回傳前端的資料盡量是已經計算過的</strong><br>Client 端使用的機器效能未知，如果把太多的邏輯運算交給前端可能導致不好的使用者體驗；我這邊建議後端最好<mark>回傳計算後的資料</mark>，前端負責顯示為主，不過這塊也要看實際的業務量以及情境來 trade-off 。</li>
<li><strong>加入 CDN Cache</strong><br>如果遇到國外客戶抱怨連線速度很慢，就可以考慮採用 CDN 來解決這個問題；CDN 服務商的做法是將 Server 建置在世界各地，將網站上的靜態內容（圖片、影音、檔案）與動態內容（資料庫查詢）Cache 到 Server，<mark>根據訪客的地理位置，從最近的 Server 提供資料</mark>；好處如下：
<ul>
<li>提升網站的穩定度（如果某個 CDN Server 掛了也有替身）。</li>
<li>節省原本 Server 的頻寬（因為流量被分散了）。</li>
<li>改善使用者體驗（過去會卡住的都是大檔案，有了 CDN Cache 後瀏覽會更流暢）。</li>
</ul>
</li>
<li><strong>提升硬體水平</strong><br>江湖傳言：「效能不夠，機器來湊！」雖然聽起來好像是在開玩笑；但實際業務上如果真的遇到效能瓶頸，許多公司的做法就是<mark>先靠硬體讓服務穩定</mark>；服務穩定了，你才有心力去 Refactor 程式。</li>
</ul>
<hr>
<h3 class="h-nomark" id="24-淺談-unit-test單元測試">2.4 淺談 Unit Test（單元測試）<a class="h-anchor" href="#24-%e6%b7%ba%e8%ab%87-unit-test%e5%96%ae%e5%85%83%e6%b8%ac%e8%a9%a6" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>為什麼 Unit Test 很重要？</strong>
<ul>
<li>有它保證<mark>程式碼的穩定性</mark>，發生錯誤時可以快速找到源頭，因此開發人員可以更大膽的開發。</li>
<li>減少手動測試的時間，大幅<mark>降低相關維運成本</mark>。</li>
<li>API 版本更新時（ex：v2 升級到 v3），<mark>確保舊版 API 可以正常運行</mark>。</li>
</ul>
</li>
<li><strong>Unit Test 使用時機</strong>
<ul>
<li>開發人員要先在本機 Run 過一遍，確認所有 Unit Test 都通過。</li>
<li>如果有使用 Git 做版本控制，在特定 branch 合併時 CI/CD 應該要有某個 Stage 來做測試，<mark>確保合併分支的程式正確性</mark>。</li>
</ul>
</li>
<li><strong>撰寫 Unit Test 基礎概念</strong>
<ul>
<li><strong>測試時使用（或自動建立）Mock data，不要動到 Server 資料</strong><br>Unit Test 著重在<mark>測試程式邏輯</mark>，建議測試時使用 Mock data，不然可能導致正式環境發生意外。</li>
<li><strong>測試要涵蓋你程式中的所有邏輯</strong>
<ul>
<li>假設一個 API 有 12 種回傳邏輯，你的 Unit Test 就要有 12 個；根據莫非定律，<mark>少寫的測試往往上線後就會爆掉</mark>。</li>
<li>如果 API 有做 JWT 防護、Request 參數驗證，我建議要<mark>把所有錯誤情境都做一次模擬</mark>；甚至要模擬 JWT 還在有效期，但實際權限已被移除的<mark>邊緣測試</mark>。</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>筆者聽過有些公司在專案正式 Release 時沒有撰寫任何的 Unit Test，直接讓客戶人肉測試，等客戶回報錯誤時再去修復 Bug；嗯&hellip;這種公司的離職率通常很高，因為有一堆歷史業障需要修但沒人敢碰。</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-你知道-put--patch-的差異嗎">3.1 你知道 PUT &amp; PATCH 的差異嗎？<a class="h-anchor" href="#31-%e4%bd%a0%e7%9f%a5%e9%81%93-put--patch-%e7%9a%84%e5%b7%ae%e7%95%b0%e5%97%8e" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：對於 API 基礎操作的掌握程度</mark></p>
<p>「PUT」是<code>替換</code>資源，而「PATCH」則是<mark>更新部份</mark>資源內容；以使用者舉例，假設他有「姓名、簡介、年齡」這幾個欄位，如果想更新「姓名」的資訊：</p>
<ul>
<li>用 PUT 方法送出更新，除了「姓名」外還要填寫所有的欄位，如果沒填寫會導致其他的欄位更新成預設值。</li>
<li>用 PATCH 方法送出更新，只需填寫要更新的「姓名」欄位即可。</li>
</ul>
<hr>
<h3 class="h-nomark" id="32-請舉例-restful-api-的命名原則">3.2 請舉例 RESTful API 的命名原則<a class="h-anchor" href="#32-%e8%ab%8b%e8%88%89%e4%be%8b-restful-api-%e7%9a%84%e5%91%bd%e5%90%8d%e5%8e%9f%e5%89%87" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認你寫 API 的習慣是否符合標準</mark></p>
<p>假設今天有一個 users 的 Table。</p>
<ul>
<li><strong>取得 user list</strong>
<ul>
<li><strong>方法</strong>：GET</li>
<li><strong>API 路徑</strong>：<code>/api/users</code></li>
</ul>
</li>
<li><strong>取得 user detail</strong>
<ul>
<li><strong>方法</strong>：GET</li>
<li><strong>API 路徑</strong>：<code>/api/users/{user_id}</code></li>
</ul>
</li>
<li><strong>新增 user</strong>
<ul>
<li><strong>方法</strong>：POST</li>
<li><strong>API 路徑</strong>：<code>/api/users</code></li>
</ul>
</li>
<li><strong>更新 user 資訊</strong>
<ul>
<li><strong>方法</strong>：PUT</li>
<li><strong>API 路徑</strong>：<code>/api/users/{user_id}</code></li>
</ul>
</li>
<li><strong>刪除 user</strong>
<ul>
<li><strong>方法</strong>：DELETE</li>
<li><strong>API 路徑</strong>：<code>/api/users/{user_id}</code></li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="33-graphql-跟-restful-api-的差異在哪裡">3.3 GraphQL 跟 RESTful API 的差異在哪裡？<a class="h-anchor" href="#33-graphql-%e8%b7%9f-restful-api-%e7%9a%84%e5%b7%ae%e7%95%b0%e5%9c%a8%e5%93%aa%e8%a3%a1" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認你對市場技術的認知程度</mark></p>
<p>過去 <code>RESTful API</code> 需要根據需求規格書給<mark>每個 API 設計回傳的資料結構</mark>，如果這個 API 在很多頁面被共用就有可能要回傳更多的資訊，但回傳的資訊中有些是只有特定頁面才有需求的，假使拆成多隻 API 也會導致日後維護困難；而 <code>GraphQL</code> 的出現正好解決了這個問題，他能夠<mark>由 Client 端來定義 Server 端要回傳的資料結構</mark>，不再回傳冗贅資訊。</p>
<ul>
<li>
<p><strong>補充：用範例了解兩者差異</strong><br>要查詢圖書館中特定書籍，回傳內容須包含：「作者、書名、出版年」。</p>
<ul>
<li><strong>GraphQL</strong>
<ul>
<li>
<p><strong>Client 端傳送的 query</strong></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>query {
</span></span><span style="display:flex;"><span>  books (id<span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">12</span>) {
</span></span><span style="display:flex;"><span>    authors {
</span></span><span style="display:flex;"><span>        firstName
</span></span><span style="display:flex;"><span>        lastName
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    title
</span></span><span style="display:flex;"><span>    yearPublished
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
<li>
<p><strong>Server 端 Response 的 result</strong></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></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#34;data&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#34;books&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a5d6ff">&#34;authors&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#a5d6ff">&#34;firstName&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;Lin&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a5d6ff">&#34;lastName&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;Dean&#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:#a5d6ff">&#34;title&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;全端工程師生存筆記&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a5d6ff">&#34;yearPublished&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;2022&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
</ul>
</li>
<li><strong>RESTful API</strong>
<ul>
<li>
<p><strong>Client 端的 Request</strong><br>需要拆成<code>GET api/books/12</code>、<code>GET api/authors/12</code>來分別取得書籍、作者資訊。</p>
</li>
<li>
<p><strong>Server 端 Response 的 result</strong><br><code>GET api/books/12</code></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></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#34;title&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;全端工程師生存筆記&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#34;authorID&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">12</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#34;yearPublished&#34;</span> <span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">2022</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#34;page&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">168</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#34;tags&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> [<span style="color:#a5d6ff">&#34;履歷&#34;</span><span style="color:#f85149">、</span><span style="color:#a5d6ff">&#34;面試&#34;</span><span style="color:#f85149">、</span><span style="color:#a5d6ff">&#34;職場&#34;</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>GET api/authors/12</code></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></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#34;firstName&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;Lin&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#34;lastName&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;Dean&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
</ul>
</li>
</ul>
<p>在上面的範例中，RESTful API 需要兩個 Request 才能取得完整資料，如果想用一個 Request 取得完整資料就要重新建立一支 API；比較起來<mark>GraphQL 對前端工程師來說是更好使用的工具</mark>；不過轉換與建立的成本較 RESTful API 高，如果想嘗試 GraphQL 帶來的效益，可以考慮漸進式的遷移。</p>
</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://ithelp.ithome.com.tw/articles/10224134">進階 RESTful API 討論</a></li>
<li><a href="https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/2e64d72594f8">透過 JWT 實作驗證機制</a></li>
<li><a href="https://blog.yyisyou.tw/5d272c64/">認識 Cookie、Session、Token 與 JWT</a></li>
<li><a href="https://segmentfault.com/a/1190000021810849">簡單聊一聊 Cookie、Session、Token、JWT 的區別和作用</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-16/images/fullstack-career-notes-day-16-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>[面試][後端]在正式 API 完成前，如何讓要串接的工程師不要空等？</title>
      <link>https://deanlin.net/blog/202109/fullstack-career-notes-day-15/</link>
      <pubDate>Thu, 30 Sep 2021 08:46:20 +0800</pubDate>
      <guid>https://deanlin.net/blog/202109/fullstack-career-notes-day-15/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>建立 Mock Server 後，前端工程師就失去耍廢時間不需要等後端做好才能串接。</description>
      <media:content url="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/cover_hu_a68742a0a8bf2016.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>建立 Mock Server 後，前端工程師就<del>失去耍廢時間</del>不需要等後端做好才能串接。</p>
<blockquote>
<p>前端工程師心裡 OS：「其實我是想要空等的。」</p>
</blockquote>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>在正式 API 完成前，如何讓未來要串接的工程師不要空等？</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 Mock Server 是什麼？學它有什麼好處？</li>
<li>2.1 用 Postman 建立 Mock Server</li>
<li>2.3 設計 Mock Server Response</li>
<li>2.4 設定 Environment 讓你快速切換執行環境</li>
<li>2.5 匯出 API documentation 給同事使用指引</li>
<li>2.6 讓 Mock Server 變成私有的</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-在正式-api-完成前如何讓要串接的工程師不要空等">1. 在正式 API 完成前，如何讓要串接的工程師不要空等？<a class="h-anchor" href="#1-%e5%9c%a8%e6%ad%a3%e5%bc%8f-api-%e5%ae%8c%e6%88%90%e5%89%8d%e5%a6%82%e4%bd%95%e8%ae%93%e8%a6%81%e4%b8%b2%e6%8e%a5%e7%9a%84%e5%b7%a5%e7%a8%8b%e5%b8%ab%e4%b8%8d%e8%a6%81%e7%a9%ba%e7%ad%89" 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>，因為許多專案都採取前後端分離的架構，前端常常要等後端完成後才能串接，如果後端提供的服務不穩定，又或是沒按造需求規格開發就容易導致前端空等。</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>你有用什麼工具，讓前端不用等你寫完 API 就能提前串接</li>
<li>你有提供什麼文檔供前端串接時參考</li>
<li>如果回答的是某個工具（ex：Postman），會再詳細詢問有使用到工具的哪些功能</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>，同時使用第三方驗證工具「<code>Postman</code>」建立 <code>Mock Server</code>，並根據 Request 的參數給予對應的 Result 結果，讓前端不需要等後端 API 完成後才能開始串接的作業。</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>市面上提供 Mock Server 以及產生 API doc 的工具很多，不過「Postman」是筆者用起來最順手的；Postman 除了 API 的基礎測試外，還有許多加速工作效率的功能，就讓我們透過這篇文章讓自己的 Postman 更專業吧！</p>
<h3 class="h-nomark" id="21-mock-server-是什麼學它有什麼好處">2.1 Mock Server 是什麼？學它有什麼好處？<a class="h-anchor" href="#21-mock-server-%e6%98%af%e4%bb%80%e9%ba%bc%e5%ad%b8%e5%ae%83%e6%9c%89%e4%bb%80%e9%ba%bc%e5%a5%bd%e8%99%95" aria-label="這個段落的連結"></a></h3>
<p>在前後端分離的架構下，前端在完成網頁版面佈局後，需等待後端完成 API 才能做後續的開發；為了不讓前端在這段時間<del>偷懶</del>苦等，Mock Server 應運而生！<mark>它能回傳自訂的資料結構供前端使用</mark>，等後端完成 API 後，前端只需要切換網址就能使用真實資料。</p>
<p>除了上面的原因外，如果你開發環境的後端 Sever 不太穩定常常罷工，Mock Server 也能保證你的工作效率不受影響，畢竟不可能每次都等到 Server 修好後再工作吧？</p>
<hr>
<h3 class="h-nomark" id="22-用-postman-建立-mock-server">2.2 用 Postman 建立 Mock Server<a class="h-anchor" href="#22-%e7%94%a8-postman-%e5%bb%ba%e7%ab%8b-mock-server" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>Postman 改版非常快速，你看到的畫面可能跟我有些許不同</p>
</blockquote>
<ul>
<li><strong>STEP 1</strong>： 建立 Mock Server
<ol>
<li>
<p>New &gt; Mock Server</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-01.webp" width="1024" height="560" alt="2.2 用 Postman 建立 Mock Server（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
<li>
<p>Create a new Collection &gt; input url &gt; Next<br>如果不是選擇已經存在的 API Collection，就需要<mark>至少填寫一個 Request URL</mark> 讓 Collections 有初始化的 API Request</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-02.webp" width="1024" height="682" alt="2.2 用 Postman 建立 Mock Server（圖 2）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>給 Mock Server 取個名字，並建議你勾選<mark>儲存 mock server URL 到環境變數</mark>的選項。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-03.webp" width="1024" height="669" alt="2.2 用 Postman 建立 Mock Server（圖 3）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
<li><strong>STEP 2</strong>： 檢查 Mock Server 是否建立成功
<ol>
<li>
<p>建立成功後便可以在「Mock Servers」的分頁中看到剛剛建立的 Server，點進去後能看到這個 Sever 的網址。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-04.webp" width="1024" height="454" alt="2.2 用 Postman 建立 Mock Server（圖 4）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>接著我們到「Environments」的分頁確認，mock server URL 是否有儲存到環境變數，</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-05.webp" width="1024" height="367" alt="2.2 用 Postman 建立 Mock Server（圖 5）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-設計-mock-server-response">2.3 設計 Mock Server Response<a class="h-anchor" href="#23-%e8%a8%ad%e8%a8%88-mock-server-response" aria-label="這個段落的連結"></a></h3>
<p>在 Mock Server 建立後，我們要設計不同 API Request 回傳的資料結構。</p>
<ul>
<li><strong>STEP 1</strong>：先用 Request 的 Default example 來了解使用方式
<ol>
<li>
<p>到「Collections」的分頁找到剛剛初始化的 Request， <code>{{url}}</code>會帶入環境變數所以右上角請選擇剛剛建立的「Mock_User」，按下「Send」後你會發現下面的 Response 一片空白，這是因為我們還沒設定 Mock Server Response。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-06.webp" width="1024" height="479" alt="2.3 設計 Mock Server Response（圖 6）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>在左側 Request 下面有一個「Default」，<mark>它就是用來自訂 Response 資料結構的 example</mark>，這邊我們用 JSON 格式寫個簡單的 Response example 後按「Save」。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-07.webp" width="1024" height="569" alt="2.3 設計 Mock Server Response（圖 7）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>回到剛剛的 Request，按下「Send」後會發現 Response 的資料結構跟剛剛填寫的範例相同。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-08.webp" width="1024" height="569" alt="2.3 設計 Mock Server Response（圖 8）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
<li><strong>STEP 2</strong>：新增其他的 Response example
<ol>
<li>
<p>除了最初的 Default 外，我們還可以<mark>自己新增其他的 Response example</mark>。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-09.webp" width="1024" height="569" alt="2.3 設計 Mock Server Response（圖 9）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>可以透過<mark>加入 Params 讓這個 Request 有不同的 Response</mark>，這裡我們新增「error」、「correct」這兩個 example 並加入「control_res」這個 Params 來做示範。</p>
<blockquote>
<p>在「DESCRIPTION」填寫的資訊會顯示在 API 文件上，寫清楚一點不但方便自己記憶，也更容易讓合作者了解。</p>
</blockquote>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-10.webp" width="1024" height="569" alt="2.3 設計 Mock Server Response（圖 10）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-11.webp" width="1024" height="569" alt="2.3 設計 Mock Server Response（圖 11）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>回到剛剛的 Request ，我們在「control_res」填入不同的 VALUE，按下「Send」就會看到對應的 Response 嚕～</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-12.webp" width="1024" height="569" alt="2.3 設計 Mock Server Response（圖 12）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-13.webp" width="1024" height="569" alt="2.3 設計 Mock Server Response（圖 13）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="24-設定-environment-讓你快速切換執行環境">2.4 設定 Environment 讓你快速切換執行環境<a class="h-anchor" href="#24-%e8%a8%ad%e5%ae%9a-environment-%e8%ae%93%e4%bd%a0%e5%bf%ab%e9%80%9f%e5%88%87%e6%8f%9b%e5%9f%b7%e8%a1%8c%e7%92%b0%e5%a2%83" aria-label="這個段落的連結"></a></h3>
<p>這是 Postman 重要的功能之一，它可以加快開發速度，讓你<mark>不用為每個環境都建立一個 Request</mark>。</p>
<ul>
<li>
<p><strong>如果沒設定會發生什麼蠢事？</strong></p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-14.webp" width="547" height="421" alt="2.4 設定 Environment 讓你快速切換執行環境（圖 14）" loading="lazy" decoding="async"></p>
<p>有沒有覺得上面這張圖片看起來很蠢，在了解 Environment 怎麼設定前，我的 Collections 就是這麼的複雜；<mark>因為開發時通常環境會分成：「Local、Dev、QA、Prod」</mark>，如果每新增一個環境你就複製一份，到最後你會無法進行妥善管理。</p>
</li>
<li>
<p><strong>STEP 1</strong>：新增 Environment<br>之前在建立 Mock Server 時其實就已經用到了 Environment，這裡我再詳細的跟大家說這個東西的好處。<br>請先到「Environments」的分頁按下「＋」的按鈕，接著輸入名稱來新增環境（我用 local_server 做示範）。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-15.webp" width="1024" height="399" alt="2.4 設定 Environment 讓你快速切換執行環境（圖 15）" loading="lazy" decoding="async"></p>
</li>
<li>
<p><strong>STEP 2</strong>：設定常用的 Environment variables</p>
<ul>
<li>
<p><strong>url</strong><br>這個是最實用的環境變數，讓你只需要調整 Request 的 Environment 就快速切換網址做測試。</p>
</li>
<li>
<p><strong>login_user、login_password</strong><br>每個環境的使用者登入資訊通常略有不同，且大部分的 API 都需要透過登入取得 token 才能使用，把他們加入環境變數可以加速你的測試。</p>
<blockquote>
<p><strong>警告</strong>：<mark>不建議把正式機的 password 加入環境變數，且測試機的 password 不要用平常自己會用的密碼</mark>，不然資安很危險的，都已經可以偷懶用 Environment，密碼自己去產生一組吧。</p>
</blockquote>
</li>
</ul>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-16.webp" width="1024" height="371" alt="2.4 設定 Environment 讓你快速切換執行環境（圖 16）" loading="lazy" decoding="async"></p>
</li>
<li>
<p><strong>STEP 3</strong>：在 Request 使用 Environment variables<br>想在 Request 中使用 Environment variables 只需要用 <code>{{your_env_var}}</code> 包起來就好，另外右上角記得要切換到正確的 Environment 才會帶入相應的資料喔！</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-17.webp" width="1024" height="435" alt="2.4 設定 Environment 讓你快速切換執行環境（圖 17）" loading="lazy" decoding="async"></p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="25-匯出-api-documentation-給同事使用指引">2.5 匯出 API documentation 給同事使用指引<a class="h-anchor" href="#25-%e5%8c%af%e5%87%ba-api-documentation-%e7%b5%a6%e5%90%8c%e4%ba%8b%e4%bd%bf%e7%94%a8%e6%8c%87%e5%bc%95" aria-label="這個段落的連結"></a></h3>
<p>相信後端工程師有一個共同經驗，無論你的 API 文件寫得再清楚、功能執行的再穩定；到了同事手中卻常常被說不能 Work，緊張的你檢查很多遍也找不出問題，百思不得其解的抵達同事座位後，才發現他原來<mark>漏了某個參數</mark>。</p>
<p>如果你有這樣的經驗，Postman 內建的 API documentation 能拯救你，讓你在 Publish 文件後可以跟同事幸福快樂的生活。</p>
<ul>
<li>
<p><strong>STEP 1</strong>：View documentation<br>選擇想要產生文檔的 Collection，按下「View documentation」後文檔就會顯示在右側，你可以透過切換右上角的環境變數來檢視文檔。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-18.webp" width="1024" height="688" alt="2.5 匯出 API documentation 給同事使用指引（圖 18）" loading="lazy" decoding="async"></p>
</li>
<li>
<p><strong>STEP 2</strong>：Publish documentation</p>
<ol>
<li>
<p>當你確定文檔內容都沒問題後按「Publish」。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-19.webp" width="1024" height="234" alt="2.5 匯出 API documentation 給同事使用指引（圖 19）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>接著會要你調整一些參數，調整完後建議先用左側的「Preview Documentation」自己確認看看是否符合。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-20.webp" width="1024" height="770" alt="2.5 匯出 API documentation 給同事使用指引（圖 20）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>確認一切都沒問題後按下「Publish Collection」就發布出去嚕～</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-21.webp" width="1024" height="450" alt="2.5 匯出 API documentation 給同事使用指引（圖 21）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
<li>
<p><strong>STEP 4</strong>：Share API Doc URL<br>你可以將 URL 複製起來分享給你的合作夥伴，如果你想把這份文檔下架，按「Unpublish」即可。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-22.webp" width="1024" height="521" alt="2.5 匯出 API documentation 給同事使用指引（圖 22）" loading="lazy" decoding="async"></p>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="26-讓-mock-server-變成私有的">2.6 讓 Mock Server 變成私有的<a class="h-anchor" href="#26-%e8%ae%93-mock-server-%e8%ae%8a%e6%88%90%e7%a7%81%e6%9c%89%e7%9a%84" aria-label="這個段落的連結"></a></h3>
<p>前面在建立 Mock Server 時，不知道有沒有人注意到「<code>Make mock server private</code>」的選項，在做這個設定前 Mock Server 的 URL 都是公開的，如果你想為自己的 Mock Server 多一層防護可以跟著下面步驟操作：</p>
<ul>
<li>
<p><strong>STEP 1</strong> : 編輯 Mock server</p>
<ol>
<li>
<p>在「Mock Servers」分頁打開之前創建的 Server，按下「Edit」。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-23.webp" width="1024" height="386" alt="2.6 讓 Mock Server 變成私有的（圖 23）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>將「Make mock server private」打勾並按下「Save changes」。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-24.webp" width="1024" height="679" alt="2.6 讓 Mock Server 變成私有的（圖 24）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>回到之前的 Request，你會發現按下「Send」後會回傳錯誤訊息，這是因為現在 <mark>Mock Server 都需要添加 API key 到 Request 的 Header 才能使用</mark>。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-25.webp" width="1024" height="522" alt="2.6 讓 Mock Server 變成私有的（圖 25）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
<li>
<p><strong>STEP 2</strong> : 產生 Postman API key</p>
<ol>
<li>
<p>進去這個 <a href="https://go.postman.co/settings/me/api-keys">網址</a> ，點擊「Generate API key」。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-26.webp" width="1024" height="425" alt="2.6 讓 Mock Server 變成私有的（圖 26）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>填上用來識別這個 API key 的名稱後按下「Generate API key」。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-27.webp" width="1024" height="457" alt="2.6 讓 Mock Server 變成私有的（圖 27）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>接著<mark>請複製這個 API key</mark>，上面已經警告你這是唯一見到它的機會了。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-28.webp" width="1024" height="482" alt="2.6 讓 Mock Server 變成私有的（圖 28）" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
<li>
<p><strong>STEP 3</strong>：將 API key 加入 Environment<br>因為日後 Mock Server 的所有 Request 都需要這個 API key，所以我們把他加入 Environment，變數名稱為「x-api-key」。</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-29.webp" width="1024" height="394" alt="2.6 讓 Mock Server 變成私有的（圖 29）" loading="lazy" decoding="async"></p>
</li>
<li>
<p><strong>STEP 4</strong>：在 Request 的 Header 加上 x-api-key<br>最後回到 Request，在 Headers 的分頁下加入「x-api-key」這個參數並將環境變數 <code>{{x-api-key}}</code> 作為值，最後按下「Send」就能看到正確的 Response 回傳嚕！</p>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-30.webp" width="1024" height="456" alt="2.6 讓 Mock Server 變成私有的（圖 30）" loading="lazy" decoding="async"></p>
</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/afc5c04742e0">讓你的 Postman 更專業(筆者部落格)</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-15/images/fullstack-career-notes-day-15-31.webp" width="1024" height="233" alt="參考資源（圖 31）" 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>[面試][後端]請簡述 Node.js 的 Event Loop</title>
      <link>https://deanlin.net/blog/202109/fullstack-career-notes-day-14/</link>
      <pubDate>Wed, 29 Sep 2021 08:46:22 +0800</pubDate>
      <guid>https://deanlin.net/blog/202109/fullstack-career-notes-day-14/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>這是一個在了解後，無論面試還是工作，CP 值都很高的題目，建議讀者要真的融會貫通。</description>
      <media:content url="https://deanlin.net/blog/202109/fullstack-career-notes-day-14/images/cover_hu_579b7f5274d9e2b1.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>熟悉的起手式：「我方便問你一個 Node.js 核心的問題嗎？」</p>
</blockquote>
<p>這是一個在了解後，無論面試還是工作，CP 值都很高的題目，建議讀者要真的融會貫通。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>請簡述 Node.js 的 Event Loop</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 Event Loop 執行的邏輯</li>
<li>2.2 用 Node.js 範例程式確認自己是否理解 Event Loop</li>
<li>2.3 解析 Node.js 範例程式執行順序</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 可以簡述 Node.js 跟 JavaScript 的差異嗎？</li>
<li>3.2 readFile 和 createReadStream 函式的差異？</li>
<li>3.3 REPL 是什麼？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-請簡述-nodejs-的-event-loop">1. 請簡述 Node.js 的 Event Loop<a class="h-anchor" href="#1-%e8%ab%8b%e7%b0%a1%e8%bf%b0-nodejs-%e7%9a%84-event-loop" 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>只要你的<code>履歷上有 Node.js</code>，10 間公司至少有 3 間會問這個<mark>常見面試題</mark>；而且問題的起手式出奇的一致：「我方便問你一個 Node.js 核心的問題嗎？」</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>你了解 Event Loop 對 Node.js 的重要性</li>
<li>你能大概說明 Event Loop 每個階段的任務</li>
<li>如果出白板題要你解釋程式執行的順序，你可以正確回答</li>
</ul>
<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>Node.js 之所以高效，是因為採取單執行緒與 Event Loop 的概念</code>；將所有需要等待結果、請求外部資源的函式，全部丟到 Event Loop 中等待；而 Event Loop 的邏輯是 Node.js 底層用 C 語言撰寫的 libuv 庫來運行的。</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-event-loop-執行的邏輯">2.1 Event Loop 執行的邏輯<a class="h-anchor" href="#21-event-loop-%e5%9f%b7%e8%a1%8c%e7%9a%84%e9%82%8f%e8%bc%af" aria-label="這個段落的連結"></a></h3>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-14/images/fullstack-career-notes-day-14-01.webp" width="1024" height="579" alt="2.1 Event Loop 執行的邏輯（圖 1）" fetchpriority="high" decoding="async"></p>
<ul>
<li><strong>了解 MacroTask 的 6 個階段</strong>
<ul>
<li><strong>Timer</strong><br>等計時器（setTimeout、setInterval）的時間一到，會把他們的 callback 放在這裡等待執行。</li>
<li><strong>Pending callbacks</strong><br>作業系統層級使用（ex：TCP errors、sockets 連線被拒絕）。</li>
<li><strong>Idle, Prepare</strong><br>內部使用。</li>
<li><strong>Polling：</strong>
<ul>
<li>如果 Queue 不為空，依次取出 callback 函數執行，直到 Queue 為空或是抵達系統最大限制。</li>
<li>如果 Queue 為空但有設置「setImmediate」就進入 Check 階段。</li>
<li>如果 Queue 為空但沒有設置「setImmediate」就會在 Polling 階段等待，直到 Queue 有東西或是 Timer 時間抵達。</li>
</ul>
</li>
<li><strong>Check</strong><br>處理 setImmediate 的 callback。</li>
<li><strong>Close callbacks</strong><br>處理關閉連線、檔案的 callback。</li>
</ul>
</li>
</ul>
<p><mark>Macrotask Queue 在 Evevnt Loop 優先層級較低，接下來介紹優先層級更高的兩個 Queue。</mark></p>
<ul>
<li>
<p><strong>NextTick Queue 優先層級最高</strong><br>「process.nextTick()」的 callback 可以隨意插隊；只要這個 Queue 有東西就會優先執行。</p>
</li>
<li>
<p><strong>MicroTask Queue 優先層級次高</strong><br>「Promise」的狀態從 pending 轉變為 resolve 或 reject 時，執行的 callback 會進入這個 Queue。</p>
<blockquote>
<p><strong>注意</strong>：<mark>Promise 在創建時是同步的</mark>，不會進入 Event Loop。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-用-nodejs-範例程式確認自己是否理解-event-loop">2.2 用 Node.js 範例程式確認自己是否理解 Event Loop<a class="h-anchor" href="#22-%e7%94%a8-nodejs-%e7%af%84%e4%be%8b%e7%a8%8b%e5%bc%8f%e7%a2%ba%e8%aa%8d%e8%87%aa%e5%b7%b1%e6%98%af%e5%90%a6%e7%90%86%e8%a7%a3-event-loop" aria-label="這個段落的連結"></a></h3>
<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>console.log(<span style="color:#a5d6ff">&#34;start&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>process.nextTick(<span style="color:#ff7b72">function</span> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;nextTick1&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>setTimeout(<span style="color:#ff7b72">function</span> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;setTimeout&#34;</span>);
</span></span><span style="display:flex;"><span>}, <span style="color:#a5d6ff">0</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">new</span> Promise(<span style="color:#ff7b72">function</span> (resolve, reject) {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;promise&#34;</span>);
</span></span><span style="display:flex;"><span>  resolve(<span style="color:#a5d6ff">&#34;resolve&#34;</span>);
</span></span><span style="display:flex;"><span>}).then(<span style="color:#ff7b72">function</span> (result) {
</span></span><span style="display:flex;"><span>  console.log(result);
</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> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;async&#34;</span>);
</span></span><span style="display:flex;"><span>})();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>setImmediate(<span style="color:#ff7b72">function</span> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;setImmediate&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>process.nextTick(<span style="color:#ff7b72">function</span> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;nextTick2&#34;</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;end&#34;</span>);
</span></span></code></pre></div><p>如果答案跟下面結果一樣，那代表你很了解 Node.js 的 Event Loop 邏輯：</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>start
</span></span><span style="display:flex;"><span>promise
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span>
</span></span><span style="display:flex;"><span>end
</span></span><span style="display:flex;"><span>nextTick1
</span></span><span style="display:flex;"><span>nextTick2
</span></span><span style="display:flex;"><span>resolve
</span></span><span style="display:flex;"><span>setTimeout
</span></span><span style="display:flex;"><span>setImmediate
</span></span></code></pre></div><hr>
<h3 class="h-nomark" id="23-解析-nodejs-範例程式執行順序">2.3 解析 Node.js 範例程式執行順序<a class="h-anchor" href="#23-%e8%a7%a3%e6%9e%90-nodejs-%e7%af%84%e4%be%8b%e7%a8%8b%e5%bc%8f%e5%9f%b7%e8%a1%8c%e9%a0%86%e5%ba%8f" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>SETP 1：</strong> 順序執行，先把同步函式解決。
<ol>
<li>
<p>因為 <code>Promise</code> 在創建時是同步的，所以會先執行。</p>
</li>
<li>
<p>在 function 前面加上 async 宣告為異步函式，執行時會被轉換為 Promise。</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>console.log(<span style="color:#a5d6ff">&#34;start&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">new</span> Promise(<span style="color:#ff7b72">function</span> (resolve, reject) {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;promise&#34;</span>);
</span></span><span style="display:flex;"><span>  resolve(<span style="color:#a5d6ff">&#34;resolve&#34;</span>);
</span></span><span style="display:flex;"><span>}).then(<span style="color:#ff7b72">function</span> (result) {
</span></span><span style="display:flex;"><span>  console.log(result);
</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> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;async&#34;</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;end&#34;</span>);
</span></span></code></pre></div></li>
</ol>
</li>
<li><strong>STEP 2：</strong> 進入 Event Loop。
<ol>
<li>
<p>如果執行過程中遇到 <code>NextTick Queue</code> 就優先執行。</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>process.nextTick(<span style="color:#ff7b72">function</span> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;nextTick1&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>process.nextTick(<span style="color:#ff7b72">function</span> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;nextTick2&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div></li>
<li>
<p>在處理完最高優先層級後，如果執行過程中遇到 <code>MicroTask Queue</code> 就執行。</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">new</span> Promise(<span style="color:#ff7b72">function</span> (resolve, reject) {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;promise&#34;</span>);
</span></span><span style="display:flex;"><span>  resolve(<span style="color:#a5d6ff">&#34;resolve&#34;</span>);
</span></span><span style="display:flex;"><span>}).then(<span style="color:#ff7b72">function</span> (result) {
</span></span><span style="display:flex;"><span>  console.log(result);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div></li>
<li>
<p>在前兩個高優先層級的 Queue 清空後，返回 Event Loop 繼續執行，<code>setTimeout</code> 設定的時間已到先執行。</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>setTimeout(<span style="color:#ff7b72">function</span> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;setTimeout&#34;</span>);
</span></span><span style="display:flex;"><span>}, <span style="color:#a5d6ff">0</span>);
</span></span></code></pre></div></li>
<li>
<p>接著再在「Check」 階段執行 <code>setImmediate</code></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>setImmediate(<span style="color:#ff7b72">function</span> () {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">&#34;setImmediate&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div></li>
</ol>
</li>
</ul>
<p>以上步驟執行完成後就是範例程式的解答啦～希望對大家理解 Event Loop 有幫助。</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-可以簡述-nodejs-跟-javascript-的差異嗎">3.1 可以簡述 Node.js 跟 JavaScript 的差異嗎？<a class="h-anchor" href="#31-%e5%8f%af%e4%bb%a5%e7%b0%a1%e8%bf%b0-nodejs-%e8%b7%9f-javascript-%e7%9a%84%e5%b7%ae%e7%95%b0%e5%97%8e" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認求職者是否理解自己天天使用的工具</mark></p>
<p>JavaScript 是程式語言，可以在合適的瀏覽器中運行；而 Node.js 是一個能執行JavaScript 的環境，它以 Chrome V8 引擎為核心，再加上 C/C++套件，讓 Sever 端也可以執行 JavaScript。</p>
<hr>
<h3 class="h-nomark" id="32-readfile-和-createreadstream-函式的差異">3.2 readFile 和 createReadStream 函式的差異？<a class="h-anchor" href="#32-readfile-%e5%92%8c-createreadstream-%e5%87%bd%e5%bc%8f%e7%9a%84%e5%b7%ae%e7%95%b0" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：判斷求職者過去專案中使否使用過這類函式，並理解差異</mark></p>
<p><code>readFile</code> 函式會將讀取到的完整內容<mark>存在記憶體</mark>後傳給使用者，而 <code>createReadStream</code> 函式則是<mark>逐塊讀取</mark>檔案，不是全部存在記憶體中；因此要讀取較大的檔案時，建議使用 createReadStream ，利用其逐塊讀取的特性能減少使用者等待時間。</p>
<hr>
<h3 class="h-nomark" id="33-repl-是什麼">3.3 REPL 是什麼？<a class="h-anchor" href="#33-repl-%e6%98%af%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：對專有名詞是否熟悉，能否解釋</mark></p>
<p>REPL（Read Eval Print Loop）是一個用來<mark>執行程式語言的虛擬環境</mark>。</p>
<p>就像是我們在瀏覽器的 Console 可以執行 JavaScript 程式碼；在安裝完 Node.js 後，新增一個 JavaScript 檔，在終端機輸入<code>node xxx.js</code>便可以執行它。</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/c7129063d0f4">Node.js 的 Event Loop (事件輪詢)到底在做什麼？(筆者部落格)</a></li>
<li>15 個常見的 Node.js 面試問題及答案</li>
<li><a href="https://ithelp.ithome.com.tw/articles/10230126">你有沒有想過，到底 Server 是如何「同時處理多個 requests」的？ - Node.js 篇</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-14/images/fullstack-career-notes-day-14-02.webp" width="1024" height="233" alt="參考資源：（圖 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>[面試][後端]你會的後端框架不只一個，可以說明一下它們之間的差異嗎？</title>
      <link>https://deanlin.net/blog/202109/fullstack-career-notes-day-13/</link>
      <pubDate>Tue, 28 Sep 2021 08:52:51 +0800</pubDate>
      <guid>https://deanlin.net/blog/202109/fullstack-career-notes-day-13/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>工程師真的很難，會得不多會被問為什麼沒學，會得很多還要被質疑專業程度；這種比較類型的問題，除非你有事先做功課，不然就算你都會用也未必能在現場對答如流。</description>
      <media:content url="https://deanlin.net/blog/202109/fullstack-career-notes-day-13/images/cover_hu_fd19aa6d1c00a16c.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="#%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 PHP VS Node.js</li>
<li>2.2 用 supervisor 讓 Node.js 熱更新</li>
<li>2.3 不一定要拼個誰輸誰贏</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 既然你先學了 Node.js 的框架，為何後來改用 PHP 的框架？</li>
<li>3.2 使用框架有什麼好處跟壞處？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-你會的後端框架不只一個可以說明一下它們之間的差異嗎">1. 你會的後端框架不只一個，可以說明一下它們之間的差異嗎？<a class="h-anchor" href="#1-%e4%bd%a0%e6%9c%83%e7%9a%84%e5%be%8c%e7%ab%af%e6%a1%86%e6%9e%b6%e4%b8%8d%e5%8f%aa%e4%b8%80%e5%80%8b%e5%8f%af%e4%bb%a5%e8%aa%aa%e6%98%8e%e4%b8%80%e4%b8%8b%e5%ae%83%e5%80%91%e4%b9%8b%e9%96%93%e7%9a%84%e5%b7%ae%e7%95%b0%e5%97%8e" 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>中顯示求職者會如下的後端（Backend）框架：</p>
<ul>
<li>PHP：Laravel</li>
<li>Node.js：Express.js、AdonisJS</li>
</ul>
<p>當你會的東西多，面試官就會想了解你是每個都學一點，還是真的了解它。</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>能否簡單敘述 PHP 與 Node.js 的差異</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>PHP 的 Laravel 框架</code>開發過內部管理系統，也曾用 <code>Node.js 的 Express.js 框架</code>完成過餐廳點餐系統。</p>
<p>兩者的<mark>差異主要因為程式語言</mark>而產生的，PHP 是同步語言、多執行緒，Node.js 是一個能執行 JavaScript 的環境，支援非同步，為單執行緒。</p>
<p>如果專案要求<mark>穩定一致的效能</mark>，我會偏向使用 PHP 框架；而如果要<mark>承接高流量</mark>，我會選擇 Node.js 框架。</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-php-vs-nodejs">2.1 PHP VS Node.js<a class="h-anchor" href="#21-php-vs-nodejs" aria-label="這個段落的連結"></a></h3>
<p>如果面對一個問題只有<mark>一種解決方案</mark>時，面試官會<mark>深入詢問</mark>這個技術；但如果面對問題有<mark>兩個以上的解決方案</mark>，他就會問你在<mark>不同情境</mark>下該使用什麼技術，每個技術都有各自的優勢，這邊我們就從不同的角度來做分析吧～</p>
<ul>
<li><strong>同步的 PHP vs 異步的 Node.js</strong>
<ul>
<li>同步的 PHP 會為每個 Request 建立一個 thread，每個 thread 都會消耗記憶體，也就是說如果想要服務更多的客戶就要花心思在<mark>硬體設備</mark>上。</li>
<li>異步的 Node.js 是非阻塞 (non-blocking) I/O 設計，搭配上 Event Loop 可以用一句很形象的話說明：「與其人多但好多人閒著；還不如一個人拼命往死裡做。」</li>
</ul>
</li>
<li><strong>多執行緒的 PHP VS 單執行緒的 Node.js</strong>
<ul>
<li>
<p><strong>多執行緒 (multi-threaded)的 PHP</strong><br>使用阻塞 (blocking) I/O 的設計</p>
<blockquote>
<p>PHP 可以為每個 Request 都開一個執行緒；如果你想要實現非阻塞也能透過 <a href="https://reactphp.org/">reactPHP</a> 來實現。</p>
</blockquote>
</li>
<li>
<p><strong>單執行緒 (single-threaded)的 Node.js</strong><br>使用非阻塞 (non-blocking) I/O 設計 + 提供非同步 (asynchronous) 處理</p>
<blockquote>
<p>Node.js 原則上是單執行緒，但可以透過 <a href="https://pm2.keymetrics.io/">PM2</a> 的 Cluster Mode 達到多執行緒。</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>應用場景</strong><br>從 Client 端與 Server 端的溝通頻率來做分析：
<ul>
<li><strong>溝通頻率不高</strong><br>PHP 是個不錯的選擇（ex：個人部落格，像 WordPress 就是用 PHP 寫的）。</li>
<li><strong>溝通頻率頻繁</strong><br>Node.js 更能激發性能（ex：single-page-application、聊天室）；它適合大吞吐量的系統，較不適合需要大量 CPU 運算的系統。</li>
</ul>
</li>
<li><strong>可使用的資源</strong>
<ul>
<li>
<p><strong>PHP 有 Composer</strong><br>Composer 的命名方式：「所有者/package 名稱」</p>
</li>
<li>
<p><strong>Node.js 有 NPM</strong><br>NPM 的命名方式：「package 名稱」</p>
<blockquote>
<p>因為 NPM 的命名方式太自由，導致上面有很多的資源都是無效資源，<mark>關鍵字第一個找到的未必是最合適的</mark>。</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>人才市場</strong>
<ul>
<li><strong>人才數量</strong><br>PHP 比 Node.js 早了 10 幾年發行，因此市場上使用 PHP 的人比使用 Node.js 的人更多；如果<mark>職位流動性高，用 PHP 開發</mark>會比較合適。</li>
<li><strong>薪資</strong><br>物以稀為貴，同樣都是新人，<code>會 Node.js 的薪水會略高於會 PHP</code> 的求職者。</li>
</ul>
</li>
<li><strong>專案容量</strong><br>用 Laravel 跟 Express.js 來比較：
<ul>
<li><strong>專案初期</strong><br>一開始 Laravel 因為包山包海所以專案體積較大。</li>
<li><strong>專案後期</strong><br>儘管 Express 是 Node.js 輕量級的框架，但因為<mark>實作過程往往要擴充一堆 package</mark>，而且一個 package 往往會有很多的 dependency；這些原因容易導致最終實作出來的是一個笨重的專案。</li>
</ul>
</li>
<li><strong>程式語言轉換</strong><br>除非你的運氣逆天，每次都剛好找到符合你技能樹的公司，而且這間公司專案用的技術還很固定；不然肯定要面對程式語言的轉換，這邊分享幾個轉換時要注意的重點。
<ul>
<li><strong>找類型相似的框架</strong><br>從 PHP 跳到 Node.js 一定會面臨諸多不適應；但如果你選擇的是跟 <mark>Laravel 很類似的 AdonisJS 框架</mark>，這份不適感會下降許多，反之亦然。</li>
<li><strong>語言語法</strong><br>如果你已經熟悉了一種程式語言，我覺得轉換到另外一個語言的學習成本不會太高；但許多<mark>內建函式都要重新學習、生命週期也要重新熟悉、錯誤處理的經驗也要重新累積</mark>。</li>
<li><strong>專案規模</strong><br>如果是只需要 CRUD 邏輯的小專案，那我覺得藉此多瞭解一門程式語言是一個很棒的機會；但如果面臨的是業務邏輯複雜的大專案，我強烈建議一定要選自己熟悉的程式語言，或者團隊中的扛霸子熟悉這門語言；不然出事根本沒人可以救得了。</li>
</ul>
</li>
<li><strong>效能</strong>
<ul>
<li><strong>響應速度</strong><br>Node.js 靠著 V8 引擎加上非同步的特型，擁有較快的響應速度。</li>
</ul>
</li>
<li><strong>面對錯誤的處理</strong>
<ul>
<li>
<p>PHP 遇到某個 Request 錯誤時，它只對這個 Request 產生影響。</p>
</li>
<li>
<p>Node.js 因為所有 Request 都在單執行緒的 Web-server 中，所以某個 Request 導致的未知錯誤就可能影響到整個 Server。</p>
<blockquote>
<p>不過這塊可以透過 PM2 的 Cluster Mode 與自動重啟來補救。</p>
</blockquote>
</li>
</ul>
</li>
<li><strong>熱更新</strong>
<ul>
<li>PHP 修改後，只要刷新頁面就能看到。</li>
<li>Node.js 預設沒有熱更新，需要關閉專案後重啟。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-用-supervisor-讓-nodejs-熱更新">2.2 用 supervisor 讓 Node.js 熱更新<a class="h-anchor" href="#22-%e7%94%a8-supervisor-%e8%ae%93-nodejs-%e7%86%b1%e6%9b%b4%e6%96%b0" aria-label="這個段落的連結"></a></h3>
<p>在開發階段推薦使用 supervisor 這款套件。</p>
<ul>
<li>
<p><strong>STEP 1</strong>：在全域安裝套件<code>npm install supervisor -g</code></p>
</li>
<li>
<p><strong>STEP 2</strong>：建立一個<code>myapp.js</code>的檔案，並複製貼上下面程式</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">var</span> http <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;http&#34;</span>);
</span></span><span style="display:flex;"><span>http
</span></span><span style="display:flex;"><span>  .createServer(<span style="color:#ff7b72">function</span> (req, res) {
</span></span><span style="display:flex;"><span>    res.writeHead(<span style="color:#a5d6ff">200</span>, { <span style="color:#a5d6ff">&#34;Content-Type&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;text/plain&#34;</span> });
</span></span><span style="display:flex;"><span>    res.end(<span style="color:#a5d6ff">&#34;Hello World!&#34;</span>);
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  .listen(<span style="color:#a5d6ff">3000</span>, <span style="color:#a5d6ff">&#34;127.0.0.1&#34;</span>);
</span></span></code></pre></div></li>
<li>
<p><strong>STEP 3</strong>：用<code>node myapp.js</code>啟動程式後，修改<code>res.end(&quot;Hello World!&quot;);</code>的內容；再刷新網頁後<mark>內容不變</mark>。</p>
</li>
<li>
<p><strong>STEP 4</strong>：改用<code>supervisor myapp.js</code>啟動程式，修改<code>res.end(&quot;Hello World!&quot;);</code>的內容；再刷新網頁後<mark>內容更新</mark>。</p>
</li>
</ul>
<p>supervisor 也可以做專案資料夾的監控，但是要記得排除一些資料夾（ex：儲存 log 檔的）；否則你的專案就無時無刻在重起了。</p>
<blockquote>
<p>如果是正式的網站且流量大，建議使用 PM2 來做處理，它也有監控資料夾的功能。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="23-不一定要拼個誰輸誰贏">2.3 不一定要拼個誰輸誰贏<a class="h-anchor" href="#23-%e4%b8%8d%e4%b8%80%e5%ae%9a%e8%a6%81%e6%8b%bc%e5%80%8b%e8%aa%b0%e8%bc%b8%e8%aa%b0%e8%b4%8f" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>存在即合理</p>
</blockquote>
<ul>
<li><strong>小專案不用花太多時間在評估框架及程式語言選擇上</strong><br>有些公司對專案要用什麼框架以及程式語言保持著非常謹慎的態度，謹慎的態度是對的，但也要考量到專案的規模；如果今天的專案就是 CRUD 的簡單應用，<mark>預估使用者數量不多或是主要來自於內部成員，用什麼框架及程式語言其實都差不多</mark>。</li>
<li><strong>看組織成員擅長的語言</strong><br>如果公司高層為了追求潮流而要求開發人員用一個不熟悉的框架去做專案，他最好祈求專案上線後平平安安；不然<mark>出事了公司沒有能夠處理的人才</mark>，沒有人會在意一個不能用的網站效能有多好。</li>
<li><strong>每個程式語言都有自己的擁護者</strong><br>在不同的條件下，每個程式語言都有自己的優勢，而且每年都會誕生新的框架與程式語言；筆者覺得與其不停盲目追逐新的技術，不如先深入研究一個成熟的技術，<mark>當你有辦法靠它解決各種現實難題時，再去學新的框架跟程式語言也不晚</mark>。</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-既然你先學了-nodejs-的框架為何後來改用-php-的框架">3.1 既然你先學了 Node.js 的框架，為何後來改用 PHP 的框架？<a class="h-anchor" href="#31-%e6%97%a2%e7%84%b6%e4%bd%a0%e5%85%88%e5%ad%b8%e4%ba%86-nodejs-%e7%9a%84%e6%a1%86%e6%9e%b6%e7%82%ba%e4%bd%95%e5%be%8c%e4%be%86%e6%94%b9%e7%94%a8-php-%e7%9a%84%e6%a1%86%e6%9e%b6" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：這題其實是想了解求職者的人格特質</mark></p>
<p>這兩個都是為了解決問題的技術，我一開始用 Node.js 的 Express 框架<mark>獨立完成</mark>了幾份專案；但後來有一個<code>需要 Team work</code> 的大型專案，因為當時的組長比較熟悉 PHP 的 Laravel 框架，所以我便藉由這個機會<mark>學習新的技術</mark>；也因為這次的技術轉換，讓我<mark>了解不同框架在部署以及效能各方面的差異</mark>。</p>
<blockquote>
<p><strong>我想透過上面的回答給面試官幾個訊息：</strong></p>
<ul>
<li>我能獨立完成專案，也可以團隊合作。</li>
<li>我願意學習新技術。</li>
<li>學習的同時會分析不同框架的優劣。</li>
</ul>
</blockquote>
<hr>
<h3 class="h-nomark" id="32-使用框架有什麼好處跟壞處">3.2 使用框架有什麼好處跟壞處？<a class="h-anchor" href="#32-%e4%bd%bf%e7%94%a8%e6%a1%86%e6%9e%b6%e6%9c%89%e4%bb%80%e9%ba%bc%e5%a5%bd%e8%99%95%e8%b7%9f%e5%a3%9e%e8%99%95" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認求職者對框架的認知</mark></p>
<ul>
<li><strong>好處</strong><br>框架能夠<mark>讓初學者快速入門並開發出有一定水平</mark>的專案，因為框架提供了許多常用的函式，因此可以<mark>大幅縮短專案時程</mark>；在團隊協作上框架優勢更為明顯，能夠讓成員<mark>有一致的開發方式</mark>。</li>
<li><strong>壞處</strong><br>因為框架已經幫開發人員做了太多事情，這會導致<mark>開發人員並不理解許多功能的原理</mark>，這也造成了<mark>一但框架無法滿足功能，初學者的生產力會斷崖式下降</mark>；如果長期使用，也容易<mark>讓自己的開發思維被限制在框架內</mark>。</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>
<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://thomashunter.name/posts/2012-06-21-php-vs-nodejs">PHP + Apache Stack vs Node.js</a></li>
<li><a href="https://web.archive.org/web/20220124190306/https://www.itread01.com/content/1549876684.html" title="原網頁已失效，連到網際網路檔案館的存檔">服務端 I/O 效能大比拼：Node、PHP、Java、Go</a></li>
<li><a href="https://jishuin.proginn.com/p/763bfbd2cab5">簡析 Node.js 特點與應用場景</a></li>
<li><a href="https://www.jianshu.com/p/6d84e5efe99d">詳細版 | 用 Supervisor 守護你的 Node.js 進程</a></li>
<li><a href="https://iter01.com/403097.html">淺談 Node.js 和 PHP 程式管理</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-13/images/fullstack-career-notes-day-13-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/202109/fullstack-career-notes-day-12/</link>
      <pubDate>Mon, 27 Sep 2021 08:27:06 +0800</pubDate>
      <guid>https://deanlin.net/blog/202109/fullstack-career-notes-day-12/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>回想當年還是前端菜鳥時，我完全信任後端前輩回傳的資料；記得當年有個產品上線前的測試一切正常，但上線幾天後 PM 就接到客戶回報系統上有頁面顯示不全的 Bug；因為網頁前端是我負責的，所以第一個被抓去興師問罪。</description>
      <media:content url="https://deanlin.net/blog/202109/fullstack-career-notes-day-12/images/cover_hu_787c03eca8e89ac4.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<h2 id="筆者背鍋小故事">筆者背鍋小故事<a class="h-anchor" href="#%e7%ad%86%e8%80%85%e8%83%8c%e9%8d%8b%e5%b0%8f%e6%95%85%e4%ba%8b" aria-label="這個段落的連結"></a></h2>
<p>回想當年還是前端菜鳥時，我完全信任後端前輩回傳的資料；記得當年有個產品上線前的測試一切正常，但上線幾天後 PM 就接到客戶回報系統上有頁面顯示不全的 Bug；因為網頁前端是我負責的，所以第一個被抓去興師問罪。</p>
<p>經驗不足的我在收到 Bug 後，先用本機的環境模擬客戶遇到的問題，但弄了老半天始終查不出問題發生的原因；最後到客戶的正式機上查看顯示不全的頁面，才發現原來是<mark>後端忘記更新正式機的 API 版本</mark>；把狀況跟後端的前輩溝通後，才順利解決這個 Bug。</p>
<p>為了避免這種狀況再次發生；之後在寫前端時，都會先驗證後端回傳的資料是否符合規定的 JSON 資料結構。</p>
<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>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 認識「JSON schema」</li>
<li>2.2 安裝「jsonschema」並了解它的優點</li>
<li>2.3 「jsonschema」驗證範例</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-%e5%9c%a8%e4%bd%bf%e7%94%a8%e5%be%8c%e7%ab%af%e7%9a%84%e8%b3%87%e6%96%99%e5%89%8d%e4%bd%a0%e6%9c%89%e5%85%88%e5%81%9a%e9%a9%97%e8%ad%89%e5%97%8e" 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>，主要確認你的前端技術力，這類題目範圍非常廣泛；有興趣的朋友可以看我放在參考資源的「<a href="https://h5bp.org/Front-end-Developer-Interview-Questions/translations/chinese-traditional/">前端工程師面試問題集</a>」；因為問題太過廣泛，幾乎無從準備，所以面試時<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>
</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>，在前端使用後端的資料前，我會先透過「jsonschema」這款套件<mark>驗證 JSON 資料結構</mark>，確認後端回傳的欄位是否齊全、格式是否正確；以此避免前端顯示有問題的資訊，在遇到問題時也可以<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-認識json-schema">2.1 認識「JSON schema」<a class="h-anchor" href="#21-%e8%aa%8d%e8%ad%98json-schema" aria-label="這個段落的連結"></a></h3>
<p>在介紹套件前，先讓大家對「JSON schema」有一個基礎認知；它是一種基於 JSON 格式定義 JSON 資料結構的規範，下面放上一段範例讓大家快速了解：</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">const</span> person_schema <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>  type<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;object&#34;</span>,
</span></span><span style="display:flex;"><span>  properties<span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>    name<span style="color:#ff7b72;font-weight:bold">:</span> { type<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;string&#34;</span> },
</span></span><span style="display:flex;"><span>    age<span style="color:#ff7b72;font-weight:bold">:</span> { type<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;number&#34;</span>, maximum<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">150</span> },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  required<span style="color:#ff7b72;font-weight:bold">:</span> [<span style="color:#a5d6ff">&#34;name&#34;</span>, <span style="color:#a5d6ff">&#34;age&#34;</span>],
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>我相信上面這個「JSON schema」應該是非常簡單易懂，他要求 JSON 的格式需要符合以下要求：</p>
<ul>
<li>name 為 string</li>
<li>age 為 number 且最大值不得超過 150</li>
<li>name 與 age 都是必需的</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-安裝jsonschema並了解它的優點">2.2 安裝「jsonschema」並了解它的優點<a class="h-anchor" href="#22-%e5%ae%89%e8%a3%9djsonschema%e4%b8%a6%e4%ba%86%e8%a7%a3%e5%ae%83%e7%9a%84%e5%84%aa%e9%bb%9e" aria-label="這個段落的連結"></a></h3>
<p>現在有許多套件提供驗證 JSON 格式的功能，今天筆者用自己熟悉的套件：「<code>jsonschema</code>」來跟讀者們分享，了解使用的邏輯後，想換成其他套件都非常容易。</p>
<h4 id="安裝步驟">安裝步驟<a class="h-anchor" href="#%e5%ae%89%e8%a3%9d%e6%ad%a5%e9%a9%9f" aria-label="這個段落的連結"></a></h4>
<ul>
<li><strong>SETP 1</strong>：建立一個專案資料夾。</li>
<li><strong>SETP 2</strong>：在終端機輸入 <code>npm init</code> 初始化專案。</li>
<li><strong>SETP 3</strong>：在終端機輸入 <code>npm i jsonschema</code> 安裝套件。</li>
</ul>
<h4 id="套件優點">套件優點<a class="h-anchor" href="#%e5%a5%97%e4%bb%b6%e5%84%aa%e9%bb%9e" aria-label="這個段落的連結"></a></h4>
<p>如果後端 API 版本變更、 DB 的 Table 欄位變更沒有通知前端，前端頁面就可能發生新資料沒有位置放、舊資料部分欄位為空的狀況；如果沒有這個套件守護前端，工程師可能就要背黑鍋了。</p>
<ul>
<li>可以詳細定義要驗證的 JSON 資料結構，包含內部所有參數的型態（string/number/array/object）。</li>
<li>驗證到錯誤格式時，會明確說明錯誤的原因。</li>
<li>可客製化錯誤訊息，轉換成好理解的文字。</li>
<li>學習及使用非常直覺。</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-jsonschema驗證範例">2.3 「jsonschema」驗證範例<a class="h-anchor" href="#23-jsonschema%e9%a9%97%e8%ad%89%e7%af%84%e4%be%8b" aria-label="這個段落的連結"></a></h3>
<ul>
<li>
<p><strong>SETP 1</strong>：在專案資料夾下建立「simple.js」來了解如何驗證 JSON 資料結構。</p>
</li>
<li>
<p><strong>STEP 2</strong>：以剛剛的 <code>person_schema</code> 作為範例來跟大家做講解，程式如下：</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">var</span> Validator <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;jsonschema&#34;</span>).Validator;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">var</span> v <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> Validator();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> personSchema <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>  type<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;object&#34;</span>,
</span></span><span style="display:flex;"><span>  properties<span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>    name<span style="color:#ff7b72;font-weight:bold">:</span> { type<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;string&#34;</span> },
</span></span><span style="display:flex;"><span>    age<span style="color:#ff7b72;font-weight:bold">:</span> { type<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;number&#34;</span>, maximum<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">150</span> },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  required<span style="color:#ff7b72;font-weight:bold">:</span> [<span style="color:#a5d6ff">&#34;name&#34;</span>, <span style="color:#a5d6ff">&#34;age&#34;</span>],
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">var</span> person <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>  name<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;baobao&#34;</span>,
</span></span><span style="display:flex;"><span>  age<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">14</span>,
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> v.validate(person, personSchema);
</span></span><span style="display:flex;"><span>console.log(<span style="color:#a5d6ff">&#34;valid：&#34;</span> <span style="color:#ff7b72;font-weight:bold">+</span> result.valid);
</span></span><span style="display:flex;"><span>console.log(result);
</span></span></code></pre></div></li>
<li>
<p><strong>STEP 3</strong>：在終端機輸入 <code>node simple.js</code>，會看到如下的驗證結果：</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>valid<span style="color:#f85149">：</span><span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span>ValidatorResult {
</span></span><span style="display:flex;"><span>  instance<span style="color:#ff7b72;font-weight:bold">:</span> { name<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;baobao&#39;</span>, age<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">14</span> },
</span></span><span style="display:flex;"><span>  schema<span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>      type<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;object&#39;</span>,
</span></span><span style="display:flex;"><span>      properties<span style="color:#ff7b72;font-weight:bold">:</span> { name<span style="color:#ff7b72;font-weight:bold">:</span> [Object], age<span style="color:#ff7b72;font-weight:bold">:</span> [Object] },
</span></span><span style="display:flex;"><span>      required<span style="color:#ff7b72;font-weight:bold">:</span> [ <span style="color:#a5d6ff">&#39;name&#39;</span>, <span style="color:#a5d6ff">&#39;age&#39;</span> ]
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  options<span style="color:#ff7b72;font-weight:bold">:</span> {},
</span></span><span style="display:flex;"><span>  path<span style="color:#ff7b72;font-weight:bold">:</span> [],
</span></span><span style="display:flex;"><span>  propertyPath<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;instance&#39;</span>,
</span></span><span style="display:flex;"><span>  errors<span style="color:#ff7b72;font-weight:bold">:</span> [],
</span></span><span style="display:flex;"><span>  throwError<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">undefined</span>,
</span></span><span style="display:flex;"><span>  throwFirst<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">undefined</span>,
</span></span><span style="display:flex;"><span>  throwAll<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">undefined</span>,
</span></span><span style="display:flex;"><span>  disableFormat<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#79c0ff">false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
<li>
<p><strong>STEP 4</strong>：了解輸出參數的意義</p>
<ul>
<li><strong>valid</strong>：用布林（boolean）型態回傳驗證是否通過（true/false）。</li>
<li><strong>instance</strong>：這是我們想要驗證的資料，此範例要驗證的資料是<code>person</code>這個變數。</li>
<li><strong>schema</strong>：我們提供驗證的規則（JSON schema）。</li>
<li><strong>errors</strong>：會以陣列的形式顯示驗證發現的錯誤，你可以透過這個參數在前端顯示錯誤原因；因此次驗證的資料結構正確，故回傳空陣列。</li>
</ul>
</li>
<li>
<p><strong>STEP 5</strong>：將<code>person</code>改為錯誤的資料結構：</p>
<ul>
<li>將必填參數<code>name</code>移除</li>
<li>把 age 的數字填寫<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">var</span> person <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>  age<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">200</span>,
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div></li>
<li>
<p><strong>STEP 6</strong>：在終端機輸入 <code>node simple.js</code>，確認驗證結果的 errors 是否捕捉到錯誤。</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>errors<span style="color:#ff7b72;font-weight:bold">:</span> [
</span></span><span style="display:flex;"><span>    ValidationError {
</span></span><span style="display:flex;"><span>      path<span style="color:#ff7b72;font-weight:bold">:</span> [Array],
</span></span><span style="display:flex;"><span>      property<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;instance.age&#39;</span>,
</span></span><span style="display:flex;"><span>      message<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;must be less than or equal to 150&#39;</span>,
</span></span><span style="display:flex;"><span>      schema<span style="color:#ff7b72;font-weight:bold">:</span> [Object],
</span></span><span style="display:flex;"><span>      instance<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">200</span>,
</span></span><span style="display:flex;"><span>      name<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;maximum&#39;</span>,
</span></span><span style="display:flex;"><span>      argument<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">150</span>,
</span></span><span style="display:flex;"><span>      stack<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;instance.age must be less than or equal to 150&#39;</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    ValidationError {
</span></span><span style="display:flex;"><span>      path<span style="color:#ff7b72;font-weight:bold">:</span> [],
</span></span><span style="display:flex;"><span>      property<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;instance&#39;</span>,
</span></span><span style="display:flex;"><span>      message<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;requires property &#34;name&#34;&#39;</span>,
</span></span><span style="display:flex;"><span>      schema<span style="color:#ff7b72;font-weight:bold">:</span> [Object],
</span></span><span style="display:flex;"><span>      instance<span style="color:#ff7b72;font-weight:bold">:</span> [Object],
</span></span><span style="display:flex;"><span>      name<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;required&#39;</span>,
</span></span><span style="display:flex;"><span>      argument<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;name&#39;</span>,
</span></span><span style="display:flex;"><span>      stack<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;instance requires property &#34;name&#34;&#39;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>],
</span></span></code></pre></div></li>
</ul>
<p>透過以上範例，相信大家都明白「jsonschema」套件的基礎使用方式，有興趣深入研究的朋友請參考<a href="https://www.npmjs.com/package/jsonschema">官網</a>文件；如果沒接觸過「JSON schema」的朋友建議自己手動實做看看，這算是非常重要的前端概念。</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%89%8d%e7%ab%af%e9%96%8b%e7%99%bc%e6%99%82%e6%9c%83%e8%80%83%e6%85%ae%e5%93%aa%e4%ba%9b%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>這就是個大哉問，每個人都有自己考慮的點，我建議回答自己擅長且真的有導入專案的部分，因為通常後續會有更深入的問題。</p>
</blockquote>
<p><mark>考點：透過一個廣泛的問題，了解你開發時最在意的點</mark></p>
<p>為了<mark>減少設計變更</mark>，在開始前會先請 UI/UX 設計好 <code>Wireframe</code>（<a href="https://www.figma.com/">Figma</a>），並向使用者說明流程<mark>取得共識</mark>，直到雙方確認都沒問題後再進入開發階段；為了讓網頁風格一致，前端在設計時會遵循 <code>Material Design</code>。</p>
<p>為了提升開發效率，會先思考哪個 <code>Framework</code> 較適合這個專案；在依照設計提供的 Protype 完成設計後，會先用<a href="https://medium.com/dean-lin/de5f9efe9f4c">Responsively</a>這款工具，確認網頁在<mark>不同解析度的裝置上是否會跑版</mark>。</p>
<p>完成前期開發後，會思考如何<mark>優化效能</mark>，像是用 lazy loading 提升頁面載入速度、減少頁面 Request 數量及 Response 時間，透過這些設計讓使用者有更好的體驗。</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/a0940809a394#08eb">拒當前端黑鍋俠！用「jsonschema」來驗證後端回傳的 json 資料結構(筆者部落格)</a></li>
<li><a href="https://github.com/ajv-validator/ajv">ajv-validator/ajv</a></li>
<li><a href="https://github.com/Hsueh-Jen/blog/issues/2">驗證資料的好幫手 - JSON Schema Validator</a></li>
<li><a href="https://h5bp.org/Front-end-Developer-Interview-Questions/translations/chinese-traditional/">前端工程師面試問題集</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202109/fullstack-career-notes-day-12/images/fullstack-career-notes-day-12-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>
