当前位置:首页 > 科技  > 软件

六个常见的 Go 接口设计错误

来源: 责编: 时间:2024-07-03 17:19:53 105观看
导读Go 是一门新兴的语言,如果你正在使用它,很可能这不是你的第一门编程语言。从不同的语言背景转来,你带来了自己的既有经验和范式。你在以前的语言中习惯做的事情,在 Go 中可能并不是一个好的方式。学习 Go 不仅仅是学习一

Go 是一门新兴的语言,如果你正在使用它,很可能这不是你的第一门编程语言。zF628资讯网——每日最新资讯28at.com

从不同的语言背景转来,你带来了自己的既有经验和范式。你在以前的语言中习惯做的事情,在 Go 中可能并不是一个好的方式。zF628资讯网——每日最新资讯28at.com

学习 Go 不仅仅是学习一种新的语法。它还涉及到学习一种新的程序思维方式。zF628资讯网——每日最新资讯28at.com

一些理论知识

Go 创始人 Rob Pike 曾经说过:“Go 的接口并不是 Java 或 C# 接口的变种,而是更多。它们是大规模编程和适应性强的进化设计关键。”zF628资讯网——每日最新资讯28at.com

图片图片zF628资讯网——每日最新资讯28at.com

正确使用接口可以带来简单性、可读性和更灵活的代码设计。(不正确使用会带来新的灾难)zF628资讯网——每日最新资讯28at.com

以下是建议了解的基本原则 TOP3:zF628资讯网——每日最新资讯28at.com

  • 接口隔离原则:客户端永远不应该被迫实现它不使用的接口,或者客户端不应该被迫依赖它们不使用的方法。
  • 多态性:一段代码根据它接收到的具体数据改变其行为。
  • 里氏替换原则:如果你的代码依赖于一个抽象,一个实现可以被另一个实现替换,而不需要改变你的代码。

常见接口设计错误

1. 你创建了太多的接口

拥有过多接口的情况叫做:接口污染。当你在编写具体类型之前就开始抽象时,就会出现这种情况。zF628资讯网——每日最新资讯28at.com

由于无法预知需要哪些抽象,因此很容易编写出过多的接口,而这些接口在日后要么是错误的,要么是无用的。zF628资讯网——每日最新资讯28at.com

Go 创始人给出了一个很棒的指导方针帮助我们避免接口污染。如下:zF628资讯网——每日最新资讯28at.com

不要使用接口设计,而是发现它们(Don’t design with interfaces, discover them.) —— Rob PikezF628资讯网——每日最新资讯28at.com

Rob 在这里指出的是:你不需要提前考虑你需要什么抽象。你可以从具体的结构体开始设计,并在设计需要时只创建接口。通过这样做,你的代码会有机地增长到预期的设计。zF628资讯网——每日最新资讯28at.com

但我仍然看到人们提前创建接口,因为他们认为他们将来可能需要多个实现。zF628资讯网——每日最新资讯28at.com

对于他们,我要说:zF628资讯网——每日最新资讯28at.com

图片图片zF628资讯网——每日最新资讯28at.com

懒惰但要懒惰得好。创建接口的完美时机是当你真正需要它的时候,而不是当你预测你需要它的时候。zF628资讯网——每日最新资讯28at.com

无用的接口往往只有一个实现。它们只是增加了一个额外的间接层,迫使程序员在他们实际上想要去实现时总是要通过这一层。zF628资讯网——每日最新资讯28at.com

一个接口有一个成本:它是你在推理你的代码时需要记住的一个新概念。正如 Djikstra 所说,一个理想的接口必须是:“一个可以绝对精确的新语义层面。”zF628资讯网——每日最新资讯28at.com

所以在创建接口之前问问自己:你有多个接口的实现吗?我强调了 “有”,因为 “将有” 意味着你可以预测未来,而你不能。zF628资讯网——每日最新资讯28at.com

2. 你的方法太多了

在 PHP 项目中看到 10 个方法的接口是很典型的。在 Go 中,接口很小,标准库中所有接口的平均方法数是 2。zF628资讯网——每日最新资讯28at.com

