<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Hubert Hsieh]]></title><description><![CDATA[工程師 Hubert Hsieh 的個人網站。作品集、前後端與 App 技術筆記、面試日記與自我思考。]]></description><link>https://huberthsieh.dev</link><generator>GatsbyJS</generator><lastBuildDate>Wed, 14 Oct 2026 17:48:53 GMT</lastBuildDate><item><title><![CDATA[後端知識 - 我以為 JS 會先印 B。]]></title><description><![CDATA[這是 backend-lab 自學紀錄的 Day 2，主題是 C# 的 async 和 await。我寫了好幾年 Promise，以為這塊會很快，結果把 C# 和 JavaScript 的同一個實驗並排跑，我連自己最熟的 JS 都猜錯。這篇記錄那次猜錯，還有後面接連踩到的幾個坑：一個永遠等不完的 Promise、一個多此一舉的 Task.Run，以及我花最久才改掉的那個誤會。 執行緒 ID 的實驗、99 個計時器只花 1002 毫秒、還有  的錯，post-11 都講過了，這篇不重複。 同一個實驗，寫兩次 C# 一份，JS 一份，看誰先印 C# 的  和 JS…]]></description><link>https://huberthsieh.dev/blog/post-13/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-13/</guid><category><![CDATA[DotNet]]></category><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;這是 backend-lab 自學紀錄的 Day 2，主題是 C# 的 async 和 await。我寫了好幾年 Promise，以為這塊會很快，結果把 C# 和 JavaScript 的同一個實驗並排跑，我連自己最熟的 JS 都猜錯。這篇記錄那次猜錯，還有後面接連踩到的幾個坑：一個永遠等不完的 Promise、一個多此一舉的 Task.Run，以及我花最久才改掉的那個誤會。&lt;/p&gt;
&lt;p&gt;執行緒 ID 的實驗、99 個計時器只花 1002 毫秒、還有 &lt;code class=&quot;language-text&quot;&gt;await setTimeout&lt;/code&gt; 的錯，&lt;a href=&quot;/blog/post-11/&quot;&gt;post-11&lt;/a&gt; 都講過了，這篇不重複。&lt;/p&gt;
&lt;h3&gt;同一個實驗，寫兩次&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;C# 一份，JS 一份，看誰先印&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C# 的 &lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt; 和 JS 的 &lt;code class=&quot;language-text&quot;&gt;Promise&lt;/code&gt; 長得很像，都是「一個之後才會有結果的東西」。我想確認它們是不是真的同一套機制，所以寫了一個最小的實驗。&lt;/p&gt;
&lt;p&gt;C# 的版本是這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; tasks &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ConsoleDisplayAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;This is A&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WhenAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;This is B&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;Task&lt;/span&gt; displayTask &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ConsoleDisplayAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;This is C&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; displayTask&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Task.Delay(1000)&lt;/code&gt; 是等一秒，&lt;code class=&quot;language-text&quot;&gt;Task.WhenAll&lt;/code&gt; 是等清單裡的全部做完，大概就是 JS 的 &lt;code class=&quot;language-text&quot;&gt;Promise.all&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;跑出來是 A、C、B。&lt;/p&gt;
&lt;p&gt;async 方法被呼叫之後，會先&lt;strong&gt;同步&lt;/strong&gt;往下跑，跑到第一個 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 才停。停下來的時候，它把一個還沒完成的 &lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt; 交給呼叫端，自己先退場。所以呼叫端接著印出 C，等那一秒過去，B 才出來。&lt;/p&gt;
&lt;p&gt;這個結果我能接受，C# 對我來說是新東西，它怎麼跑我都沒意見。&lt;/p&gt;
&lt;p&gt;接著我用 JS 寫一份一樣的。&lt;/p&gt;
&lt;h3&gt;「JS 一定是 A B C」&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;越熟的東西，越懶得跑&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;寫 JS 版之前，我很有把握地說，JS 一定是 A B C。&lt;/p&gt;
&lt;p&gt;理由我現在已經講不清楚了，大概是覺得 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 寫在 B 前面，那 B 就應該先等完再輪到 C。&lt;/p&gt;
&lt;p&gt;跑出來也是 A C B。&lt;/p&gt;
&lt;p&gt;（盯著終端機那三行看了五秒，然後把 C 前面的程式碼從頭到尾再滑一遍）&lt;/p&gt;
&lt;p&gt;兩邊的行為完全一樣。同步跑到 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt;，交出一個還沒完成的物件，控制權還給呼叫端，等到有人把它標成完成，後半段才繼續。&lt;/p&gt;
&lt;p&gt;C# 我會小心，因為我知道自己不懂。JS 我沒跑就下結論，因為我覺得自己懂。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;沒跑過的把握，是假的把握。&lt;/strong&gt; 這句我寫進了錯誤紀錄，而且特別註明，「我熟的東西」最容易翻車。&lt;/p&gt;
&lt;h3&gt;一個永遠等不完的 Promise&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Promise 不會自己完成，要有人按下去&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;為了在 JS 那邊做計時，我自己包了一個等一秒的 Promise。我寫成類似這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;start&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;end&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;第一個時間戳印出來了，第二個沒有。&lt;/p&gt;
&lt;p&gt;不是延遲，是&lt;strong&gt;消失&lt;/strong&gt;。程式就停在那裡。&lt;/p&gt;
&lt;p&gt;問題在 &lt;code class=&quot;language-text&quot;&gt;new Promise&lt;/code&gt; 給你的那個函式。它其實會收到一個參數 &lt;code class=&quot;language-text&quot;&gt;resolve&lt;/code&gt;，你要親手呼叫它，這個 Promise 才算完成。我沒有接，也沒有呼叫，所以它永遠是「等待中」，&lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 也就永遠等下去。&lt;/p&gt;
&lt;p&gt;改成這樣才對：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;計時器一秒後去按 &lt;code class=&quot;language-text&quot;&gt;resolve&lt;/code&gt;，Promise 完成，&lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 醒來。&lt;/p&gt;
&lt;p&gt;這個錯反而幫我看清了 C# 那邊。&lt;code class=&quot;language-text&quot;&gt;Task.Delay&lt;/code&gt; 之所以能被 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt;，是因為底下也有一個計時器，時間到了就去把那個 &lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt; 標成完成。兩邊都一樣，&lt;strong&gt;要有人去按那個按鈕&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;多包一層 Task.Run&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;等待不需要執行緒，我卻硬塞了一條給它&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;做 99 個計時器那個實驗的時候，我一開始不是直接把 &lt;code class=&quot;language-text&quot;&gt;Task.Delay&lt;/code&gt; 放進清單，而是包了一層 &lt;code class=&quot;language-text&quot;&gt;Task.Run&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Task.Run&lt;/code&gt; 是把一段工作丟給 thread pool 去做。thread pool 可以想成後端程式預先準備好的一群執行緒，有工作就派一條出去。它適合拿來跑「需要 CPU 算很久」的東西。&lt;/p&gt;
&lt;p&gt;可是 &lt;code class=&quot;language-text&quot;&gt;Task.Delay&lt;/code&gt; 根本不需要 CPU。它本身就是一個 &lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt;，直接放進清單就好。&lt;/p&gt;
&lt;p&gt;包了 &lt;code class=&quot;language-text&quot;&gt;Task.Run&lt;/code&gt;，等於為了「等一秒」這件事，特地叫一條執行緒出來，讓它去發起一個等待，然後再離開。&lt;/p&gt;
&lt;p&gt;程式還是會動，結果也差不多。但這個實驗本來要證明的，正是「等待不佔執行緒」。我自己寫的那一行，剛好在反著做。&lt;/p&gt;
&lt;h3&gt;還控制權，不是換人&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;這是我花最久才改掉的誤會&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Day 2 結束前，AI 要我口頭講一遍 async 的一生，從呼叫、&lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt;、等待、完成，一路到執行緒。總共六題，我答對四題。&lt;/p&gt;
&lt;p&gt;錯的兩題，錯法一模一樣。&lt;/p&gt;
&lt;p&gt;一題是「&lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 的時候，控制權還給呼叫端」，我講成「交給新的執行緒」。另一題是「等待交給計時器」，我也講成「交給新的執行緒」。&lt;/p&gt;
&lt;p&gt;只要事情一變成非同步，我腦中就自動冒出另一條執行緒，好像不開一條新的，那些事情就沒地方跑。&lt;/p&gt;
&lt;p&gt;實際上是這樣分的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;等待的那一秒，交給計時器，&lt;strong&gt;不佔任何執行緒&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;控制權還給呼叫端，還是&lt;strong&gt;同一條&lt;/strong&gt;執行緒，它只是回頭去跑呼叫端的下一行&lt;/li&gt;
&lt;li&gt;等待結束之後，後半段才由 thread pool 裡剛好有空的執行緒接手&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;被追問之後我有自己改正過來。但這個誤會之後又復發了好幾次，最後是靠印出執行緒 ID 才真的釘死，那段在 &lt;a href=&quot;/blog/post-11/&quot;&gt;post-11&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;平常寫 JS 的人，可能更容易犯這個誤會。JS 的世界裡本來就只有一條主執行緒，所以寫 JS 的時候從來不需要問「是誰在跑」。到了 C#，同一個問題突然有好幾個答案，而我的直覺選了最錯的那一個。&lt;/p&gt;
&lt;h3&gt;順帶收掉的兩筆債&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;都是 Day 1 欠下來、Day 2 補上的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;關掉 ImplicitUsings&lt;/h4&gt;
&lt;p&gt;C# 用別的命名空間裡的東西，要在檔案開頭寫 &lt;code class=&quot;language-text&quot;&gt;using&lt;/code&gt;，有點像 JS 的 &lt;code class=&quot;language-text&quot;&gt;import&lt;/code&gt;。但新專案預設開著 ImplicitUsings，編譯器會偷偷幫你加一批常用的，所以 Day 1 的程式碼一行 &lt;code class=&quot;language-text&quot;&gt;using&lt;/code&gt; 都沒寫也能跑。&lt;/p&gt;
&lt;p&gt;我把它改成 &lt;code class=&quot;language-text&quot;&gt;disable&lt;/code&gt;，想看看它到底藏了什麼。&lt;/p&gt;
&lt;p&gt;build 直接噴了一整串錯，大多是 CS0103 和 CS0246，意思都是「這個名字我不認識」。&lt;code class=&quot;language-text&quot;&gt;Console&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;List&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt; 全部找不到。&lt;/p&gt;
&lt;p&gt;我一個一個補回去。補到 &lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt; 的時候，我猜它在 &lt;code class=&quot;language-text&quot;&gt;System.Threading&lt;/code&gt;，加了 &lt;code class=&quot;language-text&quot;&gt;using System.Threading&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;還是找不到。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt; 其實在 &lt;code class=&quot;language-text&quot;&gt;System.Threading.Tasks&lt;/code&gt;，比我猜的還深一層。這讓我推出一條規則。&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;using X&lt;/code&gt; 只會打開 X 這一層，不會連 X 底下的子命名空間一起打開。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這裡我還跟 AI 吵了一下。我翻文件說 Tasks 是從 Threading 改良出來的，所以應該算在一起。最後拆成兩件事才講通，設計上的沿革是真的，但命名空間的巢狀只是分類，跟 &lt;code class=&quot;language-text&quot;&gt;using&lt;/code&gt; 能看到什麼無關。&lt;/p&gt;
&lt;h4&gt;.slnx 和 .csproj 的分工&lt;/h4&gt;
&lt;p&gt;專案裡有兩種專案檔。&lt;code class=&quot;language-text&quot;&gt;.csproj&lt;/code&gt; 描述&lt;strong&gt;一個&lt;/strong&gt;專案，要編譯哪些檔案、用什麼套件。&lt;code class=&quot;language-text&quot;&gt;.slnx&lt;/code&gt; 是把好幾個專案串在一起的清單，比較像前端 monorepo 的 workspace 設定。&lt;/p&gt;
&lt;p&gt;我憑記憶答了三題，都對：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;dotnet run&lt;/code&gt; 只認 &lt;code class=&quot;language-text&quot;&gt;.csproj&lt;/code&gt;，而且目錄裡要剛好有一個能執行的，它不會把 &lt;code class=&quot;language-text&quot;&gt;.slnx&lt;/code&gt; 當成能跑的東西&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;.slnx&lt;/code&gt; 是給 IDE 讀的，讓 Rider 知道要一起開哪些專案&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;.slnx&lt;/code&gt; 不會自己跟檔案系統同步。刪掉一個專案資料夾，清單裡那一筆還會留著&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一句話就是，&lt;strong&gt;build 認 &lt;code class=&quot;language-text&quot;&gt;.csproj&lt;/code&gt;，IDE 讀 &lt;code class=&quot;language-text&quot;&gt;.slnx&lt;/code&gt;，兩條路互不相干。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;同一個觀念，換一個語言再跑一次&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;你已經會的，可能是你最該重跑一次的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Day 2 讓我翻車的，幾乎都不是 C# 的新語法，而是我以為從 JS 帶得過來的直覺。&lt;/p&gt;
&lt;p&gt;A C B 這件事，我寫了好幾年 JS 都沒真正搞清楚，是到了 C# 才回頭發現的。&lt;/p&gt;
&lt;p&gt;所以學一個新語言的時候，我會建議把你最有把握的那幾個觀念，用新語言寫一次，再用你熟的語言寫一次，兩邊都實際跑過。&lt;/p&gt;
&lt;p&gt;猜錯的那一個，通常就是你一直沒真的懂的地方。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(來自一個 Promise 寫了好幾年、還是猜錯 JS 輸出順序的人)&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[後端知識 - Equals 說相等，== 說不相等。]]></title><description><![CDATA[這是 backend-lab 自學紀錄的 Day 2 後段，主題是 C#  的相等性。兩個內容一樣的物件算不算相等， 和一般  給的答案不同。這篇記錄我用一排實驗拆出來的三件事：相等性其實是逐個成員交給各自的  去比、 和  必須綁在一起，以及手寫  之後  不會跟著變。  那行印錯變數、數字卻剛好對的事，post-11 寫過了，這篇不重複。Day 2 前段的 async 在 post-13。 先分清楚兩種相等 長得一樣，跟是同一個，是兩件事 C# 的  是一種專門裝資料的型別，一行就能宣告： 我拿它跟一個欄位完全一樣的普通  比，各 new…]]></description><link>https://huberthsieh.dev/blog/post-14/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-14/</guid><category><![CDATA[DotNet]]></category><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;這是 backend-lab 自學紀錄的 Day 2 後段，主題是 C# &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 的相等性。兩個內容一樣的物件算不算相等，&lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 和一般 &lt;code class=&quot;language-text&quot;&gt;class&lt;/code&gt; 給的答案不同。這篇記錄我用一排實驗拆出來的三件事：相等性其實是逐個成員交給各自的 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 去比、&lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt; 必須綁在一起，以及手寫 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 之後 &lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt; 不會跟著變。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;HashSet&lt;/code&gt; 那行印錯變數、數字卻剛好對的事，&lt;a href=&quot;/blog/post-11/&quot;&gt;post-11&lt;/a&gt; 寫過了，這篇不重複。Day 2 前段的 async 在 &lt;a href=&quot;/blog/post-13/&quot;&gt;post-13&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;先分清楚兩種相等&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;長得一樣，跟是同一個，是兩件事&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C# 的 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 是一種專門裝資料的型別，一行就能宣告：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; FirstName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; Age&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;我拿它跟一個欄位完全一樣的普通 &lt;code class=&quot;language-text&quot;&gt;class&lt;/code&gt; 比，各 new 兩個內容相同的物件，看三種比法：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;                Equals   ==      ReferenceEquals
record Person   True     True    False
class           False    False   False&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ReferenceEquals&lt;/code&gt; 問的是「這兩個變數是不是指向記憶體裡同一個東西」。兩次 &lt;code class=&quot;language-text&quot;&gt;new&lt;/code&gt; 就是兩個東西，所以三種型別都是 False。&lt;/p&gt;
&lt;p&gt;普通 &lt;code class=&quot;language-text&quot;&gt;class&lt;/code&gt; 預設的 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 也是在比這個，所以它也是 False。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 不一樣。編譯器幫它生了一套比內容的 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt;，名字和年齡都一樣，就算相等。&lt;/p&gt;
&lt;p&gt;最有說服力的是 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 那一列。&lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 是 True，&lt;code class=&quot;language-text&quot;&gt;ReferenceEquals&lt;/code&gt; 是 False。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相等，但不是同一個。&lt;/strong&gt; 值相等跟身分相等，在這一列被拆開了。&lt;/p&gt;
&lt;p&gt;寫 JS 的人可以這樣對照：JS 的 &lt;code class=&quot;language-text&quot;&gt;===&lt;/code&gt; 比兩個物件，比的永遠是參考，兩個 &lt;code class=&quot;language-text&quot;&gt;{ a: 1 }&lt;/code&gt; 永遠不相等。C# 的 &lt;code class=&quot;language-text&quot;&gt;class&lt;/code&gt; 預設也是這樣，&lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 則是把「比內容」做成預設行為。&lt;/p&gt;
&lt;h3&gt;比內容，到底比多深&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;不是深比，是外包&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;知道 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 比內容之後，我馬上想到下一個問題。如果欄位本身也是物件，它會一路比進去嗎？&lt;/p&gt;
&lt;p&gt;我做了兩個實驗：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NumberList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; Numbers&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Line&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; Start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Point&lt;/span&gt; End&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;兩個 &lt;code class=&quot;language-text&quot;&gt;NumberList&lt;/code&gt; 都裝 &lt;code class=&quot;language-text&quot;&gt;{ 1, 2, 3 }&lt;/code&gt;，比出來是 False。&lt;/p&gt;
&lt;p&gt;兩個 &lt;code class=&quot;language-text&quot;&gt;Line&lt;/code&gt; 的起點終點都一樣，比出來是 True。&lt;/p&gt;
&lt;p&gt;如果 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 是深比，&lt;code class=&quot;language-text&quot;&gt;NumberList&lt;/code&gt; 應該要是 True。如果它碰到參考型別就一律放棄，&lt;code class=&quot;language-text&quot;&gt;Line&lt;/code&gt; 應該要是 False。兩個猜法都被打掉了。&lt;/p&gt;
&lt;p&gt;真正的規則是，&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 不自己比，它把每個成員交給那個成員自己的 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;List&amp;lt;int&gt;&lt;/code&gt; 是普通 class，它的 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 比的是參考，兩個不同的 List 就是 False。&lt;code class=&quot;language-text&quot;&gt;Point&lt;/code&gt; 是 record，它的 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 比內容，所以 True。&lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 只是把這些答案收集起來，全部 True 才算 True。&lt;/p&gt;
&lt;p&gt;這有點像一間公司的驗收流程。總經理不會自己去檢查每個零件，他問每個部門主管「你那邊 OK 嗎」，主管說 OK 他就簽。問題是倉儲部門的主管檢查的方式是「是不是同一箱」，不是「箱子裡的東西一不一樣」，那總經理也只會拿到這個答案。&lt;/p&gt;
&lt;h3&gt;Equals 根本沒被叫到&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;相等卻留兩份，是因為它根本沒被問&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這段是整個 Day 2 我覺得最值得記下來的。&lt;/p&gt;
&lt;p&gt;如果我不用 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt;，自己在 &lt;code class=&quot;language-text&quot;&gt;class&lt;/code&gt; 裡 override &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt;，讓它比 X 和 Y：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassOverridePoint&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; X &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; Y &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ClassOverridePoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        X &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        Y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;bool&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; obj &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassOverridePoint&lt;/span&gt; other
               &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; X &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; other&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;X
               &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; Y &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; other&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Y&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 確實回 True。但把兩個 &lt;code class=&quot;language-text&quot;&gt;(1, 2)&lt;/code&gt; 放進 &lt;code class=&quot;language-text&quot;&gt;HashSet&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;Count&lt;/code&gt; 是 2。&lt;/p&gt;
&lt;p&gt;明明相等，卻留了兩份。&lt;/p&gt;
&lt;p&gt;原因在 &lt;code class=&quot;language-text&quot;&gt;HashSet&lt;/code&gt; 找東西的方式。它不會拿新元素跟裡面每一個都比一次 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt;，那太慢了。它先算 &lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt;，用這個數字決定要放進哪一格，只有落在同一格的元素才會進一步比 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我沒有 override &lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt;，所以用的是預設版本，兩個物件算出來的數字不一樣。我實際印出來，一個是 &lt;code class=&quot;language-text&quot;&gt;23458411&lt;/code&gt;，一個是 &lt;code class=&quot;language-text&quot;&gt;9799115&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;數字不同，放進不同的格子，&lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 從頭到尾&lt;strong&gt;根本沒被執行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這就是 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt; 之間的約定。兩個物件 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 相等，&lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt; 就必須一樣。反過來不用，hash 一樣但內容不同是允許的，那只是剛好撞在同一格。&lt;/p&gt;
&lt;p&gt;編譯器其實有提醒。build 的時候會出現 CS0659 警告，說這個型別覆寫了 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt;，卻沒有覆寫 &lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;（第一次 build 看到黃色那行，我很順手地就把終端機往上捲掉了）&lt;/p&gt;
&lt;p&gt;補上一行就好：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetHashCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; HashCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Combine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;X&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Count&lt;/code&gt; 變回 1。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 從來不會有這個問題，因為編譯器生 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 的時候，&lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt; 是一起生的。&lt;/p&gt;
&lt;h3&gt;override Equals，== 不會跟著來&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;同一個物件，兩種比法給出相反的答案&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;換題重寫的時候，我把 &lt;code class=&quot;language-text&quot;&gt;Point&lt;/code&gt; 換成 &lt;code class=&quot;language-text&quot;&gt;Person&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt; 都補齊了，&lt;code class=&quot;language-text&quot;&gt;HashSet&lt;/code&gt; 也正常是 1。&lt;/p&gt;
&lt;p&gt;但我把 &lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt; 也印出來，看到這一行：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ClassOverridePerson Equals : True
ClassOverridePerson ==     : False&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;同一對物件，&lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 說相等，&lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt; 說不相等。&lt;/p&gt;
&lt;p&gt;這個矛盾是我在實驗結果裡自己注意到的，原因也是自己推出來的。&lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt; 在 C# 裡是另一個運算子，class 沒有自訂的話，它比的還是參考。override &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt; 只改了 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt;，&lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt; 完全不知道這件事。&lt;/p&gt;
&lt;p&gt;要讓 &lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt; 也比內容，得另外寫 &lt;code class=&quot;language-text&quot;&gt;operator ==&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;operator !=&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 一次幫你生的是完整的一套，兩個 &lt;code class=&quot;language-text&quot;&gt;Equals&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;==&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;!=&lt;/code&gt; 全部對齊。手寫 class 的時候，這五個你要自己一個一個補，漏一個就會出現上面那種矛盾。&lt;/p&gt;
&lt;h3&gt;常見應用：什麼時候會真的踩到&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;不是考試題，是會在正式環境出現的 bug&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這三件事在實際寫後端的時候，大概會出現在這些地方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拿物件當 &lt;code class=&quot;language-text&quot;&gt;Dictionary&lt;/code&gt; 的 key 或丟進 &lt;code class=&quot;language-text&quot;&gt;HashSet&lt;/code&gt; 去重複。漏了 &lt;code class=&quot;language-text&quot;&gt;GetHashCode&lt;/code&gt;，去重複會無聲失效，不會有任何錯誤&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 裡面放 &lt;code class=&quot;language-text&quot;&gt;List&lt;/code&gt;，以為兩筆資料內容一樣就會相等，結果永遠是 False&lt;/li&gt;
&lt;li&gt;程式碼裡有人寫 &lt;code class=&quot;language-text&quot;&gt;a.Equals(b)&lt;/code&gt;、有人寫 &lt;code class=&quot;language-text&quot;&gt;a == b&lt;/code&gt;，對手寫 class 來說，這是兩種不同的判斷&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第一個最麻煩。它不會 crash，只會讓資料多一份，而你要等到某天數字對不上才會發現。&lt;/p&gt;
&lt;p&gt;所以 CS0659 那種警告，看到就要停下來讀。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(來自一個看到黃色警告，第一個反應是捲掉終端機的人)&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[後端知識 - MapGet 不是在執行，是在登記。]]></title><description><![CDATA[這是 backend-lab 自學紀錄的 Day 1，主題是 ASP.NET Core 的 Minimal API。我原本以為 MapGet 那一行就是「收到請求時執行這段」，結果它其實只是在登記路由。這篇記錄我怎麼從一個健康檢查端點，一路挖到框架的原始碼，最後還親手做出一個 race condition。 為什麼要綁住 AI 不准代寫，上一篇講過了，這篇只講學到的東西。 第一個端點長這樣 十幾行，但每一行都比我以為的多做了事 Day 1 的題目是寫一個健康檢查端點，回傳服務狀態跟時間。這種端點是讓外部確認服務還活著用的，部署平台會定期打它，沒回應就當作服務掛了。 用的寫法叫 Minimal…]]></description><link>https://huberthsieh.dev/blog/post-12/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-12/</guid><category><![CDATA[DotNet]]></category><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;這是 backend-lab 自學紀錄的 Day 1，主題是 ASP.NET Core 的 Minimal API。我原本以為 MapGet 那一行就是「收到請求時執行這段」，結果它其實只是在&lt;strong&gt;登記&lt;/strong&gt;路由。這篇記錄我怎麼從一個健康檢查端點，一路挖到框架的原始碼，最後還親手做出一個 race condition。&lt;/p&gt;
&lt;p&gt;為什麼要綁住 AI 不准代寫，&lt;a href=&quot;/blog/post-11/&quot;&gt;上一篇&lt;/a&gt;講過了，這篇只講學到的東西。&lt;/p&gt;
&lt;h3&gt;第一個端點長這樣&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;十幾行，但每一行都比我以為的多做了事&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Day 1 的題目是寫一個健康檢查端點，回傳服務狀態跟時間。這種端點是讓外部確認服務還活著用的，部署平台會定期打它，沒回應就當作服務掛了。&lt;/p&gt;
&lt;p&gt;用的寫法叫 Minimal API，是 ASP.NET Core 寫 API 最精簡的方式。不需要另外開 Controller 類別，幾行就能架起一個服務。&lt;/p&gt;
&lt;p&gt;補全關掉之後我重寫出來的版本是這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; builder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; WebApplication&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;MapGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/health&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;HealthResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; TypedResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/// &amp;lt;summary&gt;健康檢查回應。&amp;lt;/summary&gt;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/// &amp;lt;param name=&quot;Status&quot;&gt;服務狀態。&amp;lt;/param&gt;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/// &amp;lt;param name=&quot;Timestamp&quot;&gt;回應產生時間（UTC）。&amp;lt;/param&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HealthResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; Status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DateTime&lt;/span&gt; Timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最下面那個 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 是 C# 專門用來裝資料的型別，宣告一行就有建構子跟屬性，Day 2 會再細講。&lt;code class=&quot;language-text&quot;&gt;TypedResults.Ok(...)&lt;/code&gt; 則是回傳 HTTP 200，並把內容一起送出去。&lt;/p&gt;
&lt;p&gt;我 JS 寫得比 C# 多，第一眼看這段，腦中浮現的是 Express 的 &lt;code class=&quot;language-text&quot;&gt;app.get(&apos;/health&apos;, handler)&lt;/code&gt;。這裡的 handler 指的是處理請求的那個函式。C# 那邊的 &lt;code class=&quot;language-text&quot;&gt;() =&gt; { ... }&lt;/code&gt; 叫做 lambda，其實就是 JS 的箭頭函式。&lt;/p&gt;
&lt;p&gt;感覺很熟，所以我很自然地把 &lt;code class=&quot;language-text&quot;&gt;MapGet&lt;/code&gt; 讀成「有人打 &lt;code class=&quot;language-text&quot;&gt;/health&lt;/code&gt; 的時候，執行這個 lambda」。&lt;/p&gt;
&lt;p&gt;這個讀法不算錯，但漏掉了最重要的一半。&lt;/p&gt;
&lt;h3&gt;註冊，不是執行&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;MapGet&lt;/code&gt; 跑的時候，根本還沒有任何請求&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;MapGet&lt;/code&gt; 這一行是在 &lt;code class=&quot;language-text&quot;&gt;app.Run()&lt;/code&gt; 之前執行的。那時候伺服器還沒開始聽 port，不可能有請求進來。&lt;/p&gt;
&lt;p&gt;所以它做的事情只有一件，就是把「&lt;code class=&quot;language-text&quot;&gt;/health&lt;/code&gt; 對應到這段 lambda」寫進路由表。&lt;/p&gt;
&lt;p&gt;真正執行 lambda 的，是之後每一個打進來的請求。&lt;/p&gt;
&lt;p&gt;用餐廳來想會比較清楚。開店之前，老闆把菜單寫好、規定每道菜的做法，這是 &lt;code class=&quot;language-text&quot;&gt;MapGet&lt;/code&gt;。開店之後客人點一份，廚房就照做一份，這是請求進來。&lt;/p&gt;
&lt;p&gt;廚房不會在每個客人點餐的時候，重新討論這道菜要怎麼做。&lt;/p&gt;
&lt;h3&gt;編譯期、啟動期、請求期&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;整個 Day 1 最後收斂成這三行&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;寫 JS 的時候，存檔就能直接跑。C# 不一樣，執行之前要先經過編譯器，把程式碼翻成 .NET 執行環境看得懂的格式，順便把型別全部檢查一遍。這一步就是編譯期。&lt;/p&gt;
&lt;p&gt;把「登記」跟「執行」拆開之後，我發現其實有三個時間點，不是兩個：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;編譯期  → 編譯器推導出 lambda 的 delegate 型別，記進編譯結果
啟動期  → MapGet 執行，讀這份型別資訊，決定回應策略，寫進路由表
請求期  → 請求進來，照路由表既定策略執行。不猜、不判斷。&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;delegate 是 C# 用來描述「一個函式長什麼樣子」的型別，包括吃什麼參數、回傳什麼。&lt;/p&gt;
&lt;p&gt;最前面那一段，是我用一個會失敗的實驗確認的。&lt;/p&gt;
&lt;p&gt;我故意寫了一個 lambda，某個分支回傳字串，另一個分支回傳物件。程式根本跑不起來，&lt;strong&gt;還沒執行就報錯&lt;/strong&gt;，說 delegate 的型別推導不出來。&lt;/p&gt;
&lt;p&gt;這代表「這個 handler 會回傳什麼」在編譯的時候就要定下來了。回傳 &lt;code class=&quot;language-text&quot;&gt;string&lt;/code&gt; 就是純文字，回傳物件就序列化成 JSON，框架不會等到請求進來才去看你回了什麼。&lt;/p&gt;
&lt;h3&gt;跳進原始碼，然後迷路&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;抓機制，不要啃實作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;規則裡有一條是遇到框架的「魔法」，要自己跳進原始碼看，不准問 AI 也不准 Google。&lt;/p&gt;
&lt;p&gt;我寫 C# 用的 IDE 是 JetBrains 的 Rider，游標停在方法名稱上按 Cmd+B，就會直接跳到框架裡那個方法的實作。&lt;/p&gt;
&lt;p&gt;所以我從 &lt;code class=&quot;language-text&quot;&gt;MapGet&lt;/code&gt; 一路往裡面跳，最後停在一個叫 &lt;code class=&quot;language-text&quot;&gt;RequestDelegateFactory&lt;/code&gt; 的類別。&lt;/p&gt;
&lt;p&gt;這個檔案大約一千五百行。&lt;/p&gt;
&lt;p&gt;我很認真地從頭讀，讀到中途就完全不知道自己在哪裡了 😵（然後默默把檔案往上捲回去，假裝剛剛沒讀過）&lt;/p&gt;
&lt;p&gt;後來換了一個讀法。不去理解每一行在做什麼，只找一件事的證據，就是 lambda 是不是在啟動期被加工一次，之後就不再動。&lt;/p&gt;
&lt;p&gt;證據是兩個東西。一個是裡面有 &lt;code class=&quot;language-text&quot;&gt;.Compile()&lt;/code&gt;，把 lambda 編譯成一個 &lt;code class=&quot;language-text&quot;&gt;RequestDelegate&lt;/code&gt;。另一個是我在啟動期把那個 delegate 的 &lt;code class=&quot;language-text&quot;&gt;GetType()&lt;/code&gt; 印出來，確認它真的是一個已經做好的東西，而不是每次請求才產生。&lt;/p&gt;
&lt;p&gt;讀框架原始碼的時候，你不需要懂一千五百行。&lt;/p&gt;
&lt;p&gt;你只需要知道自己要找什麼，然後找到那一行。&lt;/p&gt;
&lt;h3&gt;放錯位置的 record&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;啟動期只跑一次，所以放在那裡的東西也只會產生一次&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;搞懂三個時期之後，我回頭看 Day 1 犯的一個錯，才真的看懂它錯在哪。&lt;/p&gt;
&lt;p&gt;我一開始把 &lt;code class=&quot;language-text&quot;&gt;HealthResponse&lt;/code&gt; 建在 handler 外面，像這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;HealthResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UtcNow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;MapGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/health&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; TypedResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;程式會動，打 &lt;code class=&quot;language-text&quot;&gt;/health&lt;/code&gt; 也有回應。但不管打幾次，&lt;code class=&quot;language-text&quot;&gt;Timestamp&lt;/code&gt; 永遠是同一個時間。&lt;/p&gt;
&lt;p&gt;因為 handler 外面那一行屬於啟動期，整個伺服器生命週期只跑一次。之後每個請求拿到的，都是開店那一刻做好的那一盤菜。&lt;/p&gt;
&lt;p&gt;這個錯本身沒什麼殺傷力，頂多時間不對。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但同樣的位置，如果放的是一個會被寫入的變數，就是另一回事了。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;親手做出一個 race condition&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;說好的一百萬，最後只剩三十五萬&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以 Day 1 的最後一個實驗，是故意把這個錯放大。&lt;/p&gt;
&lt;p&gt;我在 handler 外面放一個共享的 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt;，然後開 10 個 &lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt; 同時跑，每個都對它加 10 萬次。&lt;code class=&quot;language-text&quot;&gt;Task&lt;/code&gt; 可以先想成 C# 版的 Promise，Day 2 會講。&lt;/p&gt;
&lt;p&gt;照理說，最後的數字應該是一百萬。&lt;/p&gt;
&lt;p&gt;實際跑出來是 &lt;strong&gt;353,900&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;跑之前我有先賭一把，賭結果會「比較少」，這點有賭對。但我沒想到會少這麼多，三分之二的加法直接消失了。&lt;/p&gt;
&lt;p&gt;原因在 &lt;code class=&quot;language-text&quot;&gt;count++&lt;/code&gt; 這個看起來只有一步的操作。它其實是三步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;讀出 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt; 現在的值&lt;/li&gt;
&lt;li&gt;加一&lt;/li&gt;
&lt;li&gt;寫回去&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;兩個 Task 如果同時讀到 100，各自加一變成 101，再先後寫回去，結果是 101 而不是 102。&lt;/p&gt;
&lt;p&gt;有一次加法就這樣不見了。十個 Task 一起搶的時候，這種事情會一直發生。&lt;/p&gt;
&lt;p&gt;想像十個人同時在改白板上的同一個數字。每個人都是看一眼、心算加一、擦掉重寫，看的那一眼跟寫的那一下中間，別人早就改過了。&lt;/p&gt;
&lt;p&gt;解法的方向是讓「讀、改、寫」變成一個不能被插隊的動作，也就是&lt;strong&gt;原子操作&lt;/strong&gt;。具體怎麼寫，課程排在第三週，我還沒學到。&lt;/p&gt;
&lt;p&gt;這裡我也學到要分清楚兩種情況。共享狀態如果只有讀，問題是資料舊了，像上面那個永遠不更新的時間。如果有併發在寫，問題就會變成 race condition，資料直接錯掉。&lt;/p&gt;
&lt;p&gt;前者很容易發現，後者不會報任何錯，數字就只是靜靜地變少。&lt;/p&gt;
&lt;h3&gt;Day 1 結束時，我真的會的是什麼&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;照我自己的定義，只有能憑記憶重寫的才算&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;backend-lab&lt;/code&gt; 有一張掌握度的表，綠燈的定義是「打開空白檔案、不看範例能寫出來」。Day 1 結束時，綠燈的有這幾個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Minimal API 的基本結構&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;MapGet&lt;/code&gt; 是註冊，不是執行&lt;/li&gt;
&lt;li&gt;編譯期、啟動期、請求期的時間軸&lt;/li&gt;
&lt;li&gt;回傳型別決定回應格式&lt;/li&gt;
&lt;li&gt;handler 外的共享狀態是陷阱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;還有一個是黃燈，就是&lt;a href=&quot;/blog/post-11/&quot;&gt;上一篇&lt;/a&gt;講過的，我把 &lt;code class=&quot;language-text&quot;&gt;string&lt;/code&gt; 寫成 &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果你也剛開始碰 ASP.NET Core，我覺得 Day 1 最值得帶走的是那條時間軸。之後要學的依賴注入、middleware，都是建立在同一件事上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;啟動期先登記好，請求期只是照表執行。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(來自一個想把一千五百行讀完、讀到中途就迷路的人)&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[自我思考 - 我叫 AI 不准幫我寫程式。]]></title><description><![CDATA[今年七月我開了一個學後端的練習專案 ，用 C# / ASP.NET Core 從零開始學。這個專案跟我其他專案最不一樣的地方，是我在給 AI 的設定檔裡寫的第一條規則。不准替我寫實作程式碼。 這篇在講我為什麼要這樣綁住它、這個模式抓到我哪些裝懂的地方，以及這個預計四週的課程，最後停在第二天。 為什麼要綁住一個很會寫程式的工具 我要的是自己真的懂，不是 AI 幫我處理好 現在要寫一支 API，叫 AI…]]></description><link>https://huberthsieh.dev/blog/post-11/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-11/</guid><category><![CDATA[Reflection]]></category><pubDate>Sat, 19 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今年七月我開了一個學後端的練習專案 &lt;code class=&quot;language-text&quot;&gt;backend-lab&lt;/code&gt;，用 C# / ASP.NET Core 從零開始學。這個專案跟我其他專案最不一樣的地方，是我在給 AI 的設定檔裡寫的第一條規則。&lt;strong&gt;不准替我寫實作程式碼。&lt;/strong&gt; 這篇在講我為什麼要這樣綁住它、這個模式抓到我哪些裝懂的地方，以及這個預計四週的課程，最後停在第二天。&lt;/p&gt;
&lt;h3&gt;為什麼要綁住一個很會寫程式的工具&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;我要的是自己真的懂，不是 AI 幫我處理好&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;現在要寫一支 API，叫 AI 產一個出來大概只要幾十秒，而且它會動。問題就在「它會動」這件事。&lt;/p&gt;
&lt;p&gt;會動的程式碼放在我眼前，我看得懂，也改得動，久了就會以為自己會了。但如果有人叫我打開空白檔案、不看範例重寫一次，我寫不寫得出來？老實說我不知道，而且在開這個專案之前，我從來沒有真的試過。&lt;/p&gt;
&lt;p&gt;這有點像開車靠導航。開導航可以到任何地方，每天走同一條路上下班也沒問題。但哪天導航沒訊號，你才發現自己根本不知道那條路怎麼走。&lt;/p&gt;
&lt;p&gt;你沒有迷路過，不代表你認得路。&lt;/p&gt;
&lt;p&gt;我想真的掌握 C# 跟 .NET 底層的邏輯，所以乾脆把導航關掉。&lt;/p&gt;
&lt;h3&gt;我給 AI 的規則，比給自己的還多&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;它的角色是家教跟審查者，不是寫手&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;CLAUDE.md&lt;/code&gt; 是 Claude Code 每次開工都會讀的設定檔。我在裡面寫的「絕對禁止」有五條是給它的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不准替我寫實作程式碼&lt;/li&gt;
&lt;li&gt;不准直接幫我修 bug，我卡住時只能引導&lt;/li&gt;
&lt;li&gt;不准一步給出完整答案&lt;/li&gt;
&lt;li&gt;不准給我「骨架讓我填空」，那只是包裝過的代寫&lt;/li&gt;
&lt;li&gt;不准把我還沒理解的套件或 pattern 塞進來&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;然後有三條是給我自己的，而且要它&lt;strong&gt;主動監督我&lt;/strong&gt;。IDE 的 AI 補全全部關掉、不准複製貼上我不懂的程式碼、不准跳過驗證就往下走。&lt;/p&gt;
&lt;p&gt;我卡住的時候，提示分成四層，一次只給一層：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 1 層：告訴我這是什麼概念，要我自己回想&lt;/li&gt;
&lt;li&gt;第 2 層：給方向，或問我一個對的問題&lt;/li&gt;
&lt;li&gt;第 3 層：給虛擬碼，不給 C#&lt;/li&gt;
&lt;li&gt;第 4 層：我要明講「我放棄，給我看」，它才給最小範例，而且給完要我&lt;strong&gt;關掉、憑記憶重寫&lt;/strong&gt;，重寫過才算數&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最後整份設定只繞著一個問題轉：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「Hubert 現在能不能打開空白檔案、不看任何範例、不用補全，把這段重寫出來？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;不能的話，不管程式會不會動，一律標紅燈。&lt;/p&gt;
&lt;h3&gt;第一天就被抓到用補全&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;眼睛記住，不等於手記住&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;規則是我自己寫的，但第一天寫的第一段程式碼，就是 IDE 的 AI 補全幫我寫的。😅&lt;/p&gt;
&lt;p&gt;那天的題目很簡單，寫一個 &lt;code class=&quot;language-text&quot;&gt;/health&lt;/code&gt; 端點，回傳服務狀態跟時間。補全關掉之後重寫一次，七個重點我對了六個，錯的那個是把 &lt;code class=&quot;language-text&quot;&gt;string&lt;/code&gt; 寫成 &lt;code class=&quot;language-text&quot;&gt;String&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;這個錯本身很小，C# 裡兩個都能編譯。但它說明了一件事。我明明看過好幾次 &lt;code class=&quot;language-text&quot;&gt;string&lt;/code&gt;，看的時候也完全知道它是對的，手打出來的卻不是它。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;看得懂跟寫得出，是兩種不同的記憶。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那天還抓到另一件更丟臉的事。我一直在問環境設定、專案結構、IDE 怎麼設，問了很久還沒開始寫 code。後來我在錯誤紀錄裡替它取了個名字叫「準備成癮」。感覺很像在進步，其實是在拖延真正難的那一步。&lt;/p&gt;
&lt;h3&gt;同一個錯，我犯了三次&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;講得出來的觀念不算數，印出來的數字才算&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Day 2 學 &lt;code class=&quot;language-text&quot;&gt;async&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt;。JS 的 Promise 我寫了好幾年，心裡覺得這塊應該很快。&lt;/p&gt;
&lt;p&gt;結果這是我整個專案翻車最多次的地方。&lt;/p&gt;
&lt;p&gt;我的誤會是以為 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 之後，程式會「交給另一條執行緒」去跑。所以被問到「呼叫一個 async 方法之後，呼叫端的下一行是哪條執行緒在跑」，我前後講錯了三次，一下說是空閒的執行緒，一下說是 timer。&lt;/p&gt;
&lt;p&gt;第三次講錯之後，AI 沒有再解釋一遍，而是叫我把執行緒 ID 印出來自己看。幾天後它又抽考我一次，我重寫了一個更精簡的版本：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;AAA thread = &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;Thread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CurrentThread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ManagedThreadId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;FooAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;DDD thread = &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;Thread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CurrentThread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ManagedThreadId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;FooAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;BBB thread = &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;Thread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CurrentThread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ManagedThreadId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    Console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;CCC thread = &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;Thread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CurrentThread&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ManagedThreadId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;跑出來的是：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;AAA thread = 1
BBB thread = 1
CCC thread = 5
DDD thread = 5&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 之前都在主執行緒（1 號）上跑，而且 &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 本身不會換執行緒，它只是把控制權交還給呼叫端。真正換人的是&lt;strong&gt;等待結束之後&lt;/strong&gt;。後半段由 thread pool 裡剛好有空的執行緒接手（5 號）。至於那一秒的等待，是交給 timer 處理的，它根本不佔用任何執行緒。&lt;/p&gt;
&lt;p&gt;我後來把它記成一句話，&lt;strong&gt;timer 負責等待，執行緒負責跑程式碼。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這也解釋了我覺得最神奇的一個實驗。同時開 99 個 &lt;code class=&quot;language-text&quot;&gt;Task.Delay(1000)&lt;/code&gt;，全部等完只花了 1002 毫秒，不是 99 秒。後端用 async 不是為了讓單一請求變快，是為了在等待的時候不浪費執行緒，同樣的機器可以同時扛更多請求。&lt;/p&gt;
&lt;p&gt;我在錯誤紀錄裡寫的教訓是，&lt;strong&gt;口頭最容易翻車，觀念要以實驗為準，不要相信自己的複述。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而且越是「我熟的東西」越危險，我在 JavaScript 那邊也犯了一個。&lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 了 &lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt; 的回傳值，以為它有在等。但 &lt;code class=&quot;language-text&quot;&gt;setTimeout&lt;/code&gt; 回傳的是一個數字，&lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; 一個數字等於沒等，時間戳印出來是 0ms。&lt;/p&gt;
&lt;h3&gt;最毒的 bug，是數字剛好對&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;對的數字加上錯的來源，比錯的數字還難抓&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Day 2 後段學 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 的相等性。我寫了一整排 &lt;code class=&quot;language-text&quot;&gt;HashSet&lt;/code&gt; 的實驗，驗證 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 跟一般 &lt;code class=&quot;language-text&quot;&gt;class&lt;/code&gt; 放進 &lt;code class=&quot;language-text&quot;&gt;HashSet&lt;/code&gt; 之後，重複的東西會不會被擋掉。&lt;/p&gt;
&lt;p&gt;所有數字都跟預期一樣。但其中有一行，我 &lt;code class=&quot;language-text&quot;&gt;Add&lt;/code&gt; 進的是 A 這個 set，印出來的卻是 B 的 &lt;code class=&quot;language-text&quot;&gt;Count&lt;/code&gt;。兩個 set 的數量剛好都是 2，所以數字完全對，看起來一點問題都沒有。&lt;/p&gt;
&lt;p&gt;如果數字是錯的，我會馬上去查。偏偏它是對的，所以我差點就讓它過去了。這件事讓我對「驗證」多了一層理解。不是看數字對不對就好，要較真&lt;strong&gt;這一行到底在量誰&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;預計四週，停在第二天&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;最後一筆 commit 是 2026-07-28&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;整個課程排了四週。第一週 HTTP 服務跟分層架構，第二週資料庫，第三週認證跟安全，第四週測試跟部署。&lt;/p&gt;
&lt;p&gt;我走到第一週的第二天。&lt;/p&gt;
&lt;p&gt;原因很單純。後來案子一個接一個來，而且接到的案子是 &lt;strong&gt;Python + Flask&lt;/strong&gt; 的 API，不是 C#。工作上用不到的東西，要在下班後擠出時間繼續學，說實話我沒擠出來。&lt;/p&gt;
&lt;p&gt;這個專案停下來，但我覺得它證明了一件事。第二天的兩個主題，&lt;code class=&quot;language-text&quot;&gt;async&lt;/code&gt; 跟 &lt;code class=&quot;language-text&quot;&gt;record&lt;/code&gt; 的相等性，我都是打開空白檔案、不看範例重寫過，再用實驗自己證明的。&lt;/p&gt;
&lt;p&gt;我沒有學很多，但這兩個，我是真的敢說我會。&lt;/p&gt;
&lt;h3&gt;如果你也想這樣學&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;先決定你要的是「做出來」還是「學會」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這兩個目標都沒有錯，但它們需要的 AI 用法剛好相反。趕案子的時候，讓 AI 多寫一點很合理。想學會某個東西的時候，它寫得越多，你學到的越少。&lt;/p&gt;
&lt;p&gt;如果你想試試看，我自己覺得最有用的是這三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;把規則寫下來，並且要它監督你。&lt;/strong&gt; 光靠自律，我第一天就破功了&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不懂的時候要實驗，不要要解釋。&lt;/strong&gt; AI 講得再清楚，都沒有你自己印出來的那個 5 號執行緒有說服力&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;準備一張「我犯過的錯」的表。&lt;/strong&gt; 我那張表現在是整個專案最有價值的檔案，因為它記的全是我以為自己懂、其實不懂的地方&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;接下來的案子是 Flask，我有在想用同一套規則再開一個 Python 的練習專案。至於 C# 那邊的第三天，就等案子告一段落再說了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(來自一個規則自己寫、第一天就自己破功的人)&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[後端知識 - ANR 的真兇：一張沒設計索引的資料表。]]></title><description><![CDATA[上一篇提過，那次 ANR 追到最後是三個問題疊在一起：迴圈裡查資料庫、缺索引、資料表設計本身有問題。這篇把「缺索引」這一項拉出來講，包括我是怎麼發現的，以及索引這東西到底在解決什麼問題。 我是在做開單系統的時候發現的 車牌身分查詢，資料量一大就開始卡 當時在做一個開單系統，裡面有一段是查車牌對應的身分資料。 功能本身沒有很複雜，但隨著資料越堆越多，這段查詢開始明顯變慢，慢到會卡住主執行緒、觸發 ANR。 這個專案的本地資料庫是用 GreenDAO…]]></description><link>https://huberthsieh.dev/blog/post-10/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-10/</guid><category><![CDATA[Android]]></category><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上一篇提過，那次 ANR 追到最後是三個問題疊在一起：迴圈裡查資料庫、缺索引、資料表設計本身有問題。這篇把「缺索引」這一項拉出來講，包括我是怎麼發現的，以及索引這東西到底在解決什麼問題。&lt;/p&gt;
&lt;h3&gt;我是在做開單系統的時候發現的&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;車牌身分查詢，資料量一大就開始卡&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;當時在做一個開單系統，裡面有一段是查車牌對應的身分資料。&lt;/p&gt;
&lt;p&gt;功能本身沒有很複雜，但隨著資料越堆越多，這段查詢開始明顯變慢，慢到會卡住主執行緒、觸發 ANR。&lt;/p&gt;
&lt;p&gt;這個專案的本地資料庫是用 GreenDAO 管的。我是從這段查詢往回查，才發現除了主鍵之外，整張資料表&lt;strong&gt;沒有任何一個欄位加過索引&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;不是這張表特別忘記加，是這個專案的資料表設計階段，索引這件事根本沒有被考慮進去。&lt;/p&gt;
&lt;p&gt;用了 ORM 之後，&lt;code class=&quot;language-text&quot;&gt;@Entity&lt;/code&gt; 一標、欄位一寫、表就生出來了，索引不是自動附贈的，你不特別標記，它就是不存在。&lt;/p&gt;
&lt;h3&gt;沒有索引，資料庫在做的事&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL 語法寫對，不代表資料庫找得快&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;沒有索引的查詢，資料庫的做法是&lt;strong&gt;把整張表從第一筆掃到最後一筆&lt;/strong&gt;，逐筆比對符不符合條件。&lt;/p&gt;
&lt;p&gt;表裡有一百筆，最壞情況就是比對一百次；有一百萬筆，就是一百萬次。&lt;/p&gt;
&lt;p&gt;這解釋了為什麼一開始完全沒感覺。測試階段資料量小，全表掃描跟有索引的查詢幾乎感覺不出差異。等資料真的長到「非常大」，這個差異才會被放大到使用者感覺得到。&lt;/p&gt;
&lt;p&gt;而放大到最後，就是主執行緒卡住、系統跳出 ANR 對話框。&lt;/p&gt;
&lt;p&gt;索引做的事，簡單說是幫資料庫先建好一份「排序過的索引」，查詢時不用整張表掃過去，而是直接用類似二分搜尋的方式定位到資料。&lt;/p&gt;
&lt;p&gt;差別是從「掃過去」變成「跳過去找」。&lt;/p&gt;
&lt;p&gt;GreenDAO 底層還是 SQLite，所以要看一段查詢到底有沒有吃到索引，一樣可以用 &lt;code class=&quot;language-text&quot;&gt;EXPLAIN QUERY PLAN&lt;/code&gt; 檢查，只是要先從 &lt;code class=&quot;language-text&quot;&gt;DaoSession&lt;/code&gt; 把底層的資料庫物件拿出來，才能下原生 SQL：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// DaoSession 底層包的還是 SQLiteDatabase，拿出來就能下原生 SQL&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;Cursor&lt;/span&gt; cursor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; daoSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDatabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rawQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;EXPLAIN QUERY PLAN SELECT * FROM PLATE WHERE PLATE_NUMBER = ?&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ABC-1234&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果結果顯示 &lt;code class=&quot;language-text&quot;&gt;SCAN PLATE&lt;/code&gt;，代表是全表掃描；顯示 &lt;code class=&quot;language-text&quot;&gt;SEARCH PLATE USING INDEX&lt;/code&gt;，才代表真的用到索引在找。&lt;/p&gt;
&lt;p&gt;在 GreenDAO 裡加索引，不是手寫 &lt;code class=&quot;language-text&quot;&gt;CREATE INDEX&lt;/code&gt;，是在 Entity 的欄位上標註解，重新產生程式碼即可：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Entity&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Plate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Id&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Long&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Index&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; plateNumber&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;要加唯一約束的話是 &lt;code class=&quot;language-text&quot;&gt;@Index(unique = true)&lt;/code&gt;。這一步的成本低到近乎沒有藉口。多一個註解、重新 build 一次，遠比想像中麻煩要小。&lt;/p&gt;
&lt;h3&gt;這件事回頭看，其實是設計階段就該問的問題&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;「這張表以後查詢最常用哪個欄位當條件？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;索引不是查詢慢了才補救的東西，是設計資料表的當下就該想的問題。這張表以後最常用哪個欄位去查？那個欄位就該加索引。&lt;/p&gt;
&lt;p&gt;我們當時完全沒有問過這個問題，資料表是照著「有哪些欄位」去設計的，不是照著「以後會怎麼查」去設計的。&lt;/p&gt;
&lt;p&gt;這跟前端的效能問題不太一樣。前端效能不好，通常是畫面明顯卡、掉幀，有感覺。&lt;/p&gt;
&lt;p&gt;資料庫缺索引，在資料量小的時候完全沒有任何徵兆，等你發現的時候，通常已經是資料量長到某個門檻、使用者已經在抱怨的時候了。&lt;/p&gt;
&lt;p&gt;如果你也在用 ORM 管本地資料庫，這件事更容易被忽略，因為連 &lt;code class=&quot;language-text&quot;&gt;CREATE TABLE&lt;/code&gt; 都是自動生成的，索引這種「不寫就不會出現」的東西，很容易被誤會成 ORM 已經幫你顧好了。&lt;/p&gt;
&lt;p&gt;如果你也在做會長期累積資料的系統，這是一個值得在寫下第一個 &lt;code class=&quot;language-text&quot;&gt;@Entity&lt;/code&gt; 之前先問自己的問題。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[前端知識 - React的生命週期：三年後，那篇文章有一半該重寫。]]></title><description><![CDATA[三年前我寫過一篇 React 生命週期，內文列的方法全部是 Class Component 的 、 那一套，但程式碼範例最後默默附了一個用  /  的 function component，內文一個字都沒解釋它。這篇是那篇的續集，重點在  對不上 Class 生命週期的地方，以及 StrictMode 下 effect 會跑兩次這件事。 先承認那篇的問題 不是內容寫錯，是重心押在一個現在幾乎不會用的寫法上 三年前那篇在這裡。回頭看，Mount / Update / Unmount 這三個階段的描述到今天都還成立，那部分不用改。 問題在後面：我用了整整三節去列 Class…]]></description><link>https://huberthsieh.dev/blog/post-08/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-08/</guid><category><![CDATA[JavaScript]]></category><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;三年前我寫過一篇 React 生命週期，內文列的方法全部是 Class Component 的 &lt;code class=&quot;language-text&quot;&gt;componentDidMount&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;shouldComponentUpdate&lt;/code&gt; 那一套，但程式碼範例最後默默附了一個用 &lt;code class=&quot;language-text&quot;&gt;useState&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 的 function component，內文一個字都沒解釋它。這篇是那篇的續集，重點在 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; &lt;strong&gt;對不上&lt;/strong&gt; Class 生命週期的地方，以及 StrictMode 下 effect 會跑兩次這件事。&lt;/p&gt;
&lt;h3&gt;先承認那篇的問題&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;不是內容寫錯，是重心押在一個現在幾乎不會用的寫法上&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;/blog/post-04/&quot;&gt;三年前那篇在這裡&lt;/a&gt;。回頭看，Mount / Update / Unmount 這三個階段的描述到今天都還成立，那部分不用改。&lt;/p&gt;
&lt;p&gt;問題在後面：我用了整整三節去列 Class 的生命週期方法，然後在最下面的 code block 裡貼了一個 function component，內文完全沒提到它。&lt;/p&gt;
&lt;p&gt;老實說當年我根本沒意識到那段需要解釋。我大概是想「順便附一個現代寫法」，然後就這樣貼上去了 😅。&lt;/p&gt;
&lt;p&gt;結果就是那篇文章的重心，押在一個 2026 年幾乎不會有人新寫的東西上。&lt;/p&gt;
&lt;h3&gt;&lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 不是「三個生命週期合體」&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;這是從 Class 過來的人最容易帶著的錯誤直覺&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;很多教學會給你一張對照表，說 &lt;code class=&quot;language-text&quot;&gt;useEffect(fn, [])&lt;/code&gt; 等於 &lt;code class=&quot;language-text&quot;&gt;componentDidMount&lt;/code&gt;、回傳的清理函式等於 &lt;code class=&quot;language-text&quot;&gt;componentWillUnmount&lt;/code&gt;。這個對照&lt;strong&gt;大概方向對，細節全錯&lt;/strong&gt;，而錯的地方剛好就是會咬你的地方。&lt;/p&gt;
&lt;p&gt;我在自己的專案（React 18.2）裡開了一個測試頁，把三種 &lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 掛在同一個元件上，實際跑一次看 console：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 三種寫法掛在同一個元件上，看它們各自什麼時候被呼叫&lt;/span&gt;
React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;effect deps=[] 執行&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;effect deps=[] 清理&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;effect 無 deps 執行 count=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;count&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;effect 無 deps 清理 count=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;count&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;effect deps=[count] 執行 count=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;count&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;effect deps=[count] 清理 count=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;count&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;把 &lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt; 從 0 加到 1，實際輸出是這樣：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;effect 無 deps 清理 count=0
effect deps=[count] 清理 count=0
effect 無 deps 執行 count=1
effect deps=[count] 執行 count=1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;三件事可以直接讀出來。&lt;/p&gt;
&lt;h4&gt;清理函式不是只在銷毀時跑&lt;/h4&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;componentWillUnmount&lt;/code&gt; 一輩子只跑一次，就是元件要消失的時候。但上面那段輸出裡，元件根本沒有消失，&lt;code class=&quot;language-text&quot;&gt;count&lt;/code&gt; 只是從 0 變 1，清理函式就已經跑了兩次。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;只要 effect 要重跑，它會先把上一輪清理掉。&lt;/strong&gt; 所以正確的心智模型不是「掛載時執行、卸載時清理」，而是「每一輪 effect 自己負責收拾自己」。&lt;/p&gt;
&lt;h4&gt;清理函式看到的是舊的值&lt;/h4&gt;
&lt;p&gt;注意上面那兩行清理印的是 &lt;code class=&quot;language-text&quot;&gt;count=0&lt;/code&gt;，不是 &lt;code class=&quot;language-text&quot;&gt;count=1&lt;/code&gt;。清理函式是上一輪 render 建立的閉包，它記得的是&lt;strong&gt;建立當下&lt;/strong&gt;的值，不是執行當下的值。&lt;/p&gt;
&lt;p&gt;這件事像離職交接。你交接的是自己在職期間知道的狀況，不是公司現在的狀況。如果你在清理函式裡拿某個 state 去做判斷，你拿到的是舊的那份。&lt;/p&gt;
&lt;p&gt;這是我自己踩過最久才想通的一個坑。&lt;/p&gt;
&lt;h4&gt;&lt;code class=&quot;language-text&quot;&gt;deps=[]&lt;/code&gt; 那個從頭到尾沒動&lt;/h4&gt;
&lt;p&gt;它在 update 時完全沒有出現在輸出裡。這部分確實接近 &lt;code class=&quot;language-text&quot;&gt;componentDidMount&lt;/code&gt;。但下一節就會看到，連這個也不完全成立。&lt;/p&gt;
&lt;h3&gt;沒有 &lt;code class=&quot;language-text&quot;&gt;shouldComponentUpdate&lt;/code&gt; 的等價物&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;這是對照表上最常被跳過的一格&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;useEffect&lt;/code&gt; 沒有任何辦法「阻止這次更新」。Class 的 &lt;code class=&quot;language-text&quot;&gt;shouldComponentUpdate&lt;/code&gt; 回傳 &lt;code class=&quot;language-text&quot;&gt;false&lt;/code&gt; 就整個 re-render 都不會發生，但 effect 是在 render &lt;strong&gt;之後&lt;/strong&gt;才跑的，它沒有投票權。&lt;/p&gt;
&lt;p&gt;要擋 re-render 得換工具，而且這裡很容易搞混。&lt;strong&gt;真正能跳過 re-render 的只有 &lt;code class=&quot;language-text&quot;&gt;React.memo&lt;/code&gt;&lt;/strong&gt;（對應 Class 的 &lt;code class=&quot;language-text&quot;&gt;shouldComponentUpdate&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;PureComponent&lt;/code&gt;）。&lt;code class=&quot;language-text&quot;&gt;useMemo&lt;/code&gt; 和 &lt;code class=&quot;language-text&quot;&gt;useCallback&lt;/code&gt; 本身&lt;strong&gt;不會阻止任何一次 re-render&lt;/strong&gt;。它們只是讓傳給子元件的值或函式在每次 render 之間保持同一個參考，好讓外面包了 &lt;code class=&quot;language-text&quot;&gt;React.memo&lt;/code&gt; 的子元件有機會判斷「沒變」而跳過。&lt;/p&gt;
&lt;p&gt;單獨用 &lt;code class=&quot;language-text&quot;&gt;useCallback&lt;/code&gt; 包一個 callback，什麼都擋不到。&lt;/p&gt;
&lt;p&gt;而且這些是「效能優化」而不是「生命週期」，觀念上是兩件事。這也是為什麼把 hooks 硬塞進生命週期的表格裡會怪怪的。&lt;/p&gt;
&lt;h3&gt;StrictMode 下 effect 會跑兩次&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;React 18 開始的行為，第一次遇到會以為自己寫錯了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我在測試頁裡放了兩組一樣的元件，一組直接放、一組用 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;React.StrictMode&gt;&lt;/code&gt; 包起來，同一次載入的 console 輸出：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[plain]  effect deps=[] 執行
[plain]  effect 無 deps 執行 count=0
[plain]  effect deps=[count] 執行 count=0
[strict] effect deps=[] 執行
[strict] effect 無 deps 執行 count=0
[strict] effect deps=[count] 執行 count=0
[strict] effect deps=[] 清理
[strict] effect 無 deps 清理 count=0
[strict] effect deps=[count] 清理 count=0
[strict] effect deps=[] 執行
[strict] effect 無 deps 執行 count=0
[strict] effect deps=[count] 執行 count=0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;沒包的那組跑一次，包了 StrictMode 的那組是&lt;strong&gt;執行 → 清理 → 再執行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這是 React 18 刻意加的：它在開發模式下故意把元件掛載兩次，用來逼出「清理函式沒寫好」的 bug。如果你的 effect 發了一支 API 而沒有在清理函式裡取消，你就會看到請求送兩次。&lt;/p&gt;
&lt;p&gt;那不是 React 壞掉，是它在告訴你這段程式碼經不起重複掛載。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;只在開發模式發生，production build 不會。&lt;/strong&gt; 但正因為只在開發模式，很容易被誤判成「我的程式碼有問題」而花時間去追一個不存在的 bug。上面那段 &lt;code class=&quot;language-text&quot;&gt;deps=[]&lt;/code&gt; 的 effect 在這裡也被清理了一次。所以連「&lt;code class=&quot;language-text&quot;&gt;deps=[]&lt;/code&gt; 等於 &lt;code class=&quot;language-text&quot;&gt;componentDidMount&lt;/code&gt;」都不能算完全成立。&lt;/p&gt;
&lt;h3&gt;那篇文章我不打算刪掉&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;寫錯的東西留著，比刪掉有用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Class Component 的生命週期還是有價值。維護舊專案會遇到，面試偶爾還是會問，而且理解 Class 那一套之後再看 hooks，會更清楚 hooks 到底解掉了什麼問題。&lt;/p&gt;
&lt;p&gt;真正該補的是這篇：把當年那個沒被解釋的 function component 講完。如果你也是從 Class 那個年代過來的，希望這篇能幫你少走一點我走過的冤枉路。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[自我思考 - 停更三年，回來才發現網站也壞了兩年。]]></title><description><![CDATA[這個部落格最後一篇文章是 2023-05-24，之後停了三年。這次回來想更新，第一件事不是寫文章，是發現網站根本部署不了。最後一次成功部署停在 2024-05，也就是說它已經壞了兩年，而這兩年裡沒有任何人告訴我，包括我自己。這篇是關於那三年我去做了什麼，以及為什麼「不會的時候，人是寫不出東西的」。 最後一篇文章的日期，剛好在換工作前一週 2023-05-24 發文，2023-06 進新公司 我沒有刻意停更。回頭看時間點才發現，最後那幾篇面試日記寫完、offer 拿到、進了新公司，然後就沒有然後了。 新工作跟我原本做的事不一樣。以前我是「寫前端的」，到了新公司變成前後台都要碰，而且手機 App…]]></description><link>https://huberthsieh.dev/blog/post-09/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-09/</guid><category><![CDATA[Reflection]]></category><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;這個部落格最後一篇文章是 2023-05-24，之後停了三年。這次回來想更新，第一件事不是寫文章，是發現網站根本部署不了。最後一次成功部署停在 2024-05，也就是說它已經壞了兩年，而這兩年裡沒有任何人告訴我，包括我自己。這篇是關於那三年我去做了什麼，以及為什麼「不會的時候，人是寫不出東西的」。&lt;/p&gt;
&lt;h3&gt;最後一篇文章的日期，剛好在換工作前一週&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;2023-05-24 發文，2023-06 進新公司&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我沒有刻意停更。回頭看時間點才發現，最後那幾篇面試日記寫完、offer 拿到、進了新公司，然後就沒有然後了。&lt;/p&gt;
&lt;p&gt;新工作跟我原本做的事不一樣。以前我是「寫前端的」，到了新公司變成前後台都要碰，而且手機 App 是&lt;strong&gt;開發跟維護都要做&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;不是接手別人寫好的東西改一改就好，新功能得自己從頭做，既有的也得自己顧下去。&lt;/p&gt;
&lt;p&gt;換句話說，我不只是在一個陌生的平台上修東西，是要在那個平台上做決定。而做決定這件事，需要的是「真的懂」，不是「查得到」。&lt;/p&gt;
&lt;p&gt;工作忙起來是一個原因，但那不是真正的原因。忙的時候人還是有辦法擠出時間做喜歡的事。真正的原因比較難承認。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我從一個我會的領域，跳到一個我不會的領域。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;然後我遇到了 ANR&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;前端沒有這種東西：畫面卡住不只是難看，是作業系統直接把你的 App 判死刑&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在網頁上，主執行緒被卡住的後果是畫面頓一下、動畫掉幀、使用者覺得這網站有點慢。不好，但不致命。&lt;/p&gt;
&lt;p&gt;在 Android 上，主執行緒卡太久，系統會跳出一個對話框問使用者「這個應用程式沒有回應，要關閉嗎」。這就是 ANR（Application Not Responding）。&lt;/p&gt;
&lt;p&gt;它不是你的程式碼丟出的錯誤，是作業系統對你下的判決。&lt;/p&gt;
&lt;p&gt;第一次遇到的時候我完全不知道從哪裡查起。錯誤訊息不會告訴你「你哪一行寫錯了」，因為根本沒有哪一行寫錯：每一行單獨看都是對的，錯的是它們加起來太慢。&lt;/p&gt;
&lt;p&gt;而且它不是使用者回報給我的，是我自己測出來的。把測試資料灌多一點之後，App 開始卡，卡到系統跳出那個對話框。&lt;/p&gt;
&lt;p&gt;資料少的時候一切正常，我原本以為那就代表沒問題。&lt;/p&gt;
&lt;h3&gt;從 ANR 追到 SQLite&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;症狀跟根因隔了三層，這是我學到最多的一次除錯&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;追查的路徑大概是這樣：先確認不是單一功能的 bug，而是效能問題；再往下找是哪一段耗時；最後追到資料庫。&lt;/p&gt;
&lt;p&gt;問題不只一個，是三個疊在一起：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;在迴圈裡查資料庫&lt;/strong&gt;。一個列表跑迴圈，每一筆都去查一次，十筆就是十次查詢&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺索引&lt;/strong&gt;。沒有索引的查詢就是整張表掃過去，資料少的時候快得看不出差別&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;資料表設計本身有問題&lt;/strong&gt;，讓上面兩件事變得更容易發生&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這三個有一個共同點，&lt;strong&gt;資料少的時候全部都看不出來。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而且它們不是相加的，是相乘的。迴圈的次數乘上每次查詢要掃過的資料量。&lt;/p&gt;
&lt;p&gt;資料量小的時候，小數字乘小數字還是小數字；資料一多，兩邊同時變大，乘起來就直接把主執行緒卡到系統受不了。&lt;/p&gt;
&lt;p&gt;這也是為什麼我一開始完全找不到「哪一行寫錯了」。每一行單獨看都對，錯的是這個設計在資料長大之後撐不住，而它在資料還小的時候完全沒有徵兆。&lt;/p&gt;
&lt;p&gt;這段除錯經驗跟我以前在前端做的完全不同。前端的效能問題通常看得見，開 DevTools 就有火焰圖，哪一段慢一目了然。&lt;/p&gt;
&lt;p&gt;這次是「東西在使用者手機上偶爾會死，而且不一定重現」。&lt;/p&gt;
&lt;h3&gt;三年前那篇文章，我自己變成了裡面的人&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;/blog/post-02/&quot;&gt;〈為什麼老是覺得自己不夠好？〉&lt;/a&gt;寫在 2023-05-16&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;那篇裡我寫過一段：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;很多時候我們的確會應用一些技術，不論是查看文件或是 Google 都有相當多的範例可以參考⋯⋯但深層的原理如果沒搞懂的話，下次有人問起，要描述或是解說的話大概率又講不出來。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;寫那篇的時候，我是站在「已經會前端」的位置上談這件事的。三年後我跳到 Android，變成那個「會用但講不出原理」的人本人。&lt;/p&gt;
&lt;p&gt;我還在那篇裡用三步上籃當比喻。不熟練的人一下同手同腳，一下走步，一下力道過猛。當時我覺得那是個講給別人聽的比喻。&lt;/p&gt;
&lt;p&gt;追 ANR 那段時間，我就是那個同手同腳的人。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;而人在同手同腳的時候，是寫不出文章的。&lt;/strong&gt; 不是沒時間，是沒有東西可以寫。你正在經歷的事情你自己都還沒搞懂，怎麼寫成一篇給別人看的筆記？&lt;/p&gt;
&lt;p&gt;所以停更這件事，與其說是懶惰，不如說是&lt;strong&gt;我暫時失去了「可以拿出來講的東西」&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;網站是怎麼安靜爛掉的&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;沒有人在看，也沒有任何機制會告訴你它壞了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這次回來清點，才知道這三年裡它爛成什麼樣子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;部署從 2024-05 之後就沒成功過。&lt;/strong&gt; 原因是 Vercel 上的 Node 版本被釘在 18.x，而 Node 18 已經下架，建置在 clone 完就中止。那個版本號只存在 dashboard 的下拉選單裡，沒進版本控制，所以沒有任何東西會提醒我&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;/contact&lt;/code&gt; 的表單是死的。&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;&amp;lt;form action=&quot;&quot;&gt;&lt;/code&gt;，送出不會有訊息送達任何地方。掛在站上三年，訪客按了以為寄出，我什麼都沒收到&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;/blog/post-06/&quot;&gt;post-6&lt;/a&gt; 裡我寫「目前暫定 6/7(三) 前要給回應，到時再更新狀況」&lt;/strong&gt;，然後三年沒回來改那一行&lt;/li&gt;
&lt;li&gt;7 篇文章裡有 16 處格式錯誤，中文散文被包在程式碼區塊裡，在網頁上被語法高亮切成隨機顏色&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些沒有一項是「我知道但懶得修」。全部都是&lt;strong&gt;我不知道&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一個沒有人在看、也沒有任何機制會回報狀態的系統，就是會這樣安靜地爛掉，而且爛的過程完全沒有聲音。&lt;/p&gt;
&lt;h3&gt;這次補的是機制，不是決心&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;說「以後會常常更新」沒有用，我三年前就說過類似的話了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以這次回來，我沒有先寫文章，而是先把上面那些坑補起來，而且是用&lt;strong&gt;下次會自己叫我&lt;/strong&gt;的方式補：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node 版本寫進 &lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt; 的 &lt;code class=&quot;language-text&quot;&gt;engines&lt;/code&gt;，變成一個有 diff、有 commit 記錄的東西，而不是下拉選單裡的一個選項&lt;/li&gt;
&lt;li&gt;死掉的表單整頁移除，聯絡方式改成一個實測收得到信的信箱&lt;/li&gt;
&lt;li&gt;補上每頁的 title 與 description、sitemap、RSS。在那之前，&lt;code class=&quot;language-text&quot;&gt;/blog&lt;/code&gt;、&lt;code class=&quot;language-text&quot;&gt;/portfolio&lt;/code&gt; 加上 7 篇文章頁，九個頁面全部連 &lt;code class=&quot;language-text&quot;&gt;&amp;lt;title&gt;&lt;/code&gt; 標籤都沒有&lt;/li&gt;
&lt;li&gt;寫了一支檢查器，接在存檔的 hook 上。以後只要文章格式有問題，當場就會被擋下來，不用等我想起來去檢查&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最後這一項是我覺得最重要的。那 16 處格式錯誤，我當年每一處寫下來的時候都覺得很合理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不會覺得有錯的錯誤，不會有人主動去檢查。&lt;/strong&gt; 所以能救的只有機制，不是提醒自己要小心。&lt;/p&gt;
&lt;h3&gt;所以呢&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;不做承諾，只講現在的狀態&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我不打算說「以後會固定更新」。三年前的我大概也不覺得自己會停三年。&lt;/p&gt;
&lt;p&gt;能說的只有：現在網站是活的，信箱是通的，寫壞了會有東西擋我，而我手上重新有了一些「搞懂了、可以拿出來講」的東西。就先這樣，寫得出來就寫。&lt;/p&gt;
&lt;p&gt;如果你也有一個停更很久的部落格，我猜你的狀況可能跟我一樣。不是懶，是那段時間你正在變成一個新的菜鳥。&lt;/p&gt;
&lt;p&gt;那沒什麼好丟臉的，等你從同手同腳變回會上籃，東西自然就寫得出來了。&lt;/p&gt;</content:encoded></item><item><title><![CDATA[面試日記 - 一場自己講的七零八落的線上面試，真的也是種經驗。]]></title><description><![CDATA[今天去面試的大略狀況 是台中的一間博弈產業公司，特別開設的全遠端的職缺。 該公司屬於大家認知上的那種博弈，面談過程就是自我介紹之後，彼此做一些了解以及口頭上的技術測試， 很多基本的東西我(比如 Box-Model)一時忘記名詞 ，講得是有多七零八落就多七零八落，我內心很無言 😰 面試結果 Get Offer 因為還有一些面試行程要跑，所以我還沒回覆，目前暫定 6/7(三) 前要給回應，到時再更新狀況。 筆試題目複習 題目沒有完全記得很清楚，我大致上描述，盡量還原題型。 請描述一下 Box-Model 是什麼 我第一個念頭想說，是不是在問我 Pop-up、Light-box…]]></description><link>https://huberthsieh.dev/blog/post-06/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-06/</guid><category><![CDATA[Interview]]></category><pubDate>Wed, 24 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;今天去面試的大略狀況&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;是台中的一間博弈產業公司，特別開設的全遠端的職缺。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;該公司屬於大家認知上的那種博弈，面談過程就是自我介紹之後，彼此做一些了解以及口頭上的技術測試，&lt;br&gt;
很多基本的東西我(比如 Box-Model)一時忘記名詞 ，講得是有多七零八落就多七零八落，我內心很無言 😰&lt;/p&gt;
&lt;h3&gt;面試結果&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Get Offer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因為還有一些面試行程要跑，所以我還沒回覆，目前暫定 6/7(三) 前要給回應，到時再更新狀況。&lt;/p&gt;
&lt;h3&gt;筆試題目複習&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;題目沒有完全記得很清楚，我大致上描述，盡量還原題型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;請描述一下 Box-Model 是什麼&lt;/h4&gt;
&lt;p&gt;我第一個念頭想說，是不是在問我 Pop-up、Light-box 的概念，如果要跳出一個燈箱或提示訊息我會怎麼處理？那跟面試官確認了一下知道是自己誤會大了，其實就是 CSS 的盒模型，也就是區塊元素的組成是如何組成？&lt;/p&gt;
&lt;p&gt;那大概描述一下，由內到外分別是 Content &gt; padding &gt; margin (這邊我回答時少講了border)&lt;/p&gt;
&lt;h4&gt;請描述一下，CSS排版時，要如何做垂直置中&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;我這邊是先向面試官確認，是這側的置中抑或是這側的置中？（比手畫腳），那面試官比給我的是&quot;水平、垂直&quot;都要置中&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我大致上描述可能有哪幾種方法：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;justify-content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;-50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; -50%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;請描述一下 https 跟 http 的差異是什麼&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;我還一時半會真的不知道要怎樣描述https，全世界都知道他加密，但它原理是怎樣啊？ (內心吶喊 : 好想 Google)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我大概描述了一下:基本上HTTPS 比起 HTTP 就是多了加密形式去傳輸資料，比如說我在表單送出資料之後，如果被攔截應該也會是亂碼之類的。至於加密的原理跟詳細方式，老實說我不太清楚。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這邊後來Google了一下資訊，也幫大家做個補充。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;HTTPS的基本原理就是 HTTPS 使用加密協議來加密通信。該協議稱為傳輸層安全性 (TLS)，以前叫做 SSL。該協議通過使用所謂的公鑰基礎設施來保護通信。這種類型的安全系統使用兩個不同的密鑰來加密兩方之間的通信。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/tw/compare/the-difference-between-https-and-http/&quot;&gt;參考來源 - AWS上關於http以及https的差異&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://www.seo-panda.tw/https-http-202112/#HTTPS_%E6%98%AF%E4%BB%80%E9%BA%BC%EF%BC%9F&quot;&gt;參考來源 - 熊貓先生&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://hackmd.io/@unayojanni/HyzWz9R0u/%2F%40unayojanni%2FS10cn5S1Y&quot;&gt;參考來源 - YoJanni的轉職前端筆記本&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;請描述一下一個網頁如果 console 噴錯了，你大致上排除錯誤的流程會是如何？&lt;/h4&gt;
&lt;p&gt;這邊我是描述說:大致上會先看一下錯誤訊息是噴什麼，有沒有什麼低級錯誤，然後才去確認看看是哪個 function 或是源頭在哪，確認一下是前端功能的問題還是在 Call API 回來時沒處理好的問題，可能非同步的部分有疏忽了，導致資料還沒確定取得時，所寫的功能就跑了一些方法導致噴錯。這邊突然想到TS想說也順便描述一下，但TS應該在編譯前就會噴錯。（面試官也有回應我這點，是我這裡疏忽了）。&lt;/p&gt;
&lt;h4&gt;過往有沒有遇過什麼狀況是自己排除不了，問了同事之後，甚至也問到主管之後，還是沒辦法解決的話，要怎麼處理？&lt;/h4&gt;
&lt;p&gt;這邊我是如實描述說:過往還沒有遇過真的都問到主管那端，還沒辦法解決的問題，基本上遇到問題會先盡量去排除狀況，到方向比較明確之後，才去問資深的前輩，還是不行才會到主管那邊。&lt;/p&gt;
&lt;h4&gt;有沒有遇過 CORS 的問題？怎麼處理的？&lt;/h4&gt;
&lt;p&gt;這邊我是回答:基本上如果遇到 CORS 的問題，我會先和後端確認一下是不是沒設定好，那假設今天開發上，的確需要有跨來源的請求，那應該是需要透過反向代理伺服器去和後端Server發請求，也就是前端改為向反向代理伺服器發請求而不是直接向後端Server發請求。&lt;/p&gt;
&lt;h4&gt;有客製化過什麼hook嗎？&lt;/h4&gt;
&lt;p&gt;這邊是提到說:過往有把一個撈資料用的功能，比如說 Fetch 某支 API，把非同步的功能都寫好之後，包成一支 useFetch()，那團隊可以引入這支功能，一起使用&lt;/p&gt;
&lt;p&gt;主要是避免其他人有非同步沒處理好的問題。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;但後來想想應該是想知道說，如何複用一些功能，而不是該功能在做什麼。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;面試反省&lt;/h3&gt;
&lt;p&gt;這次面試的時候稍微有點緊張，很多問題跟面試官有點牛頭不對馬嘴，一方面是第一次在線上面對多個面試官，一方面自己近期都沒在使用前端框架開發東西，很多原裡已經不太熟悉，所以之後首要就是先去熟悉以前的基本功？！&lt;/p&gt;</content:encoded></item><item><title><![CDATA[面試日記 - 當你被貓吵整晚不能睡，還要出發去面試跟筆試時。]]></title><description><![CDATA[正式面試前 心裡MurMur…]]></description><link>https://huberthsieh.dev/blog/post-05/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-05/</guid><category><![CDATA[Interview]]></category><pubDate>Tue, 23 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;正式面試前&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;心裡MurMur：一不小心騎過頭，該公司在西屯區，我差點前往龍井區品茶。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;習慣提早出門的我，想說應該給人資跟面試官一個良好印象，早一點抵達做點準備也好，結果不知道是不是都沒什麼睡，頭腦發昏外加路不熟整個騎過頭，還好沒遲到，差點就可以直接右轉回家睡覺，也不用面試了。&lt;/p&gt;
&lt;p&gt;一到公司樓下是一棟很氣派的大樓，不知是不是不成文的規定，好像博弈類的公司都在這種很氣派的大樓裡面。到公司後剛好遇到人資小姐姐經過門口幫我開門，
進去之後稍微聊一下我如何成為前端工程師，以及了解我成為工程師之前的職業跟經歷。&lt;/p&gt;
&lt;p&gt;後來面試官也有再問我一次工程師之前的經歷，履歷上面我沒完整列出從當完兵到成為工程師之前的經歷，是因為之前在Linkedin上，
有幾名獵頭建議我非工程師的經歷其實可以不用列出來，我覺得很有道理就這樣調整了。&lt;/p&gt;
&lt;p&gt;我認為工程師大多就是看過往做過哪些專案，然後技術的了解程度到哪，所以去年底開始到現在一直在盡力多做一些 Side Project 充實自己的 Github ，也盡量慢慢提升自己的能力，
以前很多東西都只在工作中用用而已，過一陣子就忘記了，沒有好好記錄下來滿可惜的，現在重新把很多資訊輸入腦袋後再做一次輸出，感覺還是挺不錯的，對自己也慢慢多了點信心。&lt;/p&gt;
&lt;h3&gt;今天去面試的大略狀況&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;是台中的一間合法博弈產業公司&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;該公司有自己的產品，一個還滿知名的線上娛樂城，廣告很常看到，這類型產業在台灣很盛行，大多是走合法路線，自己也不排斥這產業。　　&lt;/p&gt;
&lt;p&gt;面試流程除了前面跟人資小姐姐聊完之後，接著就是筆試最後再跟三位面試官面談，因為剛好該公司團隊是用自己比較喜歡的技術 &lt;strong&gt;React&lt;/strong&gt; 做開發(內心老淚縱橫😭)，加上三位面試官很願意給我回饋，就趁這個機會跟三位面試官多請教請教，一不小心就聊到快中午，自己有點不好意思耽誤他們太多時間，不然還有滿多事情想跟面試官請教一番，很謝謝能跟他們聊這麼多。&lt;/p&gt;
&lt;h3&gt;筆試題目複習&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;題目沒有完全記得很清楚，基本上從CSS到JavaScript都有考到，大概分幾個方向&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;CSS的原理及排版方式&lt;/li&gt;
&lt;li&gt;JavaScript的原理、寫法以及陣列的方法&lt;/li&gt;
&lt;li&gt;型別的運算比較，比如經典的 1&amp;#x3C;2&amp;#x3C;3 以及 3&gt;2&gt;1&lt;/li&gt;
&lt;li&gt;下面會記錄一下題目跟答案，跟我為什麼寫錯XD，然後再多做點補充&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;CSS的原理及排版方式&lt;/h4&gt;
&lt;p&gt;Id Selector &gt; Class Selector &gt; Type Selector (這題我寫成Id Selector &gt; Type Selector &gt; Class Selector)&lt;/p&gt;
&lt;p&gt;我一時忘記Type Selector是哪個選擇器，腦袋中浮現的是div[data-color=red]，記得這個跟class是同等級，後來問面試官才知道Type Selector就是指Element，h1、h2、p那些標籤。&lt;/p&gt;
&lt;p&gt;另外還有如何水平垂直置中、flex 排成直的一些排法等等，我自己通常如果是口頭測驗，會先確認水平置中、垂直置中、水平垂直置中三種考官要問哪種，不過概念大致上相同是也還好。&lt;/p&gt;
&lt;h4&gt;margin 邊界重疊的三種解法&lt;/h4&gt;
&lt;p&gt;比較要提的是 margin 邊界重疊的三種解法，自己很常利用padding去設定間距，所以很少遇到這類問題，後來詢問面試官其他的解法，原來可以幫元素加上clear-fix的屬性，我以前只知道是用來清除 float 的浮動問題，原來也可以解決 margin 邊界重疊的狀況，長知識了！&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-TW/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing&quot;&gt;參考MDN - margin 邊界重疊的內容&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Function.call() / Function.apply() 的差異&lt;/h4&gt;
&lt;p&gt;這題考如果要傳第二個參數，要傳陣列進去還是透過展開運算子傳一堆數字進去。真的滿久沒看到這兩個方法，大抵上知道是關於this的綁定，但我忘記實際寫法以及後面參數要傳什麼，最後也是跟面試官請教才回憶起來。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 定義測試用函式，箭頭函式的this會根據語意指定作用域，故這邊不用箭頭函式&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;testFunction&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 定義測試用物件&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; obj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Hubert Hsieh&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Assign function in obj&lt;/span&gt;
obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;func &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; testFunction&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 第一個參數選擇要指定this到哪，也可以是null&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;testFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 傳一個陣列進去&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;testFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 依次傳參數&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;testFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 依次傳參數&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Promise 語法&lt;/h4&gt;
&lt;p&gt;關於Promise相關的考題，這邊考了兩個方向&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Promise 內運算的結果及Promise all方法的概念。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Promise 的寫法如何用 async/await 的方式改寫。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這邊自己覺得答題時沒有寫得很好，不過基本的概念自己還算是清楚的，Promise語法的出現是為了解決早期在做非同步處理時，必須不斷的透過CallBack去確保正確取到值之後，再執行某些運算或功能，當函式結構變得龐大時，程式碼就會越來越複雜，甚至會看到類似波動拳的巢狀結構。&lt;/p&gt;
&lt;p&gt;Promise.all 則是可以等待多個 Promise 函式全部執行完成後，再回傳陣列結果，通常就是如果有多支 API 要一起執行時會運用到。&lt;/p&gt;
&lt;p&gt;那 async / await 就是可以用類似同步執行的程式碼寫法，來處理非同步的事情，本質跟用Promise其實是差不多的。但是可讀性就高很多(這點可能見仁見智，我個人是習慣這樣寫，再利用 Try 集中處理 Success 相關的功能，Catch 處理 Error 時相關的功能)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// async / await的方式&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;asyncFunction&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dataA &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fetchDatarA&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dataB &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fetchDatarB&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataA&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dataB&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; 
	&lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;async function error&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;陣列的方法 - sort、reduce&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;sort 這邊考的是，一個物件陣列如何透過某個 key 的 value 去做排序?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;reduce 我忘記題目考什麼了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;sort() 基本概念是，如果沒有指定一個比較功能的參數，它會將每個元素轉成字串之後，第一個字元的 Unicode 編碼位置去排序。reduce() 基本概念是，他可以不斷地跟上一個回傳的值再作運算，最後回傳該值。跟 map 相比不同的是，map 是回傳一個新陣列。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// sort()&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 簡單定義一個隨意排列的物件陣列&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sortArr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Alex&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Hubert&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;34&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Chris&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 依造每個元素的年齡大小去遞增排序&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sortResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sortArr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;age &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;age &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// result&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Chris&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Alex&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;33&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Hubert&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;34&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// reduce()&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 基本語法&lt;/span&gt;

