这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
自述
嗨嗨~米娜桑~又见面啦!
几个月不见,我已经从java转go,并成功找到实习了(说句实话是真不容易啊,寒气是直接淋到我头上
说句实话,我特别喜欢go语言,我觉得我被他简洁的语法给救赎了。
其实我在学习java期间是很痛苦的,我不能理解为什么要做那些注解,@autoConfigerition,@Condition等等,很痛苦。我感觉我是在一个狭小的圈子里面,看不到全貌,也看不到地基,因为地板太硬了。
后来我才意识到,Spring只会方便两种人,一种是一开始就能理解他架构,并且还能更上他优化步骤的人,另一种是安于现状的人。更多的,希望能够去深掘,能力上却又有所欠缺的人,其实是非常痛苦的。
关于go
因为有java的基础,我入门go非常快,两个月便用go找到了一份实习,我喜欢他的设计理念:一切从简
go没有宏,没有三目运算符,没有类,没有继承,没有函数重载,没有注解(没有注解我是真开心),没有任何在大型工程上会导致人心智负担的东西。相反的,他多了切片,多了map,多了组合式类型系统,多了通道,多了协程。
更让我开心的是,我能看懂许多框架的底层!我看过gin,看过kratos,看过grpc的生成代码,能理解wire所作所为。当然,因为ast水平原因,我对grpc和wire生成代码的实现还不够了解,热部署也有相同的原因暂未深入。
go将许多其他语言在运行时解决的问题提前到了编译期,因此他的运行效率,还有理解难度都开始直线下降,比如wire是如何去解决依赖倒置的,自己的代码,生成的代码一比较,一区分,依赖倒置的方法与理念一目了然
java的痛点
java对我而言,最为痛苦的可以分为三点:
- 框架难以读懂
- 不知道标准库给我提供了什么
- 语法过重
第一点
框架就不说了,之前已经解释过了。
第二点
java拥有臃肿的标准库,同时还不知道提供了哪些功能,我相信很多人会说"我读过线程池的源码","我读过map的源码","我对java底层jvm非常了解,调优也知道要点"。好的,那我请问你,java标准库提供了哪些包,这些包又分别包含哪些功能?这个问题我相信可以卡住半数以上的java程序员,他们甚至不知道jdk里面的包结构,甚至学了3年java没有听过jni协议。
第三点
语法过重。这几乎是在21世纪前出现的所有语言(仅通用性非函数式语言)的痛点。说句实话,我在使用c语言的时候,我很害怕与文件打交道,因为我觉得在调用read操作与操作系统打交道很重(没有原因,单纯就是直觉)。而java中,当我new HashMap时,其实心里是沉重的,我下意识地认为这是一个很重的操作,使用链表时也是一样。但是在go中我创建切片,创建映射,都是随性而为,没有任何心理负担,或许性能上没有差异,但是给人带来的心情却截然不同
java总是把使用者当作小孩子,你想使用什么数据类型,那就要将你的数据类型代理到我的数据结构下(比如有一个Student类,你想使用链表时就需要将他放到泛型链表LinkList下),然后java把所想到的所有链表功能一股脑全部塞给你。go语言duck like的形式却让人耳目一新,我很喜欢他sort的设计,我提供了这个功能,用与不用取决于使用者自己
go的痛点
我喷了那么久的java,并不是说go没有痛点,相反的,作为一个新兴的语言他在各个领域的积累与修养,都还尚且不够。
目前我所感受到的痛点,大体可以分为四点:
- 错误处理
- duck like 类型系统
- 泛型过晚
- 缺少枚举
第一点
或许有人说我是无病呻吟,这个问题从14年就纠纷到现在依旧是众说纷纭。但是我是实实在在在工程上体会到了这一点。实习期间,给我感觉错误处理的代码占据代码总量的3/4,具体展示如下:
func A() (*Target, error) {
target, err := trans()
if err != nil {
return nil, err
}
return target,nil
}
错误处理的篇幅让人感到视觉上有些沉重
第二点
Duck Like的接口实现机制。其实这个出发点很好,第一是编译器对于这样的接口实现更加容易,第二是能够完成依赖倒置与工程解耦。但是实际使用上很不方便,尤其是在上司们修改接口后:
- 视觉上:好像什么都没变啊
- 实际上:int 返回值修改为 uint
这会导致接口实现失败,找很久,错误提示也不够智能,在企业级代码中会是一个致命的问题;同时,这对读代码的人来说也是十分痛苦的,"它实现了哪些接口??"我知道会有人说无论是goland还是vscode,都能进行跳转,但是相信我,被折磨之后就不会这么想了
第三点
泛型过晚,在我们公司使用的是go 1.17,说句实话这对商业来说已经是十分前卫的一个版本了,但是遗憾的是go的泛型是1.18版本被支持的,因为这个原因,我在serive层被迫写了无数次相同的数据转换,但实际上只需要一个泛型就能完成。很多人喷过go的泛型,认为他的设计是一团烂泥,但实际上rust的实现机制也是类似的,go的泛型形参,泛型实参,类型约束大同小异。更多的人却只是认为,大家都是<>作为泛型标志,go是使用中括号标新立异,说句实话,很难给半吊子讲解什么是编译原理,什么是无限前瞻,什么是2类型语言,那些人喷就喷吧
package main
type Pair[T1 any,T2 any] struct {
Key T1
Value T2
}
func main() {
var student Pair[string,int]
student.Key = "123"
student.Value =100
}
pub struct Student<T1,T2>{
key:T1,
value:T2
}
fn main() {
let mut student = Student::<&str,i32>{
key:"",
value:0,
};
student.key = "123";
student.value = 100;
}
第四点
缺少枚举类型。有的人说我们有 iota,但很遗憾这是一个治标不治本的方法。在公司内部,我就发现很多人不按要求办事,他们喜欢用简单的"1, 2, 3"来替代我们表示的枚举,这是非常让人可憎的行为,因为没有人知道他们在写些什么。grpc给了一套解决方案,给各个枚举类型重命名,但是依旧是基于int类型的,因此不少图方便的人会直接用int表示,对此我的评价是畜生行为
结语
好了,今天就唠嗑到这里,大家明天见,祝大家过个好年~