不要用动态语言的方式去看待Golang

不要用动态语言的方式去看待Golang

背景

当今时代对一门语言的市场需求:快速部署、并发、可扩展、社区活跃、低成本维护和经济高效。云原生时代,不少PHP开发转型Golang开发(一般说到这都会掰扯下两者区别,我这里就不做阐述了)。虽然转型更换了语言,但是在开中还是能发现很多PHP的影子,一些语法方面的就不说了,编译的时候会报错提示。最大的问题是以前习惯用PHP的数组,现在到了Golang强类型语言这里不习惯先定义好类型再使用。还有的小伙伴以前没有使用抛异常的习惯,经常在使用约定好的值做为错误依据来做返回数据。本文不是说针对PHP开发,而是对于动态语言转Golang可能出现的一些编码习惯问题提出一些建议。

一、“尽量”使用结构体代替Map

有的小伙伴经常用map或者切片map来存储一些固定的信息,如配置信息等,有多个的话就存放在切片map中。

以下例子只是为了突出map[string]interface{}的问题,而不是说使用的时候一定是配置信息才会出现这种问题。例如:


var configMap = []map[string]string{
	{
		"svc":   "user",
		"type":  "rpc",
		"limit": "100",
	},
}

后面使用时再通过键去取值,虽然这样做能实现,但会发现Go里面因为是强类型,你在用上述例子map里面的数字值时还得做类型转换。很多人会说把map的类型换成map[string]interface{}不就可以了,那你可以动手实践下看你用的时候能不能类型断言。


// 类型断言
var configMap = map[string]interface{}{
	"svc":   "user",
	"type":  "rpc",
	"limit": "100",
}
fmt.Println(configMap)

if bar, ok := configMap["limit"].(int64); ok {
	fmt.Println(bar)
} else {
	fmt.Println(ok)
}

这其实是一个思维的转变,在Go这种强类型语言里需要养成先定义结构体类型后使用的习惯,比如上面的例子可以先定义一个结构体。


type Config struct {
	Svc    string
	Type   string
	limit  int64
}

var configs = []*Config {
	{
		Svc: "user",
		Type: "rpc",
		Limit: 100,
	},
	// ...
}

这样就能避免还得把Limit转成整型的问题了,而且编辑器还会有类型提示,不需要你去记map里的键名,可以避免把键名写错导致BUG。

除了上面说的还有的小伙伴喜欢在返回值里返回Map,这种写法除了可能会导致上面说的问题,也会让其他人使用起来不方便。
比如:我现在要调用你的方法,我还得进去看你的代码,看看你的返回值map到底有哪些字段,字段是什么类型,是否要进行类型断言,要是字段多的话岂不是全部要整一遍。

我们写Go的时候,其实map的使用要比在PHP里使用数组少,很多时候都是用结构体或者切片结构体。
对于那种key为ID,值为数据map的这种映射,也是改成Key为ID,值为自己定义好的结构体类型才对。

比如下面这个map类型变量,它的Key是ID,值的类型是我们上面定义的Config结构体:


var ConfigMap = map[int64]*Config {
	1:  {
		Svc: "user",
		Type: "rpc",
		Limit: 100,
	},
	// ...
}

总之记住一句话:“先定义类型再使用”

二、“零值”问题

在Go中,没有初始化的变量默认初始值为其类型的零值,需要注意的是slice,map,chan和*T指针类型对应的零值是nil。
这些类型的变量在没初始化前是无法直接使用的,会导致运行时错误。

常见的两种panic如下:


// 第一种
var configMap map[string]string

configMap["limit"] = "100" // panic: assignment to entry in nil map

// 第二种
type User struct {
   Name string
}

var user *User

fmt.Printf("%v\n", user)
fmt.Println(user.Name) // panic: runtime error: invalid memory address or nil pointer dereference

第一个错误是因为对一个未初始化的map进行赋值导致的,所以使用map类型的变量前要记得用make对变量进行初始化。
如果是切片slice的话使用append向空切片追加新元素就可以了,因为append会生成新的切片,在底层为切片分配底层数组。

第二个错误是因为对nil指针进行引用导致的,指针的零值nil与*T{}是不相等的。所以指针类型的变量在使用前要先new进行初始化。

说到这里就想到一点也是会影响对接端的不便:
举个例子(列表接口),前端小伙伴们不喜欢接口返回值的字段有数据的时候是个数组,没数据的时候是null。
原因是切片没有初始化导致的,如果数据库查不到数据,那么代码里就执行不到给切片追加数据的那步操作,所以就会出现这个问题。这是一个保持接口字段类型一致性的一个重要的细节,不要给定的接口文档是数组类型,返回的又是其他类型。

