iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 6
2

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

先來看看學術怎麼說

這兩個名詞是由 Larry Constantine 提出來的[1][2],並且給出了一個好的程式碼,該努力的方向,「高內聚,低耦合」。這兩個詞各別的意思如下

Cohesion (內聚) [3]
用白話文解釋,把執行某個功能所需用到的程式與資料都塞在某一個模組(function, class, package, etc)之中,使得該模組可被視為一個單獨的個體執行,那麼這個模組的 cohesion 就愈高。內聚,內聚,顧名思義就是把程式,資料這些有的沒的東西都『聚在一起』打包起來。

Coupling (耦合) [3]
如果某個模組跟『其他人(另一個模組)』有關係(例如,使用 global variables 或是接受其他模組傳入的參數)那麼這兩個模組就彼此耦合。

看來,這些解釋都還滿清楚的。(笑)
簡單的說就是

內聚,指的是和其它程式碼「無」相關
耦合,指的是和其它程式碼「有」相關

只是寫程式的人都知道「高內聚,低耦合」,對於這個口訣該如何用於運用呢?

運用時要想到的觀念

書上[4]有些線索可以找得到

Reduce complexity
you should be able to forget the details and use the routine without any knowledge of its internal workings.

意思是說好的程式碼,是要可以隱藏複雜性的,而寫完就可以忘記細節的常式(函數),就是好的程式碼。

前幾篇有引用自人月神話的觀念「複雜性」可以知道,軟體本質複雜性是無法消除,更別說被簡化了。這是完全不可能的。但是附屬性問題衍生的複雜性,可以用管理的方式消除,也就是適時的隱藏或歸納它。

整理、歸納複雜性 是好的工程師的必要能力之一,但是,這又該如何進行呢?
一樣來自於人月神話的觀念。

  • 概念整體性
  • 隱藏複雜性

概念整體性

在這些歸納、整理的階層與表達方式尋求一種一致的觀念與表達方式。
讓系統呈現的層次感不會這麼難以讓人理解。

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

每個部份都要反映相同的理念,取得各方面需求的平衡,在語法上,每個地方都是用相同的技巧,在語意上,用的都是類似的觀念。概念整體性

一個井然有序,優雅的軟體產品所展現給每一位使用者的,無論是在應用上、在執行這項應用的策略上、在使用者介面指定動作和參數的手法上,都必須是前後連貫的一套心智模式 (mental model)
--《人月神話》, 《人月神話》二十年, p.326

隱藏複雜性

好的工程師會模仿物理學家在解決問題時,使用數學工具的思考方式。

數學和物理學之所以能在過去三個世紀突飛猛進,就是藉由為複雜現象建立出簡單的模型 (model) ,然後從模型中推導出現象的特性,並透過實驗來驗證這些特性。
這種方式之所以行得通,是因為在模型中所排除掉的複雜性 並非現象的本質。
--《人月神話》, 沒有銀彈: 軟體工程的本質性與附屬性工作, p.239

實務上又要如何「高內聚,低耦合」呢?

內聚性

內聚性的展現,其實是撰寫程式碼時的大忌

三步寫糙 code

  1. 複製
  2. 貼上
  3. 改一下

為什麼又高內聚,又糙 code 呢?
高內聚不就是「好 code」嗎?
矛盾哦!!

常出現問題的情況

「咦?我改了這個,怎麼這裡沒有改到?不是應該是一起的嗎?」

糙點在於「該耦合,沒耦合,所以糙」,這樣真的是太主觀了。
但這就是內聚性,保持單一模組(某一塊程式碼)的獨立性(改了不影響別人)

耦合性

耦合性的展現,其實是撰寫程式碼時的優點: 「好 code 就是要『重複使用程式碼』」

「那太好辦啦!盡可能的重複使用的程式碼,不就好了,又是優點集一身,又是寫好 code ,忘了高內聚吧!」

常出現問題的情況
「咦?我改了這個,怎麼連這裡也改到?那裡也改到?怎麼到處都改到了呀?!(全爆了!嚇死人了)」

Sorry!! 小劇場太多了!糙點在於「該內聚,沒內聚,所以糙」,這樣真的是太主觀了。
但這就是耦合性,相關程式要參數化、連動。

那要怎麼做呢?

了解問題本身的真實面貌
問題本身指的是在它尚未以程式碼的形式呈現時,用口說語言如何表達問題的相關與獨立性。
爬疏人在理解這個問題時,認為相關與不相關的部份。

這要做得好,不可以只看見程式碼就思考如何安排內聚與耦合

關鍵: 概念整體性 的實務上要怎麼理解
「當你可以用一個詞表達一件事,那件事就有一個完整的概念

舉個例子

這是一段,原本不是 JavaScript 的 Code

給你自己幾秒鐘,看看這一段在做什麼

class WebService {
  constructor (){
    var yaml_pathname = "webservice/config/configfile.yaml");
    var xml_pathname = "webservice/config/event.json";

    var yaml_file = new yamlFile (yaml_pathanme);
    var MESSASGE_LOG = "MessageLog";

    if(!yaml_file.isExists(MESSASGE_LOG)) {
        this.fileLog.fileName = "Log/MsgLog.log";
        //...
        this.fileLog.save(yaml_pathanme, MESSASGE_LOG);
    }
    else
    {
        this.fileLog.load(yaml_pathanme, MESSASGE_LOG);
    }

    var event = new Event();
    event.loadConfig(yaml_pathanme);
    event.saveConfig(yaml_pathanme);

    event.on("Sent", Sent);
    event.on("Received", Received);
    event.on("TransactionError", transactionError);
    event.on("MonitorEventReceived", monitorEventReceived);


    var yaml = new yamlFile(yaml_pathanme);
    var system_yaml_pathanme = "webservice/config/configfile.yaml");
    var system_yaml = new yamlFile(system_yaml_pathanme);
    //...
  }
}

改寫成這樣

class WebService {
  constructor (){
    const yaml_pathname = "webservice/config/configfile.yaml");
    const xml_pathname = "webservice/config/event.json";

    initialMsgLogFile(yaml_pathname, `Log/MsgLog.log`);

    var config = new filePathConfig();
    initialSystemLogFile(config.systemIniPathname, config.logPath);

    initialEvent(yaml_pathname, xml_pathname);
  }
}

也許會有疑問

「程式碼沒有要重複使用,為什麼要弄成一個 function?」
但是,反問一下這個問題
「難道需要變成 function 的都只是因為重複使用而已?」

回歸到問題本身

「如果程式有很多個模組,要吃相同的設定檔,那麼共用設定檔路徑是理所當然的」
上述的描述,包含了「很多個模組」這件事。
所以有哪些模組是不是應該就有各個完整的概念性可以使用。

由上述整理好的程式,可以發現有 MsgLog, SystemLogEvent 幾個模組,共用設定檔。

參考資料

[1]: 内聚性(電腦科學) - 維基百科,自由的百科全書
[2]: 耦合性 (電腦科學) - 維基百科,自由的百科全書
[3]: 亂談軟體設計(1):Cohesion and Coupling
[4]: 《Code Complete 2/e》, CH7 High-Quality Routines, 7.1 Valid Reasons to Create a Routine


上一篇
宣告與定義太遙遠
下一篇
不用前額葉的命名
系列文
可不可以不要寫糙 code30

尚未有邦友留言

立即登入留言