array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;accumulator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currentValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; something&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  initialValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// example&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; array &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 0 + 1 + 2 + 3 + 4&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sumWithInitial &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;accumulator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currentValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; accumulator &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; currentValue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Expected output: 10&lt;/span&gt;
console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sumWithInitial&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/sort&quot;&gt;參考MDN - Array.prototype.sort()&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce&quot;&gt;參考MDN - Array.prototype.reduce()&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;型別的運算比較，比如經典的 1&amp;#x3C;2&amp;#x3C;3 以及 3&gt;2&gt;1&lt;/h4&gt;
&lt;p&gt;console.log以下狀況的結果，這邊牽扯 JavaScript 的各種運算子在運算時，如果是不同型別會做甚麼處理以及回傳什麼結果。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1&amp;#x3C;2&amp;#x3C;3： 可以先判斷 1&amp;#x3C;2 是 true，重點就變成 true&amp;#x3C;3 會回傳什麼呢?&lt;/li&gt;
&lt;li&gt;3&gt;2&gt;1： 可以先判斷 3&gt;2 是 true，重點就變成 true&gt;1 會回傳什麼呢?&lt;/li&gt;
&lt;li&gt;true+&apos;xyz&apos;： true 是 布林值，那重點就變成 布林值+字串會變成什麼呢?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述幾點就需要先了解 JavaScript 的強制轉型別的概念(很雞婆？)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;{key: &quot;value&quot;}： 理論上會把物件印出來沒錯，只是很有可能一般實務上物件console出來的值未必是如自己所想的那樣！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.huli.tw/2020/03/23/console-log-bug/&quot;&gt;物件的console-bug，詳情可以看看Huli大大的文章&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;面試反省&lt;/h3&gt;
&lt;p&gt;我覺得我每次面試的時候都有點嘴笨，太過緊張都有點詞不達意，比如面試官問我有沒有處理過什麼複雜的資料結構，當下心裡想的是，應該都還可以，不管接收到什麼樣的格式，看實務需求需要怎麼重新整理跟排列就去處理。
結果我跟面試官說我沒有處理過什麼太複雜的資料結構(面試官應該很驚恐?!)，其實應該先確認一下複雜的定義是什麼再作回答才對。&lt;/p&gt;
&lt;p&gt;然後討論 reduce() 的用法時，也問了面試官說 reduce() 很常用到嗎？但我意思其實是很常只透過 reduce() 處理好某筆資料，還是說會寫比較多行，比如說先 filter() 再 map() 之類的，讓整段 code 雖然變冗長，但是可讀性變高呢？&lt;/p&gt;
&lt;p&gt;之後參加任何面試應該要多思考，緩一點回答才好，想不出來的慢慢想，最後讓面試官打斷思考也沒關係，不要都不思考XD&lt;/p&gt;
&lt;p&gt;平常心平常心吧，Hubert QQ。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/Yantsharn/pen/eYPXYmE&quot;&gt;附上那天面試大致上考題的 CodePen 參考&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title><![CDATA[面試日記 - 和接案公司面對面，案子如繁星點點。]]></title><description><![CDATA[今天去面試的大略狀況 是台北的一間接案公司，台中也有辦公室，專門解決客戶提出的需求。 面試過程是在 2023.05.22 先透過 Google Meet 和人資小姐姐一面，彼此以討論聊天的形式，了解一下公司大致上有哪幾種專案、有哪些團隊的組成，以及了解一下我的職涯目標與人生規劃。
2023.05.29 的時候和技術長二面，主要問實務開發上遇到的一些問題要怎麼排除，或是如果有哪些狀況要怎麼解決。 面試結果 Get Offer…]]></description><link>https://huberthsieh.dev/blog/post-07/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-07/</guid><category><![CDATA[Interview]]></category><pubDate>Mon, 22 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;今天去面試的大略狀況&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;是台北的一間接案公司，台中也有辦公室，專門解決客戶提出的需求。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;面試過程是在 2023.05.22 先透過 Google Meet 和人資小姐姐一面，彼此以討論聊天的形式，了解一下公司大致上有哪幾種專案、有哪些團隊的組成，以及了解一下我的職涯目標與人生規劃。
2023.05.29 的時候和技術長二面，主要問實務開發上遇到的一些問題要怎麼排除，或是如果有哪些狀況要怎麼解決。&lt;/p&gt;
&lt;h3&gt;面試結果&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Get Offer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;筆試題目複習&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;題目沒有完全記得很清楚，我大致上描述，盡量還原題型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;請描述一下，假設有一個按鈕按下去會發出一個請求，那如果使用者短時間內反覆點擊，要如何確保能正確取得 Response&lt;/h4&gt;
&lt;p&gt;一開始我是先向面試官確認，在什麼情況下會用一個按鈕一直對同一個接口連續發請求？一般不是會在點擊按鈕觸發請求取得回應的過程中，透過Loading狀態去做一些判斷，避免按鈕能夠被使用者連續按？　&lt;/p&gt;
&lt;p&gt;這邊可能問的不是很好，主要是想確認說，是不是希望我描述如何透過防抖或節流的方式，去防止使用者不斷地送出請求，以我自己實際應用過的例子來舉例，比如在監聽Scroll事件時，防止一滾動就一直重複觸發事件，影響效能！
或是input輸入時，有時要依據輸入內容做請求，假設是a、b、c，每次字元KEY完都發請求的話，也會發送太多請求，所以可以透過節流的方式，當短時間內觸發的同一請求，就以最後(最新)觸發的為原則去送出請求。&lt;/p&gt;
&lt;p&gt;太晚才回來補面試的過程，很多細節忘記了，但大多數是圍繞在這一題上討論，自己後續在新公司工作其實也常常在思考一些效能上的問題，像是如何避免撈取重複的資料，然後相同的請求只要在A頁面拉取一次，B頁面也能共用，等等的課題，都是前端工程師後期應該多考慮的點。&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 簡單的防抖(debounce)概念&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; timer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 全域 Timer&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;debounce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    
    timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onscroll&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;debounce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;滾動停止&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;2026 年回頭補正：上面這段是「防抖(debounce)」不是「節流(throttle)」，當年把兩個名詞混著用了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;差別在於：&lt;strong&gt;防抖&lt;/strong&gt;是每次觸發都把計時器歸零，只有停下來超過 delay 才執行一次，所以是「最後一次說了算」；&lt;strong&gt;節流&lt;/strong&gt;是不管你觸發幾次，一段時間內最多只執行一次，是「固定頻率」。上面第二段講的「以最後(最新)觸發的為原則」其實就是防抖的行為。&lt;/p&gt;
