Go语言基础:隐藏声明 | 豆包MarsCode AI 刷题

75 阅读2分钟

变量的作用域是指它的可见性。换句话说,程序中的名称在哪部分是有效的。在 Go 中,在块中声明的变量名称可以在内部块中重新声明。这种被称为变量隐藏的原则很容易出现错误。

隐藏声明也叫隐性声明,我们以一个实际操作场景来说明:项目接入数据库,在使用数据库前我们通常是要先初始化数据库,然后在我们的项目中使用初始化好的组件。而隐藏声明就能实现这种效果。

// Package db
// @Author twilikiss 2024/5/14 23:00:00
package db

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"sgServer/config"
	"sgServer/log"
	"strconv"
	"xorm.io/xorm"
)

// Engine 隐藏的声明
var Engine *xorm.Engine

func InitDB() {
	mysqlConfig := config.Cfg.MySQL

	dbConn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
		mysqlConfig.User,
		mysqlConfig.Password,
		mysqlConfig.Host,
		mysqlConfig.Port,
		mysqlConfig.Dbname,
	)
	e, err := xorm.NewEngine("mysql", dbConn) <--------------
	if err != nil {
		log.Error("数据库连接失败", err)
		panic(err)
	}
	err = e.Ping()
	if err != nil {
		log.Error("数据库ping不通", err)
		panic(err)
	}

	maxidle, _ := strconv.Atoi(mysqlConfig.MaxIdle)
	maxconn, _ := strconv.Atoi(mysqlConfig.MaxConn)

	e.SetMaxIdleConns(maxidle)
	e.SetMaxOpenConns(maxconn)
	e.ShowSQL(true)
	log.Info("数据库初始化完成...")
	Engine = e
}
// Package login
// @Author twilikiss 2024/5/14 18:08:08
package login

import (
	"sgServer/db"
	"sgServer/net"
	"sgServer/server/login/controller"
	"xorm.io/xorm"
)

var Router = net.NewRouter()

func Init() {
	db.InitDB()
	initRouter(db.Engine)
}

func initRouter(engine *xorm.Engine) {
	controller.DefaultAccount.Router(Router, engine)
}

我们可以看到mysql.go定义并对外暴露了一个var Engine *xorm.Engine,我们在执行InitDB()之后,初始化数据库后把创建出来的Engine赋值到对外暴露的Engine,我们在init.go中直接调用db.Engine

虽然这种方式看起来是否方便,事实上这种隐藏性声明是十分危险的,我们可以看看下面这个代码:

// Engine 隐藏的声明
var Engine *xorm.Engine

func InitDB() {
	mysqlConfig := config.Cfg.MySQL

	dbConn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
		mysqlConfig.User,
		mysqlConfig.Password,
		mysqlConfig.Host,
		mysqlConfig.Port,
		mysqlConfig.Dbname,
	)
	Engine, err := xorm.NewEngine("mysql", dbConn) <--------------
	if err != nil {
		log.Error("数据库连接失败", err)
		panic(err)
	}
	err = Engine.Ping()
	if err != nil {
		log.Error("数据库ping不通", err)
		panic(err)
	}

	maxidle, _ := strconv.Atoi(mysqlConfig.MaxIdle)
	maxconn, _ := strconv.Atoi(mysqlConfig.MaxConn)

	Engine.SetMaxIdleConns(maxidle)
	Engine.SetMaxOpenConns(maxconn)
	Engine.ShowSQL(true)
	log.Info("数据库初始化完成...")
}
这个代码和原来那个`mysql.go`的区别是我们没使用临时变量`e`,而是直接赋值到对外暴露的`Engine`,这个看起来好像是没有问题。

你要是一执行就会发现,代码似乎没有报错,但是当我们调用数据库就会报空指针,debug发现我们的db.Engine根本没有赋上值。原因其实也很好理解,我们使用:=来赋值会导致该操作符使用和开始的时候相同的名称创建了一个新的Engine变量;它不会为第一个中的 Engine 变量赋值。

我们有两种方法解决:①使用=来代替我们的:=,②使用临时变量,使用=的话就要注意,我们的error也要被提取出来。

// Engine 隐藏的声明
var Engine *xorm.Engine
var err error

func InitDB() {
	mysqlConfig := config.Cfg.MySQL

	dbConn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
		mysqlConfig.User,
		mysqlConfig.Password,
		mysqlConfig.Host,
		mysqlConfig.Port,
		mysqlConfig.Dbname,
	)
	Engine, err = xorm.NewEngine("mysql", dbConn) <--------------
	if err != nil {
		log.Error("数据库连接失败", err)
		panic(err)
	}
	err = Engine.Ping()
	if err != nil {
		log.Error("数据库ping不通", err)
		panic(err)
	}

	maxidle, _ := strconv.Atoi(mysqlConfig.MaxIdle)
	maxconn, _ := strconv.Atoi(mysqlConfig.MaxConn)

	Engine.SetMaxIdleConns(maxidle)
	Engine.SetMaxOpenConns(maxconn)
	Engine.ShowSQL(true)
	log.Info("数据库初始化完成...")
}