iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0
Software Development

譯者會消失嗎?Maybe, but not today —— 你,才是更好的翻譯師系列 第 22

程式碼的壞味道:準時交差的壓力 & 用小聰明解問題的誘惑

  • 分享至 

  • xImage
  •  

鐵人賽每天準時交稿的壓力,實在有夠大。
如果每天要完成的功能,在程式碼測試過程中出了問題,
那更是讓人急得跳腳。

這時候,為了準時交差,
各種小聰明的做法就會一湧而出。 ^_^

我們常會說,這種不問過程只求結果的小聰明做法,
就是所謂的 hack 做法。

這種短視的小聰明,雖然可以迅速解決問題,
但往往因為考慮不夠周到,
因此等於埋下不定時炸彈,
未來幾乎一定會在某個時間、某個條件下爆炸。

而且往往會在最不恰當的時間、最艱困的條件下,爆炸給你看。

這就是所謂的技術債,
人在江湖,該還總是要還的。
而且技術債的利息往往超高,
最後所要付出的心血,往往遠超過當初所省下的功夫。

所以,開發過程中持續重新檢視有沒有更好的做法,
不要輕易留下技術債,終究是比較好的實務做法。

話雖如此,
比較好的實務做法就像一種道德理念、一種美好理想,
在生死交關的壓力下、或是解決問題就能好好鬆一口氣的誘惑下,
理想與道德往往最容易被犧牲或打折。

能夠堅持到底的人,簡直了不起呀!! ^_^

鐵人賽進行到現在,我自己就曾好幾度遭遇到這樣的取捨。
雖然心中非常希望繼續往下推展功能進度,
但目前程式碼中各種不規範的做法,
也已經開始阻礙新功能的導入了。

因此,今天就來稍微花點時間,
嗅一嗅程式碼裡的一些壞味道,
然後做一點清理的工作,
為接下來的開發,鋪展出更穩固的平台吧。

只要稍微檢視一下目前的程式碼,
就會發現以下幾個問題:

  • 外掛不管三七二十一,就在載入頁面之後直接建立兩個面板?在進入編輯界面時,預設面板已經存在?建立面板的最佳時機?
  • 要在面板中放入東西時,採用了多種不同的做法:append、innerHTML、用 Vue 來掛載?==》方式應單一、一致。
  • Vue 的掛載點,應該放在哪裡比較好呢?直接掛載到 body?正在編輯的句子?還是另外添加的面板區塊呢?
  • 詞語面板是否應該做為獨立面板,還是做為句子面板的子面板呢?
  • 如果要在面板裡添加子面板,是不是應該有個標準的添加流程?
    • 建立 component 元件
    • 掛載?開關切換?修改?移除?
    • 其它考量?
  • Vue 元件的 props 和 data,功能接近但又有微妙差異。何時該用何者最為恰當?

為了解決以上幾個問題,我們做了以下幾個調整:

  1. 移除所有不標準(例如 window.load 頁面載入立即添加面板)的面板相關程式碼。
  2. 移除 createPanel() 函式,改用 setPanels() 函式,然後在 content.js 裡,以【進入編輯界面】的 switchToModification() 函式做為單一切入點,僅在此載入面板相關功能,其他地方只做面板切換,面板內部的動作則統一改由相應元件自行完成。
  3. 面板功能完全用 Vue component 元件系統來實現
    1. 在 vue_components.js 裡重新定義面板結構如下(細節請參見程式碼):
      body(掛在原內容的最後面)
      • div#bt_panels(主面板)
        • div#bt_sent_panel(句子面板)
          • div#orig_sent(原文句)
            • div#orig_token(原文 token)
        • div#bt_token_panel(詞語面板)
          • div#active_token(所選中詞語)
          • div#dict_pane(查字典區塊)
    2. 將面板相關功能整合到各個元件內,讓各元件可獨立運作。
      • props 接受外部屬性值
      • template(render())呈現元件外觀
      • computed、method 處理相關計算操作
  4. 遵循 props in,event out 的原則:資料父傳子,事件向外傳。

「事件向外傳」的做法,還需要再特別說明一下。
Vue 的元件只能從 props 取得外部的資訊,
所做的工作也只局限在元件內部。
但有時候元件的某個動作,也有可能對外部產生影響。
為了不讓這種影響的關係互相交錯,變得難以管理,
因此我們會設定一個全域的 eventBus,負責接收這類的事件。

var eventBus = new Vue(); 
Vue.prototype.$EventBus = eventBus;