&lt;p&gt;回到面試那題（按鈕連點）：上面這種「停下來才執行」的防抖，用在送出按鈕上會讓使用者點完還要等 delay 才真的送出，體驗其實不好。更直接的做法就是這篇一開始自己講的：用 loading 狀態把按鈕鎖起來。防抖與節流比較適合 scroll、resize、輸入即時搜尋這類會連續觸發的事件，也就是原文舉的那兩個例子。&lt;/p&gt;
&lt;h4&gt;請描述一下，CSS排版時，要如何做垂直置中&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;經典題目，大部分公司都會問一下，確認一下基本切版的知識。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我大致上描述可能有哪幾種方法：&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;css&quot;&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;justify-content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;-50%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; -50%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; grid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;place-content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[前端知識 - React的生命週期：一元復始，萬象更新。]]></title><description><![CDATA[React的生命週期 Mount(創建): 元件被渲染到畫面上(渲染後的節點加入DOM) Update(更新): 元件的內容因為資料的更動而重新渲染(比較虛擬DOM和實際DOM之間那些地方有變化 - Re-Rendered) Unmount(銷毀): 元件從畫面上消失，移除這個元件 Mounting 在元件一開始 Mount 的時候，初始化會先經過建構子 Constructor，Constructor 會完成所有必要的 initial 過程，像是初始化state、初始化ref物件等等。 constructor() render() componentDidMount() Updating…]]></description><link>https://huberthsieh.dev/blog/post-04/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-04/</guid><category><![CDATA[JavaScript]]></category><pubDate>Thu, 18 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;React的生命週期&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mount(創建): 元件被渲染到畫面上(渲染後的節點加入DOM)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update(更新): 元件的內容因為資料的更動而重新渲染(比較虛擬DOM和實際DOM之間那些地方有變化 - Re-Rendered)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unmount(銷毀): 元件從畫面上消失，移除這個元件&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Mounting&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在元件一開始 Mount 的時候，初始化會先經過建構子 Constructor，Constructor 會完成所有必要的 initial 過程，像是初始化state、初始化ref物件等等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;constructor()&lt;/li&gt;
&lt;li&gt;render()&lt;/li&gt;
&lt;li&gt;componentDidMount()&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Updating&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;當元件的 props 或 state 更新，重新渲染 (re-rendered) DOM 時會觸發。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;static getDerivedStateFromProps()&lt;/li&gt;
&lt;li&gt;shouldComponentUpdate()&lt;/li&gt;
&lt;li&gt;render()&lt;/li&gt;
&lt;li&gt;getSnapshotBeforeUpdate()&lt;/li&gt;
&lt;li&gt;componentDidUpdate()&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Unmounting&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;註銷元件，當元件從 DOM 中被移除時會觸發。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;componentWillUnmount()&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://codepen.io/Yantsharn/pen/abRxBoz&quot;&gt;附上 React 生命週期簡易 Demo，大家可以從 Console 看看每個生命週期的 Method 是如何被調用的&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Destructuring assignment&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Component&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Children&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; PropTypes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; useEffect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; React

