許多人在實作 CMS(Content Management System)時,對於儲存文章內部的圖片時,常常很直觀的透過 JavaScript 在前端將其轉成 base64 編碼之後,與文章本文一起變成一巨大字串傳至後端後直接儲存在資料庫內,這樣的會嚴重的造成效能上的問題,首先是無法進行 lazy loading,其次是會使得 api request 封包非常巨大,需要花非常多時間下載,造成使用者等待時間較長,在這樣的狀況下,若是只能調校前端使其效能能夠提升上去,我們可以從壓縮圖片著手,將圖片壓縮之後,api request 容量減少,可以有效的提升下載效率與畫面渲染時間,提升使用者體驗。
以下是一個簡單透過 canvas 來進行圖片壓縮的方法。
首先,加入一個我們工作的 canvas tag,這個 canvas 可見與不可見(display: none
)皆行,視情形調整是否需要顯示給使用者檢視,並加上一個需要使用者上傳的 input tag,我們會需要一個 image tag 用來暫存使用者上傳的檔案,一樣,這個 image tag 也可以設定為使用者不可見。
<img src="" class="before" style="display: none"/>
<input type="file" name="upload-image" id="upload-image" required />
<canvas style="display: none"></canvas>
我們需要針對這個 input tag 進行事件的設定,當有檔案被上傳之後,我們會將其用 Base64 的方式寫入 canvas 裡面,並將其壓縮成其他不同的格式,下面我們示範轉換成 WebP 格式,你可以視需求轉換成你所需要的格式。
document.querySelector("input[name=upload-image]")
.addEventListener("change", (event) => ReadAndCompress(event));
注意到我們上面呼叫了另外一個 function ReadAndCompress
並將 event 傳入,我們在另外一個地方定義了此 function,透過 FileReader
這個內建的檔案讀取 class
將檔案讀出之後,將並將其寫入 canvas 內,並將整個 canvas 的內容透過 canvas 的內容轉成我們所想要的檔案格式,這邊我們轉成 WebP。
const ReadAndCompress = (e) => {
console.log(`Before Compression: ${(e.target.files[0].size/(1000*1024)).toFixed(2)} MB`);
首先透過 e.target.files[0].size
可以取得檔案的大小(注意單位為 K),我們先把它印出來作為壓縮前的大小對照。
const reader = new FileReader();
reader.readAsDataURL(e.target.files[0])
新增一個 FileReader 並指定檔案以 Base64 的方式讀取。
reader.onload = event => {
const img = document.querySelector("img.before");
img.src = event.target.result;
img.onload = () => {
const width = img.width;
const height = img.height;
const elem = document.querySelector('canvas');
elem.width = width;
elem.height = height;
const ctx = elem.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
const webp = ctx.canvas.toDataURL("image/webp", 0.5);
接著我們新增一個 onload 事件,並在確定 loading 結束之後,將圖片寫進 image tag 裡面,這時候,圖片會以剛剛提到的 Base64 格式寫入,並在這個圖片的 onload 事件被觸發,也就是已經確定載入完成之後,我們可以將這張圖片寫入 canvas 來進行壓縮,為了確保壓縮的圖片大小仍舊與原本圖片相同,我們 canvas tag 本身不設定寬高,寬高在圖片載入後設定上去,我們可以使用 drawImage
函式將 Base64 圖片直接寫入 canvas 裡面,之後,我們就能透過 ctx.canvas.toDataURL
函式將 canvas 的內容轉成 Base64 輸出,toDataURL
的格式如下: toDataURL(type, option)
,一般 option
即是輸出品質,在 MDN 上有提到預設 type 為 png ,若是選定的格式在這個瀏覽器上不支援的話,輸出則會自動變為 png,以上就是一個簡單的利用前端來壓縮圖片的小技巧,下面是整個範例 code,提供參考。
See the Pen Frontend Image Compress by dylandy.chang (@dylandy) on CodePen.
<!doctype html>
<html>
<head>
</head>
<body>
<input type="file" name="upload-image" id="upload-image" required />
<p name="before-compression"></p>
<p name="after-compression"></p>
<img src="" class="before" style="display:none;"/>
<canvas style="display: none;"></canvas>
<img src="" class="after" style="display:none;"/>
<script src="./index.js"></script>
</body>
</html>
const ReadAndCompress = (e) => {
const size = `Before Compression: ${(e.target.files[0].size/(1000*1024)).toFixed(2)} MB`;
document.querySelector("p[name=before-compression]").innerHTML = size;
const reader = new FileReader();
reader.readAsDataURL(e.target.files[0]);
reader.onload = event => {
const img = document.querySelector("img.before");
img.src = event.target.result;
//img.style = "display: true";
img.onload = () => {
const width = img.width;
const height = img.height;
const elem = document.querySelector('canvas');
elem.width = width;
elem.height = height;
const ctx = elem.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
const webp = ctx.canvas.toDataURL("image/webp", 0.5);
const imgAfter = document.querySelector("img.after");
imgAfter.src = webp;
//imgAfter.style = "display: true";
const head = 'data:image/webp;base64,';
const imgFileSize = (Math.round((webp.length - head.length)*3/4) / (1000)).toFixed(2);
document.querySelector("p[name=after-compression]").innerHTML =
`After Compression: ${imgFileSize} KB`;
},
reader.onerror = error => console.error(error);
}
}
document.querySelector("input[name=upload-image]")
.addEventListener("change", (event) => ReadAndCompress(event));
Ruby 2.7 的新功能列表
根據 Matz 的說法,本次的版本為最後的 2.x 版本,下個版本將會直接跳到從 2016 開始大家就在引頸期盼的 Ruby 3.0!
下面是 Matz 在 RubyConf 2019 上針對 Ruby 2.7 的說明。
下面,我們開始逐一說明本次 Ruby 2.7 的新功能。
新版 irb 支援了多行編輯的功能,只要按 alt-enter 即可換行繼續編輯,我們可以從上面的影片知道,按方向鍵往上可以將整個 block 重新叫回來編輯,另外,按一次 tab 會提示可用的關鍵字,連續按多次 tab 可以進入文件模式,在互動模式下快速查找 rdoc,之後開發者可以在不用離開互動模式的狀況下進行文件的查詢,也可以鼓勵開發者在開發程式的時候編寫 rdoc 。
這並不完全算是一個新功能,這是一個簡寫,在 Ruby 2.6 引進一個兩個新功能:union
與 difference
,在 Ruby 2.7 中更進一步將其簡化了,使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
回傳一個 Hash ,其中為給定之 Array 中內容物與其個數。
1
|
|
會回傳
1
|
|
filter_map
method 想要將 select
與 map
結合,從字面上,我們可以知道,這個 method 會回傳一個經過條件選擇的新 Array。
在過去我們如果要做到相同的效果的話,一般都需要使用 chain method 的方式來達到:
1
|
|
在 Ruby 2.7 中我們可以這樣做
1
|
|
這個功能需要一點點想想力才有辦法理解,讓我們先來看一下此功能的 proposal:
“This method produces an infinite sequence where each next element is calculated by applying the block to the previous element.”
沒錯,這個 method 理論上會回傳一個無限長的 Array,讓我看看下面的範例:
1 2 |
|
這個 method 長這樣的:Enumerator.produce(initial, &block)
,預設上 initial 即為 1 ,我們亦可不傳入值,注意到我們後面的 block 為需要產生這個 Enumerable 的條件,而最後不管我們有沒有給定 take(5)
在記憶體裡面都是一個無限長的大小,只是最後回傳的時候給出最前面五筆,這邊需要特別注意使用,可能在大型產品上會有記憶體管理方面的問題產生。
Ruby 從 Scala, Closure, Groovy 借鏡了 Block 的預設參數名稱,規則很簡單:第一個參數的預設名稱即為 _1
、第二個參數即為 _2
⋯以此類推,下面是一個簡單的範例:
1 2 3 |
|
你仍舊可以在一般的區域變數使用「底線加上數字」做為變數名稱,但是 compiler 會跳出警告,告訴你這樣是不好的行為,請盡量不要這樣做。
在演講中,Matz 提到,像是 Pattern Matching 這樣的 Functional Programming 行為,對於 Ruby 來說需要一個全局的重新思考,或許目前的行為在之後會與目前通通不同。在那之前,讓我們看看目前 Ruby 2.7 所提供的 Pattern Matching 使用方法。
Pattern Matching 會回傳一個布林值,當 Pattern 相同時,則會回傳 true 反之亦然。下面是幾個簡單的範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
在 Ruby 2.7 中引入了記憶體壓縮垃圾蒐集演算法,意即將碎片化的記憶體重組的垃圾蒐集演算法,在一些多執行緒程式執行的過程中,可能會造成記憶體使用上的碎片化,長久下來會造成高記憶體使用與效能的下降。
新的 GC method GC.compact
期望能夠將 heap 的碎片化降到最低。
詳細內容請見原 proposal
這是 Ruby 2.7 與 Ruby 2.6 以前最重要的不同!
未來 Ruby 3 之後將不向下相容 Ruby 2.6 的 Keyword Argument
Ruby 2.6 以前的 keyword argument 行為如下
1 2 3 4 5 6 7 8 9 |
|
這真的是非常地令人崩潰的行為,上面的 function 呼叫行為應該是以下這幾種問題的綜合:
但是 Ruby VM 卻沒有吐出錯誤訊息,所以在 Ruby 2.7 開始,這樣的行為將會被拋棄,在 Ruby 2.7 時只會吐出警告訊息,但是在 Ruby 3 之後就不能執行了,所以 Library 的開發者,如果有使用到這樣的行為,請儘速更新您的軟體,以符合未來的 Ruby 版本。
GC.compact
), https://bugs.ruby-lang.org/issues/15626相信很多人在剛開始接觸 JavaScript 的時候,對於異步行為是非常陌生與困惑的,到底為什麼一個號稱「單執行緒」的程式與語言,能夠以不卡住主執行緒的方式來執行,對於其他程式語言的使用者來說,這是非常不合理的行為,到底 JavaScript 的底層是怎麼處理這樣的行為呢?
首先,讓我們先看一下下面這段 code。
1 2 3 |
|
讓我們來作個小測驗吧,以上的程式碼的執行結果如何呢?
=> before time out
=> hello
=> after timeout
=> before time out
=> after time out
=> hello
你答對了嗎?如果是對於 JavaScript 不太熟的朋友,這邊幫你解釋一下上面的程式片段。console.log
是 JavaScript 中內建的將文字印在終端機上的功能,若是在瀏覽器上,則會將文字印在 debug 工具裡,而 setTimeout
則是在 JavaScript 中的一個內建 timer ,它可以設定一個定時器(以 ms 為單位),並在時間結束之後執行所傳入的 lambda function,因此,上面的範例片段中的 setTimeout
片段 setTimeout(() => console.log("hello"), 0)
意味著「在 0ms 之後,至終端機上印出 “hello” 字串」
對於從其他同步行為語言出身的人來說,直覺的會想說是第一個選項是很正常的,像是 JavaScript 這樣的執行行為,在前面跟我們說了這是「單執行緒」的程式語言來說,會覺得非常奇怪,因為同步行為語言來說,第二個選項的行為幾乎都是建立在多執行緒的條件下才能完成。嚴格來說,這樣的想法,也沒有錯啦,只是這個多執行緒發生的地點有所不同,下面就讓我試著解釋看看了。
JavaScript 是個 JIT 的程式語言,程式語言在執行的時候會先編譯過後,將原始碼轉譯成執行環境可以執行的中介碼,並由執行環境所執行的行為,而這個執行環境一般我們稱為程式虛擬機器。在虛擬機器裡面,程式語言的開發者可以透過許多不同的機制來達到他們所想要程式語言特性,可以是透過最佳化演算法來提昇效能,也可以像是 JavaScript 一樣使用 Event Loop 來提供非同步的行為。
下一集我們將會開始討論 Event Loop 的詳細執行情形,還請敬請期待。
]]>湯頭帶著鴨骨自然的鮮味,卻不會有鴨肉的腥騷味,我們知道,不論是什麼樣的動物騷味皆是來自氧化的脂肪組織,因此越新鮮的處理,味道越甜美。米粉在剛上桌前因為還沒吸飽湯汁,略顯平淡,這時可以吃吃看他們的小菜,用煙燻的方式處理的小菜非常令人驚艷,尤其推薦海帶與百葉豆腐,薄薄的煙燻味與濃厚的滷汁調和,使得口感更顯精緻,但另一方面,或許是煙燻的時間不易控制,雞腿幾乎是絲狀的質地呈現,失去了雞腿應有的多汁與細膩,實在非常可惜。
推薦給你,恆春夥計鴨肉冬粉。
1 2 3 4 5 6 7 8 9 |
|
在 Linux kernel 2.6 以後推出了一個稱為 inotify 的子系統 (sub-system),他延伸了系統對於檔案系統 (filesystem) 的掌控能力,並提供 API 讓使用者的程式能夠了解到檔案系統的變化情形,因此我們若是想要監控使用者對於某個資料夾的變化情形,我們可以用各種 inotify 的 client 端 library 來進行設計,下面是一個使用 Ruby inotify wrapper - rb-inotify 寫的小程式,我們可以看到,我們可以知道使用者這個動作是什麼樣的動作,被做動的檔名與路徑,因此,我們就可以對這些檔案進行操作。
1 2 3 4 5 6 7 8 |
|
因為 inotify 是 Linux Kernel 的子系統,使用 inotify 進行系統的監控不只是節省系統效能,也能將重要的 CPU 時間還給需要的程式使用,亦能減少硬碟 IO 頻寬,因此對於有監控資料夾內容的需求者,這會是一個不錯的選擇,一點小分享,提供給大家參考。
https://en.wikipedia.org/wiki/Inotify
https://github.com/guard/rb-inotify
本文章主要知識來源於 Daemon Processes in Ruby 的教導,希望能夠用較易理解的文字呈現出來。在寫 Daemon 之前,讓我們回頭看看一個知名的 daemonize web server 程式 rack。我們來看看他將目標 web server daemon 化的程式碼片段,讓我們透過這個程式碼片段來看看是否能夠從中學到如何 daemon 一支程式。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
我們可以從 else 片段開始看一下,在 ruby 1.9 以後提供了一個 Process.daemon
的 method 來 daemon 化目前的程式,或許有些人會覺得,「嘖!又是一個 Ruby的黑魔法,什麼功能都要包裝在抽象的黑盒子裡面。」,如果你有興趣了解 Process daemon
是如何實作的話,這邊是 Ruby MRI 的 C code 可以研究一下,在跳下去研究了一番之後,你會發現其實這段 C code 做的事情跟上面這個 if 片段做的事情本質上是一模一樣的。
ok 那我們來逐一了解一下這個程式碼片段到底做了些什麼。
1
|
|
這行 code 巧妙的運用了 fork 的特性:他會 return 兩次,一次是在父程式 (parent process) 回應子程式 (child process) 的 pid,而在子程式的部分回應 nil,因此我們可以知道在父程式的時候 fork 會是 true 而子程式會是 false ,因此父程式會被關閉,而子程式會被保留,變成孤兒程式,那麼問題來了,一個孤兒程式,既然他的父程式已經被砍掉了,那他的 ppid (parent process id) 會是多少呢?答案是 1 ,所有的孤兒程式都會被系統核心接管,而因為被系統核心接管著,我們可以保證他的生命週期是與系統核心綁在一起了,不會因為其他的程式因素而造成此程式無故的終止。
這個步驟可以讓 terminal 認為程式已經被終止了,父程式是使用者執行的程式,子程式為變成孤兒的準 daemon ,因此控制權從程式身上交還給使用者,然後執行到下面這一行:
1
|
|
執行 Process.setsid
會進行以下三件事情:
為了了解上面三件事情,我們需要從 Linux 系統更深的方向理解起。
Process groups 和 session groups 皆是用於行程控制的,在這邊「行程控制」意味著終端機控制程式的方法。 我們從 process groups 開始討論起吧。
每個程式皆屬於一個程式群組,而這個程式群組皆是由一群互相有關聯的程式所組成的,我們稱為父程式與子程式,子程式的生命週期會與父程式的生命週期互相連動,但父程式卻不受制於子程式的生命週期。一般來說,系統會指定一個隨機的 id 給這個程式群組,但是我們也可以透過下面的這個指令來指定群組的 id。
1
|
|
如果我們將下面的這段程式輸入至 irb
我們可以發現他們的值是相同的,一般來說,Process group 的 id 會和這個 process group 的最主要的父程式的 pid 相同。打個比方,如果我們在終端機下執行了 irb
,那麼 irb
的 process group id 應該會與終端機的 pid 相同,因為 irb
是屬於終端機的子程式。
1 2 |
|
如果我們看下面這段程式,雖然子程式有自己的 pid ,但是因為子程式與父程式皆屬於相同的 process group ,所以他們的 process group id 是相同的。
1 2 3 4 5 6 |
|
從這邊我們可以往回看到前面所說的孤兒程式。我們透過將一個程式的父程式從終端機(因為我們從終端機中將其開啟),設定為系統(pid 為 1),這樣,在終端機關閉的狀況下,孤兒程式仍舊會繼續執行,不會受到終端機的生命週期所影響他的存在。
然後是 session group。
Session group 是更高一層的抽象概念,每個 session group 中會含有非常多個 process group。一個使用者登入至系統後,系統會新增一個 session 給此使用者,這個使用者在這個 session 的過程中所產生的程式皆會屬於這個 session,因此一個 session group 中會含有許多不同的 process group。我們看看以下這個指令。
1
|
|
這些指令都各自為不同的 process group 但是,因為他們是由不同的 process 所產生的 child process,但是一個簡單的 Ctrl-C 就可以將他們通通關閉,這是因為他們皆屬於同一個 session group,當我們開啟一個新的 shell 的時候,這個動作將會啟動一個新的 session group,亦即,在這個 shell 裡面所做的行為都屬於同一個 session group,而對於大部分的程式而言 session group 會與終端機連通,但,有些卻不會,那就是 daemon。
你的終端機在管理 session group 上面使用了一個蠻有趣的方法:將指令傳至 session leader,而 session leader 會將此指令廣播至此 session 中的所有 proccess group,當需要被執行的程式收到此指令,程式即執行此指令。
在 Linux 上有一個 system call 可以取得目前的 session group id - getsid(2)
,但是在 Ruby 2.0 前的核心 library 裡面沒有實作這個介面,如果你真的想要對 session group id 進行管理,可以用 Process.setsid
來產生一個新的 session group 並將其 id 存下來,留著往後使用。
讓我們回到前述的 Rack 範例,第一行是新增一個子程式並關閉父程式,終端機發現其與父程式間的連結斷裂了,所以將控制權還給使用者,但此時被新增的子程式仍舊與繼承著父程式的 process groud id 與 session id,因此,在此時這個子程式並不是這些 process group 與 session group 的 leader,因此,雖然終端機將控制權交還給使用者,我們執行在背景的子程式仍舊與目前的終端機有著一絲一縷的關聯,如果此時終端機所在的 session 被中斷了,或是有人傳訊息給終端機要求關閉我們可憐的子程式,這個子程式也又只能被迫關閉,因此,我們希望完全與終端機斬斷連結,才能完整地成為一個 daemon。
透過執行 Process.setsid
,我們能夠將目前的子程式設定為一個新的 process group 與 session group 的 leader,但是需要特別注意的是:如果這個指令被執行的對象目前已經是 process group 的 leader 就會失敗,因此,要確定對象是子程式才能被正確執行,同時,由於我們產生了一個新的 session group,理論上它應該要被指定一個終端機來給使用者互動,雖然在這邊顯然的並沒有,但為了避免不必要的錯誤,我們仍舊將其離開,以確保其完全的與終端機分離,除了系統,其他人不能隨便將其關閉,換句話說,它現在自由了,沒有人可以管它了。
1
|
|
之後,Rack 將目前的工作目錄切換至系統根目錄,這步驟並不是必要的,但是就如同上面的將終端機關閉一樣,這是一個保險,避免程式因為工作目錄消失而被關閉的窘境發生。
1
|
|
在避免了因為工作目錄消失而被關閉的情形,我們需要考慮另外一個問題 – 輸出,不論是正確的 stdout 或是錯誤的輸出 stderr 都需要被忽略,因此我們將這些串流資訊導引至 /dev/null
,你可能會問說,奇怪,那我們怎麼不能直接將它的輸出直接關閉?因為我們不曉得這個程式是否需要存在著標準輸入輸出而能夠正常執行,說不定哪天,因為我們將它關閉而造成後續的執行問題是不容易找到的,因此將其轉到 /dev/null
其他軟體需要這些資訊的時候,可以自由導出,不會受到影響。
1 2 3 |
|
以上,就是從 Ruby 的角度來看如何將一個程式 daemon 化的簡單概念。 希望能夠幫助到有需要的人。
本來想要在吃完的時候就來寫這篇食記了,在台灣吃過這麼多家拉麵店,麵屋田宗是第一家讓我感受到那種對拉麵的熱愛,那種狂熱與堅持,再再都是那麼的觸動我心。經過幾個禮拜的沈澱之後,我決定試著把我對於麵屋田宗的一些感想用我拙劣的文字描繪出來。
因為求學的關係,之前在台北生活了七年的時間,身為一個台中人,在網路上看到任何有關於台中的消息總是會特別的關注,說來慚愧,因為在台北待的太久的關係,對於台中餐廳的認識並不比許多台北人來的高明,與田宗的邂逅也是在網路上看到一名廚師網友的大力推薦才初次耳聞,文中的那種英雄惜英雄讓我特別有帶入感,也讓我對於造訪田宗的旅程更是期待。
或許是我來到店家的時間有點晚了,到達餐廳時已經臨界中午打烊,上一組客人帶著滿足的笑容與我擦身而過。推開木製的大門,醬油與味淋的清香撲面而來,或許是生活習慣的關係,對於醬油與味淋的味道感到特別的令人安心,有別於其他拉麵店總是會播放各種日本音樂,店內飄蕩著輕柔和緩的爵士樂,顯示出了店主心中小小的反動。
忘記在哪裡看到的拉麵師傅這麼要求自己的拉麵的:「一碗好吃的拉麵不能是赤裸的,麵是麵、湯是湯,這樣不行。」簡單的一句話包含很多經驗在裡頭,就如湯與麵條的選擇,同時也是麵條硬度的選擇;我的湯頭是厚重系的還是清淡系的?一般來說厚重系的湯頭需要搭配較粗、較捲也較硬的麵條來帶起湯頭,反之清淡、清雅的湯頭需要搭配較為細直硬度 Al Dente[1] 的麵條為佳。
這次點的是招牌豚骨拉麵,看著店主將盛好醬汁的麵碗放至麵鍋上預熱,富有節奏感的攪拌醬汁,像跳舞般地同時照顧叉燒地燒炙,各種細節皆面面俱到做的無微不至,那種在時間與節奏的搭配上的和諧感,在上菜前已是一場視覺與嗅覺的享受。
剛上手時,湯頭的清香溢滿整個鼻腔,淡雅中帶著豚骨的積極氣息,用湯匙輕帶一下液面,與常見的各家豚骨湯頭的濃稠不同,輕柔的觸感非常特別,記憶中的博多系豚骨湯頭是一種用大火長時間滾出來的濃厚,帶著強烈的豬骨味與豬膠質黏稠與醇厚的表現[2],在吃的較為清淡的東京豚骨湯頭自然地朝向另一方向發展,加入了魚干(煮干)與柴魚等海鮮元素,使得豬味下降,更帶入一些鮮甜味道進入其中,保留了豚骨的油脂香,但是透過海鮮湯頭將其味道中和,使得整體味道更加調和,在品嚐的過程中較不會因為油膩感而產生不適[3]。田宗的湯頭亦選用了豚骨魚介的類別,但是在傳統的豚骨魚介上進行了巧妙的調整,與其他豚骨魚介店家相比,田宗的湯頭在調味上下了很大的一番功夫,將豬骨熬煮後會產生的腥臊味掩去,並提升豬肉本身的香氣,又可以吃到魚介的鮮甜,那種猶如在海邊騎著一頭小豬般暢快的感覺是怎麼回事呀。
田宗的麵條選擇在第一時間讓人覺得蠻有趣的,在較不濃厚的湯頭下,選擇了像是烏龍麵般較粗的麵條;當開始吃麵的時候那種平衡感體現出來時,才能知道店主的用心,店主習慣將麵條煮得相對 Al Dente 更軟一些,對於吃習慣二郎系拉麵的鐵麵朋友可能會有點不適應,若是到訪的話,請務必記得跟老闆提醒不要太軟,而這樣較軟的前提下,粗麵較能帶起湯汁的特性,讓湯與麵結合的相當完美,原本擔心的赤裸麵條並沒有發生。
常常在吃拉麵的時候,因為油膩感與過鹹的調味,麵條吃到一半時,煩悶與膩味感層層湧現,這時,我會將專注力從湯頭與麵條轉移到配菜上,在化解油膩與平淡的方法中,最常見的調味法是透過辛香料的添加,將鼻腔與口腔中的味道完全洗掉,讓我們有種清爽感,例如:生洋蔥絲;另一種方法則是使用酸性調味,將油脂的黏膩感洗去,就如德國豬腳搭配的酸菜般,也如日本拉麵中的筍乾的存在。或許是害怕筍乾的味道搶走湯頭的風采,許多的拉麵店特別將筍乾洗了又洗,使得其味道變得相當清淡,只保留了爽脆的口感,但卻讓筍乾的酸味失去,同時也失去了那種解膩的功能。在吃第一口時候,田宗的筍乾口味是懷念的,或許是因為爺爺家在竹山的關係,對於台灣的筍乾口味非常熟悉,從小就聽家中長輩說竹山產的筍乾大部分都是外銷到日本做拉麵,這種懷念感就像是把我帶回到竹山家的餐桌上那般懷念。和店主聊天的過程中店主說這是嘉義的筍乾,因為台灣的筍乾品質比日本產的好很多,所以選擇使用台灣本產的筍乾。
店主是一個對於味覺平衡非常厲害的廚師,一碗湯麵中,可以看到許多為了味覺平衡做作的努力,在西方的廚藝學校中流行著這麼一句話:「如果鹽是讓你的食物增色,那麼酸能使得你的食物驚艷,真正出色的關鍵。」,知名廚藝 YouTuber - Brothers Green Eats也說過類似的話, If the salt is bringing out and enhancing the flavour of the food, the acidity is just going to make it brighter and sing to the heaven.[4]在拉麵湯中的小彩蛋是一絲一絲的柚子皮,微酸的氣息與淡淡的柚子清香,柚子皮的淡淡苦澀口感,讓湯頭的黏膩與鹹澀得到紓緩,確實是相當聰明的方法,在此前,我只有在京都的豬一拉麵有吃過柚子皮入湯的方法,但是當時在豬一時柚子並不是店主的推薦吃法,反而是若我們有些膩味可以添加的選配,在田宗店主將柚子皮與湯的結合非常引人入勝,是一大佳作呢。
「每家拉麵店都有他追尋的味道。」店主這麼說著眼神中閃耀出專注的光芒,像是在闡述也像是在與自己對話,猶如問道於麵般的執著,店主在拉麵的腳步上,已經超出許多其他店家許多了。
推薦給您,麵屋田宗。
[1] Al dente
[2] 博多ラーメン
[3] 豚骨拉麵的起源,發展流行之軌跡
[4] 15 Mistakes Most Beginner Cooks Make
現在已經是資訊化的時代了,過去我們苦心積慮的想要讓孩子們學好算數,可以用紙筆進行高難度的運算,但是這些運算,在電腦普及、智慧型手機人手一支的現代,或許值得我們重新思考,我們學習數學運算的目的是什麼呢?這些能力能夠讓我們在數學成就上面得到什麼發展嗎?是不是這些能力讓我們在過往的學習路途上扼殺了許多可能的數學天才呢?我們是不是又因為一個人有著不錯的數學計算能力,就理所當然的認為他有數學天份呢?這些問題應該是進入了資訊化社會以後的我們,不論是不是老師,我想我們都應該問自己這樣的問題。我們能不能改變什麼,透過這些我們已經身在其中的機器,幫助我們運算,是不是能夠幫助我們學習數學的過程中,能夠學習得更透撤,更有系統呢?
在影片中, Conrad Wolfram 示範了一個多邊形的範例,這個範例可以讓年紀很小的小朋友都可以很容易了解極限的概念。我試著把這個小玩具製作出來,透過這個小玩具不僅可以讓自己瞭解 coffeescript 的撰寫,也希望自己可以為台灣的數學教育做出一點貢獻,身為一個有能力可以編寫程式來為我們的下一代貢獻的人,我覺得沒有什麼比這更有意義的事了。
專案頁面:
https://github.com/dylandy/polygenJS
這是一個 jQuery plugin ,你只要把 polygen.js 引入到你的頁面中,你就可以輕易的帶入你想要的參數畫出你想要的圖形。
]]>這邊建議使用 2048 bit 以上的安全加密,目前現行的商用網站加密層級至少都達到了 2048 bit ,因此,為了安全性考量,4096 bit 是很有必要的。
使用 Mac 的朋友如果要使用 ssh-copy-id 的話,需要另外安裝,這個程式沒有包含在 ssh 裡面,可以透過 brew 來安裝。
設定完成以後,請記得將 ssh 服務重開。
這樣往後就可以透過 public key 來登入了,也省下每次登入都要打一大串密碼的困擾了。
PS: 可以透過在 .ssh 資料夾下新增一個 config 檔案,將常用的登入設定寫進去,也可免去每次登入都要輸入一大堆 ip 和 domain 的麻煩,下面是一個簡單的範例。
]]>前前後後寫 Ruby 也寫了一陣子,用習慣了別人寫的套件、別人寫的 framework 以後,慢慢的也對於各種套件撰寫的原理有了些好奇,開始想要自己編寫一些屬於自己的套件和 framework 來了解一些背後的原理與方法,從過年前到最近,花了一點時間來自己摸索怎麼用 Ruby 來寫一個 web framework ,在過程中學到了許多東西,慢慢的從很多方向來思考 framework 需要什麼。 在這篇裡面我會先將各個模組說明,之後會一個一個來討論。
從以前就不喜歡看一堆的 document 來學東西,我喜歡從自己的發現中學習,高見龍前輩曾經在日本 RubyKaigi 上發表「Code Reading, Learning More about Ruby by Reading Ruby Source Code」,因此我開始觀察不同的 web framework 試試看能不能有什麼發現。
首先呢,先查了一些資料,意外的發現網路上沒有太多關於 config.ru 單獨的說明,但是可以大致上了解, config.ru 是一個 Rack 的設定檔與執行檔,那什麼是 Rack 呢?
Rack is a nice Ruby-fied replacement for CGI.
Rack sits between all the frameworks (Rails, Sinatra, Rulers) and all the app servers (thin, unicorn, Rainbows, mongrel) as an adapter.
Rack is a convenient way to build your Ruby app out of a bunch of thin layers, like a stack of pancakes. The layers are called middleware. Or pancakes, why not?
簡單的說,Rack 就是一個用 Ruby 做的 CGI ,它在應用程式和伺服器之間做資料的轉換和處理,可以讓我們用更邏輯的方式來撰寫程式,對於 Ruby framework 的抽象化有很大的幫助,而 config.ru 就是 Rack 的執行點,也可以稱為設定檔,而 config.ru 是用純 Ruby 的語法所寫成,理論上,我們可以把 framework 的所有的東西都寫在 config.ru 裡面,就能執行了。但是,我們不要,也不應該這麼做,對於 Ruby 的程式撰寫,其中最重要的一個目的在於「語意化」,用以提升程式可再利用性、可維護性、提升開發速度,為了維持語意化的目的,使用多檔案的架構勢必是不可少的,那,如何區分資料夾呢?
以前寫過的各種 MVC 框架的結構感覺上都大同小異,將 Modal , Controller , View 分開來放,將設定檔放在 config 資料夾內,可以讓設定檔和程式的邏輯分開,將 Modal , Controller , View 分開,可以在設計上更加方便,至於什麼是 MVC 架構呢?
Model–view–controller (MVC) is a software architectural pattern for implementing user interfaces on computers. It divides a given software application into three interconnected parts, so as to separate internal representations of information from the ways that information is presented to or accepted from the user.
- The model directly manages the data, logic and rules of the application.
- A view can be any output representation of information, such as a chart or a diagram. Multiple views of the same information are possible, such as a bar chart for management and a tabular view for accountants.
- The third part, the controller, accepts input and converts it to commands for the model or view.
Wikipedia - Modal View Controller
大部分的 framework 都有如以下的結構:
在 Public 資料夾中,一般是放靜態檔案的地方,系統裡的靜態檔案一般可以分成以下的幾種:
在 Rack server 的機制裡面,這個資料夾的內容可以直接被下載,用以增加頁面加載的速度,不用透過自身 server 的重新 render ( 像是 nodejs 的 pm2 的作法 ) 這樣對於有大量靜態資源的頁面來說,是個較容易省下計算資源和增加連線效率的方法。
Rake 是一個 Ruby 中的任務自訂小工具,我們可以透過在 Rakefile 裡面自己撰寫一些任務,來幫忙我們加速開發與上線的麻煩點,Rakefile 裡面使用到的都是標準的 Ruby 語法,也就是說,我們可以用簡單的幾行 Ruby 就可以進行一些專案上的控制,如:DB migration, install project …我們之後會討論如何撰寫 Rakefile 的任務。
This blog will content something about technologies, something around me and maybe some politic thinking about Taiwan as well.
Hello, 我是迪蘭帝,經過一段時間的醞釀以後,我決定將部落格從原本的 blogger 搬家到 github page 上了,blogger 雖然提供了許多方便的功能,但是畢竟已經跟不上時代的腳步,
醜醜的 RWD 介面,不容易自訂 style … 等等的缺陷,所以在最近決定跳槽了。
歡迎來到 Dylandy’s Murmur ,翻翻我的技術筆記與聽聽我的一些牢騷,希望能夠為這個枯燥的網路帶來一些有趣的聲音。
]]>