當重構遇到糟糕的物理設計

---

導語

糟糕的物理設計是對遺留大型系統中進行重構的非常棘手的一個問題,本文相機闡述了遺留系統中存在哪些糟糕的物理設計,它們對重構所帶來的哪些惡略影響,以及我們在重構過程中應該如何處理這些問題。文中后面還介紹了關于物理設計的一些工具,其中包括本人開發的自動化頭文件拆分工具。

1.物理設計VS邏輯設計

  • 物理設計
    物理設計主要是軟件設計中的物理實體(文件)的設計,例如某個函數定義應該放在哪個文件中、某個函數是否需要Inline等,從物理設計看到的是系統中的大量文件實體。

  • 邏輯設計
    邏輯設計主要針對軟件設計中的邏輯實體關系的設計,例如類之間的關系,Has a/use a/is a的關系,從邏輯設計看到的是大量的邏輯實體,如類,函數,結構體。

物理設計的主要目標是減少文件的物理依賴,而邏輯設計的主要目標是減少邏輯依賴。物理依賴更多的體現為編譯時的依賴和鏈接時的依賴,物理依賴受邏輯依賴影響,但是又不局限于邏輯依賴,在一個大型軟件系統中,物理設計和邏輯設計是完全不同的范疇,也是一個需要重點關注和考慮的問題。很多人認為物理設計主要考慮頭文件的設計,其實是非常錯誤的,正確的物理設計不僅要考慮頭文件的設計,還要考慮源文件的設計,從而達到編譯單元的物理依賴設計。

2.糟糕的物理設計有哪些

2.1 巨型文件

我們這里談論巨型文件,并不單指巨型頭文件,就像物理設計并不是單指頭文件的物理設計一樣。巨型源文件和頭文件一樣也是一種非常糟糕的物理設計。在遺留的大型系統中,巨型頭文件和巨型源文件隨處可見,正是因為這種糟糕的物理設計導致我們的系統中代碼構成一個巨大的網狀物理依賴系統,如下圖所示

物理依賴.jpg

2.2 糟糕的文件封裝

這里并不是談論C++的域名空間概念,為了清楚的說明文件封裝的概念,我們先介紹如下幾個概念。

  • 聲明
  • 定義
  • 編譯單元
  • 內部鏈接
  • 外部鏈接

一個聲明將一個名稱引入一個程序,而一個定義提供了一個實體,在程序中唯一描述。編譯單元通常指編譯過程中編譯器看到的一個單位,在C/C++中,通常是以每個源文件為一個編譯單元。如果一個名字對于他的編譯單元是局部的,并且連接時與其他編譯單元中定義的標示符名稱不沖突,那么這個名字就是內部鏈接的。如果一個名字有外部鏈接,會產生外部符號,那么在多文件系統編譯鏈接過程中,這個名字可以和其他編譯單元交互。

結構體定義具有內部鏈接性,所以在每個需要使用當前結構體定義的編譯單元都需要顯示包含結構體定義的頭文件,本質上每個編譯單元中都有一個完整的結構體定義。同樣C+ +中的類定義也是具有內部鏈接性,每個使用了類定義的編譯單元都需要包含類定義的頭文件,但是類定義中的非內聯函數屬于函數聲明,所以類的非內聯方法定義會產生外部符號,而類的內存布局定義并不會產生外部符號。根據以上分析可以發現,類定義本質上比較像結構體定義+ 函數方法的聲明。我們在平時的C++項目中,經常看到的鏈接錯誤看到的經常是找不到某個類方法的定義而不是找不到某個類的定義,因為找不到類定義屬于編譯錯誤。

在討論清楚這些之后,我們再看一下C語言系統中哪些具有內部鏈接,哪些具有外部鏈接:

1)內部鏈接:結構體定義,宏定義,typedef, enum, union
2)外部鏈接:全局變量定義,全局函數定義。

