1. 程式人生 > 實用技巧 >vue Router路由自我總結

vue Router路由自我總結

這節課講了程式碼中的軟體工程,通過分析一個選單子系統程式menu的開發過程,介紹了這個小程式由雛形發展到成型過程中所設計的軟工的思想,這種程式碼的"生長"過程十分的heuristic,值得仔細推敲。

課程ppt的地址和小程式menu的連結在這兒:參考資料

1.C/C++編譯除錯環境配置

1.1安裝Visual Studio Code

這裡的ide選擇使用vscode,vscode是一個輕量級且十分強大的程式碼編輯器,支援Windows, OS X和Linux。擁有豐富的外掛生態系統,課通過安裝外掛來支援C++, C#, Python, PHP等其他語言。(vscode下載地址

1.2安裝C/C++擴充套件外掛

開啟VSCode——點選側欄上的擴充套件外掛圖示——搜尋C++——點選安裝

1.3安裝C/C++編譯器和偵錯程式

C/C++擴充套件外掛中並不包含C++的編譯器和偵錯程式,需要我們自己下載。常用的編譯器有:

  • GCC on Linux -https://gcc.gnu.org
  • Mingw-w64 is GCC for Windows 64 & 32 bits - http://mingw-w64.org
  • Microsoft C++ compiler on Windows
  • Clang for Xcode on macOS

我用的是windows64位作業系統,這裡選擇下載並安裝MinGW-W64-install.exe

安裝過程中有幾個選項需要說明:

  • Version制定版本號,從4.9.1-8.x.0,按需選擇,沒有特殊要求就用最新版
  • Architecture跟作業系統有關,64位系統選擇x86_64,32位系統選擇i686
  • Threads設定執行緒標準可選posix或win32
  • Exception設定異常處理系統,x86_64可選為seh和sjlj,i686為dwarf和sjlj
  • Build revision構建版本號,選擇最大即可

安裝完成後,將目錄下的/bin目錄新增進環境變數後,開啟cmd,執行gcc -v看看是否安裝成功,如果看到版本號,說明安裝成功了!下圖是安裝成功時的樣子:

在你喜歡的地方建立一個C/C++專案,然後在這個目錄下開啟cmd,輸入"code ."命令,開啟vscode,這個目錄就成了你的workspace,隨便寫一段程式碼,按ctrl+F5,會選擇除錯環境和編譯器,除錯選擇C++(GDB/LLDB),編譯器選擇gcc.exe(針對C,g++.exe針對cpp),然後你的workspace下會生成一個.vscode的隱藏檔案(對於linux),裡面有兩個檔案:

  • tasks.json(build instruction)
  • launch.json(debugger settings)

其中tasks.json時告訴vscode怎麼構建這個程式的,他會呼叫gcc/g++編譯器建立一個可執行檔案。如圖中args的引數所示。

按ctrl+F5後終端就會顯示執行結果。

2.程式碼中的軟體工程

2.1模組化設計

模組化(Modularity)是在軟體系統設計時保持系統內部各部分相對獨立,以便每個部分可以被獨立的進行設計和開發。這個做法背後的原理就是關注點的分離,這是在軟體工程領域中最重要的原則,習慣上稱之為模組化,翻譯成中文的表述其實就是“分而治之”的方法。這就好比人處理複雜的問題時容易出錯,而將複雜問題拆解為一個個簡單的問題,就不那麼容易出錯了。

模組化在軟體工程中的體現就是每一個軟體模組只有一個單一的功能目標,並且相對獨立與其它軟體模組。這樣做有幾個好處:

  • 軟體模組容易理解並且容易開發
  • 如果出現bug,更容易定位並修復
  • 對整個軟體系統做出更改和維護也更容易,因為模組之間相互獨立

通常用耦合度和內聚度來衡量軟體模組化的程度。

下面分析menu程式中的模組化思想。

/********************************************************************/
/* Copyright (C) SSE-USTC, 2012-2013                                */
/*                                                                  */
/*  FILE NAME             :  linktabe.h                             */
/*  PRINCIPAL AUTHOR      :  Mengning                               */
/*  SUBSYSTEM NAME        :  LinkTable                              */
/*  MODULE NAME           :  LinkTable                              */
/*  LANGUAGE              :  C                                      */
/*  TARGET ENVIRONMENT    :  ANY                                    */
/*  DATE OF FIRST RELEASE :  2012/12/30                             */
/*  DESCRIPTION           :  interface of Link Table                */
/********************************************************************/

/*
 * Revision log:
 *
 * Created by Mengning,2012/12/30
 *
 */

#ifndef _LINK_TABLE_H_
#define _LINK_TABLE_H_

#include <pthread.h>

#define SUCCESS 0
#define FAILURE (-1)

/*
 * LinkTable Node Type
 */
typedef struct LinkTableNode
{
    struct LinkTableNode * pNext;
}tLinkTableNode;