&lt;span class=&quot;token comment&quot;&gt;// Class Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageClassComponent&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 初始化狀態、屬性&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Welcome guest, this message in the Class Component.&apos;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// static 靜態函數，因此無法操作 class 中的屬性 (ex. this.state)。&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 在每次 re-rendering 前會被調用，&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDerivedStateFromProps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;在每次 re-rendering 之前會調用 getDerivedStateFromProps&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 元件Render完成時，調用一次&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;componentDidMount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;元件Render完成，調用一次 componentDidMount()&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 如果 props 或 state 發生變化，re-rendering 前會調用， 回傳 false 會阻止按鈕點擊&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;shouldComponentUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;nextProps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nextState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nextContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;props 或 state 發生變化， shouldComponentUpdate() 回傳true / false&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 要和 componentDidUpdate 一起使用&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;getSnapshotBeforeUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevProps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prevState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;getSnapshotBeforeUpdate， 更新前取得原本狀態&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 元件屬性或狀態改變，調用一次&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;componentDidUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;prevProps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prevState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; snapshot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// console.log(&apos;prevProps&apos;, prevProps);&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// console.log(&apos;prevState&apos;, prevState);&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// console.log(&apos;snapshot&apos;, snapshot);&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; prevState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;State Change!, componentDidUpdate invoked.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 在 component 即將從DOM中移除之前，componentWillUnmount&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;componentWillUnmount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;組件即將移除，componentWillUnmount invoked once.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function-variable function&quot;&gt;handleMessage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Hey guy, you change this message.&apos;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; message &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div className&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;block&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; message &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;h2&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

                &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                    改變文字
                &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Functionl Component&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;MessageFunctionlComponent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setMessage&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Welcome Guest, this message in the Functionl Component.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 每次 render 結束時，觸發 useEffect&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;useEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;message will be ==&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;message&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; message &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;h2&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button onClick&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Hey guy, you change this message.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
                改變文字
            &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[後端知識 - 前端工程師的Spring Boot成長日記。]]></title><description><![CDATA[開發環境設定 Mac OS Homebrew oh-my-zsh MySQL Server Java 11 Talend API Tester Intellij IDEA Windows開發環境設定 MySQL Server Java 11 Talend API Tester Intellij IDEA Git Create New Project Prederences - Spring Initializr + Project SDK Java 11 基本專案結構以及了解SpringBoot特性 src/main - 放我們要寫的Code src/main/java - 放Java…]]></description><link>https://huberthsieh.dev/blog/post-03/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-03/</guid><category><![CDATA[SpringBoot]]></category><pubDate>Wed, 17 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;開發環境設定&lt;/h3&gt;
