在我编程生涯中也就使用过很多门编程语言,这其中就包括所谓的大道之间的 Go 语言,总体来说使用 Go 语言来编写逻辑是非常容易的,和天生支持 CSP 和 GMP 模型的高并发的语言,使得开发高并发应用也非常简单。它的语法简单和 25 关键字非常容易掌握,能使程序员快速掌握并且编写代码逻辑,并不意味着 Go 的设计有多么好,反而在我使用 Go 编程过程中出现很多感觉很丑陋的设计。
Go 程序的错误处理,例如:Go 错误处理,写多了满屏 if err != nil
,这个和 C 语言的一脉相承的;
没有方法重载入,还有三目表达式,没有模式匹配,内置 init 函数和自定义 init 互相冲突并且自定义 var x = func() {}() 执行顺序和内置 init 函数执行顺序冲突,函数式编程没有 lambda 表达式的支持,范型不支持协变和逆变,标识符、Struct、变量、字段的访问控制和 JSON 正反序列化矛盾的冲突。另外构造器随意创建,不支持函数重载导致需要使用魔法编程可变参数进行实现类似于其他有重载功能语言,这对于一个设计友好 API 的人来说确实需要;另外接口太小,名称统一会导致被很多其他拥有相同字段接口所隐式的实现。
在编写项目中最常见的就是把基础标量类型封装为一个 struct
类型结构体,例如下面代码是一段实现了 gossip
协议所需要的代码,分别为 Node 和 Cluster 结构体,这两个结构体要提供外部的包进行调用和访问。这两个结构体的字段都是大写开头,这是由 Go 访问控制权限规则设计要求的,但是在某些需求下为了不让外部包的调用者能直接通过 Node {}
构造器来创建 Node 类型的实例,只能通过 gossip.NewNode()
进行实例化一个节点,因为 gossip.NewNode()
内部会提供一些默认的初始化规则。在这种场景下只能使用下面的方式来封装一个 innerNode
类型,将其嵌入到 Node 中,这样就可以避免其他人滥用 Node {}
创建,代码如下:
type (
// 类似于私有化 Node {} 构造器,其他包就不能直接 {} 来创建 Node 实例
// 只能通过 gossip.NewNode() 进行实例化一个节点
innerNode struct {
ID string // 节点唯一标识
Address string // 节点地址
Heartbeat int // 心跳计数
Timestamp int64 // 最新心跳时间
Alive bool // 节点状态
Hash uint32 // 节点哈希值,用于一致性哈希
}
innerCluster struct {
Neighbors map[string]*Node // 所有已知节点的信息
Self *Node // 本节点信息
Mutex sync.Mutex // 保护 Nodes 的并发访问
Interval time.Duration // 心跳间隔
Timeout time.Duration // 失效阈值
HashRing []*Node // 一致性哈希存储范围的键
}
// Node 协议集群中的节点
Node struct {
innerNode
}
// Cluster 协议的集群集合
Cluster struct {
innerCluster
}
)
// NewNode 只能使用此暴露外部的函数进行创建
func NewNode(id, addr string) *Node {
node := &Node{
innerNode{
ID: id,
Address: addr,
Heartbeat: 0,
Timestamp: time.Now().Unix(),
Alive: true,
},
}
// 通过节点 ID 算出在哈希环中的值
node.Hash = NodeHash(id)
return node
}
struct
中的字段都是以小写开头,导致如果需要对此 Node {}
这个结构体进行 JSON 正反序列化,就会出现无法进行序列化的情况,原因就是因为上面的需求导致的,导致一个很矛盾的问题。要解决这个序列化问题,可以让要被序列化的 struct
实现自定义的 MarshalJSON
方法,这种方式要额外的编写正反序列化的代码实现。另外就是无法被反射编程。
目前个人感觉 Go 优点只是能静态编译,将程序源代码直接通过编译,编译为 Native Code 直接在计算机上运行,有着较快的冷启动和低内存占用特性。静态编译在 Cloud Native 场景下确实合适,并不意味着它能和其他基于 VM 的语言相比,例如 JVM 可以在运行时动态反射编程,Go 虽然也有,但我觉得并没有能媲美 Java 一样反射能力,毕竟写业务代码 JVM 可以支持动态类的 AOP 和 DJ 编程语言才是最爽的。
另外有人可能看到这里要和我抬扛 Go 语言不需要类似于 JVM 这样的形式来运行程序,Java 和 Go 在设计的时候存在着本质上的区别,JVM 是语言平台,基于 JVM 去实现的编程语言,你只需要实现将源代码的 AST 转换为 JVM 字节码就行了,至于内存管理和垃圾回收工作全部由 JVM 来完成,这对于想要去实现一门编程语言的人来说只需要关注于 JVM 编译器前端,对应着 AST => ByteCode
的过程,无需关系其他的实现,标准库可以服用 JRE 里面的 API 来完成。