“越大的接口抽象就越弱”,这是 Go 谚语之一。正如 Rob Pike 所说,这是关于接口最重要的事情,这意味着接口越小,它就越有用。zF628资讯网——每日最新资讯28at.com

一个接口可以有的实现越多,它就越通用。如果你有一个包含大量方法的接口,就很难有多个实现。zF628资讯网——每日最新资讯28at.com

你拥有的方法越多,接口就越具体。接口越具体,不同类型的机会就越小,它们可以显示相同的行为。zF628资讯网——每日最新资讯28at.com

io.Reader 和 io.Writer 就是有用接口的一个很好的例子,它们有数以百计的实现。或者是最经典的 error 接口,它非常强大,可以在 Go 中实现整个错误处理。zF628资讯网——每日最新资讯28at.com

记住,你可以从其他接口组合一个接口。这里有一个例子:ReadWriteCloser 由 3 个较小的接口组成。zF628资讯网——每日最新资讯28at.com

代码如下:zF628资讯网——每日最新资讯28at.com

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

3. 你没有编写行为驱动的接口

在传统语言中,诸如 User、Request 等名词性接口非常常见。而在 Go 语言中,大多数接口都有后缀:Reader、Writer、Closer 等。这是因为,在 Go 中,接口暴露了行为,而它们的名称则指向该行为。zF628资讯网——每日最新资讯28at.com

在 Go 中定义接口时,你定义的不是 "某物是什么",而是 "它提供了什么"-- 是 "行为",而不是 "事物"!zF628资讯网——每日最新资讯28at.com

这就是为什么 Go 中没有 File 接口,但有Reader 和 Writer:这些都是行为,而 File 是实现 Reader 和 Writer 的事物。zF628资讯网——每日最新资讯28at.com

《Effective Go》也提到了同样的观点:zF628资讯网——每日最新资讯28at.com

Go 中的接口提供了一种指定对象行为的方法:如果某个东西可以做到这一点,那么它就可以用在这里。zF628资讯网——每日最新资讯28at.com

在编写接口时,尽量考虑动作或行为。如果你定义了一个名为 "Thing" 的接口,问问自己为什么这个 "Thing" 不是一个结构体。zF628资讯网——每日最新资讯28at.com

4. 你在生产者端编写接口

我经常在代码审查中看到这种情况:很多开发者在同一个包,既写了具体的实现,又定义了接口。zF628资讯网——每日最新资讯28at.com

生产者定义的接口生产者定义的接口zF628资讯网——每日最新资讯28at.com

但是,也许客户端并不想使用生产者接口中的所有方法。请记住 "接口隔离原则" 中的一句话:"不应强迫客户端实现其不使用的方法"。zF628资讯网——每日最新资讯28at.com

下面是一个例子:zF628资讯网——每日最新资讯28at.com

package main// ====== producer side// This interface is not neededtype 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 sidetype UserGetter interface {    GetUser(id string)}

如果客户端想使用生产者的所有方法,可以使用具体的结构体。因为结构体方法已经提供了这些行为。zF628资讯网——每日最新资讯28at.com

即使客户端想要解耦代码并使用多种实现,他也可以在自己这边创建一个包含所有方法的接口:zF628资讯网——每日最新资讯28at.com

在客户端定义的接口在客户端定义的接口zF628资讯网——每日最新资讯28at.com

由于 Go 中的接口是隐式满足的,所以这些事情才得以实现。客户端代码不再需要导入某个接口并编写实现,因为 Go 中没有这样的关键字。zF628资讯网——每日最新资讯28at.com

如果实现与接口有相同的方法,那么实现就已经满足了该接口,可以在客户端代码中使用。zF628资讯网——每日最新资讯28at.com

5. 你正在返回接口

如果一个方法返回的是接口而不是具体的结构,那么调用该方法的所有客户端都将被迫使用相同的抽象。zF628资讯网——每日最新资讯28at.com

你需要让客户端决定他们需要什么样的抽象,因为代码是他们的庭院。zF628资讯网——每日最新资讯28at.com

