自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

六個(gè)常見(jiàn)的 Go 接口設(shè)計(jì)錯(cuò)誤

開(kāi)發(fā) 前端
今天這篇文章給大家介紹了六種常見(jiàn)的接口設(shè)計(jì)錯(cuò)誤:創(chuàng)建了太多的接口、方法太多了、沒(méi)有編寫(xiě)行為驅(qū)動(dòng)的接口、在生產(chǎn)者端編寫(xiě)接口、正在返回接口、沒(méi)有驗(yàn)證接口合規(guī)性。

Go 是一門(mén)新興的語(yǔ)言,如果你正在使用它,很可能這不是你的第一門(mén)編程語(yǔ)言。

從不同的語(yǔ)言背景轉(zhuǎn)來(lái),你帶來(lái)了自己的既有經(jīng)驗(yàn)和范式。你在以前的語(yǔ)言中習(xí)慣做的事情,在 Go 中可能并不是一個(gè)好的方式。

學(xué)習(xí) Go 不僅僅是學(xué)習(xí)一種新的語(yǔ)法。它還涉及到學(xué)習(xí)一種新的程序思維方式。

一些理論知識(shí)

Go 創(chuàng)始人 Rob Pike 曾經(jīng)說(shuō)過(guò):“Go 的接口并不是 Java 或 C# 接口的變種,而是更多。它們是大規(guī)模編程和適應(yīng)性強(qiáng)的進(jìn)化設(shè)計(jì)關(guān)鍵?!?/p>

圖片圖片

正確使用接口可以帶來(lái)簡(jiǎn)單性、可讀性和更靈活的代碼設(shè)計(jì)。(不正確使用會(huì)帶來(lái)新的災(zāi)難)

以下是建議了解的基本原則 TOP3:

  • 接口隔離原則:客戶(hù)端永遠(yuǎn)不應(yīng)該被迫實(shí)現(xiàn)它不使用的接口,或者客戶(hù)端不應(yīng)該被迫依賴(lài)它們不使用的方法。
  • 多態(tài)性:一段代碼根據(jù)它接收到的具體數(shù)據(jù)改變其行為。
  • 里氏替換原則:如果你的代碼依賴(lài)于一個(gè)抽象,一個(gè)實(shí)現(xiàn)可以被另一個(gè)實(shí)現(xiàn)替換,而不需要改變你的代碼。

常見(jiàn)接口設(shè)計(jì)錯(cuò)誤

1. 你創(chuàng)建了太多的接口

擁有過(guò)多接口的情況叫做:接口污染。當(dāng)你在編寫(xiě)具體類(lèi)型之前就開(kāi)始抽象時(shí),就會(huì)出現(xiàn)這種情況。

由于無(wú)法預(yù)知需要哪些抽象,因此很容易編寫(xiě)出過(guò)多的接口,而這些接口在日后要么是錯(cuò)誤的,要么是無(wú)用的。

Go 創(chuàng)始人給出了一個(gè)很棒的指導(dǎo)方針幫助我們避免接口污染。如下:

不要使用接口設(shè)計(jì),而是發(fā)現(xiàn)它們(Don’t design with interfaces, discover them.) —— Rob Pike

Rob 在這里指出的是:你不需要提前考慮你需要什么抽象。你可以從具體的結(jié)構(gòu)體開(kāi)始設(shè)計(jì),并在設(shè)計(jì)需要時(shí)只創(chuàng)建接口。通過(guò)這樣做,你的代碼會(huì)有機(jī)地增長(zhǎng)到預(yù)期的設(shè)計(jì)。

但我仍然看到人們提前創(chuàng)建接口,因?yàn)樗麄冋J(rèn)為他們將來(lái)可能需要多個(gè)實(shí)現(xiàn)。

對(duì)于他們,我要說(shuō):

圖片圖片

懶惰但要懶惰得好。創(chuàng)建接口的完美時(shí)機(jī)是當(dāng)你真正需要它的時(shí)候,而不是當(dāng)你預(yù)測(cè)你需要它的時(shí)候。

無(wú)用的接口往往只有一個(gè)實(shí)現(xiàn)。它們只是增加了一個(gè)額外的間接層,迫使程序員在他們實(shí)際上想要去實(shí)現(xiàn)時(shí)總是要通過(guò)這一層。

