hello-go
hello-go copied to clipboard
入门 Go 编写应用
入门 Go 编写应用
☝🏾 为何会选用 Go
- 无依赖的绿色 Native 二进制可执行文件发布;
- 跨平台支持,一次写完 macOS, Linux, Windows 均可以使用;
- 编写的应用程序启动速度快!
- 编写简单,工具链完善,语法简单,执行效率高(例如多核并发);
- 并发编写非常好使;
- 完善的标准库,例如图形图像的库不是什么语言都有的;
- 完善可用的三方库,例如 GoQuery 用于解析 HTML 结构;
- 由于编译以及 Go 的一些强制约定,使得写好的东西很稳定,哪怕没测试(基本上编译通过就没什么大问题了)。
- Gofmt 强制语法标准,完美控制,再也不用纠结语法风格,也不需要 Lint;
🌈 那些场景可以用上它
- 需要跨平台,绿色发布;
- 需要一个无环境依赖的场景;
- 大量计算型的工作;
- 用 Go 编写 CLI 应用真的非常爽!
📖 如何学习 Go 语言
阅读官方文档... (我是有段时间睡觉前手机上看的,看到自己睡着 😴)
📦 开发工具
🎁 Auto Complete 功能
提示绝对准确,并且响应速度高(1ms 以内)!
🎛 对于测试的支持
自动在测试用例部分增加点击按钮,可以单独跑测试。
顺便演示一下 Ruby 里面,我们用 TextMate 也是用这样的方式跑测试的 (光标放在一个段落,Command + Shift + R 运行)
🎈 Hello world 开始
http://github.com/huacnlee/hello-go
任何语言都是从 Hello world 开始的:
// ~/work/src/github.com/huacnlee/hello-go
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello world.")
}
然后运行:
$ cd ~/work/src/github.com/huacnlee/hello-go
$ go run main.go
🛎 理解 Go 的几个必要基础
GOPATH
$ export GOPATH=~/work
$ mkdir $GOPATH/src
最重要的环境变量,告诉 Go 的各种工具,工作路径是哪里
- import 函数需要 GOPATH 才能知道哪里能找到三方库代码
- 编译工具也需要
- 自己的项目,一般我们也放在里面,便于 import 自己,例如
github.com/huacnlee/hello-go
$ cd $GOPATH/src
$ tree -L 3
├── github.com
│ ├── PuerkitoBio
│ │ └── goquery
│ ├── huacnlee
│ │ ├── mediom
📜 语言特性
- 静态语言,需要忘掉动态语言特性,需要类型转换
strconv.Atoi("100")
; - 值类型/指针;
- Array 类型(slice) 的操作很原始,非常原始:
userIds = append(userIds, 100)
参见 SliceTricks - 各种常见返回的 error 类型;
- 你可能找不到 Class 类型,在 Go 里面是
struct
实现简单结构体; - 一个文件夹可以看作一个包,同一个文件夹里面 .go 文件的
package
必须是一样的; - 如果是应用程序,项目根目录
package
得是main
; - 函数、属性、变量等命名首字母大写是
公开
的,小写是私有
的(相对于package
); - 用不上的变量要去掉,用不上的
import
也要去掉,否则会xxx declared and not used
;
理解 defer 关键字
defer 是一个非常巧妙的设计,可以让你提前准备好要在函数结束(return)是需要做的事情,这样一个开,一个关,连在一起。
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err.Error())
}
defer db.Close()
res, err = http.Get("https://github.com")
if err != nil {
panic(err.Error())
}
defer res.Body.Close()
在线演示: https://tour.golang.org/flowcontrol/12
JSON 序列化/反序列化
你不能像动态语言(Ruby, Node.js)那样自由的做 JSON 反序列化,必须有准备好的结构体来承载。
像这样的方式你就不行了:
b = '{ "name": "Foo", "body": "Hello" }'
m = JSON.parse(b)
因为在动态语言里面,对象结构是 Runtime 期间动态创建的。
你必须先告诉编译器,你需要的 JSON 结构:
type Message struct {
// `` 是 Go 对 struct 字段的特殊描述方式,用于很多的场景
Name string `json:"name"`
Body string `json:"raw"`
Time int64
}
m := Message{"Alice", "Hello", 1294706395881547000}
// 序列化
b, err := json.Marshal(m)
// 然后 b 就等于 []byte(`{"name":"Alice","raw":"Hello","Time":1294706395881547000}`)
// 反序列化, b 要是 []byte 类型
err := json.Unmarshal(b, &m)
详见: https://blog.golang.org/json-and-go
🐵 演示编写一个常见的类
$GOPATH/github.com/huacnlee/hello-go/monkey.go
package main
// package 外部可访问
type Monkey struct {
Name string `json:"name"`
age int
Gender int `json:"gender"`
}
// package 内部可访问
type gorilla struct {
*Monkey
Weight int
}
// package 外部可访问
// monkey 带 * 表示是一个指针
// err 没带 * 表示是值类型
func BuildMonkey(name string, age, gender int) (monkey *Monkey, err error) {
err = nil
// & 表示引用指针,别担心,用错了编译不过
monkey = &Monkey{
Name: name,
age: age,
Gender: gender,
}
return
}
// monkey.Age()
func (monkey *Monkey) Age() int {
return monkey.age
}
编写测试
Go 内置简单的测试框架,据我了解大家都是用这套,只是用了一些辅助工具。
$GOPATH/github.com/huacnlee/hello-go/monkey_test.go
package main
import "testing"
func TestBuildMonkey(t *testing.T) {
monkey, err := BuildMonkey("Foo", 5, 0)
if (err != nil) {
t.Error(err)
}
if monkey.Name != "Foo" {
t.Error(monkey.Name)
}
if monkey.age != 5 {
t.Error(monkey.age)
}
if monkey.Gender != 0 {
t.Error(monkey.Gender)
}
}
然后运行
$ go test ./...
ok github.com/huacnlee/hello-go 0.006s
? github.com/huacnlee/hello-go/utils [no test files]
🏎 并发 Concurrency 编写
NOTE: 并发不等于并行 Concurrency Is Not Parallelism - “并发是一次处理(能力)很多事情,并行是一次执行(doing)很多事情”
Goroutine 是非常轻量级的线程,据我了解只会有内存 (2 KB 一个) 和 GC (1 µs 耗时) 的开销,可以把 Goroutine 看作一个异步调度器。
Goroutine 会自动根据情况分配 OS Thread 来实现多核运算(Go 1.5 以后,GOMAXPROCS 默认为 CPU 核数量)
你可以用 sync.WaitGroup 来实现同步控制,使用 chan
来在不同的 Goroutine 之间共享值。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
rand.Seed(time.Now().UTC().UnixNano())
numbers := []int{3, 4, 7, 10, 2, 5}
for i := range numbers {
number := numbers[i]
wg.Add(1)
go func() {
defer wg.Done()
sleepN := 100 + rand.Intn(100)
time.Sleep(time.Duration(sleepN) * time.Millisecond)
fmt.Println("number", number, "sleep", sleepN, "ms")
}()
}
fmt.Println("Before wait()")
wg.Wait()
fmt.Println("After wait()")
}
在线演示:https://play.golang.org/p/OTT6EdqQz7
⛺️ 三方库依赖
go get 内置工具
- 原理是基于 Git / SVN 或其它代码仓库的方式来管理三方库;
- 不会像 NPM / RubyGems 那样有一个包存储的中心服务器,而是直接用源代码来分发的;
- 安装三方库实际上是将远程代码拉到本地
$GOPATH/src/github.com/foo/bar
; - 引用三方库是写 URL 的方式,例如
import "github.com/foo/bar"
;
🌰 来一包例子
.
├── main.go
└── utils
└── format.go
package main
import (
"fmt"
"github.com/huacnlee/hello-go/utils"
)
func main() {
newName := utils.FormatName("自成")
fmt.Println("Hello world.", newName)
}
🚀 发布你的应用
默认情况下 go build
会编译成当前平台,你可以使用 GOOS
和 GOARCH
来实现 Cross Compile
例如编译 Linux 64bit 支持:
$ GOOS=linux GOARCH=amd64 go build main.go
Windows 支持:
$ GOOS=windows GOARCH=amd64 go build main.go
或者在 Linux 编译为 macOS 支持
$ GOOS=darwin GOARCH=amd64 go build main.go
编译完以后,你会获取到一个大的二进制绿色文件 📟(内含 GC 的实现,所以起步文件比较大),给谁谁都可以执行 🎊 🎊 🎊。
go build -ldflags "-s -w"
-
-s
- Omit the symbol table and debug information. -
-w
- Omit the DWARF symbol table.
https://golang.org/cmd/link/
🌏 如何编写 Web 应用
- 在 Go 的世界里面,都推荐用简单的 Web 框架
- 甚至有很多项目直接用标准库
net/http
来实现
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(ctx *gin.Context) {
monkey, _ := BuildMonkey("Foo", 5, 0)
ctx.JSON(200, monkey)
})
router.Run(":8080")
}
NOTE! 在 Go 的世界,选择那个 Web 框架几乎不重要,因为最后不同的框架都可以整合在一块儿。
🍻 入门学习参考
- https://github.com/huacnlee/mediom - 复杂例子,包含 Web、数据库实现,早期代码完整的论坛项目
- https://github.com/jinzhu/gorm - 推荐使用 ORM
- https://github.com/gin-gonic/gin - 推荐使用 Web 框架
- http://divan.github.io/posts/go_concurrency_visualize - 用动画的方式演示并发的过程