我們為什麼需要軟體工程——從一個簡單的項目進行觀察

  • 2020 年 11 月 7 日
  • 筆記

 

一、編譯和調試環境的準備

 

由於在剛下載vscode的時候就已經配置好環境了,這裡大概描述一下流程,首先下載安裝vscode中的C/C++插件,注意其中不包括編譯和調試工具,只有一些程式碼提示的編輯功能。

 

 

之後安裝windows版的GCC——MingW,並配置環境變數,最後將MingW的路徑配置到vscode的launch.json文件中。這裡我直接使用圖形介面配置。

 

 

使用g++ –version查看是否安裝成功。

 

 

由於menu這個項目已經有makefile文件來描述他的編譯過程,我們直接在項目的路徑中直接使用make指令來編譯整個工程。

但是由於MingW自身的問題,我們需要將MingW/bin目錄中的MingW-32-makefile.exe更名為make.exe,否則windows將無法識別make命令。

 

 

 

在編譯完成後我們執行生成的test.exe文件,簡單操作一下menu這個項目。

 

 

至此我們完成了環境的配置並簡單運行了menu工程。

 

二、從軟體工程的角度分析menu工程。

 

接下來我們逐個版本依次分析。

 

1、lab1:

任何軟體一開始都只有很小的雛形,之後由開發人員不斷在之前的基礎上開發,最終達到在需求分析中定義的目標,

所以說幾乎所有軟體都是迭代出來的,在每一次迭代中增加新的功能或者修改其中的bug。這裡的lab1就是整個工程剛開始的樣子。

 

2、lab2:

在lab2中,該工程有了最初的功能,即識別help,quit以及其他未定義的指令。最關鍵的是,在整個文件的頭部有了注釋,說明了跟項目有關的一些資訊。

 

 

3、lab3.1:

在lab3.1中,孟老師將命令進行了抽象,將命令定義成了一個結構體,把命令的名字、描述以及具體的動作組織在一起,同時將不同命令通過鏈表組織在了一起。這裡體現了模組化的思想。

這其中需要注意的是:

  這裡使用了函數指針,使得不同命令雖然操作不同但對外提供了相同的介面,這就讓c語言雖然不具備面向對象的性質但依然完成了類似多態的思想。

同時,使用函數指針方便我們在後期拓展命令數組head[ ]的時候只需要添加自己的行為即可,不必去修改其他的程式碼。在一定程度上體現了軟體工程的開閉原則,

即對擴展開放,對修改關閉。

 

 

這裡還有一個小細節,在lab2中,對面命令的最大長度直接使用一個數字進行定義,而這裡使用了宏定義,消滅了magic number,增強了程式碼的可讀性同時便於修改。

下圖為lab2中的定義方式:

          

一下為lab3.1中的定義方式:

 

 

 

 

4、lab3.2:

在lab3.2中,孟老師將在鏈表中查詢以及展示所有命令這兩個行為抽象成兩個新的函數。目的是方便後期對於這兩個行為的修改,

例如我們不使用鏈表存儲命令,改用紅黑樹或者哈希表的時候,只需要修改FindCmd這個函數即可,而不需要修改main函數中的程式碼,實現了查詢命令操作與整個流程式控制制的解耦。

還有一個好處就是我們可以在其他工程中復用這個函數,如果需要的話。這裡體現了模組化的思想。

 

下圖為FindCmd的定義和在main函數中的調用:

 

5、lab3.3:

在lab3.3中,孟老師將與鏈表的定義和相關的操作放在了單獨的文件中,實現了鏈表操作與主程式控制過程的解耦。這裡依然是模組化的思想,方便維護以及調試程式。

 

 6、lab4:

在這個版本中,孟老師引入了一個可重用的鏈表,將鏈表的各種操作這一可復用的部分抽象出來,方便在其他工程中繼續使用。這部分是可重用介面的使用。

同時對鏈表的相關操作進行了簡單的單元測試,確保該模組的正確性。這裡值的注意的是,可復用鏈表的節點類型是tLinkTableNode*,而真實存儲數據的節點類型是tDataNode*,

為了解決這個問題,孟老師在進行鏈表操作時將tDataNode*強制類型轉換為tLinkTableNode*,使得將業務部分的數據對鏈表操作的屏蔽,同時也完成了業務對可重用介面的解耦。

下圖是兩個強轉的例子:

 

 

 

7、lab5.1 & lab5.2

在這兩個版本中,FindCmd函數加入了callback機制,即在函數傳參時傳入一個函數,然後可以在調用函數中使用這個函數。

使用callback的好處是,可以使得程式更加靈活增加可復用性,同時也能增強函數的功能。

 

 

在這個例子中,我們可以根據不同的搜索條件只修改SearchCondition函數中的程式碼即可達成目的,而不用修改SearchLinkTableNode函數。

如果這裡沒有使用callback,我們必須修改SearchLinkTableNode中的程式碼,一定程度上違背了開閉原則。

還有一個更能體現callback機制的好處的例子。在C++ Stl中內置了可以遍歷Stl容器的函數for_each(_InIt _First, _InIt _Last, _Fn _Func)。

其中前兩個參數是要遍歷的起始迭代器和終止迭代器,第三個參數是在遍歷時執行的動作,可以是一個函數。

因為在遍歷的時候可以進行很多動作,例如輸出容器,對容器求和等,如果不適用callback,Stl需要針對每一種遍歷動作都進行相應的操作,

而在使用callback之後,Stl只需要編寫遍歷容器這一個動作即可,具體的動作可以通過回調機制由Stl的使用者自己定義並傳入。

 

 

由於在lab5.1中進行命令比較的時候使用了全局變數,使得原本只用數據耦合的模組變為公共耦合。

於是在lab5.2版本中,將被用來比較的cmd參數化並將其類型置為void* ,在降低耦合程度的同時,完成了業務對可重用介面的屏蔽。

 

 

 

 

 

 

在5.x版本中還加入了makefile文件,方便進行整個工程的編譯。

 

8、lab7.1 & lab7.2

在7.1中,孟老師對鏈表操作加入了鎖機制,保證整個鏈表操作函數是可重入的,方便多執行緒軟體對於他們的調用。

同時,還將之前的流程式控制制部分從主函數中抽象成ExecuteMenu函數,繼續進行解耦。還將menu的定義和實現分離成.c和.h文件,將menu設計成獨立的模組。

在7.2加入了readme.txt,對整個模組的功能和編譯方式進行了說明。

 

三、總結:

 

通過這個例子,我對軟體工程有了更深刻的認識。軟體工程產生的背景就是軟體的規模越來越大,變化越來越多,使得整個軟體項目難以維護和管理。

而軟體工程的出現就是為了解決這些難題,使得開發人員可以更從容的面對變化,提高軟體模組的復用性。menu工程雖然程式碼不多,但在多個地方體現了抽象的思想,

這帶給我們的啟示是—— 在軟體設計與實現時,更多考慮介面的抽象而不是具體的實現,並且考慮哪些部分是可能變化的,哪些部分是可以重複使用的。