&lt;h4&gt;Mac OS&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://brew.sh/index_zh-tw&quot;&gt;Homebrew&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ /bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;
$ brew install git //安裝git&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://ohmyz.sh/&quot;&gt;oh-my-zsh&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ sh -c &quot;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mysql.com/downloads/&quot;&gt;MySQL Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/AdoptOpenJDK/homebrew-openjdk&quot;&gt;Java 11&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ brew tap AdoptOpenJDK/openjdk
$ brew install --cask adoptopenjdk11&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm?hl=zh-TW&quot;&gt;Talend API Tester&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;jetbrains.com/idea/download/&quot;&gt;Intellij IDEA&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Windows開發環境設定&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mysql.com/downloads/&quot;&gt;MySQL Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://adoptopenjdk.net/&quot;&gt;Java 11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm?hl=zh-TW&quot;&gt;Talend API Tester&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;jetbrains.com/idea/download/&quot;&gt;Intellij IDEA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Create New Project&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Prederences - Spring Initializr + Project SDK Java 11&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;基本專案結構以及了解SpringBoot特性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;src/main - 放我們要寫的Code&lt;/li&gt;
&lt;li&gt;src/main/java - 放Java Code&lt;/li&gt;
&lt;li&gt;src/main/resource - 放Spring Boot設定檔&lt;/li&gt;
&lt;li&gt;src - test - 放測試用的Code&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;package com.example.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication &amp;lt;= 加在Class上的註解，使電腦能夠運行SpringBoot的程式
public class HelloWorldApplication { 

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication.class, args);
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Spring IoC(Inversion of Control) 控制反轉&lt;/h4&gt;
&lt;h5&gt;主要概念 - 將Object的控制權交給外部的Sprint Container來保管&lt;/h5&gt;
&lt;h5&gt;優點&lt;/h5&gt;
&lt;ol&gt;
&lt;li&gt;Loose coupling 鬆耦合&lt;/li&gt;
&lt;li&gt;Lifecycle Management 生命週期管理&lt;/li&gt;
&lt;li&gt;More Testable 方便測試程式&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;範例&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public class Teacher {

    private Printer printer = new HpPrinter();
    
    public void teach() {
        printer.print(&quot;I am a Teacher&quot;);
    };
    
}

------ 改成以下這樣 ------

public class Teacher {

    private Printer printer;
    
    public void teach() {
        printer.print(&quot;I am a Teacher&quot;);
    };
    
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;說明&lt;/h4&gt;
&lt;h5&gt;@Component 用法 (@RestController類似)&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在class上&lt;/li&gt;
&lt;li&gt;用途：將該class變成由Spring容器管理的Object(Bean)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Component &amp;lt;= 將該class變成由Spring容器管理的Object(Bean hpPrinter)
public class HpPrinter implements Printer {
    
    @Override
    public void print(String message) {
        System.out.println(&quot;HP印表機: &quot; + message);
    }
    
}

透過依賴注入 Dependency Injection
@Component
public class Teacher {
    
    @Autowired
    private Printer printer;
    
    public void teach() {
        printer.print(&quot;I am a Teacher&quot;);
    };
    
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Spring IoC 專有名詞整理&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;IoC=Inversion of Control (控制反轉)&lt;/li&gt;
&lt;li&gt;DI=Dependency Injection (依賴注入)&lt;/li&gt;
&lt;li&gt;Bean=存放在Spring容器裡的Object&lt;/li&gt;
&lt;li&gt;@Component註解=加在class上，將該class變成由Spring容器管理的bean&lt;/li&gt;
&lt;li&gt;@Autowired註解=加在變數上，取得Spring容器中的bean&lt;/li&gt;
&lt;/ol&gt;
&lt;h5&gt;@Autowired以及@Qualifier 詳解&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;兩種註解寫法沒有順序性&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;@Autowired&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：通常是加在class變數上&lt;/li&gt;
&lt;li&gt;用途：根據變數的類型，去Spring容器中尋找有沒有符合類型的bean&lt;/li&gt;
&lt;li&gt;變數的類型盡量使用Interface&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;@Qulifier&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：通常是加在class變數上，會跟@Autowired一起使用&lt;/li&gt;
&lt;li&gt;用途：指定要載入的bean的名字&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;小結&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;@Autowired：根據變數的類型，去Spring容器中尋找有沒有符合類型的bean&lt;/li&gt;
&lt;li&gt;@Qulifier：輔助@Autowired，指定要載入的bean的名字&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;創建Bean的方法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在Class上加上 @Component註解&lt;/li&gt;
&lt;li&gt;使用 @Configuration + @Bean 註解&lt;/li&gt;
&lt;li&gt;兩種方法可以混用&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;@Configuration&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在class上&lt;/li&gt;
&lt;li&gt;用途：Spring中的設定用註解，表示這個class是拿來設定Spring用的&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;
@Configuration
public class MyConfiguration { // class的名字不重要
    // do something
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;@Bean&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在帶有@Configuration class 的方法上&lt;/li&gt;
&lt;li&gt;用途：在Spring容器中創建一個Bean&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;
@Configuration
public class MyConfiguration { // class的名字不重要
    
    @Bean
    public Printer myPrintr() {
        return new HpPrinter();
    }
}

Spring容器中產生的bean的預設名字會是方法的名字 myPrinter
如果是@Bean(&quot;xxx&quot;) 產生的bean的名字就會是xxx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Bean的初始化&lt;/h4&gt;
&lt;h5&gt;@PostConstruct&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在方法上&lt;/li&gt;
&lt;li&gt;細節：使用@PostConstruct的時候
&lt;ul&gt;
&lt;li&gt;方法：public&lt;/li&gt;
&lt;li&gt;返回類型：void&lt;/li&gt;
&lt;li&gt;方法名稱：任意取名但不含參數 e.g. init()&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Component
public class HpPrinter implements Printer {

    private int count;

    @PostConstruct
    public void initialize() {
        count = 5;
    }

