iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0

啊啊啊今天我是來認錯的 ^_^

首先,昨天用 localStorage 保存翻譯資料的程式碼,
其實有個地方寫錯了 ^_^

其實也不是寫錯了,
而是採用了一種很沒效率的做法。
一開始還沒發現,
但後來嘗試六、七千句的 HTML 時,
(比如整本 epub 電子書轉成 html,就會有這麼多句子)
程式就掛掉了。

Debug 的時候才發現,
原來昨天在保存整頁的原文、譯文時,
是採用一句一句保存的做法:

// 備份各句原始文字
document.querySelectorAll("sent").forEach((node, i)=>{
  orig_htmls[i] = node.innerHTML
  ...
  // 保存到 localStorage
  ...
  localStorage.setItem('orig_htmls', JSON.stringify(orig_htmls))
  ...
}

像這種保存資料的動作,通常都比較耗時。
如果只有幾十句、上百句,
做幾十次、上百次耗時操作可能還沒感覺,
但做六、七千次就會出問題了。

在編寫程式時,
對於各種操作耗時的程度,
都應該要有點概念才行。
否則很容易寫出沒效率的程式。

J. Dean 在 2010 年列出了
每個程式設計師都應該知道的幾個耗時數字
這些數字隨著各種技術演進,
也在持續不斷進化。
各位有興趣可以參考一下 ^_^

至於這裡理想的做法,
應該是把耗時的動作拉到迴圈外面,
在迴圈內先把六、七千筆資料收集好了,
到了迴圈外面,再做一次耗時的資料保存動作,
這樣就只會耗時一次,動作肯定快多了。

// 備份各句原始文字
document.querySelectorAll("sent").forEach((node, i)=>{
  orig_htmls[i] = node.innerHTML
  ...
}
// 保存到 localStorage
...
localStorage.setItem('orig_htmls', JSON.stringify(orig_htmls))
...

像這樣只是把程式碼換了個位置,
程式效能就大大提升了!

之前的做法,就像是怕事的小員工,
每做一個小動作,就要去問一下老闆的意見。
這樣不但老闆會煩死,小員工自己也會累死。

理想的做法,應該是老闆放手讓小員工去發揮,
做到一定程度再向老闆回報。
這樣的話,小員工得到充分授權才能自由揮灑,
老闆也不必緊迫盯人,
只需適時瞭解小員工的工作進展即可。

在資料庫的領域中,
這就有點像所謂 transaction 的概念。
太細的工作,不要老是去煩資料庫,
應該把工作打包完成到一定的程度,
再整批交給資料庫去處理。

說到這個 transaction,我多說兩句好了。
這個字經常被翻譯成「交易」,
但我一看到這個翻譯就難過,
究竟是交易什麼呢?
交易的感覺是雙向的,有點交換的感覺,
與其這樣翻譯,好像不如採用「交接」的譯法。
一邊交出去,另一邊接起來,
應該比較接近所要描述的情況。

另外也有人把它翻譯成「事務」,
應該是取其名詞之意,
但「事務」這兩個字又太籠統,
看到中文很難聯想到 transaction 這個原文。
如果非要翻譯成名詞,
「交接事務」或許是個不錯的譯法。

話說回來,改正這個問題之後,
我還要再認第二個錯。。。 ^_^

之前我在使用 localStorage 時,
看到後台裡有個 Web SQL 可用,
或許是太忙太累一時之間昏了頭,
就想說可以的話來研究一下,
做為鐵人賽其中一天的主題。

但後來在維基百科W3C 的網頁中發現,
原來這個技術已經在 2010 年停止發展了!!
啊啊啊啊啊——
那還要介紹嗎?

這樣吧,請先讓我整理一下昨天的程式碼,
然後再讓我想一想吧。。。 ^_^

我想先把資料儲存相關的程式碼獨立出來。
在 js 子目錄下,建立一個 storage.js,
然後再把儲存相關的工作集中在此處。
當然,我們也要在 manifest.json 裡,
宣告一下這個檔案。

"js": [
          "js/storage.js",
          ...
          "js/content.js"
      ]

接著就可以在 storage.js 裡,
編寫資料儲存相關的函式了。

我們可以先寫兩個函式,
把昨天的一些重複程式碼整合一下。

function getValueByPath(key, path, default_value = null) {
  var item = JSON.parse(localStorage.getItem(key))
  return (item && path in item) ? item[path]:default_value 
}

function upsertValueByPath(key, path, value) {
  var value_in_storage = JSON.parse(localStorage.getItem(key))
  value_in_storage = (value_in_storage) ? value_in_storage : {};
  value_in_storage[path] = value
  localStorage.setItem(key, JSON.stringify(value_in_storage))
}

有了這兩個函式,
昨天的程式碼就會簡潔許多,
而且如果需要修改不同的做法,
也只要修改一處即可各處通用。

像這樣的整合,還有一個額外的好處,
那就是可以協助找出程式碼中考慮未盡周詳之處。
比如說,昨天我為了能讓編輯過程可以全鍵盤操作,
特別用 localStorage 記錄了最後修改翻譯的 sent id。

// 記錄之前最後一次修改翻譯內容的 sent id
localStorage.setItem('prev_sent_id', prev_sent_id)
...
// 取回之前最後一次修改翻譯內容的 sent id
prev_sent_id = localStorage.getItem('prev_sent_id')

所謂的全鍵盤操作,
也就是除了用【Ctrl+Alt+點擊】之外,
還能使用【Ctrl+Enter】、【Ctrl+上】、【Ctrl+下】,
直接進入編輯界面。
另外,今天也添加了一個新的快速鍵【Esc】,
在編輯翻譯內容時,可以取消修改,退出編輯界面。
這些與之前提到的 UX 使用者體驗,都是很有關係的。

但今天整合重複程式碼時就發現,
當初記錄這個 sent id 時,並未像其他資料一樣,
採用網址的 path 來做為索引。
如此一來,相同網頁就會使用到同一個 sent id,
這顯然不是我們想要的行為。
因此,今天在整合程式碼時,
這個問題也就順手一併處理掉了。

// 記錄之前最後一次修改翻譯內容的 sent id
upsertValueByPath('prev_sent_id_by_path', path, prev_sent_id)
...
// 取回之前最後一次修改翻譯內容的 sent id
prev_sent_id = getValueByPath('prev_sent_id_by_path', path, 'sent_0')

啊啊啊以上這些都是還不錯的程式碼改進做法。
至於那 Web SQL,
嗯。經過一整個晚上的測試,
我決定果斷放棄 WEB SQL了!!

也許是因為它長期不再發展,
目前使用起來感覺到處都是坑,
現在與其花時間去補這些坑,
不如去學習更好玩更有用的東西。

本來我想藉由它,介紹 SQL 的一些觀念和做法,
但現在姑且就此打住,
之後介紹服務端的時候,應該還有機會再介紹 SQL。

話說回來,今天在這坑上摔了一跤,
也算是一個經驗與警惕。
希望也可以做為各位的參考囉。

那麼,今天就先這樣囉。


上一篇
保存珍貴的譯文,讓好翻譯重複再利用 —— localStorage
下一篇
要記的東西好多,腦容量卻很有限 —— 彈出頁面
系列文
譯者會消失嗎?Maybe, but not today —— 你,才是更好的翻譯師30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言