軟體設計中的可除錯性
軟體除錯是我們學習軟體開發的第一課,開發往往大部分的時間不是在寫程式碼,而是在查 Bug,相信大家也深有體會。我們有很多手段可以除錯問題,除錯最常用的手段包括打日誌、GDB、分析堆疊、跟蹤系統呼叫等等。但要怎麼樣才能從設計開始就考慮降低除錯門檻,當我們的程式碼出現問題時能快速定位到問題呢?
本場 Chat 您將學到如下內容:
- 瞭解如何通過設計的手段降低除錯門檻;
- 什麼樣的程式碼比較易於除錯問題;
- 出現問題怎麼儲存現場;
- 怎麼分析和除錯問題。
軟體除錯是我們學習軟體開發的第一課,開發往往大部分的時間不是在寫程式碼,而是在查 Bug,相信大家也深有體會。
我們有很多手段可以除錯問題,除錯最常用的手段包括打日誌、GDB、分析堆疊、跟蹤系統呼叫等等。
但要怎麼樣才能從設計開始就考慮降低除錯門檻,當我們的程式碼出現問題時能快速定位到問題呢?
本場 Chat 您將學到如下內容:
- 瞭解如何通過設計的手段降低除錯門檻;
- 什麼樣的程式碼比較易於除錯問題;
- 出現問題怎麼儲存現場,怎麼分析和除錯問題;
由於軟體除錯本身是一門很複雜的技術,而且每個領域各不相同,不同方向上具體方法差別很大,所以本文不打算描述具體的除錯方法,比如怎麼用 gdb、怎麼分析 core、怎麼查記憶體洩漏等。
本文會重點放在描述如何通過在設計階段就考慮軟體除錯性,即如何通過設計上的提前考慮儘可能地降低後期的除錯門檻,提升軟體的整體質量。
期望本文能帶給大家一些思考,但由於個人知識面有限,寫作時間也有限,考慮到的問題可能不夠完善,歡迎大家指正和探討。
本文目錄
什麼是可除錯性
關於軟體開發中的可除錯性,每個人都有不同的看法,通常大家會覺得就是方便查 bug,當然這個想法是沒有問題的, 但過於籠統。
為了便於描述,我在這裡先下個不太準確的定義。
這裡講的軟體可除錯性,主要包含兩部分:
- 程式碼編碼完成後,能快速驗證是否達到預期結果
- 當結果與預期不一致時,能快速定位到問題原因
關於第一點,這裡說的能快速驗證是否達到預期結果,大家都覺得比較簡單,但實際上並不容易,特別是大型軟體開發的中,驗證成本是很高的,比如改一行程式碼,有可能需要對整個專案重新編譯、需要準備測試環境、需要執行各種測試案例等等。面且還不一定靠譜,因為這裡的驗證指的是對各種輸入的驗證,包括各類正常的和異常的輸入,大部分情況下,如果我們只是對功能做驗證,是比較難保證完全可靠的。這時就要考慮我們程式碼是否有設計良好的邊界,比如模組與模組之間是否強耦合,單個模組是否可以很方便地做測試等。
關於第二點,就涉及到具體的技術問題了,當出現問題時,我們會分析一下結果和程式碼,有經驗的程式設計師都能快速定位到問題。現代軟體開發中,很多時候由於框架層面已經為我們考慮了很多事情,我們很容易就能拿到模組相關的日誌、狀態等資訊,從而快速定位到問題點。少數情況下我們需要開啟 IDE 稍加除錯,或斷個點。 但如果我們嘗試考慮軟體整體上的可除錯性時,問題就會變得相對複雜很多。比如什麼情況下該打日誌,怎麼打?怎麼將系統的狀態透出來?如果 bug 不可重現,我們該怎麼除錯?
為什麼需要考慮可除錯性
很多人對於除錯的第一反應是,出現 bug 就除錯一下,除錯那是 bug 出現之後的事情。
甚至很多人覺得,除錯只能在開發過程中通過 IDE 來做,如果沒有 IDE 或者開發環境除錯就很難進行。
當然這是不對的,除錯可以發生在軟體生命週期的各個階段,而能不能從容應對,就考驗設計者對可除錯性的考慮。
軟體設計是一門很複雜的手藝,使用者可見的需求只是冰山上的一角,軟體設計者在整個設計過程中, 需要考慮到所有利益方的訴求。比如對於編碼者,如何快速編碼。對於測試人員,如何方便測試。對於運維方,如何開心地運維。
而可除錯性,又涉及到多個利益相關方,並直接影響軟體的整體設計,是非常重要的一環。
從系統層面上分析,我們會發現,可除錯性做的很差的程式碼,往往質量也會很差,並且開發效率也不會太高。
試想如果每寫一段程式碼都需要經過很繁瑣的驗證,每發現一個問題,都需要發很長時間去定位,那開發者想必也是很崩潰的。
由於對預期結果難於驗證、問題難於定位,往往就容易偷懶,自測做不到位,結果就會容易導致質量下降,質量下降就會導致後期更多的問題,從而陷入開發困局。
可除錯性這塊,設計初期就應該想考慮清楚怎麼去做。一般來說可除錯性很好的軟體必然是一個強內聚、弱耦合、介面明確、意圖明晰的軟體,而可除錯性差的的軟體往往具有過強的耦合和混亂的邏輯。
所以可除錯性的好壞,從某些方面來講又直接代表了一個軟體的好壞,那當然是值得我們去提前考慮的事情。
怎麼設計提高可除錯性
對於軟體的設計者而言,關於可除錯性的考慮,我認為至少需要包含幾個方面:
- 良好的邊界
- 易於測試
- 易於理解
- 可觀察性
良好的邊界
這裡說的邊界,指的是軟體中模組與模組間、類與類間、程式碼與程式碼間的邊界。
一段好的程式碼,需要保證邊界清晰,職責分明,否則就會變得很難維護,一旦出現問題,就會因為範圍太大而除錯起來耗時耗力。
模組與模組之間耦合性太大, 不僅會影響擴充套件性、複用性、維護性等,還會影響單元測試、整合測試;我們在專案中會聽到” 我的模組依賴太多單測沒法做”,這種問題歸根結底還是設計問題,耦合性太強導致。
函式與函式之間強繫結, 單測時為了測一個函式就不得不對另外一堆模組打樁;模組與模組之間強繫結, 聯調測試一個模組時就不得不對另外一堆模組打樁;良好的邊界也是軟體易於測試和易於理解的基礎,下面以 Linux 為例講述為什麼良好的邊界如此重要。
Linux 系統本身的設計是很值得學習的,如下圖展示的 Linux 層次結構圖:
Linux 在分層和解耦上,都做的非常出色,各子系統間介面非常清晰,這既讓整個系統易於理解、又讓整個系統易於除錯和測試。
實際上 Linux 系統內部是非常複雜的,下圖展示的 Linux 詳細的內部實現:
大部分讀者看到這張圖都有點眼花了,但這麼複雜的實現,Linux 在設計上仍然能做到易於理解和維護,這很大一部分原因在於,Linux 為每個部件設定了非常清晰的邊界,並給出了明確的介面。所以我們在設計系統時也應該儘可能做到邊界清晰。
可觀察性
留下有效日誌
日誌是程式開發中最常見的除錯方法,也可以說是最有用的手段。Linux 之父 Linus Torvalds 就說過,他從不用任何 Debugger 工具。
本文也不打算介紹任何 Debugger 工具,但是打算重點聊一下日誌。
雖然日誌是大家都會用的一個除錯手段,但並沒有多少人能打的很好。打日誌是很有講究的一個事情,日誌的內容需要交代清楚: 時間、地點 (程式位置)、物件、關鍵因素,如果列印所在的函式有多個呼叫點,最好交代清楚呼叫棧。
例如列印函式呼叫失敗:
初級打法:
call function fail
有效打法:
201809110800 [test.c:100] new conn src ip:port :%u.u.u.u:%d dst ip:port %u.u.u.u:u alloc memory fail size=%d
有效日誌應該包含以下特徵:
- 日誌分級, 不能所有級別的日誌混成一團, 出了問題時應該能快速定位到所需日誌。
- 日誌不能打太頻繁,需要考慮限速。
- 在關鍵位置需要留下日誌記錄,比如可能出錯的地方。
- 有執行時開關,可動態調整日誌輸出。
下在舉幾個日誌的例子:
1、下面是一段儲存軟體的日誌
這段日誌詳細地記錄了所有會修改到磁碟的操作,並單獨清晰地記錄下來,這樣一旦發現儲存資料出現問題,只需要通過分析日誌就一定能查到問題點,找出是什麼時間哪個操作導致的資料問題。
[2017-02-22 14:23:42.053233] : mkdir:31824, path: /images/cluster [2017-02-22 14:23:42.141262] : mkdir:31824, path: /images/cluster/vst_tmp1.vm [2017-02-22 14:23:42.255441] : create:31833, path: /images/cluster/vst_tmp1.vm/vm-disk-1.qcow2 [2017-02-22 14:23:44.156812] : create:32213, path: /images/cluster/vst_tmp1.vm/3436121041554.conf[2017-02-22 14:23:47.581945] : mkdir:32355, path: /images/cluster/vst_tmp2.vm [2017-02-22 14:23:53.802746] : mkdir:582, path: /images/cluster/vst_tmp3.vm [2017-02-22 14:23:53.972376] : create:600, path: /images/cluster/vst_tmp3.vm/vm-disk-1.qcow2 [2017-02-22 14:23:56.344781] : create:994, path: /images/cluster/vst_tmp3.vm/3875145219722.conf[2017-02-22 14:23:58.745449] : mkdir:1104, path: /images/cluster/vst_tmp4.vm [2017-02-22 14:24:08.479255] : mkdir:4879, path: /images/cluster/vst_tmp5.vm [2017-02-22 14:24:08.672113] : create:5005, path: /images/cluster/vst_tmp5.vm/vm
2、某容器產品日誌
該日誌記錄配置的更改過程,將變更前後的對比一併輸出,這樣後續如果發現配置出現問題,就可以通過分析日誌打到哪個時間點導致配置出現了問題。
如下日誌可以清楚地看到變化的資料,版本號從 4 到 5,update 時間修改,members 修改,這種日誌一條足夠了然。
2017-05-31 14:28:27.005945 info [sfvt_docker-status] 28994 cluster:393 change cluster from {id:'cluster-1aa5324f231e' version:4 update:'05-31 14:27:39' master:0/8527115551848 members:3 [1013873187874,3510809305044,8527115551848] to {id:'cluster-1aa5324f231e' version:5 update:'05-31 14:28:27' master:0/8527115551848 members:4 [1013873187874,3510809305044,7616676694300,8527115551848]
系統狀態可視
這裡講的系統狀態可視,指的是系統設計中,考慮能夠將部分核心狀態透出來。從而能在執行過程中,直接檢視系統的執行狀態,方便動態跟蹤軟體情況。
1、比如下面的 Golang 內建的 pprof 機制
從上圖可以看到所有 Goroutine 的執行狀態, Goroutine 是 Golang 比較重要的概念,並且容易出錯和不方便除錯。但有了 pprof,事情就變得簡單了很多。
2、某容器管理平臺提供工具快速檢視整個叢集核心元件執行狀態
整個容器平臺,是分散式部署的,本身涉及到的元件和狀態非常多,如果出了問題逐一排查,是比較麻煩的事情,通過下面的工具, 可以將整個分散式系統的大部分元件狀態,快速透出來,從而簡單系統除錯。
[email protected]:/# sxfdcloudctl status[hosts]192.168.139.233 node-005056b80c59192.168.139.232 node-005056b8493c192.168.139.231 node-005056b86c82192.168.139.234 node-005056b86ee6[haproxy]......[majorconfig]......[etcd]member 27fbacb64f7266ce is healthy: got healthy result from https://node-005056b80c59:12379member 71fd66d8f282f9cd is healthy: got healthy result from https://node-005056b86ee6:12379member 864d4384881ea091 is healthy: got healthy result from https://node-005056b86c82:12379cluster is healthy[majors]{ "etcd": { "v3": { "name": "node-005056b86ee6", "initialCluster": "node-005056b86ee6=https://node-005056b86ee6:2380", "initialClusterState": "existing", "initialClusterToken": "cluster-1e92bbce" } }, "majorPodName": "kube-major-v1-x4h7c"}.....[node]NAME STATUS AGE LABELSnode-005056b80c59 Ready 21d kontroller.sxfdcloud.io/ElasticsearchNode=true,kontroller.sxfdcloud.io/MajorCandidate=true,kontroller.sxfdcloud.io/Name=node-005056b80c59node-005056b8493c Ready 21d kontroller.sxfdcloud.io/ElasticsearchNode=truenode-005056b86c82 Ready 21d kontroller.sxfdcloud.io/MajorCandidate=true,kontroller.sxfdcloud.io/Name=node-005056b86c82node-005056b86ee6 Ready 21d kontroller.sxfdcloud.io/ElasticsearchNode=true,kontroller.sxfdcloud.io/MajorCandidate=true,kontroller.sxfdcloud.io/Name=node-005056b86ee6[kube-base]NAME READY STATUS RESTARTS AGEetcd-node-005056b80c59 1/1 Running 0 8hetcd-node-005056b86c82 1/1 Running 0 1hetcd-node-005056b86ee6 1/1 Running 0 8hkube-apiserver-node-005056b80c59 1/1 Running 0 8hkube-apiserver-node-005056b86c82 1/1 Running 0 1hkube-apiserver-node-005056b86ee6 1/1 Running 1 8hkube-scheduler-node-005056b80c59 1/1 Running 0 8hkube-scheduler-node-005056b86c82 1/1 Running 0 1hkube-scheduler-node-005056b86ee6 1/1 Running 0 8h.....[kube-system]NAME READY STATUS RESTARTS AGEheapster-v1-lhxdk 1/1 Running 6 1dinfluxdb-v1-dq0jt 1/1 Running 6 1dkube-dns-v1-lrvqr 1/1 Running 1 10hkube-kingress-v1-13kjd 1/1 Running 20 6dkube-kingress-v1-3m3wr 1/1 Running 15 6dkube-kingress-v1-97hqx 1/1 Running 18 6dkube-kingress-v1-c74vc 1/1 Running 17 6dkube-major-v1-5ln7p 1/1 Running 0 1hkube-major-v1-rtvsp 1/1 Running 0 8hkube-major-v1-x4h7c 1/1 Running 0 8h......
3、 通過 SIGUSR1 輸出內部狀態
和執行時開關類似,如果不想中斷程式,檢視內部狀態,就需要有執行時檢視的介面,比如通過 SIGUSR1 可以輸出內部狀態。
下面的機制,通過工具直接檢視程式的內部狀態,輸出三個節點的狀態資訊:
Sangfor:aCloud/node-332 /usr/lib/python2.7/site-packages/mmon # python mon.py -c Remote=Status --param1=all --remoteNode=39.39.0.639.39.0.5: start: 01-21 10:21:39 end: 01-21 10:26:18 nodeIP: 39.39.0.5 pid: 53311 count: 8 status: OK pom: 011 vipNode: 39.39.0.6 nodesCount: 3 masterIP: 39.39.0.6 onlines: 39.39.0.5,39.39.0.6,39.39.0.8 desc: SLAVE OK, Behind master(1s) SQL delay(0s)39.39.0.6: start: 01-21 10:23:38 end: 01-21 10:26:09 nodeIP: 39.39.0.6 pid: 8611 count: 3 status: OK pom: 111 vipNode: 39.39.0.6 nodesCount: 3 masterIP: 39.39.0.6 onlines: 39.39.0.5,39.39.0.6,39.39.0.8 desc: master is ok and in sync mode39.39.0.8:get status failed: connect to (39.39.0.8:54213) failed [Errno 111] Connection refused
可線上除錯
上節講的是在執行過程中,將系統狀態直接展示出來,這樣除錯時就可以有更多的系統資訊,從而在不通過 Debugger 的情況下,也能輕鬆除錯。
但有時候,光看狀態還不夠,需要能線上修改程式狀態, 提供一些直接修改系統內部狀態的手段,比如通過訊號、套接字等。
機制上和上節的內容相似,可以參考 Linux 的 proc 等機制,這裡不做詳細展開。
總結
本文主要跟大家講述了什麼是可除錯性以及如何提高系統的可除錯性,歡迎大家積極討論。
一場場看太麻煩?成為 GitChat 會員,暢享 1000+ 場 Chat !點選檢視
相關推薦
軟體設計目標—可維護性—基於面向物件技術的計算機程式
import java.io.*; class client{ public static void main(String[] args){ int intNumberA=0, intNumberB=0; try{ BufferedReader bufR = new Buffer
軟體設計中的可除錯性
軟體除錯是我們學習軟體開發的第一課,開發往往大部分的時間不是在寫程式碼,而是在查 Bug,相信大家也深有體會。我們有很多手段可以除錯問題,除錯最常用的手段包括打日誌、GDB、分析堆疊、跟蹤系統呼叫等等。但要怎麼樣才能從設計開始就考慮降低除錯門檻,當我們的程式碼出現問題時能快速
軟體設計中的可移植性的考慮
由於在下水平相當有限,不當之處還望大家不吝賜教。 參考Linux核心的思路,對應用程式的可移植性設計做出的思考。 Linux核心中,最前期的啟動程式碼是特定體系結構的程式碼,還有中斷、異常、系統呼叫的前期都會進入特定體系結構的框架程式碼,然後再轉入體系結構無關的程式碼。
論軟體設計中的哲學觀
所謂哲學,即透過事物的表面現象,通過客觀理性的分析,找出更接近事物本質的通用解。從而以一種大宇宙的視角,來觀察和解讀這個世界的種種現象。如道家所謂的“一”,佛家所云的“眾生平等”。  
漫談 · 軟體設計中的具象化
本文微信公眾號連結:https://mp.weixin.qq.com/s/PiZU1biNR5DeqrjnhXE9ag 何為具象化?要說具象,就要說說與具象有關的抽象、表象。 抽象與具象: 抽象是通過分析與綜合的途徑,運用概念在人
訊息機制在軟體設計中的應用
訊息機制是個實用的東西,不知道是何人發明,個人見識較少,最早見於WIN32程式設計,關鍵的幾句程式碼大約是:while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&m
關於軟體設計中的分歧---資源清理
背景:軟體退出,即關閉軟體。 分歧:(1)甲認為軟體退出,程序結束之後作業系統將其佔用的資源進行清除,即將其佔用的記憶體清除,將其開啟的檔案關閉,其核心使用物件遞減,同時所有使用者物件和GDI對 象均被撤銷,所以不需要對各執行緒的退出做順序化處理。
類成員的可訪問性(不管怎麼設計,實現某一個類在記憶體中只能呼叫一次)單態設計模式
為了控制建立物件的個數,需要收回建立物件的權利,下面想辦法設定Teacher為記憶體中唯一物件,在Text中建立並使用Teacher; Teacher package cn.net.sdkd.cise; public class Teacher { pri
程式設計師教程-5章-軟體工程基礎知識 軟體設計之UML—UML中的六大關係
先給出目錄結構 5.1 軟體工程概述 5.1.1 軟體生存週期 1 問題定義 2 可行性分析 3 需求分析 4 總體設計 5 詳細設計 6 編碼和單元測試 7 綜合測試 8 維護
打造 Laravel 優美架構 談可維護性與彈性設計
我的github部落格:zgxxx.github.io/ 公司專案可能需要對架構進行重建,老大給了我一個視訊讓我學習裡面的思想,看完後覺得收穫很大,主講人對laravel專案各個層次有很清晰的理解,力求做到職責單一分明,提高可維護性。下面是我看完視訊對其內容的大概整理,以及一些自己的見解,有錯誤的
JVM 中判斷物件是否 “存活” 的演算法 —— 可達性分析演算法
在堆中,幾乎存放著所有的物件例項,那麼回收這些物件例項時,我們需要判斷哪些物件是 “已死” 可以回收的,哪些物件是 “存活” 不需要回收的,下面就來介紹一下 JVM 中如何判斷上述問題的。 基本思路 通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開
軟體開發,標準化流水線式開發的實施構想 Internet 服務匯流排 嵌入式通用行業應用平臺的靈魂和搭建 快速原型開發模式在實際開發過程中的應用 公用物件請求代理(排程)程式體系結構(CORBA) UML軟體設計基礎(UML圖詳解) (篇01)企業如何軟體商業化? (篇02)企業如何軟體商業化? 在
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
軟體構建中的設計(一)
設計中的挑戰 “軟體設計”意味著去構思。創造或發明一套方案,把一份軟體的規格說明書變成功能可執行的軟體。設計就是把需求分析和編碼除錯連起來的活動。好的高層次設計能提供一個穩妥容納多個較低層次設計的結構。好的設計對於小型專案非常有用,對於大型專案就更是不可或缺。 設計是一個險惡的問題 設計是一個險惡的問題
程式碼大全 讀書筆記(3)軟體構建中的設計
1. 選擇程式語言 熟悉的語言 高階的語言 更能表達程式設計中各種概念的語言 每種語言都有自己的優點和弱點,要知道所選用語言的明確優點和弱點。 問問自己,採用的程式設計實踐是對你所用的程式語言的正確響應,還是受它的控制,記得“深入一種語言去程式設計”,不要僅“在一種語言上程式設計
電商專案可擴充套件性資料庫設計與實現
本場 Chat 主要講小編在最近重構交易系統過程中的一些心得的系列文章,本場 Chat 主要講從 PHP 版交易系統到 Java 版交易系統過程中資料庫設計的改變,從業務設計到抽象設計,使資料庫更加適應變化。 主要內容: 舊版資料庫設計與思路; 舊版資料庫設計的不足與可取之處; 新
軟體設計開發中經常出現的一些問題
在面向物件開發過程中,由於設計者的水平和業務需求的變動,軟體設計中出現這樣或者那樣的問題,下面就是一些經常出現的問題: 對於軟體中任何一部分的改動,都會引起其他多個模組的連鎖改動。改動的越多,就越說明軟體的設計有問題。 改動程式中的一小部分程式碼,程式的許多部分都會出現
軟體測試中測試用例常用的設計方法分析
一、場景法 官方:通過運用場景法對系統的功能點或業務流程描述,從而提高測試效果。場景法一般包含基本流和備選流,從一個流程開始,通過描述經過的路徑來確定的過程,經過遍歷所有的基本流和備用流來完成整個場景。 個人總結:設計
three.js中效能外掛,可除錯介面dat.GUI庫實現
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <st
編寫優質軟體——程式碼可擴充套件性的幾種實施方式
程式碼可擴充套件性的幾種實施方式 《ThePragmaticProgrammer》(Addison-Wesley,1999)一書的作者DaveThomas和AndyHunt曾經說過,所有程式設計工作都是維護的一種形式。一個類在首次鍵入幾分鐘後就會進入無限的維護
性冷淡風將死?淺談UI設計中的價值轉化!
百度說,“‘性冷淡風’”官方說法是Normcore,法國的優雅女性們都是這個範兒,即極簡主義的有味說法,代表著極簡與剋制,是去繁求簡的高階智慧。”大概是從無印良品風靡開始,越來越多的人追求了“合適就好”的概念,從此,設計師們也對自己的作品刪繁就簡,對“性冷淡”