    @Override
    public void print(String message) {
        count--;
        System.out.println(&quot;HP印表機： &quot; + message);
        System.out.println(&quot;剩餘使用次數： &quot; + count);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;實現@InitializingBean 這個 Interface&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：實現 Interface&lt;/li&gt;
&lt;li&gt;細節：使用@InitializingBean的時候，必須要再實現裡面的afterPropertiesSet()方法&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Component
public class HpPrinter implements Printer, InitializingBean{

    private int count;
        
    @Override
    public void afterPropertiesSet() throws Exception {
        count = 5;
    }
    

    @Override
    public void print(String message) {
        count--;
        System.out.println(&quot;HP印表機： &quot; + message);
        System.out.println(&quot;剩餘使用次數： &quot; + count);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h4&gt;Bean的生命週期&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;創建Bean =&gt; 初始化Bean =&gt; Bean可以被使用&lt;/li&gt;
&lt;li&gt;有多個Bean的話，要每一個Bean都可以正常被使用才能運行成功&lt;/li&gt;
&lt;li&gt;Bean之間的依賴關係 e.g. myController依賴於Printer&lt;/li&gt;
&lt;li&gt;避免以下循環依賴類型寫法&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Component
public class A {

    @Autowired
    private B b; // A依賴於B
}

@Component
piblic class B {

    @Autowired
    private A a;
    
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;小結
&lt;ul&gt;
&lt;li&gt;Bean的生命週期：創建-&gt;初始化-&gt;可以被使用&lt;/li&gt;
&lt;li&gt;創建時若有依賴其他Bean，則Spring會回過頭去「創建 + 初始化」那個被依賴的Bean&lt;/li&gt;
&lt;li&gt;不要寫出循環依賴的Code&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Bean的生命週期 讀取Spring Boot設定檔 - application.properties + @Value&lt;/h4&gt;
&lt;h5&gt;Spring Boot設定檔 - application.properties&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：使用properties語法(key=value)&lt;/li&gt;
&lt;li&gt;用途：存放Spring Boot的設定值&lt;/li&gt;
&lt;li&gt;細節：檔名一定是application&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# example
count=5
my.name=Hubert
my.age=33&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;@Value&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在Bean或是設定Spring用的class裡面的變數上&lt;/li&gt;
&lt;li&gt;用途：讀取Spring Boot設定檔(application.properties)中指定的key的值&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# application.properties
count=5
my.name=Hubert
my.age=33

public class MyBean {

    @Value(&quot;${count}&quot;)
    private Integer number; // number=5
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Spring IoC總結&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;IoC = Inversion of Control (控制反轉)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;將object的控制權交給外部的Spring容器管理&lt;/li&gt;
&lt;li&gt;存放在Spring容器裡的object，稱為Bean&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DI = Dependency Injection (依賴注入)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;和IoC相輔相成&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;創建Bean的方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Component&lt;/li&gt;
&lt;li&gt;@Configuration + @Bean&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;初始化Bean的方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@PostConstruct&lt;/li&gt;
&lt;li&gt;實現InitializingBean interface&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注入Bean的方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Autowired&lt;/li&gt;
&lt;li&gt;多個Bean可以搭配@Qualifier&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bean的生命週期&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;創建 -&gt; 初始化 -&gt; 可以被使用&lt;/li&gt;
&lt;li&gt;創建時若有依賴其他bean，則Spring會回過頭去「創建+初始化」那個被依賴的bean&lt;/li&gt;
&lt;li&gt;不要寫出循環依賴的code&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讀取Spring Boot設定檔(application.properties)的值的方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;application.properties+@Value&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Spring AOP(Aspect-Oriented Programming) 切面導向程式設計&lt;/h3&gt;
&lt;h4&gt;基本概念&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;將每個方法內重複執行的共同邏輯，設計成一個切面貫穿所有方法，由切面統一做處理。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// pom.xml 新增設定
&amp;lt;dependency&gt;
    &amp;lt;groupId&gt;org.springframework.boot&amp;lt;/groupId&gt;
    &amp;lt;artifactId&gt;spring-boot-starter-aop&amp;lt;/artifactId&gt;
&amp;lt;/dependency&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Example&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在class上，需和@Component一起使用&lt;/li&gt;
&lt;li&gt;用途：宣告這個class是一個切面&lt;/li&gt;
&lt;li&gt;換言之這個class必須成為一個bean才能夠成為切面&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Aspect
@component
public class MyAspect {
    // do something
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;@before&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在切面的class上&lt;/li&gt;
&lt;li&gt;用途：在切入點的方法執行前執行&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Aspect
@component
public class MyAspect {
    
    @Before(&quot;execution(* com.example.demo.HpPrinter.*(..))&quot;) // 切入點(Pointcut)
    public void before() {
        System.out.println(&quot;I am before&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;@After&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在切面的class上&lt;/li&gt;
&lt;li&gt;用途：在切入點的方法執行後執行&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Aspect
@component
public class MyAspect {
    
    @After(&quot;execution(* com.example.demo.HpPrinter.*(..))&quot;) // 切入點(Pointcut)
    public void before() {
        System.out.println(&quot;I am before&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;@Around&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在切面的class上&lt;/li&gt;
&lt;li&gt;用途：在切入點的方法執行前後都執行&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Aspect
@component
public class MyAspect {
    
    @Around(&quot;execution(* com.example.demo.HpPrinter.*(..))&quot;) // 切入點(Pointcut)
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(&quot;I am around before&quot;);
        Object obj = pjp.proceed(); // 去執行原本切入點的方法
        System.out.println(&quot;I am around after&quot;);
        return obj;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Spring AOP提供的註解&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;常用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Before：在切入點的方法執行前執行&lt;/li&gt;
&lt;li&gt;@After：在切入點的方法執行後執行&lt;/li&gt;
&lt;li&gt;@Around：在切入點的方法執行前後都執行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不常用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@AfterThrowing：在切入點的方法拋出異常之後執行&lt;/li&gt;
&lt;li&gt;@AfterReturning：在切入點的方法執行成功之後執行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;常見的切入點表達式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;execution(* com.example.demo.HpPrinter.*(..))：切入點為com.example.demo package裡的HpPrinter class裡的所有方法&lt;/li&gt;
&lt;li&gt;execution(* com.example.demo.*(..))：切入點為com.example.demo package裡的所有class的所有方法&lt;/li&gt;
&lt;li&gt;execution(* com.example.demo..*(..))：切入點為com.example.demo package以及所有子package裡的所有class的所有方法&lt;/li&gt;
&lt;li&gt;execution(* com.example.demo.HpPrinter.print())：切入點為com.example.demo.HpPrinter class裡的print()方法&lt;/li&gt;
&lt;li&gt;@annotation(com.example.demo.MyAnnotation)：切入點為帶有@MyAnnotation的方法&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Spring AOP的發展&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;權限驗證 / Spring Security&lt;/li&gt;
&lt;li&gt;統一的Exception / @ControllerAdvice&lt;/li&gt;
&lt;li&gt;Log紀錄&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Spring AOP的總結&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;AOP = Aspect-Oriented Programming (切面導向程式設計)&lt;/li&gt;
&lt;li&gt;創建切面的方法
&lt;ul&gt;
&lt;li&gt;@Aspect + @Component&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;可使用的切面註解
&lt;ul&gt;
&lt;li&gt;常用：@Before、@After、@Around&lt;/li&gt;
&lt;li&gt;不常用：@AfterThrowing、@AfterReturning&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring AOP的發展
&lt;ul&gt;
&lt;li&gt;權限驗證 / Spring Security&lt;/li&gt;
&lt;li&gt;統一的Exception / @ControllerAdvice&lt;/li&gt;
&lt;li&gt;Log紀錄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Spring MVC&lt;/h3&gt;
&lt;h4&gt;HTTP協議&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;負責規定資料的傳輸格式，讓前端和後端能夠有效的進行資料溝通&lt;/li&gt;
&lt;li&gt;可以分成Request(請求)和Response(回應)兩個部分&lt;/li&gt;
&lt;li&gt;Http Request(請求)
&lt;ul&gt;
&lt;li&gt;Method
&lt;ul&gt;
&lt;li&gt;GET&lt;/li&gt;
&lt;li&gt;POST&lt;/li&gt;
&lt;li&gt;PUT&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Url
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost:8080/test&quot;&gt;http://localhost:8080/test&lt;/a&gt; -&gt; 出現在瀏覽器中的網址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Request header
&lt;ul&gt;
&lt;li&gt;放一些通用的資訊&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Request body
&lt;ul&gt;
&lt;li&gt;放請求參數&lt;/li&gt;
&lt;li&gt;只有在使用POST以及PUT時才能使用&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Http Response(回應)
&lt;ul&gt;
&lt;li&gt;Status Code(Http狀態碼)
&lt;ul&gt;
&lt;li&gt;1xx：資訊&lt;/li&gt;
&lt;li&gt;2xx：成功 e.g. 200(Success)&lt;/li&gt;
&lt;li&gt;3xx：重新導向&lt;/li&gt;
&lt;li&gt;4xx：前端請求錯誤 e.g. 404(找不到網頁)&lt;/li&gt;
&lt;li&gt;5xx：後端處理有問題&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Response header
&lt;ul&gt;
&lt;li&gt;放一些通用的資訊 e.g. Content-type：text/plain&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Response body
&lt;ul&gt;
&lt;li&gt;很重要，放後端返回的數據&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;URL路徑對應&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;協議/域名/路徑 e.g. &lt;a href=&quot;http://localhost:8080/test&quot;&gt;http://localhost:8080/test&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;@RequestMapping&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在class上或方法上，小括號裡填寫url路徑
&lt;ul&gt;
&lt;li&gt;使用@RequestMapping時，class上ㄧ定要加上@Controller或是@RestController&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;用途：將url路徑對應到方法上&lt;/li&gt;
&lt;li&gt;運行原理：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@RestController
public class MyController {
    
    @RequestMapping(&quot;/test&quot;)
    public String test() {
        System.out.println(&quot;Hi&quot;);
        return &quot;Hello World&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;@Controller / @RestController&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在class上&lt;/li&gt;
&lt;li&gt;用途：將該class變成bean，並且可以使用@RequestMapping
&lt;ul&gt;
&lt;li&gt;可以思考成 @Component的加強版&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;結構化的呈現數據 Json&lt;/h3&gt;
&lt;h4&gt;Json&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;一種輕量級的資料交換格式，易於讀寫&lt;/li&gt;
&lt;li&gt;目的：結構化的呈現數據&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
    &quot;id&quot;: 3345678,      // &quot;key&quot;: value
    &quot;name&quot;: &quot;Hubert&quot;,   // &quot;key&quot;: value
    &quot;inervice&quot;: true    // &quot;key&quot;: value
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;回傳Json格式 @RestControler、@Controller + @ResponseBody&lt;/h3&gt;
&lt;h4&gt;Spring Boot使用Jackson library去轉換Java class和Json格式&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public class Student {
    String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


@RequestMapping(&quot;/detail&quot;)
@RestController
public class MyController {

    @RequestMapping(&quot;/user&quot;)
    public Student user() {
        Student student = new Student();
        student.setName(&quot;Hubert&quot;);
        return student;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Http method - GET和POST&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;有多種可以選，每一種的特性不同&lt;/li&gt;
&lt;li&gt;常用的http method
&lt;ul&gt;
&lt;li&gt;GET&lt;/li&gt;
&lt;li&gt;POST&lt;/li&gt;
&lt;li&gt;PUT&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;GET&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;最常使用的http method&lt;/li&gt;
&lt;li&gt;類似於明信片的概念，傳遞的參數會被別人看見&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:8080/test?id=123&amp;amp;name=Hubert&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;POST&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;常使用的http method&lt;/li&gt;
&lt;li&gt;類似於信封的概念，傳遞的參數不會被別人看見，安全性較高&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:8080/test

// Request Body
{
    &quot;id&quot;: 123,
    &quot;name&quot;: &quot;Hubert&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;取得前端請求參數&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;@RequestParam&lt;/li&gt;
&lt;li&gt;@RequestBody&lt;/li&gt;
&lt;li&gt;@RequestHeader&lt;/li&gt;
&lt;li&gt;@PathVariable&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;@RequestParam&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在方法的參數上&lt;/li&gt;
&lt;li&gt;用途：取得url裡面的參數 query parameter&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http:localhost:8080/test1?id=123&amp;amp;name=Hubert

public class MyController {
    
    public String test1(@RequestParam Integer id
                        @RequestParam String name) {
        // do something
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;@RequestParam 可使用的設定&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;name(or value)：指定url參數的名字，不常使用&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http:localhost:8080/test1?testId=123

public class MyController {
    
    public String test1(@RequestParam(name = &quot;testId&quot;) Integer id) {
        // do something
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;required：是否是必須的參數？
&lt;ul&gt;
&lt;li&gt;預設是true，可以改成false&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http:localhost:8080/test1?id=123&amp;amp;name=Hubert

public class MyController {
    
    public String test1(@RequestParam(required = false) Integer id) {
        // do something
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;defaultValue：提供預設值，最常使用
&lt;ul&gt;
&lt;li&gt;可以想成required=false的加強版，提供預設值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http:localhost:8080/test1?id=123&amp;amp;name=Hubert

public class MyController {
    
    public String test1(@RequestParam(defaultValue = &quot;10&quot;, required = false) Integer id) {
        // do something
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;@RequestBody&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在方法的參數上&lt;/li&gt;
&lt;li&gt;用途：取得request body裡面的參數(將Json轉為Java object)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    // class物件
    public class Student {
        Integer id;
        String name;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
    
    // example
    @RequestMapping(&quot;/test2&quot;)
    public String test2(@RequestBody Student student) {
        System.out.println(&quot;student id: &quot; + student.getId());
        System.out.println(&quot;student name: &quot; + student.getName());
        return  &quot;Hello test2&quot;;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;@RequestHeader&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在方法的參數上&lt;/li&gt;
&lt;li&gt;用途：取得request header裡面的參數，跟RequestParam很類似&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    // request header
    info:hello
    
    // example
    @RequestMapping(&quot;/test3&quot;)
    public String test3(@RequestHeader String info) { // hello

    }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;@RequestHeader 可使用的設定&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;name(or value)：指定url參數的名字，經常使用&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http:localhost:8080/test1?testId=123

public class MyController {
    
    public String test1(@RequestParam(name = &quot;Content-Type&quot;) String contentType) {
        // do something
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;required：是否是必須的參數？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;預設是true，可以改成false&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;defaultValue：提供預設值，最常使用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以想成required=false的加強版，提供預設值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http:localhost:8080/test1?id=123&amp;amp;name=Hubert

public class MyController {
    
    public String test1(@RequestParam(defaultValue = &quot;10&quot;, required = false) Integer id) {
        // do something
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;常見的 request header&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Content-Type：表示request body的格式
&lt;ul&gt;
&lt;li&gt;application/json(Json格式，最常用)&lt;/li&gt;
&lt;li&gt;application/octet-stream(用於上傳文件)&lt;/li&gt;
&lt;li&gt;multipart/form-data(用於上傳圖片)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Authorization：用於身份驗證&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;@PathVariable&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在方法的參數上&lt;/li&gt;
&lt;li&gt;用途：取得url路徑的值&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    http:localhost:8080/test4/123/Hubert
    
    // example
    @RequestMapping(&quot;/test4/{id}/{name}&quot;)
    public String test4(@PathVariable Integer id, @PathVariable String name) { // 123

    }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;@RequestParam 與 @PathVariable 的差異&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;@RequestParam：把參數放在url的最後面&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    http:localhost:8080/test1?testId=123&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;@PathVariable：把參數放在url的裡面&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;    http:localhost:8080/test4/123&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;RESTful API的設計&lt;/h3&gt;
&lt;h4&gt;取得商品列表的API&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Http Request（請求）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GET
/getProducts&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;size&lt;/td&gt;
&lt;td&gt;Integer&lt;/td&gt;
&lt;td&gt;要取得幾個商品&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;search&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;查詢的商品關鍵字&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Http Response (回應)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Sample response body
{
  &quot;productList&quot;: [
    {
      &quot;id&quot;: 123,
      &quot;name&quot;: &quot;apple&quot;,
      &quot;price&quot;: 30
    }
  ] 

}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;http://localhost:8080/getProducts?size=10

@RequestMaping(&quot;/getProducts&quot;)
public Product getProducts(@RequestParam Integer size, 
                           @RequestParam(required = false) String search) {
    // do something...
}    &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;API常見說法&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;這個getProducts API怪怪的，可以幫我看一下嗎？&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@RequestMaping(&quot;/getProducts&quot;)
public Product getProducts(@RequestParam Integer size, 
                           @RequestParam(required = false) String search) {
    // do something...
}  &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;你可以call getProducts API / 打 getProducts API&lt;/li&gt;
&lt;li&gt;可以提供API文件嗎？&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;REST風格&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;目的：簡化溝通成本&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你所設計的API符合REST風格，那麼你所設計的API就是RESTful API&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Beauty -&gt; Beautiful&lt;/li&gt;
&lt;li&gt;Peace -&gt; Peaceful&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;規範：使用http method表示動作，去對應資料庫的CRUD(增查改刪)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Http method&lt;/th&gt;
&lt;th&gt;對應的資料庫操作&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Create(新增)&lt;/td&gt;
&lt;td&gt;新增一個資源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Read(查詢)&lt;/td&gt;
&lt;td&gt;取得一個資源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;Update(修改)&lt;/td&gt;
&lt;td&gt;更新一個已存在的資源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;Delete(刪除)&lt;/td&gt;
&lt;td&gt;刪除一個資源&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;使用url路徑描述資源之間的階層關係&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Http method&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET/users&lt;/td&gt;
&lt;td&gt;取得所有user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET/users/123&lt;/td&gt;
&lt;td&gt;取得user id為123的user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET/users/123/articles&lt;/td&gt;
&lt;td&gt;取得user id為123的user所寫的所有文章&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET/users/123/articles/456&lt;/td&gt;
&lt;td&gt;取得user id為123的user所寫的、article id為456的文章&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;response body返回json或是xml格式&lt;/li&gt;
&lt;li&gt;To REST or Not To REST ?
&lt;ul&gt;
&lt;li&gt;REST風格只是一種約定俗成的習慣，不是標準規範&lt;/li&gt;
&lt;li&gt;選擇最洽當的做法即可&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;實作 RESTful API&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// 利用@NotNull註解就不用像以前一樣，如下
if(student.getId() == null) {
    throw new RuntimeException(&quot;id 不可以是 null&quot;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;驗證請求參數(常用)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;@NotNull：不能為null&lt;/li&gt;
&lt;li&gt;@NotBlank：不能為null、且不能為空白的字串，用在驗證String類型的參數上&lt;/li&gt;
&lt;li&gt;@NotEmpty：不能為null、且size必須&gt;0，用在驗證集合類型(List、Set、Map)的參數上&lt;/li&gt;
&lt;li&gt;@Min(value)：值必須&gt;=value，用在驗證數字類型的參數上&lt;/li&gt;
&lt;li&gt;@Max(value)：值必須&amp;#x3C;=value，用在驗證數字類型的參數上&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;使用@RequestBody時，要在該參數上加上@Valid註解，才能讓這個class裡的驗證請求參數的註解生效&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@PostMapping(&quot;/students&quot;)
public String create(@RequestBody @Valid Student student) {
    return &quot;執行資料庫的Create操作&quot;;
}
    
public class Student {

    @NotNull
    Integer id;

    @NotBlank
    String name;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;驗證請求參數(不常用)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;@Size(min,max)：min &amp;#x3C;= 字串長度or集合的size &amp;#x3C;= max，可以只設max or min&lt;/li&gt;
&lt;li&gt;@Email：必須符合電子郵件的格式&lt;/li&gt;
&lt;li&gt;@Pattern(regexp)：必須符合正規表達式regexp&lt;/li&gt;
&lt;li&gt;@Past：必須比當前時間早，也就是被註解的參數必須是以前的時間&lt;/li&gt;
&lt;li&gt;@Future：必須比當前時間晚，也就是被註解的參數必須是未來的時間&lt;/li&gt;
&lt;li&gt;@AssertTure：必須為true，用在驗證boolean類型的參數上&lt;/li&gt;
&lt;li&gt;@AssertFalse：必須為false，用在驗證boolean類型的參數上&lt;/li&gt;
&lt;li&gt;@Null：必須為null&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;使用@RequestParam、@RequestHeader、@PathVariable時，要在Controller上加上@Validated註解，才能夠讓驗證請求參數的註解生效&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;
@RestController
@Validated
public class StudentController {

    @PostMapping(&quot;/students/{studentId}&quot;)
    public String create(@PathVariable @Min(100) Integer studentId) {
        return &quot;執行資料庫的Create操作&quot;;
    }
}
    
public class Student {

    @NotNull
    Integer id;

    @NotBlank
    String name;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Http status code (Http狀態碼)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用來表示這次http請求的結果為何&lt;/li&gt;
&lt;li&gt;可以根據首位數字分成五大類
&lt;ul&gt;
&lt;li&gt;1xx：資訊
&lt;ul&gt;
&lt;li&gt;無常見的狀態碼&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2xx：成功
&lt;ul&gt;
&lt;li&gt;200 Ok：請求成功&lt;/li&gt;
&lt;li&gt;201 Create：請求成功且新的資源成功被創建，通常用在POST的response&lt;/li&gt;
&lt;li&gt;202 Accepted：請求已經接受，但尚未處理完成&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3xx：重新導向
&lt;ul&gt;
&lt;li&gt;301 Moved Permanently：永久性重新導向，新的url應放在response header的&quot;Location&quot;中返回，通常會用在網頁搬家上&lt;/li&gt;
&lt;li&gt;302 Found：臨時重新導向，新的臨時性的url應放在response header的&quot;Location&quot;中返回&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4xx：前端請求錯誤
&lt;ul&gt;
&lt;li&gt;400 Bad Request：前端的請求參數有錯誤(例如：前端傳給後端的參數名稱不同、請求的格式有問題)&lt;/li&gt;
&lt;li&gt;401 Unauthorized：沒有通過身份驗證&lt;/li&gt;
&lt;li&gt;403 Forbidden：請求被後端拒絕，通常是權限不足導致的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5xx：後端處理有問題
&lt;ul&gt;
&lt;li&gt;500 Internal Server Error：後端在執行程式時發生錯誤，可能是程式內有bug導致的&lt;/li&gt;
&lt;li&gt;503 Service Unavailable：由於臨時維護或著流量太大，後端目前沒有辦法處理請求&lt;/li&gt;
&lt;li&gt;504 Gateway Timeout：請求超時&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;ResponseEntity&amp;#x3C;&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：作為方法的返回類型&lt;/li&gt;
&lt;li&gt;用途：自定義回傳的http response的細節&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@RestController
public class MyController {

    @RequestMapping(&quot;/test&quot;)
    public String test() {
        return &quot;Hello World&quot;;
    }
}


@RestController
public class MyController {

    @RequestMapping(&quot;/test&quot;)
    public ResponseEntity&amp;lt;String&gt; test() {
        return ResponseEntity.status(HttpStatus.ACCEPTED) 
                .body(&quot;Hello World&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;@ControllerAdvice&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在class上，通常會和@ExceptionHandler一起用&lt;/li&gt;
&lt;li&gt;用途：將這個class變成一個bean，並且可以在內部使用@ExceptionHandler&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;@ExceptionHandler&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：只能加在方法上，通常會和@ControllerAdvice一起用&lt;/li&gt;
&lt;li&gt;用途：去catch方法所噴出的Exception&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;使用@ControllerAdvice的好處 - 統一管理Exception&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;底層由Spring AOP所實作&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// Java基礎

// 系統錯誤，不需處理
Throwable - Error - OutOfMemoryError

// 程式有錯誤，需要處理
Throwable - Exception - RuntimeException - IllegalArgumentException
Throwable - Exception - IOException

// 原生處理方式 try catch

public String test1() {
    try {
        throw new Runtime
    } catch () {
    
    }
}

// 503 exsample
// MyController &amp;amp;&amp;amp; MyExceptionHandle

@RestController
public class MyController {

    @RequestMapping(&quot;/test1&quot;)
    public ResponseEntity&amp;lt;String&gt; test() {
        throw new RuntimeException(&quot;This is test1 error&quot;);
    }
}

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity&amp;lt;String&gt; errorHandle(RuntimeException exception) {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(&quot;RuntimeException: &quot; + exception.getMessage());
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity&amp;lt;String&gt; errorHandle(IllegalArgumentException exception) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(&quot;IllegalArgumentException: &quot; + exception.getMessage());
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;攔截器Interceptor&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// Controller
@RestController
public class MyController {

    @RequestMapping(&quot;/test1&quot;)
    public String test1() {
        System.out.println(&quot;執行 test1 方法&quot;);
        return &quot;hello test1&quot;;
    }

    @RequestMapping(&quot;/test2&quot;)
    public String test2() {
        System.out.println(&quot;執行 test2 方法&quot;);
        return &quot;hello test2&quot;;
    }
}


// MyInterceptor 
@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(&quot;執行 MyInterceptor 的 preHandle 方法&quot;);
        return false;
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3&gt;Spring MVC總結&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Http 協議&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;規定前後端之間的資料傳輸格式
&lt;ul&gt;
&lt;li&gt;Http Request&lt;/li&gt;
&lt;li&gt;Http Response&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;URL路徑的對應&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL格式&lt;/li&gt;
&lt;li&gt;@RequestMapping&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;結構化的呈現數據&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Json的格式&lt;/li&gt;
&lt;li&gt;@RestController、@Controller + @ResponseBody&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Http method&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GET&lt;/li&gt;
&lt;li&gt;POST&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;取得請求參數的方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@RequestParam&lt;/li&gt;
&lt;li&gt;@RequestBody&lt;/li&gt;
&lt;li&gt;@RequestHeader&lt;/li&gt;
&lt;li&gt;@PathVariable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如何設計 RESTful API&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;REST風格：簡化溝通成本&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;實作 RESTful API&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@GetMapping&lt;/li&gt;
&lt;li&gt;@PostMapping&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;驗證請求參數的方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@NotNull、@NotBlank、@NotEmpty&lt;/li&gt;
&lt;li&gt;@Valid、@Validated&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;常見的Http狀態碼&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;依照首位數字分成五大類&lt;/li&gt;
&lt;li&gt;ResponseEntity&amp;#x3C;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Controller層統一的Exception處理&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@ControllerAdvice + @ExceptionHandler&lt;/li&gt;
&lt;li&gt;底層使用Spring AOP機制&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;攔截器Interceptor&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;preHandle方法的返回值：true=允許通過，false=拒絕&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Spring JDBC、Spring Data JPA&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;簡介：如何操作資料庫&lt;/li&gt;
&lt;li&gt;Spring JDBC：在SpringBoot中執行原始的SQL語法，去操作資料庫&lt;/li&gt;
&lt;li&gt;Spring Data JPA：使用ORM的概念，透過操作Java object的方式，去操作資料庫&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;使用IntelliJ 管理資料庫的數據&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;取代MySQL Workbench
&lt;ul&gt;
&lt;li&gt;new DataSource =&gt; MySQL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# SQL Console 

CREATE DATABASE myjdbc

CREATE table student (
    id INT PRIMARY KEY ,
    name VARCHAR(30)
)

# 新增
INSERT INTO student(id, name) value(1, &apos;Hubert&apos;)

# 查詢
SELECT * FROM student

# 更新
UPDATE student SET name = &apos;Adam&apos; WHERE id = 1

# 刪除
DELETE FROM student WHERE id = 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;資料庫連線設定&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// pom.xml 新增設定 讓SpringBoot可以使用jdbc的功能
&amp;lt;dependency&gt;
    &amp;lt;groupId&gt;org.springframework.boot&amp;lt;/groupId&gt;
    &amp;lt;artifactId&gt;spring-boot-starter-jdbc&amp;lt;/artifactId&gt;
&amp;lt;/dependency&gt;

// pom.xml 新增設定 添加MySQL Driver讓SpringBoot可以連線到MySQL資料庫
// Google maven mysql
&amp;lt;!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --&gt;
&amp;lt;dependency&gt;
    &amp;lt;groupId&gt;mysql&amp;lt;/groupId&gt;
    &amp;lt;artifactId&gt;mysql-connector-java&amp;lt;/artifactId&gt;
    &amp;lt;version&gt;8.0.22&amp;lt;/version&gt;
&amp;lt;/dependency&gt;

// application.properties
// google database timezone
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myjdbc?serverTimezone=Asia/Taipei&amp;amp;characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=springboot

設定完之後運行SpringBoot Spring JDBC會自動創建 NamedParameterJdbcTemplate這個Bean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;使用NamedParameterJdbcTemplate(SpringBoot Bean) / INSERT、UPDATE、DELETE&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;根據sql語法分成兩類
&lt;ul&gt;
&lt;li&gt;update()可執行的SQL語法
&lt;ul&gt;
&lt;li&gt;INSERT&lt;/li&gt;
&lt;li&gt;UPDATE&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;query()、queryForObject()
&lt;ul&gt;
&lt;li&gt;SELECT&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;update(String sql, Map&amp;#x3C;String, Object&gt; map) 基本用法&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@RestController
public class StudentController {

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    @PostMapping(&quot;/students&quot;)
    public String insert(@RequestBody Student student) {

        String sql = &quot;INSERT INTO student(id, name) VALUE (:studentId, :studentName)&quot;;

        Map&amp;lt;String, Object&gt; map = new HashMap&amp;lt;&gt;();
        map.put(&quot;studentId&quot;, student.getId());
        map.put(&quot;studentName&quot;, student.getName());

        namedParameterJdbcTemplate.update(sql, map);

        return &quot;執行 INSERT SQL&quot;;
    }

    @DeleteMapping(&quot;/students/{studentId}&quot;)
    public String delete(@PathVariable Integer studentId) {

        String sql = &quot;DELETE FROM student WHERE id = :studentId&quot;;

        Map&amp;lt;String, Object&gt; map = new HashMap&amp;lt;&gt;();
        map.put(&quot;studentId&quot;, studentId);

        namedParameterJdbcTemplate.update(sql, map);

        return &quot;執行 DELETE SQL&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;update(String sql, Map&amp;#x3C;String, Object&gt; map) 進階用法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;當table 的 id會自動增加時，要如何去取得id的值&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@PostMapping(&quot;/students&quot;)
    public String insert(@RequestBody Student student) {

        String sql = &quot;INSERT INTO student(name) VALUE (:studentName)&quot;;

        Map&amp;lt;String, Object&gt; map = new HashMap&amp;lt;&gt;();
        map.put(&quot;studentName&quot;, student.getName());

        KeyHolder keyHolder = new GeneratedKeyHolder();

        namedParameterJdbcTemplate.update(sql, new MapSqlParameterSource(map), keyHolder);

        int id = keyHolder.getKey().intValue();

        System.out.println(&quot;mysql 自動生成的 id 為： &quot; + id);

        return &quot;執行 INSERT SQL&quot;;
    }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;要如何大量的執行一批 INSERT/UPDATE/DELETE sql&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;
@PostMapping(&quot;/students/batch&quot;)
public String insertList(@RequestBody List&amp;lt;Student&gt; studentList) {

    String sql = &quot;INSERT INTO student(name) VALUE (:studentName)&quot;;

    MapSqlParameterSource[] parameterSources = new MapSqlParameterSource[studentList.size()];

    for(int i=0; i&amp;lt;studentList.size(); i++) {
        Student student = studentList.get(i);

        parameterSources[i] = new MapSqlParameterSource();
        parameterSources[i].addValue(&quot;studentName&quot;, student.getName());
    }

    namedParameterJdbcTemplate.batchUpdate(sql, parameterSources);

    return &quot;執行一批 INSERT sql&quot;;
}

// 在update()內使用for迴圈也可達成一樣的效果，但是使用batchUpdate()效能較好
// 需要執行大量sql的時候，需考慮效能問題

for(int i=0; i&amp;lt;studentList.size(); i++) {
    Student student = studentList.get(i);

    Map&amp;lt;String, Object&gt; map = new HashMap&amp;lt;&gt;();
    map.put(&quot;studentName&quot;, student.getName());

    namedParameterJdbcTemplate.update(sql, map);
}


MapSqlParameterSource[] parameterSources = new MapSqlParameterSource[studentList.size()];

for(int i=0; i&amp;lt;studentList.size(); i++) {
    Student student = studentList.get(i);

    parameterSources[i] = new MapSqlParameterSource();
    parameterSources[i].addValue(&quot;studentName&quot;, student.getName());
}

namedParameterJdbcTemplate.batchUpdate(sql, parameterSources);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;query(String sql, Map&amp;#x3C;String, Object&gt; map, RowMapper&amp;#x3C;T&apos;&gt; rowMapper) 基本用法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;第一個參數放的是要執行的sql語法&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&quot;SELECT id, name FROM student&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;第二個參數放的是sql語法裡面變數的值&lt;/li&gt;
&lt;li&gt;第三個參數 RowMapper 放的是將資料庫查詢出來的數據，轉變成Java object，最重要的參數&lt;/li&gt;
&lt;li&gt;在寫SELECT sql時，不要使用*號
&lt;ul&gt;
&lt;li&gt;會產生效能問題，會花費額外的網路流量&lt;/li&gt;
&lt;li&gt;無法提升資料庫查詢的速度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RowMapper：一次只能取得一條row，無法取得到其他row的資訊&lt;/li&gt;
&lt;li&gt;ResultSetExtractor：一次可以取得到所有row的資訊，可以自由的組合不同row的數據&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// StudentController
@GetMapping(&quot;/students&quot;)
public List&amp;lt;Student&gt; select() {

    String sql = &quot;SELECT id, name FROM student&quot;;

    Map&amp;lt;String, Object&gt; map = new HashMap&amp;lt;&gt;();

    List&amp;lt;Student&gt; list = namedParameterJdbcTemplate.query(sql, map, new StudentRowMapper());

    return list;
}

// RowMapper
public class StudentRowMapper implements RowMapper&amp;lt;Student&gt; {
    
    // 右鍵 Genertate implements 方法
    @Override
    public Student mapRow(ResultSet resultSet, int i) throws SQLException {

        Student student = new Student();
        student.setId(resultSet.getInt(&quot;id&quot;));
        student.setName(resultSet.getString(&quot;name&quot;));

        return student;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;query方法重點回顧&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在SpringBoot裡寫SELECT sql時，不要使用*，要一一列舉想要查詢的column&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;query方法永遠會回傳一個List&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List裡面有可能有多筆數據、或是只有一筆數據、或是完全沒有任何數據&lt;/li&gt;
&lt;li&gt;取得List中的數據前，記得要先判斷內部是否有數據&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RowMapper&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用途：將資料庫查詢出來的數據，轉換成Java object&lt;/li&gt;
&lt;li&gt;可使用resultSet.getXxx(String column)取得column名字的值(推薦使用)&lt;/li&gt;
&lt;li&gt;可使用resultSet.getXxx(int index)取得第幾順位的column的值&lt;/li&gt;
&lt;li&gt;resultSet所包含的column，就是SELECT sql中所查出來的那些column&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ResultSetExtractor&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;和RowMapper用途一樣，只是比較強大，可以組合不同row之間的數據&lt;/li&gt;
&lt;li&gt;較少用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4&gt;MVC架構模式 Controller-Service-Dao 三層式架構&lt;/h4&gt;
&lt;h5&gt;軟體工程(Software Engineering)&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;面對一個大型的系統，工程師們需要如何分工合作，一起去解決問題？
&lt;ul&gt;
&lt;li&gt;Git版本控制：管理多位工程師所寫的程式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;MVC架構模式是軟體工程中的一種軟體架構&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用途：將系統拆分成Model、View、Controller三個部分，每個部分各自負責不同的功能&lt;/li&gt;
&lt;li&gt;好處
&lt;ul&gt;
&lt;li&gt;職責分離，更容易維護程式&lt;/li&gt;
&lt;li&gt;使程式結構更直覺，有利於團隊分工&lt;/li&gt;
&lt;li&gt;可重複使用寫好的程式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;MVC架構模式 in Spring Boot&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;MVC架構模式是抽象的概念&lt;/li&gt;
&lt;li&gt;實際套用到Spring Boot時，會轉化為Controller-Service-Dao三層式架構&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Controller-Service-Dao三層式架構&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;前端發出Request =&gt; Controller接收並且驗證請求參數是否正確

Controller接收並且驗證請求參數是否正確 =Call=&gt; Service負責業務邏輯

Service負責業務邏輯 =Call=&gt; Dao(Data Access Object)負責和資料庫溝通(與資料庫存取數據)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;實作Controller-Service-Dao 三層式架構&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// Controller
@GetMapping(&quot;/students/{studentId}&quot;)
public Student select(@PathVariable Integer studentId) {
    return studentService.getById(studentId);
}

// Service
@Component
public class StudentServiceImpl implements StudentService{

    @Autowired
    private StudentDao studentDao;

    @Override
    public Student getById(Integer studentId) {
        return studentDao.getById(studentId);
    }
}

// Dao
@Component
public class StudentDaoImpl implements StudentDao{

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    @Override
    public Student getById(Integer studentId) {
        String sql = &quot;SELECT id, name FROM student WHERE id = :studentId&quot;;

        Map&amp;lt;String, Object&gt; map = new HashMap&amp;lt;&gt;();
        map.put(&quot;studentId&quot;, studentId);

        List&amp;lt;Student&gt; list = namedParameterJdbcTemplate.query(sql, map, new StudentRowMapper());

        if (list.size() &gt; 0) {
            return list.get(0);
        } else {
            return null;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;交易 Transaction&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;交易(Transaction) 是資料庫的一種用法&lt;/li&gt;
&lt;li&gt;用途：可以在一個交易裡包含多個資料庫操作，這些資料庫操作，要馬一起成功，要馬一起失敗
&lt;ul&gt;
&lt;li&gt;也就是All or Nothing 原則&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Rollback(回滾)
&lt;ul&gt;
&lt;li&gt;撤銷已執行的資料庫操作，確保數據恢復原狀&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;@Transactional&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用法：加在class上或方法上(通常是加在方法上)&lt;/li&gt;
&lt;li&gt;用途：使用交易來管理這個方法中的資料庫操作&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@Component
public class MyService {
    
    @Transactional (Transaction加上al變成形容詞，形容這個方法是具有交易特性的)
    public void transfer() {
        //...
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;多個資料庫的連線設定&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// application.properties
# 多個資料庫的連線設定
spring.datasource.test1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.test1.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezone=Asia/Taipei&amp;amp;characterEncoding=utf-8
spring.datasource.test1.username=root
spring.datasource.test1.password=springboot

spring.datasource.test2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.test2.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimezone=Asia/Taipei&amp;amp;characterEncoding=utf-8
spring.datasource.test2.username=root
spring.datasource.test2.password=springboot

# 單個資料庫的連線方式
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myjdbc?serverTimezone=Asia/Taipei&amp;amp;characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=springboot

// DataSourceConfiguration
@Configuration
public class DataSourceConfiguration {

    // 連線到 test1 資料庫的 DataSource 和 NamedParameterJdbcTemplate
    @Bean
    @ConfigurationProperties(prefix = &quot;spring.datasource.test1&quot;)
    public DataSource test1DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public NamedParameterJdbcTemplate test1JdbcTemplate(
            @Qualifier(&quot;test1DataSource&quot;) DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource);
    }


    // 連線到 test2 資料庫的 DataSource 和 NamedParameterJdbcTemplate
    @Bean
    @ConfigurationProperties(prefix = &quot;spring.datasource.test2&quot;)
    public DataSource test2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public NamedParameterJdbcTemplate test2JdbcTemplate(
            @Qualifier(&quot;test2DataSource&quot;) DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Spring Jdbc 總結&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用Intellij管理資料庫的數據&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;console編輯區&lt;/li&gt;
&lt;li&gt;圖形化介面&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;資料庫連線設定&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;連線資訊寫在Spring Boot的設定檔 - application.properties裡面&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NamedParameterJdbcTemplate的用法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;update() - 執行INSERT、UPDATE、DELETE sql&lt;/li&gt;
&lt;li&gt;query() - 執行SELECT sql&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MVC架構模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model、View、Controller&lt;/li&gt;
&lt;li&gt;軟體工程：當很多工程師一起做很大的Project時，就很需要注重軟體工程&lt;/li&gt;
&lt;li&gt;Controller-Service-Dao 三層式架構
&lt;ul&gt;
&lt;li&gt;Controller：負責接收Http Request、驗證請求參數&lt;/li&gt;
&lt;li&gt;Service：負責業務邏輯&lt;/li&gt;
&lt;li&gt;Dao：負責和資料庫溝通&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Spring Data JPA&lt;/h3&gt;
&lt;h4&gt;簡介&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Spring JDBC：執行原始的SQL語法，去操作資料庫&lt;/li&gt;
&lt;li&gt;Spring Data JPA：使用ORM的概念，透過操作Java object的方式，去操作資料庫&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;ORM&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;全名為Object-Relational Mapping&lt;/li&gt;
&lt;li&gt;用途：將Java object，去對應到資料庫的table，所以對Java object的操作，就是對資料庫的操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;JPA&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;全名為Java Persistence API，定義要如何去操作資料庫&lt;/li&gt;
&lt;li&gt;JPA提供了很多註解讓我們使用，像是@Entity、@Table、@Column...&lt;/li&gt;
&lt;li&gt;Hibernate
&lt;ul&gt;
&lt;li&gt;一種ORM框架，去實現JPA&lt;/li&gt;
&lt;li&gt;負責自動生成SQL語法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Spring JDBC VS Spring Data JPA&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Spring JDBC&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是以SQL為中心&lt;/li&gt;
&lt;li&gt;需要自己寫SQL語法，去操作資料庫&lt;/li&gt;
&lt;li&gt;開發效率較低&lt;/li&gt;
&lt;li&gt;效能較好&lt;/li&gt;
&lt;li&gt;可寫出較為複雜的SQL語法&lt;/li&gt;
&lt;li&gt;一開始比較難上手，但上手後就不會有什麼問題&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spring Data JPA&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JPA是以Java Object為中心&lt;/li&gt;
&lt;li&gt;不需要自己寫SQL語法，Hibernate會自動生成SQL語法去操作資料庫&lt;/li&gt;
&lt;li&gt;開發效率較高&lt;/li&gt;
&lt;li&gt;效能較差&lt;/li&gt;
&lt;li&gt;很難寫出複雜的查詢&lt;/li&gt;
&lt;li&gt;容易上手，但需要能妥善處理後續帶來的問題&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;資料庫連線設定&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// pom.xml 新增設定 讓SpringBoot可以使用data-jpa的功能
&amp;lt;dependency&gt;
    &amp;lt;groupId&gt;org.springframework.boot&amp;lt;/groupId&gt;
    &amp;lt;artifactId&gt;spring-boot-starter-data-jpa&amp;lt;/artifactId&gt;
&amp;lt;/dependency&gt;

// pom.xml 新增設定 添加MySQL Driver讓SpringBoot可以連線到MySQL資料庫
// Google maven mysql
&amp;lt;!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --&gt;
&amp;lt;dependency&gt;
    &amp;lt;groupId&gt;mysql&amp;lt;/groupId&gt;
    &amp;lt;artifactId&gt;mysql-connector-java&amp;lt;/artifactId&gt;
    &amp;lt;version&gt;8.0.22&amp;lt;/version&gt;
&amp;lt;/dependency&gt;

// application.properties
// google database timezone
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myjdbc?serverTimezone=Asia/Taipei&amp;amp;characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=springboot

設定完之後運行SpringBoot Spring JDBC會自動創建 NamedParameterJdbcTemplate這個Bean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;第一個Spring Data JPA 程式&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// Student Class 

@Entity
@Table(name = &quot;student&quot;)
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;)
    Integer id;

    @Column(name = &quot;name&quot;)
    String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

// StudentRepository interface
public interface StudentRepository extends CrudRepository&amp;lt;Student, Integer&gt; {
    // ...
}

// StudentController
@RestController
public class StudentController {

    @Autowired
    private StudentRepository studentRepository;

    @PostMapping(&quot;/students&quot;)
    public String insert(@RequestBody Student student) {

        studentRepository.save(student);

        return &quot;執行資料庫的 Create操作&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Spring Data JPA提供的Interface&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;CrudRepository：基本的CRUD操作 =&gt; 最常用&lt;/li&gt;
&lt;li&gt;PagingAndSortingRepository：新增分頁和排序的操作 =&gt; extends CrudRepository&lt;/li&gt;
&lt;li&gt;JpaRepository：能力最強，新增了JPA相關的flush操作 =&gt; extends PagingAndSortingRepository&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;CrudRepository 基本用法&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@RestController
public class StudentController {

    @Autowired
    private StudentRepository studentRepository;
        
    // CREATE
    @PostMapping(&quot;/students&quot;)
    public String insert(@RequestBody Student student) {

        studentRepository.save(student);

        return &quot;執行資料庫的 Create操作&quot;;
    }
        
    // READ
    @GetMapping(&quot;/students/{studentId}&quot;)
    public Student read(@PathVariable Integer studentId) {

        Student student = studentRepository.findById(studentId).orElse(null);

        return student;
    }
    
    // UPDATE
    @PutMapping(&quot;/students/{studentId}&quot;)
    public String update(@PathVariable Integer studentId,
                         @RequestBody Student student) {

        Student s = studentRepository.findById(studentId).orElse(null);

        if (s != null) {
            s.setName(student.getName());
            studentRepository.save(s);

            return &quot;執行資料庫的 Update操作&quot;;
        } else {
            return &quot;Update失敗，數據不存在&quot;;
        }

    }
    
    // DELETE
    @DeleteMapping(&quot;/students/{studentId}&quot;)
    public String delete(@PathVariable Integer studentId) {

        studentRepository.deleteById(studentId);

        return &quot;執行資料庫的 Delete操作&quot;;
    }


}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;自定義查詢條件 - findByXxx的命名規則&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public interface StudentRepository extends CrudRepository&amp;lt;Student, Integer&gt; {

List&amp;lt;Student&gt; findByName(String name); 
=&gt; SELECT * FROM Student WHERE name = ?(String name)
=&gt; 返回的類型通常是返回Student或是List&amp;lt;Student&gt;

Student findByIdAndName(Integer id, String name);
=&gt; SELECT * FROM Student WHERE id = ? AND name = ?(Integer id, String name)

}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;原生SQL查詢 @Query&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;目的：用來解決findByXxx無法寫出複雜的查詢邏輯的問題&lt;/li&gt;
&lt;li&gt;用途：在Spring Data JPA中，執行原生的SQL語法&lt;/li&gt;
&lt;li&gt;用法：加在方法上&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;public interface StudentRepository extends CrudRepository&amp;lt;Student, Integer&gt; {
    
    @Query(value = &quot;SELECT id, name FROM student WHERE id = ?1 AND name = ?2, nativeQuery = true&quot;)
    Student test1(Integer id, String name);

}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;@Query vs findByXxx
&lt;ul&gt;
&lt;li&gt;優先使用findByXxx的命名規則&lt;/li&gt;
&lt;li&gt;複雜的邏輯才使用@Query&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Spring Data JPA 總結&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;建立ORM對應關係&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Entity&lt;/li&gt;
&lt;li&gt;@Table、@Column、@Id&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CrudRepository的用法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;save()：新增/修改&lt;/li&gt;
&lt;li&gt;findById()：查詢&lt;/li&gt;
&lt;li&gt;deleteById()：刪除&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;自定義查詢條件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;findByXxx的命名規則&lt;/li&gt;
&lt;li&gt;@Query執行sql語法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;單元測試&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;目的：自動化測試程式的正確性&lt;/li&gt;
&lt;li&gt;所謂單元測試，就是一次只測試一個功能點，一個單元(Unit)可以是一個method，或是一個API&lt;/li&gt;
&lt;li&gt;其他軟體測試
&lt;ul&gt;
&lt;li&gt;整合測試(Integration Testing)&lt;/li&gt;
&lt;li&gt;端對端測試(End-To-End Testing)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// pom.xml
&amp;lt;dependency&gt;
    &amp;lt;groupId&gt;org.springframework.boot&amp;lt;/groupId&gt;
    &amp;lt;artifactId&gt;spring-boot-starter-test&amp;lt;/artifactId&gt;
    &amp;lt;scope&gt;test&amp;lt;/scope&gt;
&amp;lt;/dependency&gt;

// class
public class Calculator {
    
    public int add(int x, int y) {
        return x + y;
    }
}

// test/classTest
public class CalculatorTest {

    @Test 
    public void test() {
        Calculator calculator = new Calculator();
        int result = calculator.add(1, 2);

        assertEquals(3, result);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;單元測試的特性&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;可以被自動化運行&lt;/li&gt;
&lt;li&gt;各個單元測試互相獨立，彼此之間不能有依賴關係&lt;/li&gt;
&lt;li&gt;測試的結果是穩定的，不受外部服務影響&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;單元測試的注意事項&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;測試的程式要放在test資料夾裡面&lt;/li&gt;
&lt;li&gt;測試的class以「原class的名字加上Test做為結尾」來命名&lt;/li&gt;
&lt;li&gt;測試的class的package，要跟原class的package保持一致&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;JUnit&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;JUnit是Java單元測試的必備工具&lt;/li&gt;
&lt;li&gt;JUnit和SpringBoot的版本關係
&lt;ul&gt;
&lt;li&gt;SpringBoot版本 &amp;#x3C;= 2.1
&lt;ul&gt;
&lt;li&gt;僅能使用JUnit4&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SpringBoot版本 = 2.2、2.3
&lt;ul&gt;
&lt;li&gt;能同時使用JUnit4和JUnit5&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SpringBoot版本 &gt;= 2.4
&lt;ul&gt;
&lt;li&gt;僅能使用JUnit5&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;只要在方法上加上@Test，即可生成一個單元測試
&lt;ul&gt;
&lt;li&gt;用法：加在方法上，可以生成一個單元測試，@Test只能在test資料夾底下使用，可以將該方法變成可執行的test case(測項)&lt;/li&gt;
&lt;li&gt;細節：方法必須是public void，並且沒有任何參數&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;JUnit5的Assert用法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;若是不符合assert斷言的預期結果，則為測試失敗&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Assert&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;assertNull(A)&lt;/td&gt;
&lt;td&gt;斷言A為Null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;assertNotNull(A)&lt;/td&gt;
&lt;td&gt;斷言A不為Null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;assertEquals(A, B)&lt;/td&gt;
&lt;td&gt;斷言A和B相等，會使用equal()方法去判斷&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;assertTrue(A)&lt;/td&gt;
&lt;td&gt;斷言A為True&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;assertFalse(A)&lt;/td&gt;
&lt;td&gt;斷言A為False&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;assertThrows(exception, method)&lt;/td&gt;
&lt;td&gt;斷言執行method時，會噴出exception&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h5&gt;JUnit5的其它用法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;@BeforeEach：、@AfterEach
&lt;ul&gt;
&lt;li&gt;@BeforeEach：在每次@Test開始前，都會執行一次&lt;/li&gt;
&lt;li&gt;@AfterEach：在每次@Test結束後，都會執行一次&lt;/li&gt;
&lt;li&gt;@BeforeAll：在所有@Test開始前，執行一次 / public static void&lt;/li&gt;
&lt;li&gt;@AfterAll：在所有@Test結束後，執行一次 / public static void&lt;/li&gt;
&lt;li&gt;@Disabled：忽略該@Test不執行&lt;/li&gt;
&lt;li&gt;@DisplayName：自訂義顯示名稱&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;撰寫Service層、Dao層測試&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;@SpringBootTest、@Transactional&lt;/li&gt;
&lt;li&gt;只要在測試的class上加上@SpringBootTest，則在運行單元測試時，SpringBoot就會去啟動Spring容器，創建所有的Bean出來&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@SpringBootTest
public class DemoApplicationTests {
    
    @Autowired
    private StudentDao studentDao;
    
    @Test
    public void getById() {
        studentDao.getById(1);
    }
    
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;@SpringBootTest：不只是創建bean，所有的@Configuration設定也都會被執行，效果等同於直接運行起SpringBoot程式&lt;/li&gt;
&lt;li&gt;@Transactional(for 單元測試)：單元測試運行完成之後，SpringBoot會去rollback這個單元測試裡面的所有資料庫操作
&lt;ul&gt;
&lt;li&gt;用法：可以加在方法上、也可以加在class上&lt;/li&gt;
&lt;li&gt;用途：在單元測試結束後，rollback所有資料庫操作，將數據恢復原狀&lt;/li&gt;
&lt;li&gt;在main資料夾（正常程式）
&lt;ul&gt;
&lt;li&gt;交易管理&lt;/li&gt;
&lt;li&gt;程式運行中途發生錯誤的話，才rollback已經執行的資料庫操作，將數據恢復原狀&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在test資料夾（單元測試）
&lt;ul&gt;
&lt;li&gt;在該單元測試結束後，強制rollback所有執行的資料庫操作，將數據恢復原狀&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;撰寫Controller層測試 - MockMvc主要架構&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;目的：模擬前端的行為，測試API是否運行正確&lt;/li&gt;
&lt;li&gt;不能直接注入bean來測試，需要透過模擬真實的API call來測試&lt;/li&gt;
&lt;li&gt;MockMvc
&lt;ul&gt;
&lt;li&gt;用途：模擬真實的API call&lt;/li&gt;
&lt;li&gt;@SpringBootTest&lt;/li&gt;
&lt;li&gt;@AutoConfigureMockMvc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@SpringBootTest
@AutoConfigureMockMvc
public class StudentControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void getById() throws Exception {
        RequestBuilder requestBuilder = MockMvcRequestBuilders
                .get(&quot;/students/{studentId}, 3&quot;);
        
        // Builder設計模式（建造者模式）
        mockMvc.perform(requestBuilder)
                .andExpect(MockMvcResultMatchers.status().is(200));
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;撰寫Controller層測試 - MockMvc常用寫法&lt;/h5&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@SpringBootTest
@AutoConfigureMockMvc
public class StudentControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void getById() throws Exception {
        RequestBuilder requestBuilder = MockMvcRequestBuilders
                .get(&quot;/students/{studentId}&quot;, 3)
                .header(&quot;headerName&quot;, &quot;headerValue&quot;)
                .queryParam(&quot;graduate&quot;, &quot;true&quot;);

        MvcResult mvcResult = mockMvc.perform(requestBuilder)
                .andDo(print())
                .andExpect(status().is(200))
                .andExpect(jsonPath(&quot;$.id&quot;, equalTo(3)))
                .andExpect(jsonPath(&quot;$.name&quot;, notNullValue()))
                .andReturn();

        String body = mvcResult.getResponse().getContentAsString();
        System.out.println(&quot;返回的 response body 為: &quot; + body);
    }

    @Test
    public void create() throws Exception {
        RequestBuilder requestBuilder = MockMvcRequestBuilders
                .post(&quot;/students&quot;)
                .contentType(MediaType.APPLICATION_JSON)
                .content(&quot;{\n&quot; +
                        &quot;  \&quot;name\&quot;: \&quot;Hank\&quot;,\n&quot; +
                        &quot;  \&quot;score\&quot;: 14.6,\n&quot; +
                        &quot;  \&quot;graduate\&quot;: false\n&quot; +
                        &quot;}&quot;);

        mockMvc.perform(requestBuilder)
                .andExpect(status().is(201));
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;Mock測試 - Mockito&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;目的：避免為了測試某一個單元測試，而去構建了整個bean的dependency&lt;/li&gt;
&lt;li&gt;做法：創造一個假的Bean，去替換掉Spring容器中原有的Bean&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Mockito&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;在SpringBoot中進行Mock測試的工具&lt;/li&gt;
&lt;li&gt;功能：
&lt;ul&gt;
&lt;li&gt;模擬方法的返回值
&lt;ul&gt;
&lt;li&gt;when...thenReturn...&lt;/li&gt;
&lt;li&gt;doReturn...when...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;模擬拋出Exception
&lt;ul&gt;
&lt;li&gt;when...thenThrow...&lt;/li&gt;
&lt;li&gt;doThrow...when...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;紀錄方法的使用次數、順序
&lt;ul&gt;
&lt;li&gt;Mockito.verify(studentDao, Mockito.times(2)).getById(Mockito.any());&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;限制：
&lt;ul&gt;
&lt;li&gt;不能mock static方法&lt;/li&gt;
&lt;li&gt;不能mock private方法&lt;/li&gt;
&lt;li&gt;不能mock final class&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@MockBean：產生一個假的Bean，替換掉Spring容器中的Bean
&lt;ul&gt;
&lt;li&gt;沒有定義的方法，預設返回null&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@SpringBootTest
public class StudentServiceImplMockTest {

    @Autowired
    private StudentService studentService;

    @MockBean
    private StudentDao studentDao;

    @Test
    public void getById() {
        Student mockStudent = new Student();
        mockStudent.setId(100);
        mockStudent.setName(&quot;I&apos;m mock&quot;);

        Mockito.when(studentDao.getById(Mockito.any())).thenReturn(mockStudent);
        Mockito.doReturn(mockStudent).when(studentDao).getById(Mockito.any());

        Student student = studentService.getById(2);
        assertNotNull(student);
        assertEquals(100, student.getId());
        assertEquals(&quot;I&apos;m mock&quot;, student.getName());
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;SpyBean：Spring容器中的Bean仍舊是正常的bean，只替換其中幾個方法
&lt;ul&gt;
&lt;li&gt;沒有定義的方法，預設使用真實的方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;使用H2資料庫&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;H2是一種嵌入式資料庫&lt;/li&gt;
&lt;li&gt;用途：可以在啟動SpringBoot時被生成出來，在運行結束時被銷毀，用完即丟&lt;/li&gt;
&lt;li&gt;常用在單元測試，降低程式對實體資料庫的依賴
&lt;ul&gt;
&lt;li&gt;也就是說，任何一台電腦，不需要安裝任何軟體，都可以運行該單元測試&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在test/resources中的application.properties需要和main/resources中的application.properties同步，不然運行測試會失敗&lt;/li&gt;
&lt;li&gt;在test/resources中新增schema.sql、data.sql，並且添加SQL語法&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// application.properties setting
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=sa

// 如果是使用Spring Data Jpa的方式要另外添加這行
spring.jpa.hibernate.ddl-auto=none

// schema.sql + IF NOT EXISTS 避免H2資料庫重複創建Table
CREATE TABLE IF NOT EXISTS student (
     id INT PRIMARY KEY AUTO_INCREMENT,
     name VARCHAR(30),
     score DOUBLE,
     graduate BOOLEAN,
     create_date TIMESTAMP
);

// data.sql
INSERT INTO student (name, score, graduate, create_date) VALUES (&apos;Amy&apos;, 90.3, true, &apos;2021-09-01 10:20:33&apos;);
INSERT INTO student (name, score, graduate, create_date) VALUES (&apos;Rom&apos;, 34.6, false, &apos;2021-08-10 17:21:14&apos;);
INSERT INTO student (name, score, graduate, create_date) VALUES (&apos;Judy&apos;, 100.0, true, &apos;2021-09-05 12:19:48&apos;);
INSERT INTO student (name, score, graduate, create_date) VALUES (&apos;Mike&apos;, 87.2, true, &apos;2021-09-03 15:01:15&apos;);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;Spring Boot補充特性&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Convention over Configuration（約定大於配置 || 慣例優於設定）
&lt;ul&gt;
&lt;li&gt;不需添加任何設定，只要知道運作規則，就能直接使用&lt;/li&gt;
&lt;li&gt;所有project遵循一致的標準化設定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring Boot出現的意義就是為了簡化Spring開發&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;我們真的需要單元測試嗎&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;單元測試的數量不是越多越好
&lt;ul&gt;
&lt;li&gt;單元測試也是程式，程式越多，就表示維護成本越高&lt;/li&gt;
&lt;li&gt;單元測試的重要程度＝影響使用者的程度，使用頻率越高的功能越需要單元測試&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;要加多少單元測試
&lt;ul&gt;
&lt;li&gt;比如在金融業或是博弈業：能測多細就多細，有關金錢是很重要的&lt;/li&gt;
&lt;li&gt;在一般軟體業：
&lt;ul&gt;
&lt;li&gt;使用MockMvc測試各API是否運作正常&lt;/li&gt;
&lt;li&gt;重要的Service層可以加一些專屬的單元測試&lt;/li&gt;
&lt;li&gt;Dao層比較少測，但如果有複雜的sql語法，可以額外添加測試&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;如何寫出好的單元測試&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;在寫單元測試時，要從使用者的角度去思考
&lt;ul&gt;
&lt;li&gt;不要在實作完直接寫單元測試，可以沈澱一下再回頭寫，以免鑽牛角尖&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;一定要記得測試Error Case
&lt;ul&gt;
&lt;li&gt;在測試getById方法時，可以測試當id不存在時怎麼處理？當id的長度超過限制怎麼處理？&lt;/li&gt;
&lt;li&gt;實作Error Handling是開發中最難的一部分&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;善用Run Test With Coverage，查看單元測試覆蓋的範圍
&lt;ul&gt;
&lt;li&gt;不要為了單純提升測試覆蓋率而寫單元測試，而是要思考是否有哪些使用場景沒考慮到&lt;/li&gt;
&lt;li&gt;不要被數字迷惑，並不是一定要100%覆蓋率才是完美，要確保的是重要的功能有被測試到&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果測試中使用的@SpyBean過多，表示功能切分的不夠好
&lt;ul&gt;
&lt;li&gt;盡量從功能面去區分Service，譬如說ProductService、OrderService、UserService&lt;/li&gt;
&lt;li&gt;和外部服務有關的API call，獨立成一個Service
&lt;ul&gt;
&lt;li&gt;譬如說和Youtube有關的API call，就統一放在YoutubeService裏&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;靠經驗多累積&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;學單元測試的四個心裡階段&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;新手期：寫測試太花時間了，感覺沒什麼用，不如多解另外一個bug&lt;/li&gt;
&lt;li&gt;被逼著寫期：公司開始要求測試覆蓋率，只能開始試著寫&lt;/li&gt;
&lt;li&gt;領悟期：測試越寫越熟練，也測出越來越多bug，開始覺得寫測試真的有用&lt;/li&gt;
&lt;li&gt;開導他人期：督促新進工程師寫測試，邁入正向循環&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;測試驅動開發 Test-Driven Development&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;全稱 Test-Driven Development，常稱TDD&lt;/li&gt;
&lt;li&gt;貫徹「先寫測試、再寫開發」的精神&lt;/li&gt;
&lt;li&gt;大致上分五個步驟
&lt;ul&gt;
&lt;li&gt;選擇一個功能，先寫單元測試&lt;/li&gt;
&lt;li&gt;單元測試失敗（紅燈）&lt;/li&gt;
&lt;li&gt;實作功能程式&lt;/li&gt;
&lt;li&gt;單元測試成功（綠燈）&lt;/li&gt;
&lt;li&gt;持續重構程式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;單元測試總結&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;單元測試的特性和注意事項
&lt;ul&gt;
&lt;li&gt;可以被自動化運行&lt;/li&gt;
&lt;li&gt;單元測試彼此獨立&lt;/li&gt;
&lt;li&gt;測試結果穩定，不受外部服務影響&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Junit5用法
&lt;ul&gt;
&lt;li&gt;@Test&lt;/li&gt;
&lt;li&gt;assert斷言方法&lt;/li&gt;
&lt;li&gt;@BeforeEach、@AfterEach、@BeforeAll、@AfterAll&lt;/li&gt;
&lt;li&gt;@Disabled、@DisplayName&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如何測試Spring Boot程式
&lt;ul&gt;
&lt;li&gt;Service、Dao層的測試
&lt;ul&gt;
&lt;li&gt;@SpringBootTest&lt;/li&gt;
&lt;li&gt;@Transaction&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Controller層的測試
&lt;ul&gt;
&lt;li&gt;MockMvc&lt;/li&gt;
&lt;li&gt;主架構：RequestBuilder、mockMvc.perform()、andExpect()&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;隔絕外部服務的依賴
&lt;ul&gt;
&lt;li&gt;Mock測試 - Mockito
&lt;ul&gt;
&lt;li&gt;@MockBean、@SpyBean&lt;/li&gt;
&lt;li&gt;模擬方法的返回值：when...thenReturn、doReturn...when&lt;/li&gt;
&lt;li&gt;模擬噴出Exception：when...thenThrow、doThrow...when&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用H2資料庫
&lt;ul&gt;
&lt;li&gt;schema.sql：放創建table的sql&lt;/li&gt;
&lt;li&gt;data.sql：放插入數據的sql&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;前輩經驗談
&lt;ul&gt;
&lt;li&gt;單元測試的數量不是越多越好&lt;/li&gt;
&lt;li&gt;記得要測試Error Case&lt;/li&gt;
&lt;li&gt;善用Intellij的Run Test With Coverage功能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;測試驅動開發
&lt;ul&gt;
&lt;li&gt;Test-Driven Development（TDD）&lt;/li&gt;
&lt;li&gt;先寫測試、再寫開發&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Spring 常用功能&lt;/h3&gt;
&lt;h4&gt;Maven&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Maven是在SpringBoot開發中，負責Library管理和Project構建的工具&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Maven - Library管理&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用途：管理這個SpringBoot project可以使用哪些功能&lt;/li&gt;
&lt;li&gt;用法：透過pom.xml檔案進行管理&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Maven - Project構建&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用途：對SpringBoot project進行編譯、測試、運行、清理、打包&lt;/li&gt;
&lt;li&gt;用法：透過maven指令操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Maven Repository（Maven倉庫）&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用途：儲存SpringBoot project依賴的Library（jar檔）&lt;/li&gt;
&lt;li&gt;可以分為Local Repository（本地倉庫）和Remote Repository（遠端倉庫）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Maven指令&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;Clean：刪除src底下的target資料夾&lt;/li&gt;
&lt;li&gt;Compile &gt; Test &gt; Package &gt; Install &gt; Deploy&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;針對不同環境做不同的設定&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;application-dev.properties
application-test.properties

Intellij右上角設定 &gt; Active profiles:dev / test&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Log的級別與規範&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;private final static Logger log = LoggerFactory.getLogger(StudentController.class);

log.info(&quot;取得 student {}&quot;, studentId);
log.warn();
log.error();&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Json字串和Java Object互相作轉換&lt;/h4&gt;
&lt;h5&gt;ObjectMapper&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用途：將Json字串和Java Object互相作轉換&lt;/li&gt;
&lt;li&gt;使用條件：只要確保pom.xml中有加上spring-boot-starter-web即可&lt;/li&gt;
&lt;li&gt;用法：
&lt;ul&gt;
&lt;li&gt;writeValueAsString()：Java Object -&gt; Json字串&lt;/li&gt;
&lt;li&gt;readValue()：Json字串 -&gt; Java Object&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// 過濾掉null的key
@JsonInclude(JsonInclude.Include.NON_NULL)

// 在Json字串轉換成User的過程中，發現有一些key不存在的話，就忽略
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {

    Integer id;
    String name;

    // 將現有變數對應到名字如有不同的key上
    @JsonProperty(&quot;contact_email&quot;)
    String contactEmail;

    public String getContactEmail() {
        return contactEmail;
    }

    public void setContactEmail(String contactEmail) {
        this.contactEmail = contactEmail;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;在SpringBoot中發起Http請求 - RestTemplate&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;RestTemplate
&lt;ul&gt;
&lt;li&gt;用途：在SpringBoot中，發起一個REST風格Http請求
&lt;ul&gt;
&lt;li&gt;即是可以發起GET、POST、PUT、DELETE的Http請求&lt;/li&gt;
&lt;li&gt;並且可以將收到的reponse body中的Json字串，轉換成Java object&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用條件：只要確保pom.xml中有加上spring-boot-starter-web即可&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;@RestController
public class RestTemplateController {

    @GetMapping(&quot;/getForObject&quot;)
    public String getForObject() {

        RestTemplate restTemplate = new RestTemplate();

        Student student = restTemplate.getForObject(
                &quot;https://mocki.io/v1/7eda2b1b-d2a8-408e-a228-0cfe6bd6bec6&quot;,
                Student.class);

        System.out.println(&quot;Student 的 id 值為: &quot; + student.getId());
        System.out.println(&quot;Student 的 name 值為: &quot; + student.getName());
        System.out.println(&quot;Student 的 phone 值為: &quot; + student.getContactPhone());


        return &quot;getForObject success&quot;;
    }


    @GetMapping(&quot;/getForObjectWithParam&quot;)
    public String getForObjectWithParam() {
        RestTemplate restTemplate = new RestTemplate();

        Map&amp;lt;String, Object&gt; queryParamMap = new HashMap&amp;lt;&gt;();
        queryParamMap.put(&quot;graduate&quot;, true);

        Student student = restTemplate.getForObject(
                &quot;https://mocki.io/v1/77f69435-224e-4c90-b7cd-53f07bbf5604&quot;,
                Student.class,
                queryParamMap
        );

        return &quot;getForObject with param success&quot;;
    }


    @GetMapping(&quot;/getForEntity&quot;)
    public String getForEntity() {
        RestTemplate restTemplate = new RestTemplate();

        ResponseEntity&amp;lt;Student&gt; studentEntity = restTemplate.getForEntity(
                &quot;https://mocki.io/v1/b7a68eb2-f77c-43cf-bffe-8c647e9a2514&quot;,
                Student.class
        );

        System.out.println(&quot;http 狀態碼為: &quot; + studentEntity.getStatusCode());

        Student student = studentEntity.getBody();

        System.out.println(&quot;student 的 id 值為: &quot; + student.getId());
        System.out.println(&quot;student 的 name 值為: &quot; + student.getName());
        System.out.println(&quot;student 的 phone 值為: &quot; + student.getContactPhone());

        return &quot;getForEntity success&quot;;
    }


    @GetMapping(&quot;/postForObject&quot;)
    public String postForObject() {
        RestTemplate restTemplate = new RestTemplate();

        Student studentRequestBody = new Student();
        studentRequestBody.setName(&quot;John&quot;);

        Student result = restTemplate.postForObject(
                &quot;https://mocki.io/v1/b7a68eb2-f77c-43cf-bffe-8c647e9a2514&quot;,
                studentRequestBody,
                Student.class
        );

        return &quot;postForObject success&quot;;
    }

    @GetMapping(&quot;/postForEntity&quot;)
    public String postForEntity() {
        RestTemplate restTemplate = new RestTemplate();

        Student studentRequestBody = new Student();
        studentRequestBody.setName(&quot;John&quot;);

        ResponseEntity&amp;lt;Student&gt; responseEntity = restTemplate.postForEntity(
                &quot;https://mocki.io/v1/b7a68eb2-f77c-43cf-bffe-8c647e9a2514&quot;,
                studentRequestBody,
                Student.class
        );

        return &quot;postForEntity success&quot;;
    }


    @GetMapping(&quot;/exchange&quot;)
    public String exchange() {

        RestTemplate restTemplate = new RestTemplate();

        // 使用 exchange 發起 GET 請求
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.set(&quot;header1&quot;, &quot;123&quot;);

        HttpEntity requestEntity = new HttpEntity(requestHeaders);

        Map&amp;lt;String, Object&gt; queryParamMap = new HashMap&amp;lt;&gt;();
        queryParamMap.put(&quot;graduate&quot;, true);

        ResponseEntity&amp;lt;Student&gt; getStudentEntity = restTemplate.exchange(
                &quot;https://mocki.io/v1/b7a68eb2-f77c-43cf-bffe-8c647e9a2514&quot;,
                HttpMethod.GET,
                requestEntity,
                Student.class,
                queryParamMap
        );



        // 使用 exchange 發起 POST 請求
        HttpHeaders requestHeaders2 = new HttpHeaders();
        requestHeaders2.set(&quot;header2&quot;, &quot;456&quot;);
        requestHeaders2.setContentType(MediaType.APPLICATION_JSON);

        Student studentRequestBody = new Student();
        studentRequestBody.setName(&quot;John&quot;);

        HttpEntity requestEntity2 = new HttpEntity(studentRequestBody, requestHeaders2);

        ResponseEntity&amp;lt;Student&gt; postStudentEntity = restTemplate.exchange(
                &quot;https://mocki.io/v1/b7a68eb2-f77c-43cf-bffe-8c647e9a2514&quot;,
                HttpMethod.POST,
                requestEntity2,
                Student.class
        );

        return &quot;exchange success&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;前端模板引擎 - Thymeleaf&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用途：SpringBoot中的前端模板引擎，支援Html頁面的開發和渲染&lt;/li&gt;
&lt;li&gt;使用條件：在pom.xml中加上spring-boot-starter-thymeleaf&lt;/li&gt;
&lt;li&gt;其他常見SpringBoot前端模板引擎
&lt;ul&gt;
&lt;li&gt;JSP&lt;/li&gt;
&lt;li&gt;Freemarker&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Intellij的Debug用法&lt;/h4&gt;
&lt;h5&gt;Debug模式&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;用途：
&lt;ul&gt;
&lt;li&gt;可以新增斷點，一行一行的執行程式&lt;/li&gt;
&lt;li&gt;可以馬上知道運行中的變數的值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Intellij的實用技巧&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;第一類：一定要背的&lt;/li&gt;
&lt;li&gt;第二類：學了可以有效去提高開發效率的
&lt;ul&gt;
&lt;li&gt;刪除多餘的import：Ctrl + Option + O&lt;/li&gt;
&lt;li&gt;如何快速美化程式排版：Command + Option + L&lt;/li&gt;
&lt;li&gt;查詢某個class或是檔案在哪裡：Shift連續按兩下&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第三類：有趣的，不學也不會怎樣的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Lombok&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用途：透過添加註解去自動生成對應的程式，讓程式碼變得更簡潔&lt;/li&gt;
&lt;li&gt;使用條件：
&lt;ul&gt;
&lt;li&gt;在pom.xml中加上lombok&lt;/li&gt;
&lt;li&gt;在Intellij中安裝lombok插件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Student {

    Integer id;
    String name;
}


@Data -&gt; 包含許多方法
public class Student {

    Integer id;
    String name;
}

@RestController
public class StudentController {

    @GetMapping(&quot;/test&quot;)
    public Student test() {
        Student student = new Student();
        student.setId(1);
        student.setName(&quot;Hubert&quot;);

        return student;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[自我思考 - 為什麼老是覺得自己不夠好？]]></title><description><![CDATA[…]]></description><link>https://huberthsieh.dev/blog/post-02/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-02/</guid><category><![CDATA[Reflection]]></category><pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;我覺得我辦不到？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;過往失敗的經驗累積，影響了現在的自信&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;過去在人生中遇到一些任務時，比如工作上的任務難度較高，沒完成的很好或是出了差錯，
再比如轉換職務找工作面試，遇到筆試卻寫不出來交白卷，想約心儀的妹子吃飯結果被拒絕，很容易就會覺得自己怎麼這麼糟糕、這麼失敗。爾後再遇到類似的挑戰時，就會退縮、害怕、自我懷疑。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;逃避雖可恥但有用，但會後悔&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;遇到壓力時，想放棄跟想逃走都是很正常的，大部分人其實都是這樣，你並不孤單，
只是在這種時候一定要想想，當初為什麼會選擇這條路，也許是很喜歡這件事，也許是這工作的收入可以提升你的生活品質，可怕的下場跟後果大多都是自己想像出來的，雖然失敗或是被拒絕的確心裡會很難受，但要告訴自己，不管最後是甚麼結果，只要有結果就是好事，勇敢走下去就對了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如何阻止自己逃避&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;去大吃大喝或者好好出去旅遊一趟，這世界很大你的工作只是滄海一粟。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;客觀去分析你自身遇到的問題，比如硬實力不足就多花一點時間學習，而不是總是在追劇。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;對自己要有信心，不要害怕失敗。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;培養自信，建立自己的知識映射&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;知識映射這名字是我亂取的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;明明工作上能夠應用，但每次面試考題都不知道怎麼描述，這代表你知道這件事情怎麼處理，但沒辦法靠反射動作就處理好，就像打籃球三步上籃，不熟練的人老是同手同腳，
常常不小心多走一步就變成走步，不然就是力道過猛或不足而沒投進，畢竟人家傳球給你，要你上籃的目的就是投進並且得分，結果你沒做到，旁邊的人想當然就認為你，就是&quot;不會上籃&quot;。&lt;/p&gt;
&lt;p&gt;那回到工程師的職場上，很多時候我們的確會應用一些技術，不論是查看文件或是Google都有相當多的範例可以參考，如果遇到一時半會查不到的問題，現在還有ChatGPT的幫助，一下子就能解決，但深層的原理如果沒搞懂的話，下次有人問起，要描述或是解說的話多半又講不出來。
畢竟會用跟真的懂可能是用來判斷你到底屬不屬於資深工程師的依據。&lt;strong&gt;(來自筆者面試常常交白卷的反省)&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;拆解問題或任務的三大步驟&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;遇到超出自己能力範圍的任務或問題時，先不要太慌張&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;工作上其實很常遇到突然要使用沒用過的新技術，或者換新工作時通常每間公司的開發方式跟慣用技術都不同，遇到超出自己能力範圍的任務或問題，先不要慌張就急著到處問人，
先試著把問題或任務拆解成小步驟，然後照著先能動再優化的想法去執行，不要一口氣就要做到最美好的情況，內心壓力也會小一點，也能在逐步完成的過程中，獲得成就感。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Make it work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make it right.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make it fast.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title><![CDATA[前端知識 - Cookie、SessionStorage、LocalStorage。]]></title><description><![CDATA[瀏覽器提供本地端儲存資料的方式有三種 Cookie LocalStorage SessionStorage Cookie 可設定失效時間，沒設定的話預設是瀏覽器關閉後失效。 第一次用戶端瀏覽器在發送請求後，伺服器端會在回應的標頭中，添加一個 Set-Cookie 的選項並將 cookie 放入到回應中，送回用戶瀏覽器端後，會儲存在用戶端本地。此外 cookie 會在用戶瀏覽器下一次發送請求時，一同被攜帶並發送回伺服器上。 大小約 4kb 每次請求時都會在 header 帶上 LocalStorage以及SessionStorage SessionStorage…]]></description><link>https://huberthsieh.dev/blog/post-01/</link><guid isPermaLink="false">https://huberthsieh.dev/blog/post-01/</guid><category><![CDATA[JavaScript]]></category><pubDate>Mon, 15 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;瀏覽器提供本地端儲存資料的方式有三種&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Cookie&lt;/li&gt;
&lt;li&gt;LocalStorage&lt;/li&gt;
&lt;li&gt;SessionStorage&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cookie&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;可設定失效時間，沒設定的話預設是瀏覽器關閉後失效。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;第一次用戶端瀏覽器在發送請求後，伺服器端會在回應的標頭中，添加一個 Set-Cookie 的選項並將 cookie 放入到回應中，送回用戶瀏覽器端後，會儲存在用戶端本地。此外 cookie 會在用戶瀏覽器下一次發送請求時，一同被攜帶並發送回伺服器上。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大小約 4kb&lt;/li&gt;
&lt;li&gt;每次請求時都會在 header 帶上&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Domain - 指定網域可以使用，預設是當前網域但不包含子網域。

Path - 作用範圍，指定路徑可以使用這個 cookie，預設是都可存取。

Expires - 有效期限，該 cookie 過期的時間，值為Date.toUTCString() 格式的時間字串。

Max-Age - 有效期限，該 cookie 可以留存的時間，單位為秒。如果同時設定 Max-Age 以及 Expires，則 Max-Age 優先。

Secure - 安全性，是否只能在 https 下傳遞，格式為 boolean 值。

HttpOnly - 安全性，此 cookie 只能從 web server 訪問，以避免不正確的進入來取得竄改。&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;LocalStorage以及SessionStorage&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;SessionStorage : 關閉頁面或是關閉瀏覽器後失效。&lt;br&gt;
LocalStorage : 基本上永久保存，除非手動去清除。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;LocalStorage以及SessionStorage是HTML5提供兩種在客戶端儲存資料的方法，彌補了cookie儲存量小、不適用於大量資料本地儲存的問題。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大小約 5mb (依各瀏覽器會有些許不同)&lt;/li&gt;
&lt;li&gt;都藉由key-value的方式去儲存資料&lt;/li&gt;
&lt;li&gt;僅能儲存字串的資料型態，可以將陣列或物件透過 JSON.stringify 轉JSON字串儲存， JSON.parse 將JSON字串轉回原本物件取出。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;javascript&quot;&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// localStorage 存入數據&lt;/span&gt;
localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;keyName&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// localStorage 讀取數據&lt;/span&gt;
localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;keyName&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// sessionStorage 存入數據&lt;/span&gt;
sessionStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;keyName&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// sessionStorage 讀取數據&lt;/span&gt;
sessionStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;keyName&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 刪除指定數據&lt;/span&gt;
localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;keyName&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
sessionStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;keyName&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 刪除全部數據&lt;/span&gt;
localStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
sessionStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;常見應用&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;工程師在開發網站時，在瀏覽器儲存資料的應用滿廣泛的，最常見的當然就是會員登入狀態，購物車資料暫存或者分步驟表單驗證等等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cookie 比較常用的場景就是判斷用戶是否登錄，針對登錄過的用戶，服務器端會在他登錄時，往 Cookie 插入一段加密過的辨識碼，下次只要讀取這個值就可以判斷用戶是否登錄。&lt;/p&gt;
&lt;p&gt;SessionStorage 如果遇到一些內容特別多的表單，為了提升用戶體驗，可能要把表單頁面拆分成多個子頁面，然後按照步驟引導用戶。SessionStorage 這時候就可以發揮出效用。&lt;/p&gt;
&lt;p&gt;LocalStorage 除了可以用來代替 Cookie 做購物車的資料保存，也能做會員登入狀態的持久化。或者在電商網站、遊戲通常會產生一些使用者數據，這時就很適合用 LocalStorage 來保存這些數據。&lt;/p&gt;</content:encoded></item></channel></rss>