<?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>Frontend Development｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/frontend-development/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>Frontend Development｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/frontend-development/</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, 19 Jan 2023 10:55:37 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/frontend-development/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>認識 Jest 測試框架，開始動手寫單元測試（TypeScript）</title>
      <link>https://deanlin.net/blog/202301/jest-unit-testing-typescript/</link>
      <pubDate>Thu, 19 Jan 2023 10:55:37 +0800</pubDate>
      <guid>https://deanlin.net/blog/202301/jest-unit-testing-typescript/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>這篇文章會從建立專案開始，依序帶你了解需要設定的環境、單元測試的結構，接著帶你完成第一個單元測試。 在對單元測試有基礎的概念後，會透過實際的範例讓你了解不同匹配器（Matchers）的使用時機，話不多說，我們直接開始吧！</description>
      <media:content url="https://deanlin.net/blog/202301/jest-unit-testing-typescript/images/jest-unit-testing-typescript-og_hu_b8982ad14b0c0dbf.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>如果你還在猶豫是否學習測試框架，不妨先看看筆者在<a href="https://deanlin.net/blog/202301/frontend-testing-roadmap/">上一篇文章</a>的分享喔！</p>
</blockquote>
<p>這篇文章會從建立專案開始，依序帶你了解需要設定的環境、單元測試的結構，接著帶你完成第一個單元測試。</p>
<p>在對單元測試有基礎的概念後，會透過實際的範例讓你了解不同匹配器（Matchers）的使用時機，話不多說，我們直接開始吧！</p>
<p><img src="https://deanlin.net/blog/202301/jest-unit-testing-typescript/images/jest-unit-testing-typescript-01.webp" width="1147" height="599" alt="認識 Jest 測試框架，開始動手寫單元測試（TypeScript）（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>大綱