一個(gè)接口有一個(gè)成本:它是你在推理你的代碼時(shí)需要記住的一個(gè)新概念。正如 Djikstra 所說(shuō),一個(gè)理想的接口必須是:“一個(gè)可以絕對(duì)精確的新語(yǔ)義層面?!?/p>

所以在創(chuàng)建接口之前問(wèn)問(wèn)自己:你有多個(gè)接口的實(shí)現(xiàn)嗎?我強(qiáng)調(diào)了 “有”,因?yàn)?“將有” 意味著你可以預(yù)測(cè)未來(lái),而你不能。

2. 你的方法太多了

在 PHP 項(xiàng)目中看到 10 個(gè)方法的接口是很典型的。在 Go 中,接口很小,標(biāo)準(zhǔn)庫(kù)中所有接口的平均方法數(shù)是 2。

“越大的接口抽象就越弱”,這是 Go 諺語(yǔ)之一。正如 Rob Pike 所說(shuō),這是關(guān)于接口最重要的事情,這意味著接口越小,它就越有用。

一個(gè)接口可以有的實(shí)現(xiàn)越多,它就越通用。如果你有一個(gè)包含大量方法的接口,就很難有多個(gè)實(shí)現(xiàn)。

你擁有的方法越多,接口就越具體。接口越具體,不同類(lèi)型的機(jī)會(huì)就越小,它們可以顯示相同的行為。

io.Reader 和 io.Writer 就是有用接口的一個(gè)很好的例子,它們有數(shù)以百計(jì)的實(shí)現(xiàn)?;蛘呤亲罱?jīng)典的 error 接口,它非常強(qiáng)大,可以在 Go 中實(shí)現(xiàn)整個(gè)錯(cuò)誤處理。

記住,你可以從其他接口組合一個(gè)接口。這里有一個(gè)例子:ReadWriteCloser 由 3 個(gè)較小的接口組成。

代碼如下:

type ReadWriteCloser interface {
    io.Reader
    io.Writer
    io.Closer
}

3. 你沒(méi)有編寫(xiě)行為驅(qū)動(dòng)的接口

在傳統(tǒng)語(yǔ)言中,諸如 User、Request 等名詞性接口非常常見(jiàn)。而在 Go 語(yǔ)言中,大多數(shù)接口都有后綴:Reader、Writer、Closer 等。這是因?yàn)椋?Go 中,接口暴露了行為,而它們的名稱(chēng)則指向該行為。

在 Go 中定義接口時(shí),你定義的不是 "某物是什么",而是 "它提供了什么"-- 是 "行為",而不是 "事物"!

這就是為什么 Go 中沒(méi)有 File 接口,但有Reader 和 Writer:這些都是行為,而 File 是實(shí)現(xiàn) Reader 和 Writer 的事物。

《Effective Go》也提到了同樣的觀點(diǎn):

Go 中的接口提供了一種指定對(duì)象行為的方法:如果某個(gè)東西可以做到這一點(diǎn),那么它就可以用在這里。

在編寫(xiě)接口時(shí),盡量考慮動(dòng)作或行為。如果你定義了一個(gè)名為 "Thing" 的接口,問(wèn)問(wèn)自己為什么這個(gè) "Thing" 不是一個(gè)結(jié)構(gòu)體。

4. 你在生產(chǎn)者端編寫(xiě)接口

我經(jīng)常在代碼審查中看到這種情況:很多開(kāi)發(fā)者在同一個(gè)包,既寫(xiě)了具體的實(shí)現(xiàn),又定義了接口。

生產(chǎn)者定義的接口生產(chǎn)者定義的接口

但是,也許客戶(hù)端并不想使用生產(chǎn)者接口中的所有方法。請(qǐng)記住 "接口隔離原則" 中的一句話(huà):"不應(yīng)強(qiáng)迫客戶(hù)端實(shí)現(xiàn)其不使用的方法"。

下面是一個(gè)例子:

package main

// ====== producer side

// This interface is not needed
type UsersRepository interface {
    GetAllUsers()
    GetUser(id string)
}

type UserRepository struct {
}

func (UserRepository) GetAllUsers()      {}
func (UserRepository) GetUser(id string) {}

// ====== client side

// Client only needs GetUser and
// can create this interface implicitly implemented
// by concrete UserRepository on his side
type UserGetter interface {
    GetUser(id string)
}

