之前是 PHP/Java 程序员,转到用 golang 开发,下面几个问题让我着实学习了好久才弄明白
谜一般的 GOPATH,到底该怎么配!
为什么有 error,还需要 panic?
golang 的接口是一种什么样的存在?
goroutine panic 居然会导致进程退出!!!
channel 满天飞,这样真的好么?
咱尽量简明扼要,只谈要点。
谜一般的 GOPATH,到底该怎么配!
官方一开始的设想是这样的
my-gopath/
└── src
├── github.com
│ └── project1
└── golang.com
└── project2
把 my-gopath 设置为 GOPATH,然后这个就是你开发的工作目录了。src目录下有你所有的代码。但是这种本机设置一个 GOPATH 的模式会导致非常多的问题
项目之间的依赖不隔离,容易错误引用到不希望的依赖
无法分清楚哪些代码是自己的,哪些代码是别人的
提交代码到 git 仓库的时候怎么办?
所以我们实际的目录结构应该是
my-projects/
├── project1 (GOPATH)
│ └── src
│ └── github.com│ └── project1 (GIT根目录)
│ └── vendor
│ ├── github.com│ │ ├── others-lib1
│ │ └── others-lib2
│ └── vendor.json
└── project2 (GOPATH)
└── src
└── golang.com
└── project2 (GIT根目录)
这个结构是普通人类第一眼就能想出来的么!!!这是我经过无数次的试错之后发现的最佳的目录设置
项目隔离的需求:每个项目有自己的单独的 GOPATH。
GIT 提交的需求:在 git clone 之前把
my-projects/project1/src/http://github.com给创建出来,然后 git clone project1 到这个目录里。
区分自己和别人的代码:使用 vendor 目录
你以为这就完美了?如果你要提供一个build.sh在编译机上做打包怎么办?编译机上的GOPATH 怎么设置呢?最佳实践:在 build.sh 里自己创建一个完整的 GOPATH(下图里的tmp目录),用符号链接指向自己。
my-projects/
├── project1
│ └── src
│ └── github.com│ └── project1
│ ├── tmp
│ │ └── src
│ │ └── github.com│ │ └── project1 -> ../../../../project1
│ └── vendor
│ ├── github.com│ │ ├── others-lib1
│ │ └── others-lib2
│ └── vendor.json
└── project2
└── src
└── golang.com
└── project2
这是一种什么样的妖孽啊!另外友情提示一个坑,Intellij 的 golang 插件和指向上级目录的符号链接不兼容,会导致无法自动提示。把 GOPATH 调整到 IDE 高兴,git 高兴,项目隔离,build.sh 可工作,不知道花了多少时间。
为什么有 error,还需要 panic?
panic 表示进程内的错误。panic 的原因来自于代码的逻辑 bug,比如强制类型转换失败,比如数组越界。这个代表了程序员的责任不到位,导致了程序的panic。
error 代表进程外的错误。比如输入符合预期。比如访问外部的服务失败。这些都不是程序员可以设计控制的。这些情况的错误处理是业务逻辑的一部分。
Java 在设计的时候 checked exception 就是 error,runtime exception 就是 panic。但是玩崩了。checked exception 和 error 一样都是想强制让程序员思考 error 的业务逻辑,但是没有成功。
golang 的接口是一种什么样的存在?
public function myFunc(SomeClass someObj) SomeResponseClass {
someObj.method1();
someObj.method2();
}
Java 这样的语言设置会导致的问题是容易导致依赖于实现,而不是依赖于接口。也就是这个 myFunc 依赖的输入可能只需要一个 method1(), method2(),但是 SomeClass 上除了这两个方法之外还有很多其他的行为。把输入接口设置为 SomeClass,导致了接口的“扩大化”。
public myFunc($someObj) {
$someObj->method1();
$someObj->method2();
}
PHP 的写法其实要比 SomeClass 更好。动态语言是 “duck typing”的,也就是你只要给一个实现了 method1(),method2() 的方法的对象,那么就能够调用成功。也就是 myFunc 的接口恰到好处的,不会因为类型声明而使得接口扩大化。但是 PHP 的问题是,不看实现,你永远不知道应该传一个什么样的 obj 进来。接口模糊了,导致调用方要查看对方的实现。
public function myFunc(SomeInterface someObj) SomeResponseClass {
someObj.method1();
someObj.method2();
}
Java 为了解决传实体类的问题,创造了 interface。interface 很好的解决了接口”扩大化“的问题。你可以给myFunc的需求,创造一个恰到好处的interface。但是 Java 的 interface 的问题是,需要 class 定义方的配合。我如果声明了一个interface,需要传入的对象在定义的时候就写了”implement interface"。相比动态类型来说,这就很不方便了。
综上
Java Class,依赖具体实现,而不是接口。导致接口扩大化
PHP Object,依赖的是接口,不存在接口扩大化的问题。但是调用方很痛苦,需要来看你的内部实现才知道你的接口是什么。
Java Interface,依赖接口,但是需要提前定义。无法随时按需定制接口。实践中仍然会导致 interface 的扩大化。
golang 的实现兼顾了 Java 的静态类型,和 PHP 的 duck typing 的好处。它使得你可以给 myFunc 定制一个最精确的接口依赖。只要实现了 method1() 和 method2() 的对象,自动就符合了调用的条件,可以被传入。这个行为非常类似 duck typing。但是相比纯动态语言的 duck typing,golang interface 又有一个肉身的实体存在,可以很方便查看。其实当我们在动态语言里做 duck typing 的时候
public myFunc($someObj) {
$someObj->method1();
$someObj->method2();
}
// 这里对$someObj 的使用,隐式地定义了$someObj 的interface,只是这个interface缺少一个肉身
golang 的interface就是避免了duck typing缺陷的,duck typing。其鼓励地行为是给你的函数定义“精确”地依赖接口,不要过大,也不要过小,精确。
这种精确也体现在了返回值允许多个上面。如果不允许返回多个返回值,我们被迫返回一个结构体。而定义很多小的结构体是非常麻烦的。这个实践中,就会导致很多人写没有返回值的的函数,把返回值隐藏到对一个大的结构体的变更中。或者返回一个很宽泛的结构体(比如map),仅仅因为给每个方法定义一个struct作为response太麻烦了。
golang简单务实,让你返回多个返回值,这样你就可以避免去定义一大堆小的struct来代表函数的返回值。目标就是让你精确地定义函数的输入输出。
goroutine panic 居然会导致进程退出!!!
前面说了,checked exception 是 error。runtime exception 是 panic。
Java 的 thread 里抛里抛异常,thread 挂掉,但是进程不挂掉。
Go 的 goroutine 里panic,整个进程挂掉。
goroutine 必须经过包装使用。
goroutine 越多,代码的线索就越多。线索越多,线索打结的可能性就越高。千万不要随手搞一个 go,fork 一堆 goroutine 出来。
channel 满天飞,这样真的好么?
channel 是一个很新鲜的东西。只需要一天就可以学会channel,然后需要剩下的时间让你忘掉它。
channel 不是一种抽象手段,不要用 channel 来组装逻辑。channel 是并发的控制手段,不牵涉并发的,不要过度设计。
channel 不能用来搞进程内的微服务,你写一块逻辑,我写一块逻辑,我们之间用 channel 通信。因为 rpc 是同步的,相对好掌控。channel 是纯异步的,你们搞不定。别说我看不起你们。
channel 漫天飞导致 goroutine 漫天飞。如非必要,勿增实体。goroutine 越多,代码理解起来复杂度成指数增加
You have to be this tall to use go/channel
go 相比 thread,channel 相比 lock,再简化也是有复杂度的。这些东西能不碰就不碰。它们始终是控制并发的工具,组织业务逻辑还是靠朴实的函数套函数吧。
任何一个 go/channel 关键字出现的地方,想一下是不是必要的。再想一下是不是必要的。再仔细想一下是不是必要的。
总结
按照我给的模板使用 GOPATH,虽然看起来很不优雅
你控制不了的进程外错误用 error,凡是程序员的锅,用panic
golang 鼓励你精确定义函数的输入和输出,感谢 golang。error 作为函数接口的一部分,checked exception 未竟的事业在 golang 发扬光大。
任何一个 go/channel 关键字出现的地方,三思再三思
免责声明:转载自网络 不用于商业宣传 版权归原作者所有 侵权删