我們提倡內部鏈接的東西盡量放到源文件中,同時盡量減少定義具有外部鏈接名字,然而在遺留系統中經常并關注這些概念,從而導致系統中存在大量的這些問題。

  • 全局變量隨處可見
  • 全局函數不加管制
  • 宏使用泛濫
  • 全局定義隨處可見

2.3 巨型接口頭文件

巨型接口頭文件從嚴格意識上將并不會引起系統內各個模塊的物理依賴,但是它也是一種非常糟糕的物理設計。
在我們的業務代碼中,在出現下面幾種現象時都需要包含一整個公共接口頭文件。

  • 如果只使用了公共頭文件接口中的一個結構體,我們需要包含這個頭文件。
  • 如果只使用了公共頭文件接口中的一個宏定義,我們需要包含這個頭文件。
  • 當包含的某個公共頭文件編譯又依賴于另外的公共接口頭文件,那么我們還需要包含依賴的相應頭文件。
  • 公共接口頭文件的結構體定義限制了前置聲明,導致我們只使用公共頭文件中某個結構體的指針或者引用的情況下也需要包含整個頭文件。

前面幾條規則很好理解,這里單獨解釋一下第四條。當我們在重構過程新增加的代碼中,如果只使用某個結構體的指針或者引用的時候,通常情況下只需要前置聲明即可,并不需要包含相應的頭文件,這是C++減少物理依賴的一個非常重要的手段。但是現有的公共頭文件中的結構體定義形式有下面兩種:

typedef struct
{
    WORD32        dwValue;
}T_StructName1;

typedef struct tagStructName2
{
    WORD32        dwValue;
}T_StructName2;

顯然上面這兩種結構體定義是C語言的遺產,不能很好的支持前置聲明。第一種方式T_StrictName1屬于typedef重定義的名字,是不支持前置聲明的。第二種方式僅支持tagStructName2的前置申明,但是與真實使用名字T_StructName2不一致,同樣編譯器會報錯。所以下面給出的這種方式是很好的兼容老式的C語言和C+ +的一種方式,具體定義形式如下:

typedef struct StructName2
{
    WORD32        dwValue;
}StructName2;

3.糟糕的物理設計的影響

3.1 復用已有功能模塊困難

一般情況下,我們談復用的時候,的確是希望復用一些軟件中的一些邏輯實體,看似和物理設計無關,其實不然。真實的復用肯定要承載一定的物理實體上,如果我們期望復用某一個邏輯實體,需要把承載相應的邏輯實體的相關物理文件編譯鏈接進來。

如下圖所示,雖然functionA()與functionB(), functionC()在邏輯上沒有任何關系,但是由于糟糕的物理設計,我們想復用functionA(),就必須把兩個編譯單元fileA.C和fileB.c兩個編譯單元作為一個整體才能夠被復用。

//fileA.c
void functionA()
{
    printf("hello world\n");
}
int functionB()
{
    return functionC();
}
//fileB.c
int functionC()
{
    return 10;
}

在理想狀態下,我們期望我們系統中的開發的功能和特性每一個塊都是可以獨立復用的,而不是所有的功能和特性作為一個整體才可以復用,如下圖所示,左側系統的可復用性是優于右側的。但是針對遺留系統而言,由于糟糕的物理設計導致系統各個編譯單元之間經常是右側網狀依賴,從而導致了系統所有功能實體必須做為一個統一的整體才能被復用。

分層依賴設計.jpg

而我們在對大型遺留系統進行重構的過程中,并不是剛開始就對整個系統進行重構,而是選擇其中的一部分模塊進行重構,那么就需要復用其他模塊,而遺留系統中網狀的物理依賴關系導致我們想復用已有系統的每個模塊都非常困難。

3.2 系統難以理解

軟件功能自從誕生依賴,可理解性都一直扮演著非常重要的角色,自從簡單設計四原則被提出來之后,大家對可理解性有更深一步的認識,但是遺留系統的可理解性通常都非常差。

