《編程匠藝》之開發工具與技巧
第二部分: 代碼的神祕生命(代碼開發的技術與技巧)
1. 欲善其事,先利其器(使用工具)
- 儘量全面的瞭解你的經常使用工具, 爲此投入的時間是值得的.
- 使用工具發揮做用:
- 瞭解它能作什麼
- 學習如何駕馭它
- 瞭解它適合什麼任務
- 檢查它是否可用
- 找到了解更多信息的途徑
- 工具鏈的組成
- 源代碼編輯工具
- 代碼編輯器
- 代碼處理工具(diff比較工具, sed流查找修改工具, awk樣式匹配工具, grep正則匹配工具, find/locate文件查找工具)
- 代碼瀏覽工具
- 版本控制工具
- 源代碼生成工具
- 源代碼美化工具
- 代碼構建工具
- 編譯器(優化到什麼程度?編譯器遵循的標準是什麼?如何生成正確指令集的代碼)
- 連接器(是否能夠生成庫對象, 靜態庫,動態庫;是否能生成可調試文件)
- 構建環境(make, automake, cmake等)
- 測試工具鏈(如自動化單元測試框架, 模擬內存不足, 負載太高等)
- 調試和調查工具
- 調試器(gdb, ddd)
- 分析器(性能分析等)
- 代碼校驗器(靜態檢查如lint, 動態工具如內存分配/邊界檢查器)
- 度量工具(統計函數,注/代 比, 圈複雜度, 重複度等)
- 反彙編工具
- 缺陷跟蹤系統
- 語言支持工具
- 編程語言自己(語言特性,如go協程)
- 運行時和解釋程序(如java虛擬機)
- 組件和庫()
- 其餘工具
- 工具總結
- 你的工具箱中有哪些工具?哪些是頻繁使用的?
- 是否已經發揮了工具的最大做用?是否嘗試優化使用?
- 是否已是能找到的最好工具?
2. 測試代碼的魔術(保證代碼質量)
- 測試分爲不一樣的層次,做爲開發人員,主要關注的是在代碼實現過程當中的測試.
- 關於測試的疑問:
- 爲何須要測試?這個應該不須要再複述了,爲了保證交付的軟件是知足要求的.
- 誰來測試?本身應該爲本身寫的代碼進行充分的測試,而不是寄但願與測試人員.
- 測試內容都有哪些?這是個很好的問題,須要測試代碼中的函數,類,流程等,根據測試的內容不一樣,能夠分爲單元測試和功能測試與集成測試.
- 什麼時候開始測試?越早開始成本越低.
- 影響測試難度的因素:
- 分支與條件多少
- 代碼規模
- 依賴關係
- 外部輸入
- 多線程
- 代碼演變
- 測試類型
- 單元測試(測試類或者函數)
- 組件測試(驗證一個或者多個單元組成的完整組件行爲)
- 集成測試(測試下一個系統中的多個組件,確保正確相連)
- 迴歸測試
- 負載測試(確保性能達到)
- 壓力測試(確保超負荷時,不會亂成一團,用於獲取軟件的實際容量)
- 疲勞測試(使代碼在較高負荷下連續工做一段時間,以肯定是否有內存泄露和內存碎片化形成的性能下降)
- 可用性測試(用戶使用測試)
- 挑選單元測試用例
- 既然不能測試全部的狀況,那麼怎麼挑選用例呢?能夠挑選下面的主要點:
- 良好的輸入
- 很差的輸入(如極大值,極小值, 過長或者太短字符串, 空字符串)
- 邊界值(邊界值自己,邊界值上方, 邊界值下方)
- 隨機數(自動化隨機產生)
- 零值
- 編寫可測試的代碼
- 使各部分代碼自包含,儘可能減小沒必要要的關聯
- 不要依賴於全局變量
- 限制代碼的複雜度,拆分代碼.
- 保持代碼的可觀測性.
- 測試自動化
- 將自動化運行的單元測試成爲你構建的一部分
- 測試的代碼應儘可能邏輯簡單,避免測試代碼出現問題
- 故障描述
- 在測試出現問題時,須要詳細的描述問題,描述的內容包括:
- 出現問題時的環境(軟件版本,硬件版本)
- 可使問題復現的最簡單的步驟
- 關於問題出現的可重複性和頻率
- 有可能相關聯的其餘事物
- 在測試時出現的問題就應該進行跟蹤,並對其進行自動化測試的覆蓋.
- 開發測試管理
- 缺陷跟蹤系統(報告故障, 分配責任, 肯定優先級, 標記狀態)
- bug審查(重點在於討論缺陷以及如何處理, 不要討論修改細節)
- 單元測試須要到什麼程度?若是一段代碼簡單的看一下已經不能證實是否正確的時候,就該引入測試用例了.
- 測試驅動開發的模式, 編寫代碼以前的測試只能是黑盒測試.
3. 尋找缺陷, 並解決它(如何解決問題)
- bug的種類(若是你能準確知道它, 就能控制它)
- 編譯失敗
- 運行時崩潰
- 非預期的輸出行爲
- 句法錯誤(避免的方式是打開全部編譯告警,並使代碼經過lint檢查)
- 構建錯誤(完全清除中間構建, 從頭構建)
- 語義錯誤(變量未初始化, 比較浮點數, 數值溢出, 隱士類型轉換; 使用lint檢查)
- 段錯誤(主要是錯誤的指針使用)
- 內存溢出(表現多是運行很遠處出現莫名的錯誤)
- 內存泄露()
- 內存耗盡
- 數學錯誤(浮點異常, 溢出, 除數爲0等)
- 程序暫停(無限循環, 死鎖, 競爭)
- 除錯的藝術
- 調試工具
- 調試器(如gdb)
- 內存訪問校驗器(確認是否有內存泄露和溢出)
- 系統調用追蹤(如strace)
- 內核轉儲core文件
- 日誌
- 靜態分析器
- 調試箴言
- 避免使用調試器"閒逛", 要注意調試黃金法則:多動腦子.
4. 代碼構建
- 主要的構建機制有三種:
- 同一項目中,全部的成員都應該使用相同的構建系統,不然,構建的就不是同一個軟件,可能存在參數等差別.
- 構建應該注意的事項:
- 構建完成後,須要爲發行打包
- 每一個版本都須要存檔存儲,或者在git上打一個版本tag.
- 每一個版本都須要一個發行說明
- 當構建版本時,必須選擇正確的編譯器開關集.
5. 優化代碼(追求速度和效率)
- 軟件優化的含義:
- 程序的執行速度加快
- 減少可執行文件的大小
- 提升代碼的質量
- 提升計算結果的準確性
- 將啓動時間減到最小
- 增長數據的吞吐量
- 減小存儲開銷
- 形成代碼臃腫,運行慢,體積大的緣由?
- 沒必要要的複雜性
- 間接(額外的中間層)
- 重複(重複的調用複雜的計算過程)
- 糟糕的設計(加大了溝通的模塊的距離)
- I/O等待
- 爲何不進行優化?
- 爲何要進行優化?
- balabala...特殊領域如遊戲, dsp, 實時系統, 金融計算等領域.
- 怎麼進行優化?
- 肯定程度運行的慢, 並證實確實須要優化(可是要注意,也許並非慢在代碼級,而是設計上有問題)
- 找出運行最慢的代碼,以這段代碼爲目標(使用合適的分析工具)
- 先測試這段的性能
- 對這段代碼優化
- 測試優化後的代碼是否功能正常
- 測試速度提高多少,並決定下一步
- 怎麼分析哪段代碼最慢?
- 使用合適的分析工具
- 手動添加計時
- 計算每一個函數的調用頻率(有工具, 也能夠利用編譯器的hook)
- 經過單個函數變慢來測試它對整個程序執行時間的影響
- 在進行分析時,要謹慎的選擇分析數據,能夠選擇基本的數據集, 高負荷的數據集和普通的數據集.
- 優化的技術
- 優化有兩種大的方向:修改設計和修改代碼
- 基於運行速度的優化包括:
- 加快較慢代碼的速度
- 儘可能少作較慢的事情
- 將較慢的事情推遲到不得不進行的時候
- 代碼設計層的修改包括:
- 添加緩存層, 加快較慢的數據的訪問
- 使用資源池
- 爲速度犧牲一點精度
- 變串行爲並行,使用多線程模型
- 使用更合適的算法和數據結構
- 代碼層的修改包括:
- 編譯器的優化級別提升
- 循環展開
- 代碼內嵌(inline)
- 移到編譯時(如經過設置uint, 省掉<0 的檢查)
- 強度折減(使用等價的操做替代其餘指令, 如使用移位代替除法)
- 子表達式(對於多個地方會用到的同一個操做,抽出來)
- 無用代碼刪除
- (下面的方式更推薦)
- 若是發現一個函數慢, 那麼不要頻繁調用, 緩存結果
- 跨語言封裝, 好比把java從新在c中實現
- 從新整理代碼(推遲工做, 對函數作檢查及時跳出, 循環條件中不作計算)
- 空間換時間, 提早緩存須要大量計算的結果
- 利用短路判斷,把可能致使退出的條件放在前面
- 對程序性能產生深入影響的決策有:
- 功能數量 VS 代碼規模
- 程序速度 vs 內存消耗
- 存儲和緩存 vs 按需計算
- 近似的計算 vs 精確的計算
- 內嵌 vs 函數調用; 單一的 vs 模塊化的
- 經過引用或者地址傳遞 vs 傳遞副本
- 經過硬件實現 vs 經過軟件
- 寫死的直接訪問 vs 間接訪問
- 預先肯定的固定的值 vs 可變可配置的值
- 編譯時工做 vs 運行時工做
- 本地函數調用 vs 遠程函數調用
- 巧妙的算法 vs 清晰的算法
- 較慢的程序的瓶頸可能在哪?
- 內存顛簸(不停的換出)
- 等待磁盤/網口等訪問(等待I/O慢)
- 等待較慢的數據庫事務
- 存在鎖等待
- 對使用的語言和操做系統, 要大體瞭解其相關成本, 如函數調用, inline函數, 能夠經過查看對應的指令, 來了解大體時間級別.
6. 安全的代碼(防止被黑)
- 作安全防禦的第一步是:瞭解你擁有哪些重要的資源, 是否擁有一些敏感的信息或者特定的能力.
- 不安全的軟件源頭:
- 不安全的設計和體系
- 緩衝區溢出
- 嵌入的查詢字符串
- 競爭情況(常出如今複雜的多現場模型裏)
- 整數溢出
- 編碼中的保護方法
- 限制設計中的輸入數量,安排全部的通訊經過系統某部分進行
- 在儘量低的權限上運行程序
- 避免開發並不真正須要的功能
- 不要依賴於不可靠的庫
- 避免存儲敏感數據
- 要對輸入進行檢查(包括命令行參數, 環境變量, web表單, 文件大小等);要檢查輸入的大小, 格式, 有效性以及數據的真正內容.
歡迎關注本站公眾號,獲取更多信息