iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 5
3

良好程式碼的優點大同小異。
不好的程式碼的糙點卻各有巧妙之處。

在此,拿一段修改前的 bug[1] (引用時,有把不必要介紹的糙點先修掉)。

這是繪出每個講者在歷年每月講的次數的程式。

talk_pre_month_statistic_chart 要儲存個人記錄。

*.vue

{
  //...
  MemberTalkStatistics: function() {
    let talks = this.talks;
    let talk_pre_month_statistic_chart = this.getMonthInYears();
    let speaker_map = talks.reduce((map, talk) => {
      return this.speakerDetailStatistics(map, talk, talk_pre_month_statistic_chart);
      // this.speakerDetailStatistics(talk, speaker_map);
    }, new Map());

    for (let [key, value] of speaker_map) {
      this.monthTalksStatistic(speaker_map.get(key));
    }
    return speaker_map;
  },
  speakerDetailStatistics: function(map, talk, talk_pre_month_statistic_chart) {
    map.set(talk.speaker, {
      talk_count: 0,
      speaker: talk.speaker,
      photo: talk.speaker_img.includes("imgur") ? talk.speaker_img : "/goodidea.png",
      talks: [],
      chart_data: {
        rows: talk_pre_month_statistic_chart
      },
    });
    map.get(talk.speaker).talk_count += 1;
    map.get(talk.speaker).talks.push(talk);
    return map;
  },
  //...
}

執行結果

執行結果: 每個人的個人紀錄長得一模一樣。

糙點在哪?

這不是糙,這是 bug,但是,這是一個糙點造成的 bug

先說這 bug 怎麼修,再說說糙點吧

第五行的程式,長出了一個 Array。

let talk_pre_month_statistic_chart = this.getMonthInYears();

然後將這個 Array 賦值到每個人的紀錄。

      chart_data: {
        rows: talk_pre_month_statistic_chart
      },

所以,是 Array 被共用造成的?
那在賦值時,產生新的 Array 如何?

      chart_data: {
        rows: [...talk_pre_month_statistic_chart]
      },

結果

沒錯,結果一樣!!

原因是,每個元素還是共用在不同的 Array。

這,這要怎麼辦呢?

漏洞窗 和 跨度

有一個叫 "window of vulnerability"[2]的觀念,大意是「造成漏洞方可能會出現在哪」

The code between references to a variable is a “window of vulnerability.” [name=《Code Complete 2/e》, Ch10, 10.4]

提外話: 中文版的這一段沒有校稿,擁有紙本書的朋友要注意

In the window, new code might be added, inadvertently altering the variable, or someone reading the code might forget the value the variable is supposed to contain.
「改成區域變數」的意思,他翻譯成「本地化語系」

繼續看書

The idea of localizing references to a variable is pretty self-evident, but it’s an idea that lends itself to formal measurement. One method of measuring how close together the references to a variable are is to compute the “span” of a variable.

另外有一個觀念叫「span」,翻釋成「跨度」。(翻得不錯)

這是什麼意思呢?

        漏洞窗       漏洞窗
        跨度         跨度
變數宣告 ---- 變數賦值 ---- 變數改值 (賦值)

漏洞窗 用跨度來量化,可以進行漏洞的量測。
跨度的單位是行數。

回過頭來看看前面的程式碼

let talk_pre_month_statistic_chart = this.getMonthInYears();

到這一行

rows: talk_pre_month_statistic_chart

的跨度是 9 行左右
如果,把它的跨度降低到 0 行是不是就沒有問題了?

副作用

跨度降低,固然可以把變數「改成區域變數」,降低變數存取的權限,也可以降低變數在不知情的情況被改變。

但是,往往會造成更難讀懂的 code

const price = 400

if (customer.age < 5 && customer.age > 65)
  return price / 2

這段若要再縮小 price 的跨度

if (customer.age < 5 && customer.age > 65)
  return 400 / 2

就變成了 magic number!!

要小心呀

平衡點

我主強在系統設計時,保有概念整體性 (conceptual integrity) 是最重要的原則[name=《人月神話》, 第 4 章 專制、民主與系統設計, p.67]

先讀懂程式碼語意

看看有問題的版本

MemberTalkStatistics: function() {
    let talks = this.talks;
    let talk_pre_month_statistic_chart = this.getMonthInYears();
    let speaker_map = talks.reduce((map, talk) => {
      return this.speakerDetailStatistics(map, talk, talk_pre_month_statistic_chart);
      // this.speakerDetailStatistics(talk, speaker_map);
    }, new Map());

    for (let [key, value] of speaker_map) {
      this.monthTalksStatistic(speaker_map.get(key));
    }
    return speaker_map;
  },

speakerDetailStatistics() 主要的作用是要將 Array -> Map 所以 talk_pre_month_statistic_chart 由外部傳入,是一件很奇怪的事。

它應該是這個意思 speakerDetailStatistics(output, input)

任何 output 容器中的元素,都應該由 input 提供。

MemberTalkStatistics: function() {
    let talks = this.talks;
    let speaker_map = talks.reduce((map, talk) => {
      return this.speakerDetailStatistics(map, talk);
      // this.speakerDetailStatistics(talk, speaker_map);
    }, new Map());

    for (let [key, value] of speaker_map) {
      this.monthTalksStatistic(speaker_map.get(key));
    }
    return speaker_map;
  },

每個個人統計表,不管是容器還是它的元素,都會是各別產生。

speakerDetailStatistics: function(map, talk) {
map.set(talk.speaker, {
  talk_count: 0,
  speaker: talk.speaker,
  photo: talk.speaker_img.includes || "/goodideas.png",
  talks: [],
  chart_data: {
    rows: this.getMonthInYears();
  },
});
map.get(talk.speaker).talk_count += 1;
map.get(talk.speaker).talks.push(talk);
return map;
},

所以,看似難解的問題,因為這樣的觀念,就變得很好 (莫名的) 解掉。

參考資料

[1]: turtle0617/wonderKnow_statistics - Github
[2]: 《Code Complete 2/e》Ch10


上一篇
糙 code 與他們的產地 - if 的判斷式
下一篇
實務上的高內聚與低耦合
系列文
可不可以不要寫糙 code30

尚未有邦友留言

立即登入留言