可理解性不等于注釋,遺留系統中經常增加了很多注釋,但是這些注釋對可理解性方面收效甚微。相反,遺留系統中隨處可見的全局變量,不加控制的全局方法,導致我們在閱讀和理解代碼的過程中很難搞清楚每個模塊真正對外提供的接口是什么,同時也就非常難理解模塊真正干了哪些事情。

3.3 構建測試用例困難

易復用的東西通常是易測試的,而遺留系統糟糕的物理設計導致可測試性極差。相對而言針對遺留系統而言,構造系統級別的FT測試會容易一些,但是FT測試在前期有較大收益但是存在一定的局限性。通常FT測試可以覆蓋開發系統的大部分功能,但是系統中還存在一些功能使用FT測試非常困難同時成本也是非常大。所以我們需要構建UT, FT, SAT等一整套測試體系,那么糟糕的物理設計對構造UT測試來說簡直就是噩夢。

通常構造UT級別測試的過程中,需要做的事情有一下幾個方面:

  • 構造測試輸入輸出
  • 依賴邊界打樁,
  • 借用mockcpp幫助測試

通常情況下我們可以針對系統中每個可復用單元來構造UT測試,如果系統可復用單元粒度比較小,那么測試構造就會非常容易,UT測試的編譯和開發都會比較小,那么使用Testngpp測試框架就可以非常容易的構造UT用例。如果系統中的可復用單元比較大,為了構造測試用例,我們需要構造的輸入輸出上下文成本就會變大,經常就不得不使用mockcpp或者自己構造的樁函數,然后糟糕的物理設計,會顯著增加我們在這方面的成本。

3.4 編譯時間過長

在大型遺留系統重構項目中,為了消除重復和達到更好的可理解性,我們更期望去開發一些更小的,且具有單一職責的類。這個時候,一個奇怪的問題出現了,隨著我們重構代碼量越大,新開發的類越多,編譯時間越來越長!

仔細分析之后,發現又是巨型接口頭文件惹的禍。遺留系統中公共接口頭文件通常都非常大,有的甚至超過3000行。大家應該都知道C++編譯期間,默認都是以每個cpp文件為一個編譯單元,然后把頭文件在cpp文件中的位置展開并進行編譯。所以我們在重構過程中增加的一些很小的cpp文件,看似非常小,但是真實編譯的時候如果包含了接口頭文件,那么編譯起來也很長。

編譯時間長會嚴重的影響重構的節奏,使得重構變得非常困難。很多人可能會說,我們可以使用并行編譯呀,make的時候加一個-j就搞定了呀!那我告訴你沒有最快,只有更快。對于期望編譯時間可以到達秒級的程序員來說,編譯時間沒有上限。

還有人可能會說,我們可以用聯合編譯呀,把多個cpp文件打包成一個大文件來進行編譯呀,我不得不承認這個方法的確可以在很大的程度上改善巨型頭文件引入的編譯時間過長問題,但是我這里必須鄭重的提醒你一下,我們一定要慎用聯合編譯,因為聯合編譯破壞了文件封裝性,導致原來文件中的static 定義,匿名namespace的就變得不再是本文件內可見,所以一定要慎用。

4.重構中如何應對糟糕的物理設計

4.1將物理依賴層次化

在遺留系統中,循環物理依賴隨處可見,很大程度上影響了可理解性和可復用性,然而針對C語言的遺留系統中,消除循環物理依賴可以通過層次化物理依賴來解決。

//AB.c
void function A()
{
    functionB();
    functionD();
}

void function B()
{

}
//CD.c
void function C()
{
    functionB();
    functionD();
}

void function D()
{

}

如上代碼所示,編譯單元AB和編譯單元BD之間存在循環依賴的情況,可以通過拆分分層把B和D拆分到單獨的編譯單元中,來消除互相循環依賴的情況,如下圖所示,將不再存在循環依賴的情況。

循環依賴.jpg

