接口命名约定 Golang

37

我只是发表我的代码:

/*
*  Role will ALWAYS reserve the session key "role".
 */
package goserver

const (
    ROLE_KEY string = "role"
)

type Role string

//if index is higher or equal than role, will pass
type RolesHierarchy []Role

func (r Role) String() string {
    return string(r)
}

func NewRole(session ServerSession) Role {
    return session.GetValue(ROLE_KEY).(Role)
}

func (this Role) IsRole(role Role, hierarchy RolesHierarchy) bool {
    if role == this {
        return true
    }
    if len(hierarchy) == 0 {
        return false
    }
    var thisI int = 0
    var roleI int = 0
    //Duped roles in hierarchy are verified in verifyConfig during parse
    for i, r := range hierarchy {
        if this == r {
            thisI = i
        }
        if role == r {
            roleI = i
        }
    }
    //TODO I can probably condense what follows into one if
    if thisI == 0 && roleI == 0 {
        return false
    }
    return thisI >= roleI
}

func (this *Role) AssumeRole(session ServerSession, role Role) {
    session.SetValue(ROLE_KEY, role)
    *this = role
}

需要注意的是,ServerSession也是一个接口,称之为"ServerSessioner"感觉很奇怪。

我在考虑创建一个拥有IsRole()和AssumeRole()方法的接口,但是"Roler"听起来很奇怪。我开始意识到我并不真正知道或者从来没有遇到过接口命名规范,除了标准的"er"后缀以外。我似乎记得VS C++的约定就是在每个名称前面加上"I"。这种方式是否符合"惯用语"呢?

有什么建议吗?


1
我只会称其为RoleSupport,但是前往英文论坛寻求帮助也是一个有趣的尝试。请不要使用this来命名方法接收者:这被认为是Go语言中不符合惯用法的做法。这里有一个讨论此类问题的例子。 - kostix
1
不要使用单个字母作为缩写,而应该使用有意义的缩写,对于短函数可以使用单个字母(包括你自己的函数)。"任何其他语言"这种说法肯定是夸张了。但出于某种原因,对我来说这不是问题:不同的语言感觉上确实不同。新手程序员通常会努力成为一只只会一种技能的狗,试图将他们学到的技能套用到任何新的语言中(我自己也曾经历过这种情况),但更好的方法是理解语言背后的哲学并坚持它。 - kostix
1
至少这能降低下一个处理你代码的程序员的WTF系数。;-) - kostix
2
WTF因子,“this”或“self”在我“了解”的至少六种语言中是“惯用语”。 - Dale
3
@Dale,这并不是关于Go语言的问题,请查看:In Go is naming the receiver variable 'self' misleading or good practise? - icza
显示剩余2条评论
3个回答

51

针对您的情况,我会将它们命名为RoleCheckerRoleAssumer,合并后命名为RoleCheckerAssumer。或者,如果您选择单一接口,可以命名为RoleHelperRoleChecker

ServerSession也可以,甚至可以只用Session(特别是如果没有“客户端”会话)。另一方面,ServerSessioner就不好了,Session不是动词,也不是接口的方法。


关于惯例已经有很多帖子了。

Effective Go: Interface names:

按照惯例,只有一个方法的接口由方法名称加上“-er”后缀或类似修改构造代理名:如Reader, Writer, Formatter, CloseNotifier等。

有许多这样的名称,遵守它们以及它们所捕获的函数名称是有益的。Read, Write, Close, Flush, String等具有规范的签名和含义。为避免混淆,请不要给您的方法赋予这些名称之一,除非它具有相同的签名和含义。反之,如果您的类型实现了与已知类型上的方法具有相同含义的方法,请给它相同的名称和签名;将您的字符串转换器方法称为String而不是ToString

接口类型 @ What's in a name? - Talks at golang.org

指定一个方法的接口通常只是该函数名称加上 'er' 后缀。

type Reader interface {
    Read(p []byte) (n int, err error)
}
有时候结果并不是正确的英语,但我们仍然这样做。
type Execer interface {
    Exec(query string, args []Value) (Result, error)
}

有时我们用英语使它更加优雅:

type ByteReader interface {
    ReadByte() (c byte, err error)
}

当一个接口包含多个方法时,选择一个准确描述其目的的名称(例如:net.Conn、http.ResponseWriter、io.ReadWriter)。

对于接收器名称,不要使用 this 或者 self 等类似的名称。相反:

Receivers @ What's in a name? - Talks at golang.org

接收器是一种特殊类型的参数。按照惯例,它们是反映接收器类型的一个或两个字符,因为它们通常出现在几乎每一行代码中:

func (b *Buffer) Read(p []byte) (n int, err error)

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request)