一、初始化專案、設定測試環境
二、了解單元測試的結構，撰寫程式
三、了解 Jest 常用的匹配器（Matchers）
</code></pre><blockquote>
<p>讀者可以直接到<a href="https://github.com/dean9703111/jest-typescript-starter/tree/master/jest-basic">筆者的 Github</a> ，將專案 Clone 下來跑跑看，會更好理解裏面的邏輯（順手按個 ⭐ 更好 🤩）。</p>
</blockquote>
<h2 id="一初始化專案設定測試環境">一、初始化專案、設定測試環境<a class="h-anchor" href="#%e4%b8%80%e5%88%9d%e5%a7%8b%e5%8c%96%e5%b0%88%e6%a1%88%e8%a8%ad%e5%ae%9a%e6%b8%ac%e8%a9%a6%e7%92%b0%e5%a2%83" aria-label="這個段落的連結"></a></h2>
<p><strong>SETP 1</strong>：初始化專案＆安裝 Jest。</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-bash" data-lang="bash"><span style="display:flex;"><span>mkdir jest-practice
</span></span><span style="display:flex;"><span>cd jest-practice
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>npm init -f
</span></span><span style="display:flex;"><span>npm install jest babel-jest ts-jest @types/jest
</span></span></code></pre></div><p><strong>SETP 2</strong>：進入「package.json」增加測試用 scripts。</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-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f85149">...</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;scripts&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;test&#34;</span>: <span style="color:#a5d6ff">&#34;jest&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f85149">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>SETP 3</strong>：在根目錄下新增「jest.config.ts」。</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>module.exports <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>  transform<span style="color:#ff7b72;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;^.+\\.tsx?$&#39;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;ts-jest&#39;</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  moduleFileExtensions<span style="color:#ff7b72;font-weight:bold">:</span> [<span style="color:#a5d6ff">&#39;ts&#39;</span>],
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p><strong>SETP 4</strong>：在根目錄下新增「tsconfig.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-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#7ee787">&#34;compilerOptions&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;esModuleInterop&#34;</span>: <span style="color:#79c0ff">true</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 id="二了解單元測試的結構撰寫程式">二、了解單元測試的結構，撰寫程式<a class="h-anchor" href="#%e4%ba%8c%e4%ba%86%e8%a7%a3%e5%96%ae%e5%85%83%e6%b8%ac%e8%a9%a6%e7%9a%84%e7%b5%90%e6%a7%8b%e6%92%b0%e5%af%ab%e7%a8%8b%e5%bc%8f" aria-label="這個段落的連結"></a></h2>
<p>我們就用最簡單的「加法」來做說明：<code>a + b = c</code></p>
<p>如果把加法寫成單元測試，可以拆解成下面的結構：</p>
<ul>
<li><strong>測試的目標</strong>：我們要證明 <code>a + b = c</code>。</li>
<li><strong>要測試的 function</strong>：要測試 <code>sum</code> 這個 function。</li>
<li><strong>測試期待的結果</strong>：確認 sum 執行 <code>a + b</code> 的結果等於 <code>c</code>。</li>
</ul>
<p><strong>SETP 1</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-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 放測試的 function</span>
</span></span><span style="display:flex;"><span>touch public.ts
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 測試的腳本</span>
</span></span><span style="display:flex;"><span>touch public.test.ts
</span></span></code></pre></div><p><strong>SETP 2</strong>：在 public.ts 撰寫 <code>sum</code> 這個 function，並於 public.test.ts 用測試程式來驗證他。</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-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// public.ts
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">export</span> <span style="color:#ff7b72">function</span> sum (a: <span style="color:#ff7b72">number</span> ,b: <span style="color:#ff7b72">number</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> a <span style="color:#ff7b72;font-weight:bold">+</span> b;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// public.test.ts
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { sum } <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">require</span>(<span style="color:#a5d6ff">&#39;./public&#39;</span>);
</span></span><span style="display:flex;"><span>test(<span style="color:#a5d6ff">&#39;測試 8 + 9 等於 17&#39;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(sum(<span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">9</span>)).toBe(<span style="color:#a5d6ff">17</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p><strong>SETP 3</strong>：運行測試指令 <code>npm run test</code> ，如果順利就會看到下圖的結果：</p>
<p><img src="https://deanlin.net/blog/202301/jest-unit-testing-typescript/images/jest-unit-testing-typescript-02.webp" width="694" height="520" alt="二、了解單元測試的結構，撰寫程式（圖 2）" loading="lazy" decoding="async"></p>
<p>最後我們再來回顧一下先前提到的結構：</p>
<ul>
<li><strong>測試的目標</strong>：test(<code>測試 8 + 9 等於 17</code>, () =&gt; {});</li>
<li><strong>要測試的 function</strong>：<code>sum(8, 9)</code></li>
<li><strong>測試期待的結果</strong>：expect(sum(8, 9)).toBe(<code>17</code>);</li>
</ul>
<hr>
<h2 id="三了解-jest-常用的匹配器matchers">三、了解 Jest 常用的匹配器（Matchers）<a class="h-anchor" href="#%e4%b8%89%e4%ba%86%e8%a7%a3-jest-%e5%b8%b8%e7%94%a8%e7%9a%84%e5%8c%b9%e9%85%8d%e5%99%a8matchers" aria-label="這個段落的連結"></a></h2>
<p>匹配器（Matchers）是讓我們驗證執行結果是否符合預期的工具，下面筆者放上幾個常用的範例。</p>
<ul>
<li>toBe：最常用的匹配器。</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-typescript" data-lang="typescript"><span style="display:flex;"><span>test(<span style="color:#a5d6ff">&#39;toBe 匹配器其實能取代許多 Matcher&#39;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    expect(<span style="color:#a5d6ff">1</span> <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">2</span>).toBe(<span style="color:#a5d6ff">3</span>);
</span></span><span style="display:flex;"><span>    expect(<span style="color:#79c0ff">null</span>).toBe(<span style="color:#79c0ff">null</span>);
</span></span><span style="display:flex;"><span>    expect(<span style="color:#79c0ff">null</span>).toBeNull();
</span></span><span style="display:flex;"><span>    expect(<span style="color:#79c0ff">undefined</span>).toBe(<span style="color:#79c0ff">undefined</span>);
</span></span><span style="display:flex;"><span>    expect(<span style="color:#79c0ff">undefined</span>).toBeUndefined();
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><ul>
<li>toEqual：如果要物件（object）間的比對可以使用它。</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-typescript" data-lang="typescript"><span style="display:flex;"><span>test(<span style="color:#a5d6ff">&#39;toEqual 主要是比對物件&#39;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> data <span style="color:#ff7b72;font-weight:bold">=</span> { one: <span style="color:#ff7b72">1</span> };
</span></span><span style="display:flex;"><span>    data[<span style="color:#a5d6ff">&#39;two&#39;</span>] <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2</span>;
</span></span><span style="display:flex;"><span>    expect(data).toEqual({ one: <span style="color:#ff7b72">1</span>, two: <span style="color:#ff7b72">2</span> });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><ul>
<li>toBeCloseTo：應用在小數點的計算。</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-typescript" data-lang="typescript"><span style="display:flex;"><span>test(<span style="color:#a5d6ff">&#39;小數點相加&#39;</span>, () <span style="color:#ff7b72;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> value <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0.1</span> <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">0.2</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 有小數點的請使用 toBeCloseTo，否則會有 rounding error
</span></span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// expect(value).toBe(0.3);
</span></span></span><span style="display:flex;"><span>    expect(value).toBeCloseTo(<span style="color:#a5d6ff">0.3</span>); <span style="color:#8b949e;font-style:italic">// This works.
</span></span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><ul>
<li>toMatch：字串比對。</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>test(<span style="color:#a5d6ff">&#39;透過 toMatch 確認「stop」是否存在於字串&#39;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    expect(<span style="color:#a5d6ff">&#39;Christoph&#39;</span>).toMatch(<span style="color:#79c0ff">/stop/</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><ul>
<li>toContain：確認「值」是否存在於陣列。</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>test(<span style="color:#a5d6ff">&#39;toContain 會透過迭代來確認陣列是否存在某值&#39;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> shoppingList <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#39;diapers&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#39;kleenex&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#39;trash bags&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#39;paper towels&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#39;milk&#39;</span>,
</span></span><span style="display:flex;"><span>    ];
</span></span><span style="display:flex;"><span>    expect(shoppingList).toContain(<span style="color:#a5d6ff">&#39;milk&#39;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><ul>
<li>toThrow：確認吐出的錯誤訊息是否符合期待。</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>test(<span style="color:#a5d6ff">&#39;toThrow 可以用來檢核錯誤訊息&#39;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    expect(() =&gt; doSomething()).toThrow();
</span></span><span style="display:flex;"><span>    expect(() =&gt; doSomething()).toThrow(Error);
</span></span><span style="display:flex;"><span>    expect(() =&gt; doSomething()).toThrow(<span style="color:#a5d6ff">&#39;Something wrong&#39;</span>);
</span></span><span style="display:flex;"><span>    expect(() =&gt; doSomething()).toThrow(<span style="color:#79c0ff">/wrong/</span>);
</span></span><span style="display:flex;"><span>    expect(() =&gt; doSomething()).toThrow(<span style="color:#79c0ff">/^Something wrong!$/</span>);<span style="color:#8b949e;font-style:italic">// 若要求完全相同
</span></span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>以上是 Jest 測試框架的入門應用，如果想更深入的了解，可以參考筆者的文章： <a href="https://deanlin.net/blog/202301/jest-advanced-mock-setup-teardown/">Jest 框架進階應用（describe、Setup and Teardown、Mock function）</a></p>
<a class="linkcard" href="https://deanlin.net/series/frontend-testing/">
  <span class="lc-body">
    <span class="lc-title">前端單元測試</span>
    <span class="lc-desc">替前端專案導入測試：學習地圖、Jest 基礎與進階，再到 Vuetify（Vite &amp; TypeScript）中的 @vue/test-utils。</span>
    <span class="lc-site"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M5 12h14M13 6l6 6-6 6"/></svg>林鼎淵 Dean Lin</span>
  </span>
</a>

]]></content:encoded>
    </item>
    <item>
      <title>[前端＆後端]工程師常犯的錯誤 — 下班有約系列文</title>
      <link>https://deanlin.net/blog/202202/common-frontend-backend-mistakes/</link>
      <pubDate>Wed, 23 Feb 2022 17:53:45 +0800</pubDate>
      <guid>https://deanlin.net/blog/202202/common-frontend-backend-mistakes/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>筆者跟擔任 Tech Lead 的朋友聊天時，發現在幫新人 Code Review、Code Refactoring、Bug Fix 時遇到了許多類似的問題，針對這個部分我安排了 3 篇文章做講解，今天這篇文章我挑選了幾個工程師常犯的錯誤來分享。</description>
      <media:content url="https://deanlin.net/blog/202202/common-frontend-backend-mistakes/images/common-frontend-backend-mistakes-og_hu_aaf049d3404ffd4c.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>筆者跟擔任 Tech Lead 的朋友聊天時，發現在幫新人 Code Review、Code Refactoring、Bug Fixing 時遇到了許多類似的問題，針對這個部分我安排了 3 篇文章做講解，今天這篇文章我挑選了幾個工程師常犯的錯誤來分享。</p>
<p><img src="https://deanlin.net/blog/202202/common-frontend-backend-mistakes/images/common-frontend-backend-mistakes-01.webp" width="1400" height="745" alt="\[前端＆後端\]工程師常犯的錯誤 — 下班有約系列文（圖 1）" fetchpriority="high" decoding="async"></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:#f85149">➤</span> 保留太多無用的 console.log
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 沒有註明輸出的 console.log 要做什麼事
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 忽視 Console 吐出的警告<span style="color:#f85149">、</span>錯誤
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 不同函式共用<span style="color:#f85149">「</span>命名相同的全域變數<span style="color:#f85149">」</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 大量存取不必要的 API
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 將很多用不到程式碼註解而非刪除
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 沒寫註解<span style="color:#f85149">，</span>或是寫沒有意義的註解
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> Copy <span style="color:#ff7b72;font-weight:bold">&amp;</span> Paste 後<span style="color:#f85149">，</span>Function<span style="color:#f85149">、</span>Variable 命名沒有改
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 沒有寫 <span style="color:#ff7b72">try</span> <span style="color:#ff7b72">catch</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 在他人協助下完成功能後<span style="color:#f85149">，</span>沒有真正了解原理
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 沒有做 Refactoring
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 詢問別人前沒有自己嘗試
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 詢問的問題不夠精確
</span></span><span style="display:flex;"><span><span style="color:#f85149">➤</span> 管理者權限與使用者權限混在一起
</span></span></code></pre></div><h2 id="-保留太多無用的-consolelog">保留太多無用的 console.log<a class="h-anchor" href="#-%e4%bf%9d%e7%95%99%e5%a4%aa%e5%a4%9a%e7%84%a1%e7%94%a8%e7%9a%84-consolelog" aria-label="這個段落的連結"></a></h2>
<p>為了追蹤資料流向，我們會用 console.log 來確認程式是否依照我們的想法在執行；但有不少人只會一直增加新的 console.log，而不把已經不需要的 console.log 給移除。</p>
<h2 id="-沒有註明輸出的-consolelog-要做什麼事">沒有註明輸出的 console.log 要做什麼事<a class="h-anchor" href="#-%e6%b2%92%e6%9c%89%e8%a8%bb%e6%98%8e%e8%bc%b8%e5%87%ba%e7%9a%84-consolelog-%e8%a6%81%e5%81%9a%e4%bb%80%e9%ba%bc%e4%ba%8b" aria-label="這個段落的連結"></a></h2>
<p>筆者曾在按下 F12 開啟開發人員工具幫人 debug 時，看到 Console 這個分頁下印出一堆 API 回傳的 response data，但命名完全一樣都叫「result」，這樣我完全無法知道這些 result 是由哪個 API 回傳的ＱＱ</p>
<p><img src="https://deanlin.net/blog/202202/common-frontend-backend-mistakes/images/common-frontend-backend-mistakes-02.webp" width="862" height="433" alt="沒有註明輸出的 console.log 要做什麼事（圖 2）" loading="lazy" decoding="async"></p>
<p>不然就是很多意義不明只有自己才知道是什麼意思的 console.log（這類型的往往一個月後連本人都不知道在做什麼…）</p>
<p><img src="https://deanlin.net/blog/202202/common-frontend-backend-mistakes/images/common-frontend-backend-mistakes-03.webp" width="862" height="509" alt="沒有註明輸出的 console.log 要做什麼事（圖 3）" loading="lazy" decoding="async"></p>
<h2 id="-忽視-console-吐出的警告錯誤">忽視 Console 吐出的警告、錯誤<a class="h-anchor" href="#-%e5%bf%bd%e8%a6%96-console-%e5%90%90%e5%87%ba%e7%9a%84%e8%ad%a6%e5%91%8a%e9%8c%af%e8%aa%a4" aria-label="這個段落的連結"></a></h2>
<p>有些網頁看似運行的很正常，但在按下 F12 開啟開發人員工具後，你會看到一排讓人血壓上升的紅色 Error Message，如果沒有及時處理遲早有一天會業力引爆的。</p>
<p><img src="https://deanlin.net/blog/202202/common-frontend-backend-mistakes/images/common-frontend-backend-mistakes-04.webp" width="862" height="712" alt="忽視 Console 吐出的警告、錯誤（圖 4）" loading="lazy" decoding="async"></p>
<h2 id="-不同函式共用命名相同的全域變數">不同函式共用「命名相同的全域變數」<a class="h-anchor" href="#-%e4%b8%8d%e5%90%8c%e5%87%bd%e5%bc%8f%e5%85%b1%e7%94%a8%e5%91%bd%e5%90%8d%e7%9b%b8%e5%90%8c%e7%9a%84%e5%85%a8%e5%9f%9f%e8%ae%8a%e6%95%b8" aria-label="這個段落的連結"></a></h2>
<p>像是 Vue 框架下用來儲存變數資訊的 「Vuex」，裡面 State 的資訊是可以在多個頁面共同使用，下面舉例幾個不夠謹慎會造成的問題：</p>
<ul>
<li>如果在 loading 資料前沒有清空先前的 state 資訊，會導致進入頁面時會閃現上一次進入的資料。</li>
<li>如果把第幾頁(page)、分頁筆數(per-page)這些參數在多個頁面共用，那你就會看到有些頁面明明是第一次進入，卻跳轉到其他頁數。</li>
</ul>
<h2 id="-大量存取不必要的-api">大量存取不必要的 API<a class="h-anchor" href="#-%e5%a4%a7%e9%87%8f%e5%ad%98%e5%8f%96%e4%b8%8d%e5%bf%85%e8%a6%81%e7%9a%84-api" aria-label="這個段落的連結"></a></h2>
<p>這裡舉出兩個常見的例子：</p>
<ul>
<li>登入後就「一次呼叫所有的 API」，但許多 API 回傳的資料可能使用者根本還用不到。</li>
</ul>
<blockquote>
<p>這邊提醒一下，適當的「預載」可以增加使用者體驗，但如果在系統首頁就全部載入，除了會消耗使用者的資源外，一旦大量使用者同時上線，對伺服器端會造成很大的壓力。</p>
</blockquote>
<ul>
<li>有些搜尋功能會隨著使用者填入的文字去呼叫 API 來即時顯示搜尋結果或是建議關鍵字（ex：Google），但如果沒有把呼叫 API 的時間間隔設定好（debounceTime），使用者又不停更改搜尋列文字，那就會呼叫非常多不必要的 API。</li>
</ul>
<h2 id="-將用不到程式碼註解而非刪除">將用不到程式碼註解而非刪除<a class="h-anchor" href="#-%e5%b0%87%e7%94%a8%e4%b8%8d%e5%88%b0%e7%a8%8b%e5%bc%8f%e7%a2%bc%e8%a8%bb%e8%a7%a3%e8%80%8c%e9%9d%9e%e5%88%aa%e9%99%a4" aria-label="這個段落的連結"></a></h2>
<p>在開發階段有時會想到很多的解決方案，因為不確定哪個效能較高，所以就把每個演算法實現出來。</p>
<p>但有些人在決定使用某個解決方案後，依舊沒把其他的方案刪除，這樣的做法容易造成未來維護的困難度。</p>
<h2 id="-沒寫註解或是寫沒有意義的註解">沒寫註解，或是寫沒有意義的註解<a class="h-anchor" href="#-%e6%b2%92%e5%af%ab%e8%a8%bb%e8%a7%a3%e6%88%96%e6%98%af%e5%af%ab%e6%b2%92%e6%9c%89%e6%84%8f%e7%be%a9%e7%9a%84%e8%a8%bb%e8%a7%a3" aria-label="這個段落的連結"></a></h2>
<p>雖然有神人說過：最好的註解就是程式碼本身</p>
<p>但我想：「<strong>絕大多數的人都難以到達這個境界，所以要乖乖地寫註解ＸＤ」</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 style="color:#ff7b72">let</span> mode <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">// do somethings ...
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (mode <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// excute logic 1
</span></span></span><span style="display:flex;"><span>} <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (mode <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">1</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// excute logic 2
</span></span></span><span style="display:flex;"><span>} <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span> (mode <span style="color:#ff7b72;font-weight:bold">===</span> <span style="color:#a5d6ff">2</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// excute logic 3
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/463d3dc91bc4e9789a29bcc4323560d9" rel="noopener">bad_remark_example.js</a>（GitHub Gist）</p>
<p>像是上面這段 Code，我們可以知道 mode 這個變數應該是應用在某個狀態的判斷上；但具體在哪個地方使用、總共有哪些狀態、各自代表什麼意義，在沒有註解的狀態下只能一行一行的閱讀，沒辦法第一時間理解程式。</p>
<h3 class="h-nomark" id="-註解的好處">👍 註解的好處<a class="h-anchor" href="#-%e8%a8%bb%e8%a7%a3%e7%9a%84%e5%a5%bd%e8%99%95" aria-label="這個段落的連結"></a></h3>
<ol>
<li>交代前因後果：為什麼這樣寫?遇到了什麼困難?解決了什麼問題?</li>
<li>作為文件說明：有些技術是需要被學習後才能夠看得懂的(ex:引入的模組、套件)，你的註解能讓他快速理解這些工具怎麼使用</li>
</ol>
<h3 class="h-nomark" id="-請不要有註解如下">🙅 請不要有註解如下<a class="h-anchor" href="#-%e8%ab%8b%e4%b8%8d%e8%a6%81%e6%9c%89%e8%a8%bb%e8%a7%a3%e5%a6%82%e4%b8%8b" aria-label="這個段落的連結"></a></h3>
<ol>
<li>毫無意義的牢騷(ex:我好崩潰啊，這段程式我寫了一個禮拜…)</li>
<li>把註解當成版本控制(ex:2020/08/23 完成此函式)</li>
<li>說明這段程式是誰寫的(簽名檔)</li>
<li>把舊的程式碼註解起來怕以後會用到(但通常再也不會用到，而且這個問題用版控就能解決)</li>
</ol>
<h2 id="-copy--paste-後functionvariable-命名沒有改">Copy &amp; Paste 後，Function、Variable 命名沒有改<a class="h-anchor" href="#-copy--paste-%e5%be%8cfunctionvariable-%e5%91%bd%e5%90%8d%e6%b2%92%e6%9c%89%e6%94%b9" aria-label="這個段落的連結"></a></h2>
<p>如果寫專案時發現之前做過類似的功能，我們會複製過來再做微調，以此加快開發的速度。</p>
<p>但<strong>請修改 Function、Variable 命名！</strong>這不會花你太多時間，但在未來當你需要維護專案時，你會感謝現在的自己。</p>
<blockquote>
<p>筆者之前曾在幫忙 Debug 時，於Ａ系統裡面看到一堆Ｂ系統的命名，當下還以為自己開錯專案了…</p>
</blockquote>
<h2 id="-沒有寫-try-catch">沒有寫 try catch<a class="h-anchor" href="#-%e6%b2%92%e6%9c%89%e5%af%ab-try-catch" aria-label="這個段落的連結"></a></h2>
<p>程式在缺乏 try-catch 的機制下是非常脆弱的，只要發生例外事件就容易崩潰。</p>
<p>如果你直接假定 API 只會回傳正確的資訊，而沒有撰寫 try-catch 去捕捉可能的 400、500 系列錯誤，就很可能導致系統許多行為錯誤如：</p>
<ol>
<li>跳轉到無權限的頁面</li>
<li>程式進入無限迴圈</li>
<li>瘋狂 Call 某隻 API 直到變成 429 錯誤（Too Many Requests）</li>
</ol>
<h2 id="-在他人協助下完成功能後沒有真正了解原理">在他人協助下完成功能後，沒有真正了解原理<a class="h-anchor" href="#-%e5%9c%a8%e4%bb%96%e4%ba%ba%e5%8d%94%e5%8a%a9%e4%b8%8b%e5%ae%8c%e6%88%90%e5%8a%9f%e8%83%bd%e5%be%8c%e6%b2%92%e6%9c%89%e7%9c%9f%e6%ad%a3%e4%ba%86%e8%a7%a3%e5%8e%9f%e7%90%86" aria-label="這個段落的連結"></a></h2>
<p>我相信大部分的人都是樂於幫助別人解決問題的，但同時也相信，沒有人希望再次被「相同的問題」打擾。</p>
<p>我這邊強烈建議請他人協助問題前，先 commit 一版，這樣無論對方成功還是失敗，你都有一個記錄在。</p>
<p>如果對方成功解決這個問題，一定找時間回到當時 commit 的那版，用自己的邏輯把功能完成；<strong>請記住，別人完成的任務不會自動成為你的知識。</strong></p>
<h2 id="-沒有做-refactoring">沒有做 Refactoring<a class="h-anchor" href="#-%e6%b2%92%e6%9c%89%e5%81%9a-refactoring" aria-label="這個段落的連結"></a></h2>
<p>演算法相關的 Refactor 有一定的難度，但有一些很基礎的 Refactor 我認為是有必要的，像是變數命名這一塊。</p>
<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">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> responseData;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (result[result.length <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">if</span> (<span style="color:#a5d6ff">&#34;check_in&#34;</span> <span style="color:#ff7b72">in</span> result[result.length <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> result[result.length <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>].check_in <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#79c0ff">null</span>) {
</span></span><span style="display:flex;"><span>    state.check_in <span style="color:#ff7b72;font-weight:bold">=</span> result[<span style="color:#a5d6ff">0</span>].check_in.toString();
</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">if</span> (result[result.length <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">if</span> (<span style="color:#a5d6ff">&#34;check_out&#34;</span> <span style="color:#ff7b72">in</span> result[result.length <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> result[result.length <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>].check_out <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#79c0ff">null</span>) {
</span></span><span style="display:flex;"><span>    state.check_out <span style="color:#ff7b72;font-weight:bold">=</span> result[result.length <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>].check_out.toString();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/3c6c9d59d698b8738771eee07056b8fd" rel="noopener">need_refactor.js</a>（GitHub Gist）</p>
<p>上面這個是要取得回傳的「最後一筆」資料做邏輯處理，但是全部都使用「<strong>result[result.length -1]</strong>」來表示，相信大家都會看得有點眼花。</p>
<p>下面我們做個簡單的 Refactor，看起來就會舒服很多：</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">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> [];
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">let</span> last_record <span style="color:#ff7b72;font-weight:bold">=</span> result[result.length <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">if</span> (last_record) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">if</span> (<span style="color:#a5d6ff">&#34;check_in&#34;</span> <span style="color:#ff7b72">in</span> last_record <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> last_record.check_in <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#79c0ff">null</span>) {
</span></span><span style="display:flex;"><span>    state.check_in <span style="color:#ff7b72;font-weight:bold">=</span> last_record.check_in.toString();
</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">if</span> (last_record) {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">if</span> (<span style="color:#a5d6ff">&#34;check_out&#34;</span> <span style="color:#ff7b72">in</span> last_record <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> last_record.check_out <span style="color:#ff7b72;font-weight:bold">!==</span> <span style="color:#79c0ff">null</span>) {
</span></span><span style="display:flex;"><span>    state.check_out <span style="color:#ff7b72;font-weight:bold">=</span> last_record.check_out.toString();
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/f9afe9487cbf14ddec3b92a61335f2f5" rel="noopener">after_refactor.js</a>（GitHub Gist）</p>
<h2 id="-詢問別人前沒有自己嘗試">詢問別人前沒有自己嘗試<a class="h-anchor" href="#-%e8%a9%a2%e5%95%8f%e5%88%a5%e4%ba%ba%e5%89%8d%e6%b2%92%e6%9c%89%e8%87%aa%e5%b7%b1%e5%98%97%e8%a9%a6" aria-label="這個段落的連結"></a></h2>
<p>有些人在遇到問題時沒有先從網路找資料，而是遇到問題先問同事、前輩。</p>
<p>儘管這樣可以最快速得到解答，但卻少了很多自我思考的部分，長期來講對工程師職涯是非常不健康的。</p>
<h2 id="-詢問的問題不夠精確">詢問的問題不夠精確<a class="h-anchor" href="#-%e8%a9%a2%e5%95%8f%e7%9a%84%e5%95%8f%e9%a1%8c%e4%b8%8d%e5%a4%a0%e7%b2%be%e7%a2%ba" aria-label="這個段落的連結"></a></h2>
<p>有時會收到主旨模糊的問題，像是：「這個搜尋功能常常會 fail，可以幫我看看嗎？」</p>
<p>如果可以，收到問題的人會希望問題敘述微調一下：「在人資系統中，我如果在職員列表頁面的搜尋條件中加入部門篩選，會發現搜尋出的結果不合預期，有時會找出其他部門的人員，可以幫我看一下是不是資料庫語法錯誤呢？」</p>
<blockquote>
<p><strong>結構為</strong>：哪個系統-&gt;哪個頁面-&gt;你調整了哪些參數-&gt;錯誤結果為何-&gt;初步判定錯誤來源</p>
</blockquote>
<h2 id="-管理者權限與使用者權限混在一起">管理者權限與使用者權限混在一起<a class="h-anchor" href="#-%e7%ae%a1%e7%90%86%e8%80%85%e6%ac%8a%e9%99%90%e8%88%87%e4%bd%bf%e7%94%a8%e8%80%85%e6%ac%8a%e9%99%90%e6%b7%b7%e5%9c%a8%e4%b8%80%e8%b5%b7" aria-label="這個段落的連結"></a></h2>
<p>很多系統都會分成前台（一般使用者）以及後台（管理者），有時後端為了增加程式共用率，而讓他們使用同一隻 API 存取資料，如果權限有設計好那沒問題，但如果沒設計好可能就出大包了。</p>
<p>以人資系統來說，假設有一隻 API 是用來存取個人資訊的，API 格式為：「users/{user}」</p>
<p>假使把這隻 API 共用，一般使用者透過修改中間的「user id」就有可能看到其他人的隱私資訊，這在資訊安全上是非常危險的。</p>
<p>因此建議將一般使用者與管理者的 API 切割清楚，避免讓使用者看到權限外的資料。</p>
<hr>
<blockquote class="pullquote"><p>筆者曾經犯過文章中許多錯誤，沒有人一開始就是強者，但至少要走在變強的道路上。</p>
</blockquote>
<p><strong>如果你對目前的職涯感到迷茫，想從不同視角檢視自己的選擇，相信《<a href="https://www.tenlong.com.tw/products/9786264142083">工程師下班有約</a>》這本書能為你帶來靈感與啟發：</strong></p>
<figure class="figure"><img src="https://deanlin.net/blog/202202/common-frontend-backend-mistakes/images/common-frontend-backend-mistakes-05.webp" width="1400" height="319" alt="林鼎淵著作《工程師下班有約：企業內訓講師帶你認清職涯真相！》" loading="lazy" decoding="async">
  <figcaption><a href="https://www.tenlong.com.tw/products/9786264142083">https://www.tenlong.com.tw/products/9786264142083</a></figcaption>
</figure>

<pre tabindex="0"><code>下班有約系列文
▋ [前端]新手工程師容易卡住的問題
▋ [前端＆後端]工程師常犯的錯誤(本篇)
▋ 讓開發團隊更好協作的方式
▋ 談談 Pair Programing
▋ 談談工程師的話語權
▋ 出社會後，新鮮人要及早知道的 5 件事
▋ 面試的性向測驗竟然準到讓人心裡發寒？
▋ 反其道而行的任務分配
▋ 現在的 Daily Scrum 真的適合團隊嗎？
▋ 用低成本也能請到好員工？資深 HR 不想告訴你的秘密！
</code></pre>]]></content:encoded>
    </item>
  </channel>
</rss>