經常調整物理設計和物理依賴,可以把原有系統的網狀物理依賴可以調整為分層的物理依賴,如下所示:

分層依賴設計.jpg

分層的物理依賴主要原則是,每一個層的編譯單元只物理依賴于它下層的編譯單元。當系統的物理設計滿足分層架構的情況下,不僅非常有利于增量式測試,也有利于代碼復用和重構。如上圖所示,每個編譯單元都可以與它下層依賴的編譯單元組合起來為一個可復用單元,那么我們就可以針對每個可復用單元設計包圍測試并進行重構了。

4.2提升文件封裝性

上面小節主要談論物理依賴,更多的談論是編譯單元之間的物理依賴,而這里討論的文件封裝性是另外一個層面,主要是針對介紹提升文件的封裝性,減少對外部鏈接域的污染,減少對全局名字域的污染,同時提升已有功能模塊的可理解性。

具體措施有如下一些:

  • 消滅遺留C系統中的extern 關鍵字。
  • 可以內部鏈接的方法都需要static
  • 結構體定義盡量的移入到源文件中

舉一個簡單的例子,代碼重構前:

//oldModule.h
typedef struct TYPE_A
{
    int a;
    int b;
}TYPE_A

typedef struct TYPE_B
{
    int c;
    char d;
}TYPE_B

extern int g_openswitch;

void funA(TYPE* A);
int funB(TYPE* B);
//oldModule.c

#include "oldModule.h"

int g_openswitch;

int funB(TYPE* B)
{
    return b.c* b.d;
}

void funA(TYPE* A)
{
    TYPE_B b;
    b.c =3;
    b.d = '4';
    a. b = funB(&b);
}

重構后

//oldModule.h
typedef struct TYPE_A
{
    int a;
    int b;
}TYPE_A

bool isSwitchOn();
void funA(TYPE* A);

//oldModule.c

#include "oldModule.h"

static int g_openswitch;

bool isSwitchOn()
{
    return g_openswitch == 1;
}
static int funB(TYPE* B)
{
    return b.c* b.d;
}

void funA(TYPE* A)
{
    TYPE_B b;
    b.c =3;
    b.d = '4';
    a. b = funB(&b);
}

如上所示,C語言并不規定所有的結構體定義必須要放到頭文件中,也不是所有的函數都需要聲明。為了體現更好的實現封裝性,我們需要把不需要對外暴露的結構體放到源文件中,把不需要對外暴露的接口static到源文件中,同時消除全局變量。這樣頭文件可以看做對外暴露較少的外部鏈接接口,只應該看到必要的結構體定義和公共函數接口聲明。

我們在對遺留系統中某個編譯單元或者模塊進行重構的過程中,首先需要理解原有系統。要理解原有系統,第一步必須要清楚系統中的每個模塊哪些是對外公共的接口,哪些是內部實現。然后我們才可以針對外部公共接口構造包圍測試,重構相應的編譯單元或者模塊。

4.3 消滅巨型接口頭文件

在對遺留系統進行重構的過程與開發新的系統有很多的差異,因為遺留系統對重構增加了很多內在的約束,如下所示:

  1. 原有子系統的公共接口頭文件通常我們并沒有所有權,那就意味著我們不能做任何修改。
  2. 重構團隊經常需要與原有團隊同步前進,重構團隊需要不斷同步適應公共接口頭文件的變更。

優秀的公共接口頭文件應該滿足如下要求:

  • 單獨可以編譯通過(自滿足)。
  • 每個接口頭文件中應該只包含單個結構體定義。
  • 接口中的結構體定義支持前置聲明。
  • 公共接口頭文件中只包含必要的頭文件。