/*
 * LinkTable Type
 */
typedef struct LinkTable tLinkTable;

/*
 * Create a LinkTable
 */
tLinkTable * CreateLinkTable();
/*
 * Delete a LinkTable
 */
int DeleteLinkTable(tLinkTable *pLinkTable);
/*
 * Add a LinkTableNode to LinkTable
 */
int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
/*
 * Delete a LinkTableNode from LinkTable
 */
int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);
/*
 * Search a LinkTableNode from LinkTable
 * int Conditon(tLinkTableNode * pNode,void * args);
 */
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args);
/*
 * get LinkTableHead
 */
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);
/*
 * get next LinkTableNode
 */
tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode);

#endif /* _LINK_TABLE_H_ */

/**************************************************************************************************/ /* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015 */ /* */ /* FILE NAME : menu.c */ /* PRINCIPAL AUTHOR : Mengning */ /* SUBSYSTEM NAME : menu */ /* MODULE NAME : menu */ /* LANGUAGE : C */ /* TARGET ENVIRONMENT : ANY */ /* DATE OF FIRST RELEASE : 2014/08/31 */ /* DESCRIPTION : This is a menu program */ /**************************************************************************************************/ /* * Revision log: * * Created by Mengning, 2014/08/31 * */ #include <stdio.h> #include <stdlib.h> #include "linktable.h" int Help(); int Quit(); #define CMD_MAX_LEN 128 #define DESC_LEN 1024 #define CMD_NUM 10 /* data struct and its operations */ typedef struct DataNode { tLinkTableNode * pNext; char* cmd; char* desc; int (*handler)(); } tDataNode; int SearchCondition(tLinkTableNode * pLinkTableNode, void * args) { char * cmd = (char*) args; tDataNode * pNode = (tDataNode *)pLinkTableNode; if(strcmp(pNode->cmd, cmd) == 0) { return SUCCESS; } return FAILURE; } /* find a cmd in the linklist and return the datanode pointer */ tDataNode* FindCmd(tLinkTable * head, char * cmd) { return (tDataNode*)SearchLinkTableNode(head,SearchCondition,(void*)cmd); } /* show all cmd in listlist */ int ShowAllCmd(tLinkTable * head) { tDataNode * pNode = (tDataNode*)GetLinkTableHead(head); while(pNode != NULL) { printf("%s - %s\n", pNode->cmd, pNode->desc); pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode); } return 0; } int InitMenuData(tLinkTable ** ppLinktable) { *ppLinktable = CreateLinkTable(); tDataNode* pNode = (tDataNode*)malloc(sizeof(tDataNode)); pNode->cmd = "help"; pNode->desc = "Menu List:"; pNode->handler = Help; AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode); pNode = (tDataNode*)malloc(sizeof(tDataNode)); pNode->cmd = "version"; pNode->desc = "Menu Program V1.0"; pNode->handler = NULL; AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode); pNode = (tDataNode*)malloc(sizeof(tDataNode)); pNode->cmd = "quit"; pNode->desc = "Quit from Menu Program V1.0"; pNode->handler = Quit; AddLinkTableNode(*ppLinktable,(tLinkTableNode *)pNode); return 0; } /* menu program */ tLinkTable * head = NULL; main() { InitMenuData(&head); /* cmd line begins */ while(1) { char cmd[CMD_MAX_LEN]; printf("Input a cmd number > "); scanf("%s", cmd); tDataNode *p = FindCmd(head, cmd); if( p == NULL) { printf("This is a wrong cmd!\n "); continue; } printf("%s - %s\n", p->cmd, p->desc); if(p->handler != NULL) { p->handler(); } } } int Help() { ShowAllCmd(head); return 0; } int Quit() { exit(0); }

上面兩段程式碼中,第一段是這個小程式對底層用到的連結串列的資料結構以及其操作的定義,第二段是對這個小程式的業務邏輯的實現。可以看到,第一段中的函式只與連結串列有關,這就是模組化化設計的思想,將底層資料結構的定義與業務邏輯的實現分離,要求第一個模組只做一件事,就是讓它做好連結串列資料結構和操作的設計,不設計選單業務功能,從而達到易理解,易開發,易維護,易擴充套件的目的。

2.2可重用介面

2.2.1介面的理解

為什麼要有介面這個東西?我們做好了初步的模組化設計,每個模組的功能內聚,模組與模組之間相對獨立,但是,最終做成整個專案,是要把這些模組拿來用的,有點像搭積木一樣搭起來,而為了讓搭好的積木整體不散架,每個積木之間的接觸部分就要相配合。

介面是互相聯絡的雙方共同遵守的一種協議規範,在軟體系統內部一般的介面方式時通過定義一組API函式來約定軟體模組之間的溝通方式。換句話說,介面定義了軟體模組對系統的其他部分提供了怎樣的服務,以及系統的其他部分如何訪問做提供的服務。

