---
# System prepended metadata

title: clean code
tags: [Go]

---

# 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`