如何解决Golang中的golang-ci lint问题

如何解决Golang中的golang-ci lint问题 我编写了以下代码来仅创建一次 dbSession,但我从 golang ci-lint 收到了以下警告:

once 是一个全局变量 (gochecknoglobals) 导出的 func NewDbSession 返回了未导出的类型 dbSession,这在使用时可能会令人困扰 (golint)

var (
	once   sync.Once
	dbSess dbSession
)

type dbSession struct {
	session *gocql.Session
}

func NewDbSession() dbSession {
	once.Do(func() {
		if dbSess.session == nil {
			session, err := createSession()
			if err != nil {
				panic(err)
			}
			dbSess = dbSession{session: session}
		}
	})

	return dbSess
}

func createSession() {
	....
}

这被认为是 Go 语言中实现单例模式的惯用方式。

  • 首先,我们如何在不使用全局变量的情况下实现单例?
  • 其次,有时我们不想通过导出的函数暴露类型。在 Go 语言中,对于返回值的导出函数,是否必须拥有导出的返回类型,这是一条硬性规定吗?

更多关于如何解决Golang中的golang-ci lint问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

恰当的做法是公开类型,以便人们可以使用它来论证和推理自己的代码,同时不暴露其内部字段。

更多关于如何解决Golang中的golang-ci lint问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


lutzhorn:

你可以将全局变量命名为 _once,这样是可以的。(参见 https://github.com/leighmcculloch/gochecknoglobals#exceptions

这并没有解决问题。 我仍然收到 _once 是一个全局变量 (gochecknoglobals) 的错误。

siddhanta_rath:

  • 首先,我们如何在不使用全局变量的情况下实现单例模式?

你可以将全局变量命名为 _once,这是允许的。(参见 GitHub - leighmcculloch/gochecknoglobals: Check that no globals are present in Go code.

siddhanta_rath:

  • 其次,有时我们不想通过导出的函数暴露类型。

为什么?

为什么?

  • 避免运行时错误
  • 实现封装

为什么 golint 将其视为警告?

在 Go 中,使用这种模式/结构很常见。

type sample struct {
    // 字段放在这里
}

fun NewSample(/* 输入变量 */) *sample {
    // 特定的初始化逻辑放在这里
    // 此包不直接导出 sample 结构体
    return &s
}

golint 会警告:“exported func NewSample returns unexported type *main.sample, which can be annoying to use (golint)”

关于golangci-lint警告的专业解答

1. 解决全局变量警告 (gochecknoglobals)

您可以使用包级变量但避免全局状态的方式重构代码。以下是几种解决方案:

方案A:使用sync.Once作为结构体字段

type Database struct {
    once    sync.Once
    session *gocql.Session
}

var dbInstance *Database

func GetDatabase() *Database {
    if dbInstance == nil {
        dbInstance = &Database{}
    }
    dbInstance.once.Do(func() {
        var err error
        dbInstance.session, err = createSession()
        if err != nil {
            panic(err)
        }
    })
    return dbInstance
}

方案B:完全避免包级变量(推荐)

type database struct {
    session *gocql.Session
}

var (
    dbInstance *database
    dbOnce     sync.Once
)

func GetSession() *gocql.Session {
    dbOnce.Do(func() {
        session, err := createSession()
        if err != nil {
            panic(err)
        }
        dbInstance = &database{session: session}
    })
    return dbInstance.session
}

2. 解决导出函数返回未导出类型的问题 (golint)

方案A:返回接口而不是具体类型

type Session interface {
    Query(stmt string, values ...interface{}) *gocql.Query
    Close()
    // 其他需要的方法
}

type dbSession struct {
    session *gocql.Session
}

func (d *dbSession) Query(stmt string, values ...interface{}) *gocql.Query {
    return d.session.Query(stmt, values...)
}

func (d *dbSession) Close() {
    d.session.Close()
}

func NewDbSession() Session {
    var instance *dbSession
    once.Do(func() {
        session, err := createSession()
        if err != nil {
            panic(err)
        }
        instance = &dbSession{session: session}
    })
    return instance
}

方案B:返回导出的结构体包装器

type DBSession struct {
    session *gocql.Session
}

func (d *DBSession) Query(stmt string, values ...interface{}) *gocql.Query {
    return d.session.Query(stmt, values...)
}

func NewDbSession() *DBSession {
    var instance *DBSession
    once.Do(func() {
        session, err := createSession()
        if err != nil {
            panic(err)
        }
        instance = &DBSession{session: session}
    })
    return instance
}

方案C:返回函数闭包(当只需要少数方法时)

type SessionFunc func(stmt string, values ...interface{}) *gocql.Query

func NewDbSession() SessionFunc {
    var session *gocql.Session
    once.Do(func() {
        var err error
        session, err = createSession()
        if err != nil {
            panic(err)
        }
    })
    
    return func(stmt string, values ...interface{}) *gocql.Query {
        return session.Query(stmt, values...)
    }
}

// 使用示例
queryFunc := NewDbSession()
query := queryFunc("SELECT * FROM users")

3. 完整的最佳实践示例

package database

import (
    "sync"
    "github.com/gocql/gocql"
)

type Session interface {
    Query(stmt string, values ...interface{}) *gocql.Query
    Close() error
    ExecuteBatch(batch *gocql.Batch) error
}

type cassandraSession struct {
    once    sync.Once
    session *gocql.Session
}

func (cs *cassandraSession) initialize() error {
    var initErr error
    cs.once.Do(func() {
        cluster := gocql.NewCluster("127.0.0.1")
        cluster.Keyspace = "mykeyspace"
        session, err := cluster.CreateSession()
        if err != nil {
            initErr = err
            return
        }
        cs.session = session
    })
    return initErr
}

func (cs *cassandraSession) Query(stmt string, values ...interface{}) *gocql.Query {
    if err := cs.initialize(); err != nil {
        panic(err)
    }
    return cs.session.Query(stmt, values...)
}

func (cs *cassandraSession) Close() error {
    if cs.session != nil {
        return cs.session.Close()
    }
    return nil
}

func (cs *cassandraSession) ExecuteBatch(batch *gocql.Batch) error {
    if err := cs.initialize(); err != nil {
        return err
    }
    return cs.session.ExecuteBatch(batch)
}

var globalSession *cassandraSession
var globalOnce sync.Once

func GetSession() Session {
    globalOnce.Do(func() {
        globalSession = &cassandraSession{}
    })
    return globalSession
}

关于Go语言规范的说明:

  1. 导出函数返回未导出类型:这不是语法错误,但违反了Go的导出约定。导出的函数应该返回导出的类型,否则调用者无法声明该类型的变量或将其作为参数传递。

  2. 单例模式实现:在Go中,通常使用包级函数配合sync.Once来实现单例,但可以通过将状态封装在结构体中来减少全局变量的使用。

这些修改既符合golangci-lint的规范,也遵循了Go语言的惯用写法。

回到顶部