《編程匠藝》之開發工具與技巧

第二部分: 代碼的神祕生命(代碼開發的技術與技巧)

1. 欲善其事,先利其器(使用工具)

  1. 儘量全面的瞭解你的經常使用工具, 爲此投入的時間是值得的.
  2. 使用工具發揮做用:
    • 瞭解它能作什麼
    • 學習如何駕馭它
    • 瞭解它適合什麼任務
    • 檢查它是否可用
    • 找到了解更多信息的途徑
  3. 工具鏈的組成
    1. 源代碼編輯工具
      • 代碼編輯器
      • 代碼處理工具(diff比較工具, sed流查找修改工具, awk樣式匹配工具, grep正則匹配工具, find/locate文件查找工具)
      • 代碼瀏覽工具
      • 版本控制工具
      • 源代碼生成工具
      • 源代碼美化工具
    2. 代碼構建工具
      • 編譯器(優化到什麼程度?編譯器遵循的標準是什麼?如何生成正確指令集的代碼)
      • 連接器(是否能夠生成庫對象, 靜態庫,動態庫;是否能生成可調試文件)
      • 構建環境(make, automake, cmake等)
      • 測試工具鏈(如自動化單元測試框架, 模擬內存不足, 負載太高等)
    3. 調試和調查工具
      • 調試器(gdb, ddd)
      • 分析器(性能分析等)
      • 代碼校驗器(靜態檢查如lint, 動態工具如內存分配/邊界檢查器)
      • 度量工具(統計函數,注/代 比, 圈複雜度, 重複度等)
      • 反彙編工具
      • 缺陷跟蹤系統
    4. 語言支持工具
      • 編程語言自己(語言特性,如go協程)
      • 運行時和解釋程序(如java虛擬機)
      • 組件和庫()
    5. 其餘工具
      • 文檔工具
      • 項目管理
  4. 工具總結
    • 你的工具箱中有哪些工具?哪些是頻繁使用的?
    • 是否已經發揮了工具的最大做用?是否嘗試優化使用?
    • 是否已是能找到的最好工具?

2. 測試代碼的魔術(保證代碼質量)

  1. 測試分爲不一樣的層次,做爲開發人員,主要關注的是在代碼實現過程當中的測試.
  2. 關於測試的疑問:
    • 爲何須要測試?這個應該不須要再複述了,爲了保證交付的軟件是知足要求的.
    • 誰來測試?本身應該爲本身寫的代碼進行充分的測試,而不是寄但願與測試人員.
    • 測試內容都有哪些?這是個很好的問題,須要測試代碼中的函數,類,流程等,根據測試的內容不一樣,能夠分爲單元測試和功能測試與集成測試.
    • 什麼時候開始測試?越早開始成本越低.
  3. 影響測試難度的因素:
    • 分支與條件多少
    • 代碼規模
    • 依賴關係
    • 外部輸入
    • 多線程
    • 代碼演變
  4. 測試類型
    • 單元測試(測試類或者函數)
    • 組件測試(驗證一個或者多個單元組成的完整組件行爲)
    • 集成測試(測試下一個系統中的多個組件,確保正確相連)
    • 迴歸測試
    • 負載測試(確保性能達到)
    • 壓力測試(確保超負荷時,不會亂成一團,用於獲取軟件的實際容量)
    • 疲勞測試(使代碼在較高負荷下連續工做一段時間,以肯定是否有內存泄露和內存碎片化形成的性能下降)
    • 可用性測試(用戶使用測試)
  5. 挑選單元測試用例
    • 既然不能測試全部的狀況,那麼怎麼挑選用例呢?能夠挑選下面的主要點:
      1. 良好的輸入
      2. 很差的輸入(如極大值,極小值, 過長或者太短字符串, 空字符串)
      3. 邊界值(邊界值自己,邊界值上方, 邊界值下方)
      4. 隨機數(自動化隨機產生)
      5. 零值
  6. 編寫可測試的代碼
    • 使各部分代碼自包含,儘可能減小沒必要要的關聯
    • 不要依賴於全局變量
    • 限制代碼的複雜度,拆分代碼.
    • 保持代碼的可觀測性.
  7. 測試自動化
    • 將自動化運行的單元測試成爲你構建的一部分
    • 測試的代碼應儘可能邏輯簡單,避免測試代碼出現問題
  8. 故障描述
    • 在測試出現問題時,須要詳細的描述問題,描述的內容包括:
      • 出現問題時的環境(軟件版本,硬件版本)
      • 可使問題復現的最簡單的步驟
      • 關於問題出現的可重複性和頻率
      • 有可能相關聯的其餘事物
  9. 在測試時出現的問題就應該進行跟蹤,並對其進行自動化測試的覆蓋.
  10. 開發測試管理
    • 缺陷跟蹤系統(報告故障, 分配責任, 肯定優先級, 標記狀態)
    • bug審查(重點在於討論缺陷以及如何處理, 不要討論修改細節)
  11. 單元測試須要到什麼程度?若是一段代碼簡單的看一下已經不能證實是否正確的時候,就該引入測試用例了.
  12. 測試驅動開發的模式, 編寫代碼以前的測試只能是黑盒測試.