其他條目很好理解,這里單獨解釋一下第二條:”每個接口頭文件應該只包含單個結構體的定義“,這個應該完全是我自己提出的概念,存在少許爭議。我們平時提到的接口隔離原則,要求針對不同的客戶定義獨立的接口,從而不讓客戶看到它不關心的接口,但并沒有嚴格要求到每個接口頭文件只包含單個結構體定義。我提出這個規則是基于這樣的一個前提,如果重構項目中類的職責很單一,大部分都是很小的類,那么通常情況下只使用某一個結構體是常態,如果在一些特殊情況下,需要使用多個結構體,那么包含多個頭文件也并無大礙。

為了解決遺留巨型接口頭文件對我們重構的制約,最容易想到的方式就是新增加頭文件,然后把結構體按照我們的期望的格式添加到里面,然后在我們重構的新代碼中,使用新增加的頭文件即可。這時我們碰到了三個新問題,第一個問題就是,我們破壞了結構體的dry原則,我們新增加的頭文件中的結構體與原來公共頭文件的結構體本質上是一個結構體,但是卻用兩個定義。第二個問題,我們的公共接口頭文件中,一共有800多個結構體定義,如果每個結構體都拆分一個頭文件,那就需要拆分800次。我們也經常發現,當新開發一個小類的時候,拆分頭文件時間遠遠大于開發新代碼的時間。第三個問題,如果我們能夠一次搞定也就罷了,但是我們重構版本經常需要跟著大版本一起前行,如果大版本中的公共頭文件接口發生修改,我們怎么保證和內部新增加的頭文件中的結構體還是一致的,難道需要再把800個結構體重新拆頭文件一次嗎?

為了解決這里問題,需要借助自動化工具,請參考5.2自動化頭文件拆分工具。

5物理設計相關工具集

5.1 biicode

biicode 是一個支持多平臺的 C/C++ 依賴管理器,可以很方便集成到 Visual Studio 和 Eclipse CDT 中,目前已經開源了客戶端的代碼在github上面, 官方稱會逐漸開源全部代碼.

  • 官方博客: blog.biicode.com/biicode-open-source-client/
  • 項目主頁: biicode.github.io/biicode/

這個項目算是彌補了C/C++一直沒有一個像樣的包管理器的缺陷,當你代碼使用第三方庫的過程中,原則上你可能只使用庫中的一部分代碼實現,但是我們通常是把這個庫文件完整的編譯進你的系統中。使用biicode之后,如果你只包含了庫中的某個頭文件,那么它只會把庫中和你相關的實現編譯到你的系統中。這個時候物理設計就扮演著非常重要的一個環節,如果你系統有良好的物理設計,那么biicode就會發揮比較大的價值。

未完待續。

5.2自動化頭文件拆分工具

本章節主要介紹本人開發的自動化頭文件拆分工具的實現,以及如何解決遺留系統中的巨型頭文件問題。

5.2.1消除預編譯宏

我們想做的第一件事情就是去除頭文件中的預編譯宏。如下面頭文件,我們重構的時候只關注某個單板類型,但是在頭文件里面,我們卻看到了很多我們不關心的產品的結構體定義;另外一方面結構體中過多的預編譯宏也著實讓我們很難受,所以我們首先要消滅它。


typedef struct {
    WORD16 wGid;
#if (_LOGIC_BOARD == _LOGIC_XXX_BOARD_1)
    UCHAR ucSimultANAndSRS;
    UCHAR aucRsv0[3];
#endif
#if (_LOGIC_BOARD == _LOGIC_XXX_BOARD_2)
    UCHAR aucRsv2[12];
#endif
} T_PucchRbScheInfo;

剛開始我首先想到是使用ruby腳本去解析這些預編譯宏來判斷到底哪些代碼是我們關注的。但是我很快的發現頭文件中有很多很復雜的預編譯宏,而且有很多包含嵌套,我很認真的分析各種嵌套關系,最后終于在考慮了5層嵌套的情況下搞定了這些預編譯宏,同時也針對新寫腳本增加了測試用例。但是我還是對這些復雜的腳本代碼極度的不信任,這時一位團隊成員給了我一個很重要的建議,利用編譯器來做這些事情,它們更專業。

