惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
Blog — PlanetScale
Blog — PlanetScale
MyScale Blog
MyScale Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
WordPress大学
WordPress大学
Vercel News
Vercel News
D
Docker
博客园 - 聂微东
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
D
DataBreaches.Net
B
Blog RSS Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - Franky
Microsoft Security Blog
Microsoft Security Blog
美团技术团队
F
Fortinet All Blogs
MongoDB | Blog
MongoDB | Blog
T
The Blog of Author Tim Ferriss
GbyAI
GbyAI
N
Netflix TechBlog - Medium
G
Google Developers Blog
腾讯CDC

Nic Lin's Blog

謝明真 - 高效領導力的課後筆記 NFT 開發實戰!基礎智能合約入門 (3) NFT 開發實戰!基礎智能合約入門 (2) NFT 開發實戰!基礎智能合約入門 (1) 如何自我檢測 log4j CVE 漏洞 Rails 如何在資料寫入時記錄來源 IP 位置 如何經營工程師 Youtube 頻道 - Part 8 營收篇 如何經營工程師 Youtube 頻道 - Part 7 酸民文化篇 如何經營工程師 Youtube 頻道 - Part 5 設備器材篇 如何經營工程師 Youtube 頻道 - Part 4 後製剪輯篇 如何經營工程師 Youtube 頻道 - Part 3 文案企劃篇 如何經營工程師 Youtube 頻道 - Part 2 設備器材篇 如何經營工程師 Youtube 頻道 - Part 1 制訂頻道方向篇 如何經營工程師 Youtube 頻道 - Part 0 Rails 中避免 race condition 的最佳實踐(二) Rails 中避免 race condition 的最佳實踐(一) 10 分鐘整合 google sheet 做自動化開發功能週報 經營 Side Project 300 天所帶來的收穫及挑戰 我的 Youtube 影片製作流程 API 設計時必須注意的 HTTP header 底線問題 如何提升你的程式可讀性之實務技巧(三) 如何提升你的程式可讀性之實務技巧(二) 如何提升你的程式可讀性之實務技巧(一) Ruby 中使用 freeze 優化效能的時機 避免 React 中的 useEffect 無限 render 在 Rails 內輕量使用 Vue Component 的最佳實踐 如何在區域網路用 Docker 架設有 SSL 的 Gitlab 從被問到問人,那些我常問的面試問題 [Rails] 如何漂亮寫出可維護的 query (Maintainable Rails Query) 在已知長度情況下優化 slice 的性能
避免過度的 Defensive Programming 防禦性程式設計
Nic Lin · 2019-07-18 · via Nic Lin's Blog

防禦性程式發生在程式設計師不相信輸入的參數,所以對其做檢查,有可能在呼叫者(caller)和被呼叫者(callee)都做了相同的檢查來避免出錯,一般來說我們不需要測試輸入的資料來確保功能正常。系統中大部分的程式碼應該可以假設輸入的資料是正確的,只要在資料輸入進系統時被檢查過,應該就可以假設他是正確的。

多餘的檢查看似可以提升穩定性,但也隨之而來的是

  • 資源浪費
  • 效能浪費
  • 增加維護難度

防禦性設計的出發點是「不信任」,我們當然要避免可能發生的邊界情況,以 Public API 來說有兩種策略應付不乖的參數

  1. 修正或略過資料的錯誤(defensive, compensate)
  2. 丟出錯誤(offensive, Fail-fast)
# defensive programming
# 如果參數不符合條件,就略過或修正它
def generate_account(currency)
  if currency && currency.include?(["btc", "eth"]) # 檢查 currency 種類,如果傳進來的不是預期的就跳過
    # create account
  else
    # ignore, nothing happend.
  end  
end

# non-defensive programming
def generate_account(currency)
  # create account
end

# offensive programming (fail fast)
# early return,檢查完出錯直接噴錯讓你知道,而且有明確的訊息
def generate_account(currency)
  raise "currency: #{currency} is invalid." if currency.blank? || !currency.include?(["btc", "eth"])
  
  # create account
end

過度的防禦性設計更易造成問題的掩蓋,為什麼可以傳不在預期的 value 進來卻沒事,或是只記一條 Log?

這應該是要去 trace 的問題,因為這樣長久下來整個系統除了多了一大堆檢查的冗餘 code 以外,不確定性越來越多也就越來越難以維護。

另一種情況是濫用 rescue 捕捉 exception,如果程式碼裡面經常看到 begin rescue,這就是一種 bad smell。

在調用外部接口時,一定要抓 exception,但有一部份是新手常犯的錯誤,會在自己的邏輯外在套上 begin rescue,而且不明確捕捉類型,當你問他要抓哪種錯誤的時候通常是答不出,對於自己寫的程式碼邏輯錯誤不應該一次全家桶的捕獲,而是查出錯誤來源,從源頭解決問題。

# 不明確捕捉 exception, 任何錯誤情況都回 false
def add_funds!(account, amount)
  begin
    account.update(amount: amount)
  rescue
    false
  end
end

在 call 這個 method 時,我拿到 false 回應時我可能會去看為什麼?

然後直到我看到這段 code 時我更無法確定

  1. update 失敗?
  2. 沒傳 account?
  3. 沒傳 amount?
  4. amount 數字過大或過小?
  5. amount 型態錯誤?

此例中,無論發生什麼錯誤都不會有事不會 crash,只是徹底把問題隱藏起來。

所以說 Overly Defensive Programming 基本上就是 Hide the Problem Programming

然而 Fail-fast 的作法應該是盡快的將錯誤 exception,拋給 caller

既然是 caller 亂傳參數或是參數有問題,當然請 caller 根治問題,而我執行方也就直接 fail 掉不跟你囉唆這樣。

def add_funds!(account, amount)
  raise "amount is valid" if amount <= 0
  raise "account is locked" if account.is_locked?

  account.update(amount: amount)
end

雖然有些人也會認為 Fail-fast 的手段也是 defensive programming 的一種,但我覺得防禦性設計本身沒有問題,有問題的部分是在於「隱藏問題」,而很常見的原因都是 exception handling 亂用導致。

結論

避免 Overly Defensive Programming,請用 Fail-fast 來增加找到錯誤的效率。

  1. 系統邊界內,盡量相信輸入,不做檢查
  2. 需要檢查的部分,採用錯誤直接中斷的作法,避免隱藏問題

參考來源