3. 尋找缺陷, 並解決它(如何解決問題)

  1. bug的種類(若是你能準確知道它, 就能控制它)
    • 從遠處看(三類)
    1. 編譯失敗
    2. 運行時崩潰
    3. 非預期的輸出行爲
    • 從近處看(更細緻的分類)
    1. 句法錯誤(避免的方式是打開全部編譯告警,並使代碼經過lint檢查)
    2. 構建錯誤(完全清除中間構建, 從頭構建)
    3. 語義錯誤(變量未初始化, 比較浮點數, 數值溢出, 隱士類型轉換; 使用lint檢查)
    • 從更近處看(語義缺陷)
    1. 段錯誤(主要是錯誤的指針使用)
    2. 內存溢出(表現多是運行很遠處出現莫名的錯誤)
    3. 內存泄露()
    4. 內存耗盡
    5. 數學錯誤(浮點異常, 溢出, 除數爲0等)
    6. 程序暫停(無限循環, 死鎖, 競爭)
  2. 除錯的藝術
    • 地下之路
    • 地上之路
  3. 調試工具
    • 調試器(如gdb)
    • 內存訪問校驗器(確認是否有內存泄露和溢出)
    • 系統調用追蹤(如strace)
    • 內核轉儲core文件
    • 日誌
    • 靜態分析器
  4. 調試箴言
    • 避免使用調試器"閒逛", 要注意調試黃金法則:多動腦子.

4. 代碼構建

  1. 主要的構建機制有三種:
    • 解釋型語言
    • 編譯型語言
    • 字節型語言
  2. 同一項目中,全部的成員都應該使用相同的構建系統,不然,構建的就不是同一個軟件,可能存在參數等差別.
  3. 構建應該注意的事項:
    • 構建完成後,須要爲發行打包
    • 每一個版本都須要存檔存儲,或者在git上打一個版本tag.
    • 每一個版本都須要一個發行說明
    • 當構建版本時,必須選擇正確的編譯器開關集.