当你想使用结构体中的某项功能时,却因为接口没有公开它而无法使用,这是很烦人的。这种限制可能是有原因的,但并非总是如此。zF628资讯网——每日最新资讯28at.com

下面是一个人为的例子:zF628资讯网——每日最新资讯28at.com

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 structfunc NewCircle(radius float64) Shape {    return Circle{Radius: radius}}func main() {    circle := NewCircle(5)    // we lose access to circle.Radius}

在上面的示例中,我们不仅无法访问 circle.Radius,而且每次要访问它时都需要在代码中添加类型断言:zF628资讯网——每日最新资讯28at.com

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

因此在设计上,请遵循 Postel 定律:“发送时要保守,接受时要宽松”,从方法中返回具体的结构,并选择接受接口。zF628资讯网——每日最新资讯28at.com

结合现实的代码,例如原本的代码是:zF628资讯网——每日最新资讯28at.com

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

但入参是 f *os.File,这并不灵活,也不便于测试。我们可以通过上面提到的接口方式,改造为如下代码:zF628资讯网——每日最新资讯28at.com

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

6. 你没有验证接口合规性

假设一个场景,你有一个导出名为 User 的类型的软件包,你实现了 Stringer 接口。zF628资讯网——每日最新资讯28at.com

因为出于某种业务原因,当你打印时,你不希望显示 email 字段。需要如此实现逻辑:zF628资讯网——每日最新资讯28at.com

package userstype User struct {    Name  string    Email string}func (u User) String() string {    return u.Name}

客户端的代码如下:zF628资讯网——每日最新资讯28at.com

package mainimport (    "fmt"    "pkg/users")func main() {    u := users.User{       Name:  "脑子进煎鱼了",       Email: "xxx@gmail.com",    }    fmt.Printf("%s", u)}

这将正确输出:脑子进煎鱼了。zF628资讯网——每日最新资讯28at.com

现在,假设你进行了重构,不小心删除或注释了 String() 的实现,你的代码看起来就像这样:zF628资讯网——每日最新资讯28at.com

package userstype User struct {    Name  string    Email string}

在这种情况下,您的代码仍然可以编译和运行,但输出结果将是 {脑子进煎鱼了 xxx@gmail.com}。没有任何程序反馈来提示你出现了问题。编译器也不会报错。zF628资讯网——每日最新资讯28at.com

这种场景下,为了强制校验某个类型是否实现了某个接口,我们可以这样做:zF628资讯网——每日最新资讯28at.com

package usersimport "fmt"type User struct {    Name  string    Email string}var _ fmt.Stringer = User{} // User implements the fmt.Stringerfunc (u User) String() string {    return u.Name}

我们再尝试一次。现在如果我们删除 String() 方法,就会在构建时得到如下结果:zF628资讯网——每日最新资讯28at.com

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

在该行中,我们试图将一个空的 User{} 赋值给一个 fmt.Stringer类型的变量。由于 User{} 不再实现 fmt.Stringer,我们得到了程序的报错反馈。zF628资讯网——每日最新资讯28at.com

我们在变量名中使用了 _,因为我们并没有真正使用它,所以不会执行真正的分配。zF628资讯网——每日最新资讯28at.com

上面我们看到 User 实现了接口。User 和 *User 是不同的类型。因此,如果你想让 *User 实现它,你可以这样实现:zF628资讯网——每日最新资讯28at.com

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

并且通过这种实现,IDE 会显式提示我们的方法是否有缺失。少了的话会有报错提示:zF628资讯网——每日最新资讯28at.com

图片图片zF628资讯网——每日最新资讯28at.com

还是非常方便的。当然,这个小技巧,不需要对每个实现接口的类型都这样做,根据需求对于有必要强校验的接口即可。zF628资讯网——每日最新资讯28at.com

总结

今天这篇文章给大家介绍了六种常见的接口设计错误:创建了太多的接口、方法太多了、没有编写行为驱动的接口、在生产者端编写接口、正在返回接口、没有验证接口合规性。zF628资讯网——每日最新资讯28at.com

我还记得以前看到某个项目里,写了个接口,20~30 个有待实现的方法。然而他就真的只是一个接口,那么多年过去了也没有人再去实现这个接口。zF628资讯网——每日最新资讯28at.com

当然,本文提供的很多接口优化建议,也是需要结合你的实际代码(业务)场景去考虑的。大家各取所需,学习之即可。zF628资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-98556-0.html六个常见的 Go 接口设计错误

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 秒杀系统架构解析:应对高并发的艺术

下一篇: CSS 实现3d轮播图的一些思路,你学会了吗?

标签:
  • 热门焦点
  • 影音体验是真的强 简单聊聊iQOO Pad

    影音体验是真的强 简单聊聊iQOO Pad

    大公司的好处就是产品线丰富,非常细分化的东西也能给你做出来,例如早先我们看到了新的vivo Pad2,之后我们又在iQOO Neo8 Pro的发布会上看到了iQOO的首款平板产品iQOO Pad。虽
  • 小米降噪蓝牙耳机Necklace分享:听一首歌 读懂一个故事

    小米降噪蓝牙耳机Necklace分享:听一首歌 读懂一个故事

    在今天下午的小米Civi 2新品发布会上,小米还带来了一款新的降噪蓝牙耳机Necklace,我们也在发布结束的第一时间给大家带来这款耳机的简单分享。现在大家能见到最多的蓝牙耳机
  • 如何正确使用:Has和:Nth-Last-Child

    如何正确使用:Has和:Nth-Last-Child

    我们可以用CSS检查,以了解一组元素的数量是否小于或等于一个数字。例如,一个拥有三个或更多子项的grid。你可能会想,为什么需要这样做呢?在某些情况下,一个组件或一个布局可能会
  • 一文搞定Java NIO,以及各种奇葩流

    一文搞定Java NIO,以及各种奇葩流

    大家好,我是哪吒。很多朋友问我,如何才能学好IO流,对各种流的概念,云里雾里的,不求甚解。用到的时候,现百度,功能虽然实现了,但是为什么用这个?不知道。更别说效率问题了~下次再遇到,
  • 2天涨粉255万,又一赛道在抖音爆火

    2天涨粉255万,又一赛道在抖音爆火

    来源:运营研究社作者 | 张知白编辑 | 杨佩汶设计 | 晏谈梦洁这个暑期,旅游赛道彻底火了:有的「地方」火了——贵州村超旅游收入 1 个月超过 12 亿;有的「博主」火了&m
  • 10天营收超1亿美元,《星铁》比《原神》差在哪?

    10天营收超1亿美元,《星铁》比《原神》差在哪?

    来源:伯虎财经作者:陈平安即便你没玩过《原神》,你一定听说过的它的大名。恨它的人把《原神》开服那天称作是中国游戏史上最黑暗的一天,有粉丝因为索尼在PS平台上线《原神》,怒而
  • 微博大门常打开,迎接海外画师漂洋东渡

    微博大门常打开,迎接海外画师漂洋东渡

    作者:互联网那些事“起猛了,我能看得懂日语了”。“为什么日本人说话我能听懂?”“中文不像中文,日语不像日语,但是我竟然看懂了”…&hell
  • 国行版三星Galaxy Z Fold5/Z Flip5发布 售价7499元起

    国行版三星Galaxy Z Fold5/Z Flip5发布 售价7499元起

    2023年8月3日,三星电子举行Galaxy新品中国发布会,正式在国内推出了新一代折叠屏智能手机三星Galaxy Z Fold5与Galaxy Z Flip5,以及三星Galaxy Tab S9
  • 三星获批量产iPhone 15全系屏幕:苹果史上最惊艳直屏

    三星获批量产iPhone 15全系屏幕:苹果史上最惊艳直屏

    按照惯例,苹果将继续在今年9月举办一年一度的秋季新品发布会,有传言称发布会将于9月12日举行,届时全新的iPhone 15系列将正式与大家见面,不出意外的话
Top