一語中的,那就讓編譯器來幫我來做吧。這里首先給大家介紹一個概念吧,那就是預編譯。我們的編譯器在編譯之前首先會做一次預編譯,預編譯工作主要包括頭文件原位置插入和宏替換,同時也消除了預編譯宏和注釋。再補充一點,我們團隊有一套比較強大的makefile,同時支持模塊級別的,子系統模塊集成級別,以及單板級別的make.當然也支持針對每個文件的預編譯了,那后面的事情就非常簡單了!

實現上面功能的主要代碼如下,由于編譯器的預處理只會對cpp文件進行預編譯,所以我們還要先對頭文件進行一些預處理,并轉換為cpp文件,構造一個在命令行上的make命令,然后執行make,具體ruby代碼如下所示。

    def generate_make_cpp()
        remove_old_file(@make_cpp_path)
        make_cpp = File.open(@make_cpp_path, "w")
        lines = File.open(@header_path,"r").readlines
        lines.each do |line|
             if ((line.include?"#include") || (line.include?"#define"))
                 next
             end
             make_cpp.puts line
        end
       make_cpp.close
    end

    def run_gcc()
        gccCmd = @gcc + @make_cpp_path + " > " + @make_i_path
        run_cmd(gccCmd)
    end

大家可能發現我們我在頭文件轉換為cpp文件時候,把頭文件中的宏定義,還有包含頭文件的行都刪除了,仔細想想很容易就能得到答案。刪除#define主要是為了確保我們拆分的結構體中的宏不會變成魔術數字,而刪除#include主要是為了不讓我們重復對一個結構體進行重復的拆分。經過預編譯處理之后的中間文件幫我們消除了預編譯宏,同時也消除了那些雜亂無章的注釋,代碼干凈漂亮如下所示,我們就可以基于這樣的代碼進行拆分了。

typedef struct {
    WORD16 wGid;
    UCHAR aucRsv2[12];
} T_PucchRbScheInfo;

5.2.2有效識別接口文件中的結構體

要做到針對每個結構體拆分單獨的頭文件,那么首先需要準確的識別結構體或者枚舉,并生成拆分頭文件的文件名。這里大家可能會有疑惑,我們為什么沒有把結構體按照我們的期望的駝峰式進行重新命名,道理很簡單因為這些結構體在以前遺留代碼中還在使用,我們并不想因為這點就去就修改遺留代碼。

要做到上面這點,我們需要在接口頭文件中識別一個結構體的開始定義,結構體的名字,還有結構體定義的末尾。要做到這些也很容易,因為每個腳本語言都具有非常強大的正則表達式模式匹配功能,當然ruby也不例外。讓我們看看ruby是如何做到的。

        def is_type_def_begin(line)
           (line.include?"typedef")
        end

        def is_type_def_end(line)
             /[}][\s]*[TE]/.match(line)
        end

        def get_struct_name(line)
            struct2 = /[TE][\w]+/.match(line.force_encoding("gb2312"))
            struct2[0]
        end

同時我們還需要根據結構體的名字,生成拆分的頭文件名字,同樣我也可以利用正則表達式。

def generate_header_file_name_from(structname)
    header_file_name = structname.gsub(/[ET]_/,"ce_");
    header_file_name = header_file_name.gsub(/[a-z][A-Z]/){|s| s[0] + '_' + s[1]}
    header_file_name = header_file_name.gsub(/2/, "_to_")
    header_file_name = header_file_name.gsub(/4/, "_for_")
    header_file_name = header_file_name.gsub(/[A-Z][A-Z][a-z]/){|s| s[0]+'_'+s[1]+ s[2]}
    header_file_name = header_file_name + ".h"
    header_file_name.downcase
end

當生成頭文件名字之后,再根據頭文件生成頭文件保護宏就很容易了,這里不做說明了。

5.2.3按照要求生成拆分后的頭文件