如果元件有什麼東西需要往外傳,
往這個 eventBus 傳就對了。

 methods: {
  ...
  tokenClicked: function(e) {
    this.$EventBus.$emit('token-clicked', this.token); 
  }
  ...

如果有任何元件想要接收這個事件的通知,
進而觸發某種後續的操作,
則可以利用 created 設定,
讓元件從一開始就知道要監聽 eventBus 的某個事件:

created: function () {
  let self = this; 
  this.$EventBus.$on("token-clicked", function (token) { 
    self.active_token = token; 
  }); 
},

另外,這裡有幾個很容易做錯的地方,在此特別說明一下:

  1. let self = this
  2. props、data 傻傻搞不清楚

首先,this 是個很棘手的東西。
它所代表的東西,往往會因為所處的位置而改變。
以上面的情況為例,this 第一次出現時,代表的是元件本身。
但隨後到了 eventBus 裡頭,this 的意思就改變了。
但我們希望 this 還是代表元件呀!
或者說,我們要操作的對象還是元件呀!
由於這個 this 的意思會改變,
我們只好在它還沒改變之前,先把它記錄起來。
這就是 let self = this 的作用了。
如此一來,self 指的就是元件本身,
就算後來 this 所指的對象改變了,
我們還是可以用 self 指向所要操作的元件。

至於 props 與 data,也是兩個很容易搞混的東西。
簡單說,props 就像 function 的外部參數,可以接收外部值,
在元件內部則不會去改變其值。
唯有從外部改變了 props 的值,元件內部相應的東西才會進行更新。

data 則像是 function 的內部變數,其值可以隨時改變,
而且只要 data 改變,元件內相應的東西全都會進行更新。

如果元件內容想要隨 props 改變,就要用 props 來進行設定。
但由於在元件內部,無法改變 props 的值,
所以當元件收到 eventBus 的通知時,若想改變元件的相應顯示,
這些相應顯示就不能使用 props 來進行設定,
而應該改用 data 來進行設定。

我們若想讓元件的顯示,可以隨 props 改變,也可以讓 eventBus 改變,
那就要在元件內部設定 data,讓顯示的內容隨 data 而改變。
如此一來, eventBus 可改變 data,也就可以改變元件的外觀顯示,
props 則可當做 data 的初始值,同樣可以改變元件的外觀顯示。

props: [ 'xxx', 'yyy']
data () {
  return {
    a: xxx,
    b: yyy,
  }
}
// 後面使用 a、b 做相應的設定。。。

總之,我們想讓元件外觀即時變化,
若只希望外觀受外部參數影響而改變,可以直接用 props,
但若想透過其他途徑改變,就要用 data 了。

以我們的詞語面板為例,原本的 props 是這樣:

    props: [
        ... 
        'active_token',
        ... 
    ],

因為 props 的值,只能從父元件送進來,
無法以其他方式改變其值,
因此我們必須另外建立一個 data 變數 my_active_token,
讓它以 active_token 這個 props 外部屬性值做為「初始值」:

    data () {
        return {
            my_active_token: this.active_token,
        }
    },

然後在所有使用到 active_token 的地方(例如 template 中),
全都改用 my_active_token 取而代之,
隨後如果 eventBus 或其他操作改變這個 data 裡的值,
只要有用到這個 data 資料的地方,全都會即時進行更新:

以我們上面的元件架構為例,
當我們在【原文 token】點擊某個單詞之後,
就可以發出一個事件,
而此時監聽這個事件的元件(例如詞語面板),
就可以在收到此事件通知時,進行相應的動作。

特別要提醒的是,
在整個架構中,或許有很多個地方需要監聽同一個事件,
此時我們只需把 this.$EventBus.$on 放在這些元件其中最上層的那個元件,
然後再透過 props 往下傳往其他元件即可。

好啦!
這樣一來,整個事件的處理機制總算是完整了。

完成以上調整之後,
我們所外加的面板功能,總算比較獨立而有一致性了。
將來如果要添加新的面板或新的功能:

  1. 先在 vue_component.js 裡建立所需元件
    1. 利用 props 引入所需的外部屬性值
    2. 利用 render() 定義元件所要呈現的外觀(template 樣板)
    3. 利用 computed、methods 等其他設定,定義元件相應的操作
  2. 最後把建立好的元件,掛載到想擺放的父元件 template 樣板中即可。

由於我們會在 content.js 裡,
透過【進入編輯界面】的 switchToModification() 函式,
呼叫 sentPanels() 設定好面板,
因此以上設定會自動代入。

到此為止,我們不但整合了面板功能,
將來需要擴充新面板功能時,也有一套標準的做法了。

明天我們就來增添一個新功能吧 ——詞語代換功能!


上一篇
Vue 呀 Vue 你讓我吃那麼多苦,也該給點甜頭了吧!
下一篇
看不慣,就換掉!——詞語代換功能
系列文
譯者會消失嗎?Maybe, but not today —— 你,才是更好的翻譯師30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言