如果客戶(hù)端想使用生產(chǎn)者的所有方法,可以使用具體的結(jié)構(gòu)體。因?yàn)榻Y(jié)構(gòu)體方法已經(jīng)提供了這些行為。

即使客戶(hù)端想要解耦代碼并使用多種實(shí)現(xiàn),他也可以在自己這邊創(chuàng)建一個(gè)包含所有方法的接口:

在客戶(hù)端定義的接口在客戶(hù)端定義的接口

由于 Go 中的接口是隱式滿(mǎn)足的,所以這些事情才得以實(shí)現(xiàn)??蛻?hù)端代碼不再需要導(dǎo)入某個(gè)接口并編寫(xiě)實(shí)現(xiàn),因?yàn)?Go 中沒(méi)有這樣的關(guān)鍵字。

如果實(shí)現(xiàn)與接口有相同的方法,那么實(shí)現(xiàn)就已經(jīng)滿(mǎn)足了該接口,可以在客戶(hù)端代碼中使用。

5. 你正在返回接口

如果一個(gè)方法返回的是接口而不是具體的結(jié)構(gòu),那么調(diào)用該方法的所有客戶(hù)端都將被迫使用相同的抽象。

你需要讓客戶(hù)端決定他們需要什么樣的抽象,因?yàn)榇a是他們的庭院。

當(dāng)你想使用結(jié)構(gòu)體中的某項(xiàng)功能時(shí),卻因?yàn)榻涌跊](méi)有公開(kāi)它而無(wú)法使用,這是很煩人的。這種限制可能是有原因的,但并非總是如此。

下面是一個(gè)人為的例子:

type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// NewCircle returns an interface instead of struct
func NewCircle(radius float64) Shape {
    return Circle{Radius: radius}
}

func main() {
    circle := NewCircle(5)

    // we lose access to circle.Radius
}

在上面的示例中,我們不僅無(wú)法訪問(wèn) circle.Radius,而且每次要訪問(wèn)它時(shí)都需要在代碼中添加類(lèi)型斷言:

shape := NewCircle(5)

if circle, ok := shape.(Circle); ok {
    fmt.Println(circle.Radius)
}

因此在設(shè)計(jì)上,請(qǐng)遵循 Postel 定律:“發(fā)送時(shí)要保守,接受時(shí)要寬松”,從方法中返回具體的結(jié)構(gòu),并選擇接受接口。

結(jié)合現(xiàn)實(shí)的代碼,例如原本的代碼是:

// Save writes the contents of doc to the file f.
func Save(f *os.File, doc *Document) error

但入?yún)⑹?nbsp;f *os.File,這并不靈活,也不便于測(cè)試。我們可以通過(guò)上面提到的接口方式,改造為如下代碼:

// Save writes the contents of doc to the supplied
// Writer.
func Save(w io.Writer, doc *Document) error

6. 你沒(méi)有驗(yàn)證接口合規(guī)性

假設(shè)一個(gè)場(chǎng)景,你有一個(gè)導(dǎo)出名為 User 的類(lèi)型的軟件包,你實(shí)現(xiàn)了 Stringer 接口。

因?yàn)槌鲇谀撤N業(yè)務(wù)原因,當(dāng)你打印時(shí),你不希望顯示 email 字段。需要如此實(shí)現(xiàn)邏輯:

package users

type User struct {
    Name  string
    Email string
}

func (u User) String() string {
    return u.Name
}

客戶(hù)端的代碼如下:

package main

import (
    "fmt"

    "pkg/users"
)

func main() {
    u := users.User{
       Name:  "腦子進(jìn)煎魚(yú)了",
       Email: "xxx@gmail.com",
    }
    fmt.Printf("%s", u)
}

這將正確輸出:腦子進(jìn)煎魚(yú)了。

現(xiàn)在,假設(shè)你進(jìn)行了重構(gòu),不小心刪除或注釋了 String() 的實(shí)現(xiàn),你的代碼看起來(lái)就像這樣:

package users

type User struct {
    Name  string
    Email string
}

在這種情況下,您的代碼仍然可以編譯和運(yùn)行,但輸出結(jié)果將是 {腦子進(jìn)煎魚(yú)了 xxx@gmail.com}。沒(méi)有任何程序反饋來(lái)提示你出現(xiàn)了問(wèn)題。編譯器也不會(huì)報(bào)錯(cuò)。