在面向過程的程式設計中,介面定義了資料結構及操作這些資料結構的函式;而在面向物件的程式設計中,介面是對外開放的一組屬性和方法的集合。一般來說,介面規格包含五個基本要素:

  • 介面的目的
  • 介面使用前所需要滿足的條件,一般稱為前置條件或假定條件
  • 使用介面的雙方遵守的協議規範
  • 介面使用之後的效果,一般稱為後置條件
  • 介面所隱含的質量屬性

2.2.2以一個例子分析介面

tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable);

這是從連結串列中取出連結串列頭節點的函式宣告,以此為例我們先來理解介面規格包含的五個基本要素。

  • 該介面的目標是從連結串列中取出連結串列的頭節點,函式名GetLinkTableHead清晰明確地表明瞭介面的目標;
  • 該介面的前置條件是連結串列必須存在使用該接口才有意義,也就是連結串列pLinkTable != NULL;
  • 使用該介面的雙方遵守的協議規範是通過資料結構tLinkTableNode和tLinkTable定義的;
  • 使用該介面之後的效果是找到了連結串列的頭節點,這裡是通過tLinkTableNode型別的指標作為返回值來作為後置條件,C語言中也可以使用指標型別的引數作為後置條件;
  • 該介面沒有特別要求介面的質量屬性,如果搜尋一個節點可能需要在可以接受的延時時間範圍內完成搜尋
int ShowAllCmd(tLinkTable * head)
{
    tDataNode * pNode = (tDataNode*)GetLinkTableHead(head);
    while(pNode != NULL)
    {
        printf("%s - %s\n", pNode->cmd, pNode->desc);
        pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode);
    }
    return 0;
}

這是呼叫了這個介面的地方。傳入的引數head遵守了介面定義中的形參型別要求,是tKinktable*,而使用了這個介面後得到了連結串列的頭節點,這裡又用型別轉換轉成了別的型別的節點指標,隱含了多型的思想。並且由於輸出型別可以被轉換,大大增加了這個介面的可重用性。別的地方如果需要獲得連結串列的頭節點,也可以用這種方式來獲取。

2.3執行緒安全

2.3.1執行緒的定義

執行緒是作業系統能夠進行運算排程的最小單位。他包含在程序之中,是程序中的實際運作單位。一個執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務。一般預設一個程序中只包含一個執行緒。多核多執行緒的CPU可以讓不同的程序執行在不同的CPU核的不同執行緒上,從而大大減少程序排程程序切換的資源消耗。

2.3.2 可重入函式

可重入(reentrant)函式可以由多於一個任務併發使用,而不必擔心資料錯誤。相反,不可重入(non-reentrant)函式不能由超過一個任務所共享,除非能確保函式的互斥(或者使用訊號量,或者在程式碼的關鍵部分禁用中斷)。可重入函式可以在任意時刻被中斷,稍後再繼續執行,不會丟失資料。可重入函式要麼使用區域性變數,要麼在使用全域性變數時保護自己的資料。

2.3.3 執行緒安全

如果你的程式碼所在的程序中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。

執行緒安全問題都是由全域性變數及靜態變數引起的。若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行讀寫操作,一般都需要考慮執行緒同步,否則就可能影響執行緒安全。

2.3.4 函式的可重入性與執行緒安全之間的關係

可重入的函式不一定是執行緒安全的,可能是執行緒安全的也可能不是執行緒安全的;可重入的函式在多個執行緒中併發使用時是執行緒安全的,但不同的可重入函式(共享全域性變數及靜態變數)在多個執行緒中併發使用時會有執行緒安全問題;不可重入的函式一定不是執行緒安全的。

2.3.5 menu專案中的執行緒安全分析

這個專案中的可重入函式是對連結串列的各個操作,為了能保持各個執行緒執行這些操作時能保持執行緒安全,在連結串列中添加了互斥鎖。

/*
 * LinkTable Type
 */
struct LinkTable
{
    tLinkTableNode *pHead;
    tLinkTableNode *pTail;
    int            SumOfNode;
    pthread_mutex_t mutex;
};

如果要用到修改連結串列的操作,就得先上鎖,防止別的執行緒訪問時得到非預期的結果。

tLinkTableNode * p = pLinkTable->pHead;
pthread_mutex_lock(&(pLinkTable->mutex));//上鎖
pLinkTable->pHead = pLinkTable->pHead->pNext;
pLinkTable->SumOfNode -= 1 ;
pthread_mutex_unlock(&(pLinkTable->mutex));//釋放
free(p);

3.總結

模組化設計,可重入介面,執行緒安全是軟體工程中很重要的思想,這三者相輔相成,缺一不可,沒有可重入,模組化就沒有用武之地,沒有執行緒安全,可重入就可能帶來災難性的而後果,而沒有模組化設計,可重入核線程安全就無從談起。