# clean code https://medium.com/trell-labs/refactoring-for-clean-and-extensible-codebase-ee746040fddf https://github.com/jincheng9/practical-go-cn ## If-Else 階梯 ![](https://i.imgur.com/oNbGLlm.png) 添加我們的日誌記錄和錯誤處理,它看起來像這樣。 ![](https://i.imgur.com/hV1qVfQ.png) 這些“過濾器”分組在同一數據類型下。這樣,我們可以將它們存儲在一個數組中,然後循環遍歷它們。 ![](https://i.imgur.com/qQliVDc.png) 我們在這裡並不特別需要接口。我們也可以通過在 Golang 中定義自定義類型來做到這一點。像這樣的東西 ![](https://i.imgur.com/GDB8Dmj.png) 但是通過這樣做,我們失去了Name()在記錄時非常有用的方法 **重構!!** ![](https://i.imgur.com/rPtR0L8.png) ![](https://i.imgur.com/TQKUByd.png) ## screaming Architecture https://medium.com/kuma%E8%80%81%E5%B8%AB%E7%9A%84%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E6%95%99%E5%AE%A4/%E8%AE%93%E7%B5%90%E6%A7%8B%E5%B0%96%E5%8F%AB%E5%90%A7-%E8%A4%87%E7%BF%92-clean-code-%E6%9C%89%E6%84%9F-f1d918e2a568 https://blog.cleancoder.com/uncle-bob/2011/09/30/Screaming-Architecture.html ## The Great Wall a+b+c但將來可能會更改為a*b/c ![](https://i.imgur.com/zolhIDp.png) 存儲這些值的更好方法是在 HashMap 中。這看起來像這樣score = map["weightA"] + map["weightB"] + map["weightC"]。 在執行此類操作時,我不太喜歡使用原始字符串。有多種原因,我不會在這裡深入探討。我將展示一種稍微好一點的方法來實現這一點,而無需使用字符串。 **重構** ![](https://i.imgur.com/FudDA7Z.png) ![](https://i.imgur.com/T45abOW.png) 因為我不想在我的 HashMap 中使用原始字符串,所以我將不得不定義一個我可以使用的不同數據類型。 最終產品 ![](https://i.imgur.com/WTVOjbn.png) ## 可重複使用的管道 它從一個數據源獲取數據,對其進行處理並將其放入另一個數據源。當我們必須同時從多個來源獲取數據時,就會出現混亂。並且根據我們從哪個數據源獲取數據,我們必須對其進行略微不同的處理,具體是查詢數據庫中的不同表。 我將通過一個例子更詳細地解釋這種情況。 我們的一項子服務具有三個不同的“管道”。 MT5 DTOD YT 在所有這些流水線中,我們所做的工作都是一樣的。從數據庫獲取事件,處理它們,將它們保存到另一個數據庫中。唯一的區別是我們必須為所有三個管道使用不同的表。 ![](https://i.imgur.com/GgCiPb9.png) 由於我們有多個管道要使用,我們需要修改這段代碼來處理它們 ![](https://i.imgur.com/7hzoQRH.png) 重構 解決方案再次在於接口 ![](https://i.imgur.com/bvvJBSp.png) 類似於某種“策略模式” 最終 ![](https://i.imgur.com/66RTpHT.png) ![](https://i.imgur.com/NvJ3g0W.png) 第二階段 fetchEvents()正在調用ProcessEvents()處理事件。那麼……它如何知道需要將哪個存儲庫傳遞給它。它必須知道它正在處理哪個管道並創建它。正確的?(debug的時候要怎知道是哪個錯誤) 定義的接口 ![](https://i.imgur.com/K5pCcy4.png) 我們用來獲取Fetcher和的工廠方法Processor。 ![](https://i.imgur.com/e3CVuWb.png) fetcher從內部看起來的樣子 ![](https://i.imgur.com/CqzNtO6.png) 最終 ![](https://i.imgur.com/zH3VFjw.png) ## 自訂struct 處理重複 https://github.com/jincheng9/practical-go-cn#762-writeresponse old ``` type Header struct { Key, Value string } type Status struct { Code int Reason string } func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error { _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason) if err != nil { return err } for _, h := range headers { _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value) if err != nil { return err } } if _, err := fmt.Fprint(w, "\r\n"); err != nil { return err } _, err = io.Copy(w, body) return err } ``` 首先,我們使用 構建狀態行fmt.Fprintf,並檢查錯誤。然後為每個標頭我們編寫標頭鍵和值,每次檢查錯誤。最後,我們用一個額外的 終止標頭部分\r\n,檢查錯誤,並將響應正文複製到客戶端。最後,雖然我們不需要檢查錯誤 from io.Copy,但我們需要將其從返回的兩個返回值形式io.Copy轉換為返回的單個返回值WriteResponse。 這是很多重複性的工作。但是我們可以通過引入一個小的包裝器類型來使我們自己更容易errWriter。 errWriter履行io.Writer合同,因此它可用於包裝現有的io.Writer. errWriter將寫入傳遞給其底層編寫器,直到檢測到錯誤。從那時起,它會丟棄所有寫入並返回先前的錯誤。 new ``` type errWriter struct { io.Writer err error } func (e *errWriter) Write(buf []byte) (int, error) { if e.err != nil { return 0, e.err } var n int n, e.err = e.Writer.Write(buf) return n, nil } func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error { ew := &errWriter{Writer: w} fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason) for _, h := range headers { fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value) } fmt.Fprint(ew, "\r\n") io.Copy(ew, body) return ew.err } ``` 應用errWriter到WriteResponse極大地提高了代碼的清晰度。每個操作不再需要用錯誤檢查來括起來。通過檢查字段,報告錯誤被移動到函數的末尾ew.err,避免了 `io.Copy 返回值的煩人翻譯。 ## 只處理一次錯誤 只處理一次錯誤 https://github.com/jincheng9/practical-go-cn#77-only-handle-an-error-once ###### tags: `Go`