這種場(chǎng)景下,為了強(qiáng)制校驗(yàn)?zāi)硞€(gè)類(lèi)型是否實(shí)現(xiàn)了某個(gè)接口,我們可以這樣做:

package users

import "fmt"

type User struct {
    Name  string
    Email string
}

var _ fmt.Stringer = User{} // User implements the fmt.Stringer

func (u User) String() string {
    return u.Name
}

我們?cè)賴(lài)L試一次?,F(xiàn)在如果我們刪除 String() 方法,就會(huì)在構(gòu)建時(shí)得到如下結(jié)果:

cannot use User{} (value of type User) as fmt.Stringer value in variable declaration: User does not implement fmt.Stringer (missing method String)

在該行中,我們?cè)噲D將一個(gè)空的 User{} 賦值給一個(gè) fmt.Stringer類(lèi)型的變量。由于 User{} 不再實(shí)現(xiàn) fmt.Stringer,我們得到了程序的報(bào)錯(cuò)反饋。

我們?cè)谧兞棵惺褂昧?nbsp;_,因?yàn)槲覀儾](méi)有真正使用它,所以不會(huì)執(zhí)行真正的分配。

上面我們看到 User 實(shí)現(xiàn)了接口。User 和 *User 是不同的類(lèi)型。因此,如果你想讓 *User 實(shí)現(xiàn)它,你可以這樣實(shí)現(xiàn):

var _ fmt.Stringer = (*User)(nil) // *User implements the fmt.Stringer

并且通過(guò)這種實(shí)現(xiàn),IDE 會(huì)顯式提示我們的方法是否有缺失。少了的話(huà)會(huì)有報(bào)錯(cuò)提示:

圖片圖片

還是非常方便的。當(dāng)然,這個(gè)小技巧,不需要對(duì)每個(gè)實(shí)現(xiàn)接口的類(lèi)型都這樣做,根據(jù)需求對(duì)于有必要強(qiáng)校驗(yàn)的接口即可。

總結(jié)

今天這篇文章給大家介紹了六種常見(jiàn)的接口設(shè)計(jì)錯(cuò)誤:創(chuàng)建了太多的接口、方法太多了、沒(méi)有編寫(xiě)行為驅(qū)動(dòng)的接口、在生產(chǎn)者端編寫(xiě)接口、正在返回接口、沒(méi)有驗(yàn)證接口合規(guī)性。

我還記得以前看到某個(gè)項(xiàng)目里,寫(xiě)了個(gè)接口,20~30 個(gè)有待實(shí)現(xiàn)的方法。然而他就真的只是一個(gè)接口,那么多年過(guò)去了也沒(méi)有人再去實(shí)現(xiàn)這個(gè)接口。

當(dāng)然,本文提供的很多接口優(yōu)化建議,也是需要結(jié)合你的實(shí)際代碼(業(yè)務(wù))場(chǎng)景去考慮的。大家各取所需,學(xué)習(xí)之即可。

責(zé)任編輯:武曉燕 來(lái)源: 腦子進(jìn)煎魚(yú)了
相關(guān)推薦

2023-09-07 11:53:05

2024-01-07 13:25:32

Go編程代碼

2024-01-15 06:45:29

Go編程代碼

2022-06-28 10:13:09

Pandas錯(cuò)誤Python

2018-03-17 09:04:35

2022-06-28 10:17:23

安全職位首席信息安全官

2014-04-22 09:33:49

云計(jì)算云安全云遷移

2023-11-15 13:12:16

2024-05-21 11:21:26

數(shù)字化轉(zhuǎn)型

2019-03-29 15:34:39

Go框架Web

2024-05-10 09:28:57

Python面向?qū)ο?/a>代碼

2009-06-29 16:09:20

JSP編程

2016-04-18 09:18:28

用戶(hù)體驗(yàn)設(shè)計(jì)產(chǎn)品

2020-11-09 10:18:04

網(wǎng)絡(luò)安全

2021-12-07 14:08:45

人工智能AI深度學(xué)習(xí)

2021-04-22 08:00:00

人工智能機(jī)器學(xué)習(xí)數(shù)據(jù)

2022-05-17 15:34:08

視覺(jué)效果UI 界面設(shè)計(jì)

2019-06-21 13:50:33

數(shù)據(jù)中心

2022-11-15 16:54:54

2019-06-11 14:20:29

人工智能AI
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)