网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Go 语言相对于其他传统热门编程语言来说,有很多优点,特别是其高效编译速度和天然并发特性,让其成为快速开发分布式应用的首选语言。Go 语言是静态类型语言,也就是说 Go 语言跟 Java、C# 一样需要编译,而且有完备的类型系统,可以有效减少因类型不一致导致的代码质量问题。因此,Go 语言非常适合构建对稳定性和灵活性均有要求的大型 IT 系统,这也是很多大型互联网公司用 Golang 重构老代码的重要原因:传统的静态 OOP 语言(例如 Java、C#)稳定性高但缺乏灵活性;而动态语言(例如 PHP、Python、Ruby、Node.js)灵活性强但缺乏稳定性。因此,“熊掌和鱼兼得” 的 Golang,受到开发者们的追捧是自然而然的事情,毕竟,“天下苦 Java/PHP/Python/Ruby 们久矣“。
不过,Go 语言并不是没有缺点。用辩证法的思维方式可以推测,Golang 的一些突出特性将成为它的双刃剑。例如,Golang 语法简单的优势特点将限制它处理复杂问题的能力。尤其是 Go 语言缺乏泛型(Generics)的问题,导致它构建通用框架的复杂度大增。虽然这个突出问题在 2.0 版本很可能会有效解决,但这也反映出来明星编程语言也会有缺点。当然,Go 的缺点还不止于此,Go 语言使用者还会吐槽其啰嗦的错误处理方式(Error Handling)、缺少严格约束的鸭子类型(Duck Typing)、日期格式问题等。下面,我们将从 Golang 语言特点开始,由浅入深多维度深入分析 Golang 的优缺点以及项目适用场景。
语言特点
简洁的语法特征
Go 语言的语法非常简单,至少在变量声明、结构体声明、函数定义等方面显得非常简洁。
变量的声明不像 Java 或 C 那样啰嗦,在 Golang 中可以用 :=
这个语法来声明新变量。例如下面这个例子,当你直接使用 :=
来定义变量时,Go 会自动将赋值对象的类型声明为赋值来源的类型,这节省了大量的代码。
func main() {
valInt := 1 // 自动推断 int 类型
valStr := “hello” // 自动推断为 string 类型
valBool := false // 自动推断为 bool 类型
}
复制代码
Golang 还有很多帮你节省代码的地方。你可以发现 Go 中不会强制要求用 new
这个关键词来生成某个类(Class)的新实例(Instance)。而且,对于公共和私有属性(变量和方法)的约定不再使用传统的 public
和 private
关键词,而是直接用属性变量首字母的大小写来区分。下面一些例子可以帮助读者理解这些特点。
// 定义一个 struct 类
type SomeClass struct {
PublicVariable string // 公共变量
privateVariable string // 私有变量
}
// 公共方法
func (c *SomeClass) PublicMethod() (result string) {
return “This can be called by external modules”
}
// 私有方法
func (c *SomeClass) privateMethod() (result string) {
return “This can only be called in SomeClass”
}
func main() {
// 生成实例
someInstance := SomeClass{
PublicVariable: “hello”,
privateVariable: “world”,
}
}
复制代码
如果你用 Java 来实现上述这个例子,可能会看到冗长的 .java
类文件,例如这样。
// SomeClass.java
public SomeClass {
public String PublicVariable; // 公共变量
private String privateVariable; // 私有变量
// 构造函数
public SomeClass(String val1, String val2) {
this.PublicVariable = val1;
this.privateVariable = val2;
}
// 公共方法
public String PublicMethod() {
return “This can be called by external modules”;
}
// 私有方法
public String privateMethod() {
return “This can only be called in SomeClass”;
}
}
…
// Application.java
public Application {
public static void main(String[] args) {
// 生成实例
SomeClass someInstance = new SomeClass(“hello”, “world”);
}
}
复制代码
可以看到,在 Java 代码中除了容易看花眼的多层花括号以外,还充斥着大量的 public
、private
、static
、this
等修饰用的关键词,显得异常啰嗦;而 Golang 代码中则靠简单的约定,例如首字母大小写,避免了很多重复性的修饰词。当然,Java 和 Go 在类型系统上还是有一些区别的,这也导致 Go 在处理复杂问题显得有些力不从心,这是后话,后面再讨论。总之,结论就是 Go 的语法在静态类型编程语言中非常简洁。
内置并发编程
Go 语言之所以成为分布式应用的首选,除了它性能强大以外,其最主要的原因就是它天然的并发编程。这个并发编程特性主要来自于 Golang 中的协程(Goroutine)和通道(Channel)。下面是使用协程的一个例子。
func asyncTask() {
fmt.Printf(“This is an asynchronized task”)
}
func syncTask() {
fmt.Printf(“This is a synchronized task”)
}
func main() {
go asyncTask() // 异步执行,不阻塞
syncTask() // 同步执行,阻塞
go asyncTask() // 等待前面 syncTask 完成之后,再异步执行,不阻塞
}
复制代码
可以看到,关键词 go
加函数调用可以让其作为一个异步函数执行,不会阻塞后面的代码。而如果不加 go
关键词,则会被当成是同步代码执行。如果读者熟悉 JavaScript 中的 async/await
、Promise
语法,甚至是 Java、Python 中的多线程异步编程,你会发现它们跟 Go 异步编程的简单程度不是一个量级的!
异步函数,也就是协程之间的通信可以用 Go 语言特有的通道来实现。下面是关于通道的一个例子。
func longTask(signal chan int) {
// 不带参数的 for
// 相当于 while 循环
for {
// 接收 signal 通道传值
v := <- signal
// 如果接收值为 1,停止循环
if v == 1 {
break
}
time.Sleep(1 * Second)
}
}
func main() {
// 声明通道
sig := make(chan int)
// 异步调用 longTask
go longTask(sig)
// 等待 1 秒钟
time.Sleep(1 * time.Second)
// 向通道 sig 传值
sig <- 1
// 然后 longTask 会接收 sig 传值,终止循环
}
复制代码
面向接口编程
Go 语言不是严格的面向对象编程(OOP),它采用的是面向接口编程(IOP),是相对于 OOP 更先进的编程模式。作为 OOP 体系的一部分,IOP 更加强调规则和约束,以及接口类型方法的约定,从而让开发人员尽可能的关注更抽象的程序逻辑,而不是在更细节的实现方式上浪费时间。很多大型项目采用的都是 IOP 的编程模式。如果想了解更多面向接口编程,请查看 “码之道” 个人技术博客的往期文章《为什么说 TypeScript 是开发大型前端项目的必备语言》,其中有关于面向接口编程的详细讲解。
Go 语言跟 TypeScript 一样,也是采用鸭子类型的方式来校验接口继承。下面这个例子可以描述 Go 语言的鸭子类型特性。
// 定义 Animal 接口
interface Animal {
Eat() // 声明 Eat 方法
Move() // 声明 Move 方法
}
// ==== 定义 Dog Start ====
// 定义 Dog 类
type Dog struct {
}
// 实现 Eat 方法
func (d *Dog) Eat() {
fmt.Printf(“Eating bones”)
}
// 实现 Move 方法
func (d *Dog) Move() {
fmt.Printf(“Moving with four legs”)
}
// ==== 定义 Dog End ====
// ==== 定义 Human Start ====
// 定义 Human 类
type Human struct {
}
// 实现 Eat 方法
func (h *Human) Eat() {
fmt.Printf(“Eating rice”)
}
// 实现 Move 方法
func (h *Human) Move() {
fmt.Printf(“Moving with two legs”)
}
// ==== 定义 Human End ====
复制代码
可以看到,虽然 Go 语言可以定义接口,但跟 Java 不同的是,Go 语言中没有显示声明接口实现(Implementation)的关键词修饰语法。在 Go 语言中,如果要继承一个接口,你只需要在结构体中实现该接口声明的所有方法。这样,对于 Go 编译器来说你定义的类就相当于继承了该接口。在这个例子中,我们规定,只要既能吃(Eat)又能活动(Move)的东西就是动物(Animal)。而狗(Dog)和人(Human)恰巧都可以吃和动,因此它们都被算作动物。这种依靠实现方法匹配度的继承方式,就是鸭子类型:如果一个动物看起来像鸭子,叫起来也像鸭子,那它一定是鸭子。这种鸭子类型相对于传统 OOP 编程语言显得更灵活。但是,后面我们会讨论到,这种编程方式会带来一些麻烦。
错误处理
Go 语言的错误处理是臭名昭著的啰嗦。这里先给一个简单例子。
package main
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
编程方式会带来一些麻烦。
错误处理
Go 语言的错误处理是臭名昭著的啰嗦。这里先给一个简单例子。
package main
[外链图片转存中…(img-SP2giY3x-1715751549419)]
[外链图片转存中…(img-FPLLZgzZ-1715751549420)]
[外链图片转存中…(img-aCB26E65-1715751549420)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新