三、使用error返回方法错误

在使用PHP时,接口错误一般是通过抛出异常,有的是通过返回0,false等来表示错误(不推荐这种做法)


// 第一种,抛异常
public function dataUpdateProvider(UpdateDto $updateDto): ?UpdateDto
{
    try {
        // 数据库操作
    	// ...
    } catch (DbException $e) {
        throw new SystemException('Error Msg', '500' , $e);
    }

    return $updateDto;
}

// 第二种,返回约定值去表示错误
public function dataUpdateProvider(UpdateDto $updateDto): bool
{
	// 数据库操作
	$res = '......';

	if (!$res) {
		return false;
	}
	return true;

}

在Go中虽然没有异常机制,但是可以让方法返回error表明遇到的错误。大多数情况下我们的方法都需要返回error的,除非确定方法不需要返回error。
所以在定义方法时要明确返回数据和error的区别,两种返回值的职责范围不一样。要通过方法返回的error是否为空,而不是返回数据为0或者false等去判断方法是否执行成功。


function DataUpdateProvider(UpdateDto requests.UpdateDto) error {
	// 数据库操作
	res, err := "....."

	if err != nil {
		return err
	}

	return nil
}

可以看下这篇文章,对日常开发中Golang错误处理的个人建议

四、不要使用map[string]interface{}做参数

写过PHP的小伙伴都知道,PHP里的数组几乎是万能的,还能保证数组里面key的遍历顺序,这点是很多语言的map类型办不到的事情。

很多刚从PHP开发转到用Go开发的小伙伴还是带着在PHP里使用数组参数的习惯,在Go语言里最像PHP数组的可能就是map[string]interface{}了,这种还是属于典型的动态语言编程的思维

在使用Go的时候,针对比较复杂的一类事物的参数,我们也是应该先定义好结构体,然后使用结构体指针或者切片结构体指针作为参数。
尽量不使用map[string]interface{}这种类型的参数,即使是强大的IDE也没法提示这些参数的内部结构,这让其他人使用这个代码时就会十分痛苦,还得先看看方法的具体实现代码里用到了哪些字段。(map[string]interface{}作为返回值的问题在上面第一点有说明)

下面这两个方法的对比,一目了然:


type UserRequest struct{
	Name     string
	Age      int
}
func AddUser(params *UserRequest) error {
	// 简单模拟业务逻辑
	// ...

	// 数据库操作
    CreateUser(params.Name, params.Age)
    // ...
}

func FindUser(params map[string]interface{}) error {
	// 简单模拟业务逻辑
	// ...

	// 字段类型转换或者类型断言

	// 数据库操作
    GetUser(input["name"], input["age"])
    // ...
}

一般业务开发中,要保存一些额外信息到数据表中才用到map[string]interface{}类型。写表之前把额外信息这部分数据转成JSON格式再写入。当然还是得看使用场景,这里只是一些在编码习惯上的建议。

我是六涛sheliutao,文章编写总结不易,转载注明出处,喜欢本篇文章的小伙伴欢迎点赞、关注,有问题可以评论区留言或者私信我,相互交流!!!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Golang(又称为Go)是Google公司开发出的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go语言具有以下特点:简洁、高效、安全、并发、跨平台等。在学习Golang的入门阶段,你可以了解以下几个方面: 1. Golang的特点:Go语言具有简洁、高效、安全、并发、跨平台等特点,这些特点使得Golang成为一种非常流行的编程语言。 2. Golang的变量作用域:在Golang中,变量的作用域可以分为全局作用域和局部作用域。 3. Golang的执行流程的两种方式Golang的执行流程可以通过顺序执行和条件执行两种方式来实现。 4. 在Linux上安装Golang语言开发包:要开始学习Golang,你需要在Linux上安装Golang语言开发包。你可以在Golang官方网站上下载适合你的操作系统和架构的Golang安装包,并按照官方文档进行安装。 5. Golang变量的基本使用:在Golang中,你可以使用var关键字声明变量,并使用:=运算符进行变量的初始化和赋值操作。 6. Golang中整数的类型:在Golang中,整数类型可以分为有符号整数和无符号整数,不同的整数类型有不同的取值范围。 7. Golang基本数据类型的默认值:在Golang中,当你声明一个变量但没有进行初始化时,它的默认值将根据其数据类型而定。 8. 基本数据类型转换为String类型:在Golang中,你可以使用strconv包提供的函数将基本数据类型转换为字符串类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sheliutao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值