DIY一個代碼質量管理平臺
這不是一篇技術文章,所以請不要把她當成技術文章來讀。只是想表達一個開發人員對美好生活的憧憬。
背景
代碼質量,衡量開發人員成果物的重要標準。那麼,作爲開發人員如何能交付高質量代碼呢?不好意思,我也不知道正確答案:(。不過,我們代碼要經過,“靜態掃描、動態掃描、單元測試、集成測試、系統測試、性能測試……”,一系列的流程,我想這點大家應該是認同的。
我們再具體一點,一個開發人員從編碼到交付集成測試至少需要經過,靜態掃描、單元測試、動態掃描,並提供各階段的成果物。各階段成果物包括:靜態檢查結果、動態檢查結果、單元測試代碼、單元測試結果、單元測試覆蓋率等等。
如此多的交付內容,是否有統一的規範、格式以及審覈流程呢?根據各個項目具體要求這部分流程一定會有,但形式可能會有所不同,大多可能是以文本或表格的形式存在。但不管以哪種形式都不夠直觀(如果是直接看各種檢查工具的命令行輸出,我想一定會有頭暈、眼花等不同程度的生理反應:),我們總是期待我們世界會變得更加美好。
那麼……,好吧……,讓我們回到主題,《DIY一個代碼質量管理平臺》。
原材料
—— 君子生非異也,善假於物也。
人類社會的飛速發展主要仰仗於對已有資源、工具,經驗的利用和改造,不斷創造新的資源、新的工具並積累更多的寶貴經驗,並把她們不斷的傳承下去。
所謂原材料本質上也是一系列的工具,把它們定位成原材料是因爲要對它們的輸出結果進行再加工。
開始動手前,讓我們先了解一下原材料需要哪些,當然,所有的原材料都是免費的,所以不用太多考慮成本。
原材料清單如下:
靜態檢查(Cppcheck)
先看看市面上C/C++靜態檢查的原材料都有哪些?PC-Lint/FlexeLint、Splint、Cppcheck。
1.PC-Lint/FlexeLint:功能很強大。但是是商用的,要錢。
2.Splint:不要錢,但只適用於C。
3.Cppcheck:不要錢,C++可以用,而且可以和 jenkins集成,也可以和許多IDE進行集成(Eclipse,Codeblock,VS)。
·Cppcheck放進購物車`。
動態檢查(valgrind)
動態檢查材料選擇起來就比較簡單了,個人認爲valgrind
是功能最強大的,開源的動態檢查工具,沒有之一。有不同見解的小夥伴可以交流一下(其實我是想說,不服來辯)。
valgrind
不是一個工具,它是一個工具集,包括以下工具包:
Callgrind:它主要用來檢查程序中函數調用過程中出現的問題。
Cachegrind:它主要用來檢查程序中緩存使用出現的問題。
Helgrind:它主要用來檢查多線程程序中出現的競爭問題。
Massif:它主要用來檢查程序中堆棧使用中出現的問題。
Extension:可以利用core提供的功能,自己編寫特定的內存調試工具
valgrind添加到購物車
。
PS. kachegrind
這貨不錯,感興趣的可以試試。
單元測試(cppunit)
選單元測試材料就有點鬱悶了,我的首選是gtest
,但由於要傳承之前項目積累的成果,所以,退而求其次,cppunit進入了購物車,gtest暫時收藏
。
本質上沒有太大差別,但setup()/teardown(),選擇性執行用例,包括打樁,gtest的優勢還是比較明顯的。
代碼覆蓋率(gcovr/lcov)
這個也沒有必要多說,可選擇的不多。我們主要是用lcov,沒有別的原因,界面友好,幾乎不需要做加工。
再推薦一個gcovr,是python寫的C/C++的覆蓋率統計工具,效果和lcov差不多,可以考慮用gcovr來代替lcov,原因是lcov對so的支持不好,恰巧,我們項目使用了大量的so。必要時更換原材料。
lcov加入購物車,gcovr添加收藏
。
工具
—— 工慾善其事必先利其器。
原材料準備好了,找幾個能加工這些材料的工具。這塊不多說,工具太多,找幾個順手來用就可以了。
先選兩瓶膠水(語言);shell,python
再來一個能放在口袋裏的數據庫:sqlite
找個盒子把我們作品裝起來:Django/Beego
最後弄個漂亮點的包裝:bootstrap,jQuery,jqplot
開始動手
東北有句話,“能動手的,就儘量少說話。”
我們的目標是用最短的時間,最簡單的方法,實現一個實用的界面友好日常工具。
好吧……,儘量少說話!開工!!!
第一步 初加工
所謂初加工就是利用原材料自帶的屬性,通過參數,命令行選項得到我們想好的半成品。
導出cppcheck檢查結果
cppcheck --enable=all --xml you_code_dir 2 > check.xml
以xml格式保存靜態檢查結果。
導出valgrind檢查結果
valgrind --tool=memcheck --leak-check-full --xml=yes --xml-file=valgrind.xml
以xml格式保存動態檢查結果。
導出單元測試結果
上面說了,cppunit沒有gtest那麼好用了。需要改代碼才能輸出我們想要的結果。
std::ofstream xmlFileOut("unit.xml");
CppUnit::XmlOutputter xmlOut(&result, xmlFileOut);
統計代碼覆蓋率
# 先要加兩個編譯選項
-fprofile-arcs -ftest-coverage
# 再鏈一個庫
-lgcov
編譯執行後導出結果,直接用lcov
lcov -c -o cov.info -d ${.gcno .gcda目錄} -b ${源代碼目錄}
# 生成html
```bash
genhtml cov.info -o cov_html
至此,我們需要的靜態掃描,動態掃描,單元測試,測試覆蓋率的半成品結果都有了。
第二步 深加工
—— 言治骨角者,既切之而復磋之;治玉石者,既琢之而復磨之;治之已精,而益求其精也。
那麼,我們對上面的半成品再切磋琢磨一番。
數據入庫
個人習慣是把有規律的數據放到數據庫裏面,一是歸類保存方便,二是用sql查找要比grep更實用。如果有相應的客戶端,那麼你還會有個友好的操作界面。
處了代碼覆蓋率是html,先不管它。其它都是xml。
拿出一瓶標籤是python膠水,把xml粘到sqlit上。
#!/usr/bin/python
#coding=utf8
"""
# Author: frank
# Created Time : 2017-08-26 14:46:49
# File Name: import_cppcheck.py
# Description:
"""
from xml.dom.minidom import parse
import sqlite3
import os
import sys, getopt
def help():
print '''usage:
-h help
-f cppcheck result file *.xml_file
-d import sqlite3 db file *.db'''
sys.exit(1)
opts, args = getopt.getopt(sys.argv[1:], "hf:d:")
xml_file = ''
db_file = ''
for op, value in opts:
if op == "-f":
xml_file = value
elif op == "-d":
db_file = value
elif op == "-h":
help()
if xml_file == '' or db_file == '':
help()
# if os.path.exists(db_file):
# os.remove(db_file)
conn = sqlite3.connect(db_file)
c = conn.cursor()
c.execute('''CREATE TABLE CPPCHECK_RESULT
(ID INT PRIMARY KEY NOT NULL,
FILE TEXT,
LINE INT,
TYPE TEXT NOT NULL,
SEVERITY TEXT NOT NULL,
MSG TEXT NOT NULL);
''')
id = 0
#打開xml文件
dom = parse(xml_file)
results = dom.documentElement
errors = results.getElementsByTagName("error")
for error in errors:
id = id + 1
if error.getAttribute("file"):
f_file = error.getAttribute("file")
else:
f_file = ''
if error.getAttribute("line"):
f_line = error.getAttribute("line")
else:
f_line = 0
c.execute ('''INSERT INTO CPPCHECK_RESULT (ID,FILE,LINE,TYPE,SEVERITY,MSG)
VALUES (''' + str(id) + ''',"''' + f_file + '''",''' + str(f_line) + ''',"''' + error.getAttribute("id") + '''","''' + error.getAttribute("severity") + '''","''' + error.getAttribute("msg") + '''");''')
conn.commit()
conn.close()
很容易,去掉註釋也就50~60行。這個是將cppcheck結果導入sqlite的代碼。另外兩個xml的導入,如法炮製。稍微調整一下表結構,分分鐘就可以搞定。
但有人會問,爲啥分開寫呢?一個腳本把3個xml都讀到sqlite不行嗎?分開寫,可以分開用。拿出來也是個單獨的工具。
那麼,問題來了,分開寫調用的時候就需要調用3次,多麻煩!好吧,拿出另一瓶標籤是shell的膠水。
#########################################################################
# File Name: import.sh
# Author: Frank
# mail: [email protected]
# Created Time: 2017-08-26 21:51:15
#########################################################################
#!/bin/bash
set -e
function help()
{
echo "usage:
-h help
-c cppcheck result file *.xml
-u cppunit result file *.xml
-v valgrind result file *.xml
-d import sqlite3 db file *.db"
}
while getopts "c:u:v:d:h" arg
do
case $arg in
c)
check_file=$OPTARG
;;
u)
unit_file=$OPTARG
;;
v)
valgrind_file=$OPTARG
;;
d)
db_file=$OPTARG
;;
h)
help
exit 1
;;
?)
help
exit 1
;;
esac
done
if [ ! -n "$check_file" -o ! -n "$unit_file" -o ! -n "$valgrind_file" -o ! -n "$db_file" ] ;then
help
exit 1
fi
rm -rf $db_file
python import_cppcheck.py -f $check_file -d $db_file
python import_cppunit.py -f $unit_file -d $db_file
python import_valgrind.py -f $valgrind_file -d $db_file
粘上了,一個腳本搞定。
展示結果
把各種檢查結果寫到sqlite不是最終目的,要把它展示出來。
既簡單又快的方式,B/S結構是首選。做這種小東西其實php是最佳選擇,因爲*“php是世界上最好的編程語言”*,-這個段子大家應該聽過。用php的一個小問題就是部署稍稍有一點點麻煩(其實已經很簡單了,但有更簡單的,我們一定不用簡單的,這個是大原則!),雖然httpd服務linux默認安裝了,但還免不了一些配置。
好吧……,再見php!那用啥呢?Django,nodejs還是beego,部署簡單,不需要服務器,你想要的腳手架功能它們都有。
我先選了了Django,必要時還可以把sqlite這層去掉,直接python解析xml然後展示,看上去不錯!
說幹就幹。
pip install django -g
看似簡單的命令,但在向內網遷移的時候……,一萬頭XXX在我胸口奔騰而過。(XXX這玩意兒學名叫羊駝),各種包依賴,各種包版本問題,一個一個下載,一個一個手動安裝。本來是一根網線加一條pip命令可以解決的問題,結果鼓搗了一個多小時。
好吧……,不帶你玩了!有這時間都搞完了~~
穩妥點,不需要遷移,外網搞完了直接可以拿到內網用,妥妥的用Go,直接發佈二進制可執行文件總行了吧。
說幹就幹。儘量少說話,直接上圖吧。
代碼覆蓋率展示方面,lcov做了我們想要的一切,不做任何修改,直接拿來用。在Beego裏面直接設置其靜態目錄爲lcov導出html的文件夾目錄。
beego.SetStaticPath("/datas/cov_html","lcov")
和Django,nodejs一樣Beego做這上面的事情簡單得令人髮指。只需以下步驟:
- 寫個router指定跳轉的url與controler的關聯
- 利用自帶的orm,將表映射爲Go的struct,並寫一些簡單的查詢,完成model
- 通過controler調用model,處理數據,並在view中展示。
- 編寫view,靜態html加上template,將數據進行展示。
典型的MVC架構(這裏不講技術,況且我現在是個C++程序員:))。
至此,數據展示部分完成。“太low了吧,這玩意兒用你做?弄個sqlite客戶端就完了唄。”
好吧……,你說的對。但我們總是嚮往更加美好的世界……。
包裝
上面工序完成後幾乎和Excel沒啥區別,那就讓它更像一個工具吧。對它進行包裝。
可能看到這大家會有個疑問,如果只是展示,那爲什麼要把數據存到sqlite裏面,直接用xml一樣可以實現web展示功能。沒錯,但我並不想只是單純的展示,對這些數據指標進行分析是終極目標之一。
先來幾個我們關心的指標吧:
- 靜態檢查問題數
- 靜態檢查問題分佈情況
- 測試用例失敗數
- 測試通過率
- 代碼覆蓋率
- 動態檢查問題數
這些東西很容易搞定,因爲有sql:)。
數據方面利用數據庫能夠很方便的進行統計分析。如果有新的指標要統計,只要sqlite裏面有數據源,也是很容易實現的。
另一個層面的包裝就是美工了,這個我不專業,但js還是略知一二。其實不知道也沒關係,現成的東西一抓一大把,拿來就用,我們的原則是有現成的就對不去造輪子。
bootstrap,jqplot……,拿來就用。
儘量少說話,上圖~
至此,產品功能基本完成。
裝箱
收工前還有一個重要的事情,裝箱發佈。前面一直在提“拿來就用”這個詞,別人的東西我們拿來直接用,同樣,我們的東西要讓別人也能夠方便的“拿去就用”。那麼我們完成最後一道工序,裝箱發佈。
瞭解Beego的同學一定都知道Bee這個工具,她提供了一些非常方便的工具,其中一個就是打包。
在你的工程目錄下執行
bee pack
她會打包生成一個壓縮包,包括可執行文件、配置文件、靜態文件和自定義文件/文件夾,解壓後直接可以運行。
另外,bee bale
命令可以將所有的靜態文件變成一個變量申明文件,全部編譯到二進制文件裏面,用戶發佈的時候攜帶靜態文件(該命令尚未正式發佈)。
你會發現,世界真的是很美好,有如此多的工具可供我們是用,一條命令,分分鐘搞定。
另外一件事就是寫個一鍵腳本。看看腳本內容包括啥(這裏不貼代碼了,因爲所有命令,上文已經提到,剩下的就是寫上你的目錄)?
- 靜態檢查,指定生成xml到指定目錄
- 編譯程序(確定把上面說的兩個選項和lgcov庫鏈上)
- valgrind 執行測試用例,指定單元測試結果xml的目錄,指定valgrind結果文件目錄。
- 執行xml導入sqlite的腳本,指定sqlite輸出目錄。
- 執行ut_frame。
- 休息一下,泡杯茶。
- 打開瀏覽器,看看我們這次提交的質量吧。
至此,里程。
演進路線
v0.1 完成基本功能。
v0.2 兼容gcovr,以便支持對so覆蓋率的統計。
v0.3 點擊靜態/動態檢查問題記錄,可以定位到源代碼文件。
v0.5 支持更多的統計指標。
v0.7 支持多次檢查的數據結果對比,分析質量提升項。
v1.0 目標是以插件形式接入ITAS系統(作爲ITAS前期開發人員,再貢獻一點餘熱)
誰用誰拿,隨用隨拿。
尾聲
“更加美好的世界”是我們的嚮往。“拿來主義”是我們的原則。花幾個小時會讓你的世界變得更加美好(慚愧的是寫下上面的文字似乎用去了我更長的時間,所以請小編手下留情,酌情刪減,碼字不易啊~~)。
最後以一段魯迅先生的話作爲結束吧,先生說:”總之,我們要拿來。我們要或使用,或存放,或毀滅。那麼,主人是新主人,宅子也就會成爲新宅子。然而首先要這人沉着,勇猛,有辨別,不自私。沒有拿來的,人不能自成爲新人,沒有拿來的,文藝不能自成爲新文藝“。
參考文獻
- 《荀子·勸學》
- 《論語·衛靈公》
- 《拿來主義》