5. 優化代碼(追求速度和效率)

  1. 軟件優化的含義:
    • 程序的執行速度加快
    • 減少可執行文件的大小
    • 提升代碼的質量
    • 提升計算結果的準確性
    • 將啓動時間減到最小
    • 增長數據的吞吐量
    • 減小存儲開銷
  2. 形成代碼臃腫,運行慢,體積大的緣由?
    • 沒必要要的複雜性
    • 間接(額外的中間層)
    • 重複(重複的調用複雜的計算過程)
    • 糟糕的設計(加大了溝通的模塊的距離)
    • I/O等待
  3. 爲何不進行優化?
    • balabala...備選方案
  4. 爲何要進行優化?
    • balabala...特殊領域如遊戲, dsp, 實時系統, 金融計算等領域.
  5. 怎麼進行優化?
    • 肯定程度運行的慢, 並證實確實須要優化(可是要注意,也許並非慢在代碼級,而是設計上有問題)
    • 找出運行最慢的代碼,以這段代碼爲目標(使用合適的分析工具)
    • 先測試這段的性能
    • 對這段代碼優化
    • 測試優化後的代碼是否功能正常
    • 測試速度提高多少,並決定下一步
  6. 怎麼分析哪段代碼最慢?
    • 使用合適的分析工具
    • 手動添加計時
    • 計算每一個函數的調用頻率(有工具, 也能夠利用編譯器的hook)
    • 經過單個函數變慢來測試它對整個程序執行時間的影響
    • 在進行分析時,要謹慎的選擇分析數據,能夠選擇基本的數據集, 高負荷的數據集和普通的數據集.
  7. 優化的技術
    • 優化有兩種大的方向:修改設計和修改代碼
    • 基於運行速度的優化包括:
      • 加快較慢代碼的速度
      • 儘可能少作較慢的事情
      • 將較慢的事情推遲到不得不進行的時候
    • 代碼設計層的修改包括:
      • 添加緩存層, 加快較慢的數據的訪問
      • 使用資源池
      • 爲速度犧牲一點精度
      • 變串行爲並行,使用多線程模型
      • 使用更合適的算法和數據結構
    • 代碼層的修改包括:
      • 編譯器的優化級別提升
      • 循環展開
      • 代碼內嵌(inline)
      • 移到編譯時(如經過設置uint, 省掉<0 的檢查)
      • 強度折減(使用等價的操做替代其餘指令, 如使用移位代替除法)
      • 子表達式(對於多個地方會用到的同一個操做,抽出來)
      • 無用代碼刪除
      • (下面的方式更推薦)
      • 若是發現一個函數慢, 那麼不要頻繁調用, 緩存結果
      • 跨語言封裝, 好比把java從新在c中實現
      • 從新整理代碼(推遲工做, 對函數作檢查及時跳出, 循環條件中不作計算)
      • 空間換時間, 提早緩存須要大量計算的結果
      • 利用短路判斷,把可能致使退出的條件放在前面
  8. 對程序性能產生深入影響的決策有:
    • 功能數量 VS 代碼規模
    • 程序速度 vs 內存消耗
    • 存儲和緩存 vs 按需計算
    • 近似的計算 vs 精確的計算
    • 內嵌 vs 函數調用; 單一的 vs 模塊化的
    • 經過引用或者地址傳遞 vs 傳遞副本
    • 經過硬件實現 vs 經過軟件
    • 寫死的直接訪問 vs 間接訪問
    • 預先肯定的固定的值 vs 可變可配置的值
    • 編譯時工做 vs 運行時工做
    • 本地函數調用 vs 遠程函數調用
    • 巧妙的算法 vs 清晰的算法
  9. 較慢的程序的瓶頸可能在哪?
    • 內存顛簸(不停的換出)
    • 等待磁盤/網口等訪問(等待I/O慢)
    • 等待較慢的數據庫事務
    • 存在鎖等待
  10. 對使用的語言和操做系統, 要大體瞭解其相關成本, 如函數調用, inline函數, 能夠經過查看對應的指令, 來了解大體時間級別.

6. 安全的代碼(防止被黑)

  1. 作安全防禦的第一步是:瞭解你擁有哪些重要的資源, 是否擁有一些敏感的信息或者特定的能力.
  2. 不安全的軟件源頭:
    • 不安全的設計和體系
    • 緩衝區溢出
    • 嵌入的查詢字符串
    • 競爭情況(常出如今複雜的多現場模型裏)
    • 整數溢出
  3. 編碼中的保護方法
    • 限制設計中的輸入數量,安排全部的通訊經過系統某部分進行
    • 在儘量低的權限上運行程序
    • 避免開發並不真正須要的功能
    • 不要依賴於不可靠的庫
    • 避免存儲敏感數據
    • 要對輸入進行檢查(包括命令行參數, 環境變量, web表單, 文件大小等);要檢查輸入的大小, 格式, 有效性以及數據的真正內容.
相關文章
相關標籤/搜索