為了保證我們的拆分生成的頭文件是自滿足的。通過上面分析,我們期望的每個結構體的頭文件定義中所需要包含的頭文件如下:

  • 包含基本類型定義的頭文件
  • 包含結構體中定義需要的宏定義
  • 僅包含結構體中嵌套的子結構體的頭文件

由于已經提前確定了基本類型的頭文件和宏定義的頭文件,在生成拆分結構體的接口頭文件的時候只有需要在最開始的時候包含進來就可以了。在處理的過程中,為了達到一次遍歷遺留系統的巨型頭文件,在掃描的過程中新建文件@test_struct_include_path,然后把結構體中包含的子結構所需要的頭文件先臨時添加到這個文件中,新建臨時文件@test_struct_file_path,把結構體的定義內容拷貝到這個文件內,最后結束之后再把兩個臨時文件拼接到一起生成最終我們需要的頭文件,下面為核心代碼邏輯樣例。

    def generate_for_struct()
        lines = File.open(@spliter_header_path,"r").readlines
        lines.each do |line|
                if (line.include?"#")
                    next
                end
        if is_type_def_begin(line)
            @write_file = File.new(@test_struct_file_path,"w")
                    @include_file = File.new(@test_struct_include_path, "w")
                    @is_write_able = 1
                    @is_struct_type = line.include?("struct")
                    if(@is_struct_type)
                        @include_file.puts "#include \"l0-infra/base/BaseTypes.h\""
                        @include_file.puts "#include \"ce_defs.h\""
                    end
        end
            if(@is_write_able == 1)
            @write_file.puts line
            end
            if is_contain_struct(line)
               include_struct_name = get_struct_name(line)
               add_include_file_path(include_struct_name)
            end
        if is_type_def_end(line)
           structname = get_struct_name(line)
                   do_generate_head_file(structname)
                   @is_write_able = 0
        end
        end
    end

最后我們還需要處理一種特殊情況,就是對結構體的名字進行重定義,代碼如下:

        def do_generate_redefine(line)
            struct = /[TE]_[\w]+/.match(line)
            include_struct_name = struct[0]
            add_include_file_path(include_struct_name)
            temp = line.gsub(struct[0], " ")
            struct = /[TE]_[\w]+/.match(temp)
            struct_name = struct[0]
            do_generate_head_file(struct_name)
        end

5.2.4在原項目中的效果

1)拆分之后的頭文件首先按照原來的不同子系統之間進行目錄隔離,如下:

1228_1.jpg

2)然后每個目錄里面包含了原來公共接口下所有拆分的結構體頭文件,如下:

1222_4.jpg

3)拆分之后每個頭文件的形式如下:

1222_3.jpg

使用這套ruby的自動化腳本之后,只需要在命令行中敲入命令1秒之后,原來子系統之間所有的接口頭文件都已經按照自己的要求拆分完成。當我開發完成這套腳本之后,我們團隊也在第一時間使用了,后續團隊在開發和版本同步升級過程中再也不需要手動拆頭文件,很大的節省團隊這上面的時間浪費。

結束語

在對遺留系統進行重構的過程中,首先需要解決一些糟糕的物理設計問題,通過可以經過一些初級的重構從而改善遺留系統的物理設計問題,然后才可以基于此之上才開始構建測試體系,然后再深度重構。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,523評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • 一、溫故而知新 1. 內存不夠怎么辦 內存簡單分配策略的問題地址空間不隔離內存使用效率低程序運行的地址不確定 關于...
    SeanCST閱讀 7,883評論 0 27
  • 湘西的風景很美,只是,很多故事有很美的開頭,卻沒有很美很美的結尾。 你們可能會想著,童話里的故事,可惜,我...
    Alen寒夢閱讀 280評論 5 4
  • 上周五,毒舌在公眾號上分享了中國醫學界的紀錄片《人間世》,豆瓣評分9.7分。引來大片電影愛好者喜歡! 周末翻開網絡...
    艾麗婭君閱讀 556評論 0 5