iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 9
2

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

這次來介紹一個有趣的物件,叫 God Object[1]。

  • 任何 variable 都丟進去,導致某一個物件全知。
  • 任何 function 都丟進去,導致某一個物件全能。

任何問題找他就對了。這種全知全能的物件,就被稱為 God Object。

在後端遇見神

為了避免程式碼外洩,我們只看 minimap

這段程式碼,總共有 755 行。
minimap 大概只顯示 200 行。
可視區一次顯示 30 行。

而且這是三位一體,基督教的三一真神[2]呀!!!
(三個 GodObject 互相呼叫的架構。)

後端從 route 來之後,會依序經過這三層

  1. Kernel: 用來將 API 的資料 (JSON之類的) 存放在臨時物件中。
  2. Implement: 用來將臨時物件做一些處理 (通常不處理)
  3. Transactions: 用來將臨時物件的內容放進 Database

以刪除成員為例!!
此程式碼已經修改成 js 的實作。(原本不是)

kernel

class serviceKernel {
  //...
  delMember(req, res) {
  res.body = {
    returncode: 0,
    returnMessage: ""
  };

  let input = new serviceImplement.DelRestoreMemberData();
  let output = new serviceImplement.BaseResultData();

    input.Id      = req.body["id"];
    input.Version = req.body["version"];

    serviceImplement.processDelMember(input, output);

    res.body["returncode"] = output.returnCode;
    res.body["returnmessage"] = output.returnMessage;
  }
  //...
}

implement

class serviceImplement {
  //...
  processDelMember( filter, resultData /*out*/ ) {
    let execute_result_code = serviceTransactions.processDelMember(filter, resultData);

    if ( execute_result_code != SUCCESS ) {
      console.log(execute_result_code)
    }

    resultData.returnCode = execute_result_code;
    resultData.returnMessage = getErrMessages(execute_result_code);
  }
  //...
}

Transactions

class serviceTransactions {
  //...
  processDelMember(delFilter, resultData) {
    let result_code = SUCCESS;

    let db_emp_list = database.employee.getDataset({
      id: delFilter.id
    });

    if(!db_emp_list.length) {
      result_code = ID_NOT_EXIST;
      return result_code;
    }
    else if( db_emp_list.length > 1 ) {
      console.log("DATABASE_DATA_FAIL");
      console.log("Database have two the same record or more in database.employee");
      console.log("id: " + delFilter.id);

      result_code = DATABASE_DATA_FAIL;
      return result_code;
    }

    let PARAM = database.employee.id;
    let db_sys = database.systemParameters.getData({
      sectionId: 'admin',
      targetParmeter: 'id',
      targetValue: delFilter.Id
    });

    if( !db_sys ) {
      result_code = DISALLOW_DELETE_ADMINISTRATOR_ACCOUNT;
      return result_code;
    }

    let db_emp = db_emp_list.shift();
    db_emp.modifyDate = new Date();
    database.save();

    return result_code;
  }
  //...
}

serviceKernel, serviceImplement, serviceTransactions 三個 class 中,是把所有的 API 通通放進去當作 function 。

若是用 express 的架構來反思 God Object 其實是不需要的,因為它的 docs 裡介紹的 function 沒有屬於任何的 object 而在此介紹的範例有。

這是什麼樣的糙點呢?

請再回頭仔細看看,這所謂的三層之間的責任是什麼呢?各別做了什麼事?

再想想,什麼是 MVC 架構?

邏輯的分類

組合語言 演進成 C 語言 的時代,是不是就是把邏輯分類呢?

在這樣的 God Object 產出,原因多半是開發者沒有仔細了解這個程式架構中,有什麼概念是保有完整性的呢?換句話說,有什麼「主詞」可以先被抓出來。

在一個後端中,最重要的主詞是 API。每一個 API 都應該是一個抽象,它有它自己的邏輯。

另外,如果有接資料庫的話,每一個 Table 也有一個自己的邏輯和資料。(ORM 的概念)

所以,後端出了兩個主詞: 「API」、「Table」

class api {
  //...
}

class table {
  //...
}

這樣要怎麼寫呢?
換一個樣子來看看。

class controller {
  //...
}

class model {
  //...
}

這樣有沒有和 MVC 有呼應到了呢?

前端怎麼辦?也要分嗎?

在這個時代,前端依然有在分類邏輯,而且,不這麼做就 GG 了。

「前端框架是以資料驅動的方式設計而成。」

這是什麼意思呢?

資料,有資料的邏輯,靠開發者撰寫。
畫面,有畫面邏輯,由資料決定,資料與畫面之間的邏輯,由開發者撰寫。

這樣要怎麼寫呢?
換一個樣子來看看。

vuex 存放資料,vuex 的邏輯放在 action 裡,透過 mutation, getter 存取 store 的資料。
components,有 components 的邏輯,由資料決定,開發者透過 v-if, v-for...語法完成資料與畫面的邏輯。

其實前端早就正在進行邏輯的分類了。

Helper Object

一直以來,我沒有寫過這樣的物件。但是一直聽說它的存在。
尤其是在 Java 的世界。原因在於有些類似 middleware 的工具需要存在,但不屬於誰,就放在 Helper Object 中。

但是,我個人覺得,這個一個也許不用存在的物件。但是幫他分類有時太花心思,不過,如果不分類新人接手怎麼會知道裡面的「巧妙之處」呢?

參考資料

[1]: God object
[2]: 三位一體
[3]: MVC - 維基百科,自由的百科全書


上一篇
不必要的註解
下一篇
「聰明」與「自作聰明」的 code
系列文
可不可以不要寫糙 code30

尚未有邦友留言

立即登入留言