func (r Rectangle) Size() Point
方法的接收器名称应该在类型的所有方法中保持一致。(不要在一个方法中使用r,在另一个方法中使用rdr。) Go代码审查注释:接收器名称: 方法接收器的名称应反映其标识,通常可以使用其类型的一个或两个字母缩写(例如"Client"的"c"或"cl")。不要使用通用的名称,如"me"、"this"或"self",这些是面向对象语言的标识符,强调方法而非函数。接收器的名称不需要像方法参数那样具有描述性,因为它的角色是明显的,不起文档作用。它可以非常短,因为它将出现在类型的每个方法的几乎每一行中; 熟悉使得简洁变得容易。还要保持一致性:如果您在一个方法中称接收器为"c",就不要在另一个方法中称其为"cl"。

2
单方法接口更“容易”。 “Is<something>()”让我感到困惑。 最终我只使用了“checker()”。 是的,抱歉,不会使用单个或两个字母的标识符。 我不在乎这里的习惯用语是什么。 我知道有半打语言使用this或self,为什么我要因为语言规范中的某些文档而打破惯例呢? this或self正是我所需要的。 最终,我需要阅读我的代码,如果我在我的代码中寻找一些单个字母的标识符,那还有什么意义呢? - Dale
4
@Dale 你可以做任何你想要做的事情,没有人会强迫你做任何事情。只要你独自编码,就不会影响到其他人。但是如果你想与他人合作,或者其他人必须使用你的代码,你需要使用“共同”的语言。关于thisself作为方法接收者:在Go中将接收者变量命名为“self”是误导还是良好的实践? - icza
1
谢谢。我不想争论。我计划开源这个堆。也许反馈会迫使我重新考虑我的决定。我仍然不相信设计师在这里的决定。话虽如此,我在Go中看到的几乎所有其他东西都非常令人印象深刻。 - Dale

4
我理解了,原来我可以使用"er"约定。
/*
*  Role will ALWAYS reserve the session key "role".
 */
package goserver

const (
    ROLE_KEY string = "role"
)

type Role string

//if index is higher or equal than role, will pass
type RolesHierarchy []Role

type RoleChecker interface {
    IsRole(Role, RolesHierarchy) bool
}

type RoleAssumer interface {
    AssumeRole(ServerSession, Role)
}

type RoleCheckerAssumer interface {
    RoleChecker
    RoleAssumer
}

func (r Role) String() string {
    return string(r)
}

func NewRole(session ServerSession) Role {
    return session.GetValue(ROLE_KEY).(Role)
}

func (this Role) IsRole(role Role, hierarchy RolesHierarchy) bool {
    if role == this {
        return true
    }
    if len(hierarchy) == 0 {
        return false
    }
    var thisI int = 0
    var roleI int = 0
    //Duped roles in hierarchy are verified in verifyConfig during parse
    for i, r := range hierarchy {
        if this == r {
            thisI = i
        }
        if role == r {
            roleI = i
        }
    }
    //TODO I can probably condense what follows into one if
    if thisI == 0 && roleI == 0 {
        return false
    }
    return thisI >= roleI
}

func (this *Role) AssumeRole(session ServerSession, role Role) {
   session.SetValue(ROLE_KEY, role)
   *this = role
}

感谢Sarathsp让我认真思考这个问题。

3

在Go语言中,按照惯例,只有一个方法的接口名称应该是表示执行某个动作的名词。例如:

the `Read` method implements the `Reader` interface, and
the `Generate` method implements the `Generator` interface.

无论具体内容是什么,清晰明确地说明公约的细节最好。当接口只需要一个函数或者一组非常具体的函数时,这个做法会非常有用。

在函数的最低通分母前面使用 I 前缀的惯例被广泛采用,在此情况下,IRole 将成为更好的接口名称,因为该接口定义了两个函数,所有代表 Role 的类型都必须满足这两个函数。


IsRoler和AssumeRoler -> IsserAssumer?哈哈,这个问题可能更适合在英语Stack Exchange上讨论。 - Dale
有时候结果并不是正确的英语,但我们仍然这样做。 - Sarath Sadasivan Pillai
@SarathSadasivanPillai.. 让我试着理解一下...如果我在这个上下文中引入一个结构体(与方法相关联的数据结构),我宁愿说“任何实现‘Read’方法的结构体都成为Reader”,这有意义吗?; 然后让我在这里再加一个快速问题:用作读取源的结构体的名称是什么?“Sourcer”??(我知道在Java中它将是“Readable!”) - Victor
@Victor 接口名称取决于它实现的功能。 - Sarath Sadasivan Pillai
@SarathSadasivanPillai,我想我明白了。问题在于,由于我多年使用JAVA,我的基于OO的思维仍然让我认为接口描述了对象(同一种类型)的行为,而Go中的接口也描述了对象的行为。两种方式都是用于描述一个对象的一个或多个方法,我们都同意这一点吧?我猜在JAVA中,你正在寻找一个描述客户端可以对对象执行的操作的单词(对象可以被“告知”要做的事情),而在GOLANG中,这个单词仅与对象可以执行的操作相关。这引起了我对可读性/易读性的问题。 - Victor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接