为什么要学 Go? 
以下引用自左耳听风专栏。
 
第一,语言简单,上手快 。Go 语言的语法特性简直是太简单了,简单到你几乎玩不出什么花招,直来直去的,学习难度很低,容易上手。
第二,并行和异步编程几乎无痛点 。Go 语言的 Goroutine 和 Channel 这两个神器简直就是并发和异步编程的巨大福音。像 C、C++、Java、Python 和 JavaScript 这些语言的并发和异步的编程方式控制起来就比较复杂了,并且容易出错,但 Go 语言却用非常优雅和流畅的方式解决了这个问题。这对于编程多年受尽并发和异步折磨的我来说,完全就是眼前一亮的感觉。
(图片来自 Medium:Why should you learn Go?)
第三,Go 语言的 lib 库 “麻雀虽小,五脏俱全” 。Go 语言的 lib 库中基本上有绝大多数常用的库,虽然有些库还不是很好,但我觉得这都不是主要问题,因为随着技术的发展和成熟,这些问题肯定也都会随之解决。
第四,C 语言的理念和 Python 的姿态 。C 语言的理念是信任程序员,保持语言的小巧,不屏蔽底层且对底层友好,关注语言的执行效率和性能。而 Python 的姿态是用尽量少的代码完成尽量多的事。于是我能够感觉到,Go 语言是想要把 C 和 Python 统一起来,这是多棒的一件事。
(图片来自 Medium:Why should you learn Go?)
所以,即便 Go 语言存在诸多的问题,比如垃圾回收、异常处理、泛型编程等,但相较于上面这几个优势,我认为这些问题都是些小问题。于是就毫不犹豫地入坑了。
学习资源 资源合集:https://github.com/developer-learning/learning-golang 
官方教程:https://learn.go.dev/ 
入门 
首首推,通过 TDD 学习 Go https://studygolang.gitbook.io/learn-go-with-tests/ 
首推 Go by Example  作为你的入门教程。然后,Go 101  也是一个很不错的在线电子书。
The Go Programming Language 中译本:Go 语言圣经
Go 语言官方的 Effective Go  是必读的,这篇文章告诉你如何更好地使用 Go 语言,以及 Go 语言中的一些原理。
web 开发:https://github.com/astaxie/build-web-application-with-golang/  33.7k 星
进阶 
查询 Go 项目,https://gowalker.org/,感觉是  GitHub advance search 的封装。
Go 语言高级编程 https://github.com/chai2010/advanced-go-programming-book 
Go 语言原本 https://changkun.de/golang/  学习源码
如何写出优雅的 Go https://draveness.me/golang-101 
https://github.com/qcrao/Go-Questions 
博客 
只收录有深度的博客,请享用!
Go 语言最突出之处是并发编程,Unix 老牌黑客罗勃・派克(Rob Pike)在 Google I/O 上的两个分享,可以让你学习到一些并发编程的模式。
Go Concurrency Patterns( 幻灯片 和演讲视频 )。 
Advanced Go Concurrency Patterns(幻灯片 、演讲视频 )。 
 
然后,Go 在 GitHub 的 wiki 上有好多不错的学习资源,你可以从中学习到多。比如:
此外,还有个内容丰富的 Go 资源列表 Awesome Go ,推荐看看。
类似 awesome-go https://github.com/hackstoic/golang-open-source-projects 
Go roadmap https://github.com/Alikhll/golang-developer-roadmap 
http://tmrts.com/go-patterns/ 
Uber Go Style Guide https://github.com/uber-go/guide/blob/master/style.md 
官方规范 https://github.com/golang/go/wiki/CodeReviewComments 
GoLand Tips & Tricks https://www.bilibili.com/video/av57075824 
Goroutine Leak 检测器,Uber出品。https://github.com/uber-go/goleak 
有关安全项目 库
gopacket - Go语言用于处理网络数据包的库 
xorm - Go语言实现的ORM库,支持多种数据库 
 
代码安全
Go-SCP - Go语言安全编码实践指南 
gosec - Go语言源码安全分析工具 
 
安全工具
certigo - Go语言编写用于检查/验证证书信息的命令行工具 
Blind-SQL-Injector - Go语言编写的手工盲注辅助工具 
lonely-shell - Go语言实现的反弹Shell后门 
hershell - Go语言反弹Shell后门 
go-deliver - Go语言编写的Payload交互工具 
go-shellcode - Go语言编写的ShellCode执行工具 
go-mimikatz - Go语言版本的Mimikatz 
NtlmSocks - 一个工作在网络层的跨平台哈希传递工具 
CHAOS - Go语言编写的Windows远控工具 
judas - Go语言编写的反向钓鱼工具 
Modlishka - Go语言编写的反向代理钓鱼工具 
Gophish - Go语言编写的开源钓鱼框架 
goddi - Go语言编写的活动目录信息导出工具 
goHackTools - Go语言编写的黑客工具集 
honeybits - 一款Go语言开发的蜜罐 
xsec-checker - Go语言编写的服务器安全检测辅助工具 
janusec - Golang打造的开源WAF网关 
xsec-ip-database - Go语言实现的恶意IP和域名库 
xsec-traffic - Go语言编写的轻量级恶意流量分析程序 
GoCrack - Go语言编写密码爆破平台 
 
扫描工具
blacksheepwall - Go语言编写的域名信息搜集工具 
amass - Go语言编写的子域名收集工具 
vuls - Go语言编写的Linux/FreeBSD漏洞扫描器 
gryffin - 大规模Web安全扫描平台 
Gobuster - Kai下敏感目录扫描工具 
OnionScan - Go语言编写的暗网扫描仪 
x-crack - Go语言编写的弱口令扫描器 
kraken - Go语言编写的YARA跨平台扫描器 
 
网络工具
GoReplay - Go语言编写HTTP流量记录重放工具 
NATBypass - LCX/Htran在Golang下的实现 
ngrok - 反向代理/内网穿透工具 
brook - Go语言编写的一款跨平台代理应用 
Hyperfox - HTTP/HTTPS流量监控工具 
gost - Go语言编写多功能网络代理转发工具 
gomitmproxy - Go语言实现的Mitmproxy 
netcap - Go语言编写的网络流量分析框架 
 
基础语法 变量声明 每个类型都有默认的初值,比如 0,“”,false
定义的变量必须要用到,实在不用的可以用 “_”
var  a, b, c int  = 1 , 2 , 3 var  a, b, c = 1 , 'a' , false var  (    home = os.Getenv("HOME" )     user = os.Getenv("USER" ) ) a, b, c := 1 , 'a' , false    
 
变量类型 
必须显式强制类型转换
 
布尔:bool
整型:int、(u)int8、(u)int16、(u)int32、(u)int64、uintptr
浮点:float32、float64、原生复数:complex64、complex128
字符串:string
字符:rune(int32 别名)
byte:(int8 别名)
派生: 
结构体 struct 
channel 
func 
slice 
interface 
map 
 
可用 type 设置别名
 
运算符 Go operator precedence: 1. *   /   %  <<  >>  &  &^ 2. +   -   |  ^ 3. ==  !=  <  <=  >   >= 4. && 5. || 
 
new 与 make Go 提供了两种分配原语,即 new 和 make。
new 是分配一个内存,返回一个内存地址,它不会初始化内存,只会将内存置零。
type  SyncedBuffer struct  {    lock sync.Mutex     buffer bytes.Buffer } p := new (SyncedBuffer)   
 
make 只用于创建 slice、map 和 channel,并返回一个“已初始化”的值。
常量与枚举 未指定类型的常量类似替换,不需要关心类型转换
Go 里的大写有其他含义,常量不再大写
const  b string  = "string"   const  a = "string" const (    a = 1      b = 2      c = 3  ) const (    cpp = ioat     python     java ) const (    _ = iota        b = 1  << (10  * iota )     kb     mb     gb     tb ) 
 
条件语句 if  contents, err := ioutil.ReadFile(filename); err != nil  {   } switch  i {    case  1 :     case  2 :     case  3 , 4 , 5 :     default :     	 } g := ""  switch  {    case  g: } whatAmI := func (i interface {})   {     switch  t := i.(type ) {         case  bool :         case  int :         default :     } } whatAmI(true ) 
 
循环 i := 1  for  i <= 3  {    fmt.Println(i)     i  } for  j := 1 ; j < 3 ; j++ {     } for  {    fmt.Println("loop" )     break  } 
 
函数 Go 只有值传递,每次传值都拷贝了一个副本,只是这副本里的某个部分可能指着同一块内存,比如 slice。
可传递指针
返回一个局部变量的地址没有问题,该局部变量对应的数据在函数返回后依然有效,编译器采用逃逸分析技术。
func  eval (a, b int , opt string )  (int , error)   {    return  -1 , fmt.Errorf("error: %s" , err) } func  test ()  t  string   {    t = "test"      return  } func  apply (op func (int , int )  int , a , b  int ) int   {    p := reflect.ValueOf(op).Pointer()     opName := runtime.FuncForPC(p).Name()     fmt.Printf("Calling function %s with args %d, %d" , opName, a, b)     return  op(a, b) } apply(func (a, b int )  int   {     return  a + b }, 1 , 2 ) var  f func (int )  int f  = func (int )  int   {    f() } func  sumArgs (members ...int )  int   {    s := 0      for  i := range  numbers {         s += numbers[i]     }     return  s } arr := [...]int {1 , 2 , 3 } sumArgs(arr...) 
 
指针 指针不能运算
var  a int  = 2 var  pa *int  = &a*pa = 3  
 
数组 Go 一般不用数组,用切片
var  arr [5 ]int b := [5 ]int {1 , 2 , 3 , 4 , 5 } b := [...]int {1 , 2 , 3 , 4 , 5 } var  grid [2 ][3 ]int var  grid [][]int  = [][]int {{1 , 2 , 3 }, {4 , 5 , 6 }}grid := [2 ][3 ]int {{1 , 2 , 3 }, {4 , 5 , 6 }} for  k, v := range  grid {    fmt.Println(k, v) } func  printArray (arr [10]int )   {    fmt.Println(arr) } 
 
切片 切片本身没有数据,是对底层数组的一个 view,是功能强悍的”动态数组“。
切片通过对数组进行封装,为数据序列提供了更通用、强大的接口。
除了矩阵变换这类需要明确维度的情况外, Go 中大部分数组编程都是通过切片来实现的。
切片保存了对底层数组的引用,若将某个切片赋值给另一个切片,它们将引用同一个数组。
len(s) 用来获取长度,当前有多少个值,用了多少
cap(s) 切片总容量
append(s, tg) 添加元素
copy(dst, src) 拷贝切片
slice := []int  slice := []int {1 , 2 , 3 , 4 } slice := make ([]type , len )   slice := make ([]type , len , cap ) fmt.Println(arr[:]) fmt.Println(arr[:3 ]) fmt.Println(arr[2 :]) fmt.Println(arr[3 :5 ]) s := arr[3 :5 ] rs := s[4 :6 ] b = append (b, 10 ) b = append (b, s...)   fmt.Println(b) 
 
排序 intList := []int {2 , 4 , 3 , 5 , 7 , 6 , 9 , 8 , 1 , 0 } float8List := []float64 {4.2 , 5.9 , 12.3 , 10.0 , 50.4 , 99.9 , 31.4 , 27.81828 , 3.14 } stringList := []string {"a" , "c" , "b" , "d" , "f" , "i" , "z" , "x" , "w" , "y" } sort.Ints(intList) sort.Sort(sort.IntSlice(intList)) sort.Sort(sort.Reverse(sort.IntSlice(intList))) sort.Sort(sort.Reverse(sort.Float64Slice(float8List))) sort.Sort(sort.Reverse(sort.StringSlice(stringList))) type  IntSlice []int func  (p IntSlice)  Len ()  int             { return  len (p) }func  (p IntSlice)  Less (i, j int )  bool   { return  p[i] < p[j] }func  (p IntSlice)  Swap (i, j int )        { p[i], p[j] = p[j], p[i] }func  (p IntSlice)  Sort ()   { Sort(p) }sort.Slice(arr, func (i, j int )  bool   {     	return  arr[i][0 ] < arr[j][0 ]     }) type  ByLength []string func  (s ByLength)  Len ()  int   {    return  len (s) } func  (s ByLength)  Swap (i, j int )   {    s[i], s[j] = s[j], s[i] } func  (s ByLength)  Less (i, j int )  bool   {    return  len (s[i]) < len (s[j]) } fruits := []string {"peach" , "banana" , "kiwi" } sort.Sort(ByLength(fruits)) 
 
就地去重,需要先排序 import  "sort" in := []int {3 ,2 ,1 ,4 ,3 ,2 ,1 ,4 ,1 }  sort.Ints(in) j := 0  for  i := 1 ; i < len (in); i++ {    if  in[j] == in[i] {     	continue      }     j++                    in[j] = in[i] } result := in[:j+1 ] fmt.Println(result)  
 
删除元素 a = append (a[:i], a[i+1 :]...) a = a[:i+copy (a[i:], a[i+1 :])] a := []string {"A" , "B" , "C" , "D" , "E" } i := 2  a[i] = a[len (a)-1 ]  a[len (a)-1 ] = ""     a = a[:len (a)-1 ]    fmt.Println(a)  copy (a[i:], a[i+1 :]) a[len (a)-1 ] = ""       a = a[:len (a)-1 ]      
 
切片比较 reflect.DeepEqual(s1, s2) 
 
reverse To replace the contents of a slice with the same elements but in reverse order:
for  i := len (a)/2 -1 ; i >= 0 ; i-- {    opp := len (a)-1 -i     a[i], a[opp] = a[opp], a[i] } 
 
The same thing, except with two indices:
for  left, right := 0 , len (a)-1 ; left < right; left, right = left+1 , right-1  {    a[left], a[right] = a[right], a[left] } 
 
底层结构 https://github.com/golang/go/blob/440f7d64048cd94cba669e16fe92137ce6b84073/src/runtime/slice.go 
加深理解:https://www.calhoun.io/why-are-slices-sometimes-altered-when-passed-by-value-in-go/ 
所以传值的时候,slice 对应的变量是拷贝的,但里面指向的 array 没变,除非 append 等操作改变了这个指针。
type slice struct {     array unsafe.Pointer     len   int     cap   int }       +--------+       |        |       |  ptr   |+------------+-------+-----------+       |        |                     |           |       +--------+                     |           |       |        |                     |           |       |        |                     |           |       | len  5 |                     |           |       |        |                     |           |       +--------+                     v           v       |        |             +-----+-----+-----+-----+----+       |        |             |     |     |     |     |    |       | cap  5 |     [5]int  |  0  |  1  |  2  |  3  | 4  |       |        |             +-----+-----+-----+-----+----+       +--------+        slice := arr[1:4]             arr := [5]int{0,1,2,3,4} 
 
package  mainimport  (    "fmt"  ) func  main ()   {    s := make ([]int , 0 , 7 )     for  i := 1 ; i <= 3 ; i++ {     	s = append (s, i)     }     reverse(s)     fmt.Println(len (s), cap (s))     fmt.Println(s) } func  reverse (s []int )   {    newElem := 999      for  len (s) < cap (s) {     	fmt.Println("Adding an element:" , newElem, "cap:" , cap (s), "len:" , len (s))     	s = append (s, newElem)     	newElem++     }     fmt.Println(len (s), cap (s))     fmt.Println(s)     for  i, j := 0 , len (s)-1 ; i < j; i++ {     	j = len (s) - (i + 1 )     	s[i], s[j] = s[j], s[i]     } } Adding an element: 999  cap : 7  len : 3  Adding an element: 1000  cap : 7  len : 4  Adding an element: 1001  cap : 7  len : 5  Adding an element: 1002  cap : 7  len : 6  7  7 [1  2  3  999  1000  1001  1002 ] 3  7 [1002  1001  1000 ] 
 
Copy b = make([]T, len(a)) copy(b, a) // or b = append([]T(nil), a...) // or b = append(a[:0:0], a...)  // See https://github.com/go101/go101/wiki 
 
Cut a = append(a[:i], a[j:]...) 
 
Delete without preserving order a[i] = a[len(a)-1]  a = a[:len(a)-1] 
 
NOTE  If the type of the element is a pointer  or a struct with pointer fields, which need to be garbage collected, the above implementations of Cut and Delete have a potential memory leak  problem: some elements with values are still referenced by slice a and thus can not be collected. The following code can fix this problem:
Cut 
 
copy(a[i:], a[j:]) for k, n := len(a)-j+i, len(a); k < n; k++ {     a[k] = nil // or the zero value of T } a = a[:len(a)-j+i] 
 
Delete 
 
if i < len(a)-1 {   copy(a[i:], a[i+1:]) } a[len(a)-1] = nil // or the zero value of T a = a[:len(a)-1] 
 
Delete without preserving order 
 
a[i] = a[len(a)-1] a[len(a)-1] = nil a = a[:len(a)-1] 
 
Expand a = append(a[:i], append(make([]T, j), a[i:]...)...) 
 
Extend a = append(a, make([]T, j)...) 
 
Filter (in place) n := 0 for _, x := range a {     if keep(x) {     	a[n] = x     	n++     } } a = a[:n] 
 
Insert a = append(a[:i], append([]T{x}, a[i:]...)...) 
 
NOTE  The second append creates a new slice with its own underlying storage and copies elements in a[i:] to that slice, and these elements are then copied back to slice a (by the first append). The creation of the new slice (and thus memory garbage) and the second copy can be avoided by using an alternative way:
Insert 
 
s = append(s, 0 /* use the zero value of the element type */) copy(s[i+1:], s[i:]) s[i] = x 
 
InsertVector a = append(a[:i], append(b, a[i:]...)...) 
 
Push Front/Unshift  
Pop Front/Shift  
Filtering without allocating This trick uses the fact that a slice shares the same backing array and capacity as the original, so the storage is reused for the filtered slice. Of course, the original contents are modified.
b := a[:0] for _, x := range a {     if f(x) {     	b = append(b, x)     } } 
 
For elements which must be garbage collected, the following code can be included afterwards:
for i := len(b); i < len(a); i++ {     a[i] = nil // or the zero value of T } 
 
Reversing Shuffling Fisher–Yates algorithm:
Since go1.10, this is available at math/rand.Shuffle 
 
for i := len(a) - 1; i > 0; i-- {     j := rand.Intn(i + 1)     a[i], a[j] = a[j], a[i] } 
 
Batching with minimal allocation Useful if you want to do batch processing on large slices.
actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} batchSize := 3 batches := make([][]int, 0, (len(actions) + batchSize - 1) / batchSize) for batchSize < len(actions) {     actions, batches = actions[batchSize:], append(batches, actions[0:batchSize:batchSize]) } batches = append(batches, actions) 
 
Yields the following:
[[0 1 2] [3 4 5] [6 7 8] [9]] 
 
Map 哈希表实现,除了 slice、map、function 的内建类型都可以做 key,不含这些字段的 Struct 也可
m := make (map [string ]int )   m["one" ] = 1  m["two" ] = 2  m := map [string ]int  {     "one" : 1 ,     "two" : 2 , } var  m map [string ]int one := m["one" ] one, ok := m["one" ] for  k, v := range  m {    fmt.Println(k, v) } delete (m, "one" )
 
stack 
项目要用的话直接 import “github.com/golang-collections/collections/stack”
用 list 实现 stack
 
package  stack  import  "container/list" type  Stack struct  {    list *list.List } func  NewStack ()  *Stack   {    list := list.New()     return  &Stack{list} } func  (stack *Stack)  Push (value interface {})   {    stack.list.PushBack(value) } func  (stack *Stack)  Pop ()  interface  {} {    e := stack.list.Back()     if  e != nil  {         stack.list.Remove(e)         return  e.Value     }     return  nil  } func  (stack *Stack)  Peak ()  interface  {} {    e := stack.list.Back()     if  e != nil  {         return  e.Value     }     return  nil  } func  (stack *Stack)  Len ()  int   {    return  stack.list.Len() } func  (stack *Stack)  Empty ()  bool   {    return  stack.list.Len() == 0  } 
 
heap  
字符串 Go 特意做了优化,设计了 rune,可以完美的支持多语言。
'a' 为字符 rune、"abc" 为字符串,`` 可包含复杂的字符串。
s := "我爱Go语言!"  for  i, ch := range  []rune (s) {    fmt.Printf("(%d %c)" , i, ch) } s[i] 并不是字符串,而是 uint8 ,即 ASCII 码,需要转一下 str := string (s[i]) 
 
fmt.Printf("%t\n" , 1 ==2 ) fmt.Printf("%t\n" , true )   fmt.Printf("二进制:%b\n" , 255 ) fmt.Printf("八进制:%o\n" , 255 ) fmt.Printf("十进制:%d\n" , 255 ) fmt.Printf("十六进制:%X\n" , 255 ) fmt.Printf("%c\n" , 33 )   fmt.Printf("浮点数:%f\n" , math.Pi) fmt.Printf("字符串:%s\n" , "hello world" ) fmt.Printf("%q\n" , "\"string\"" )   fmt.Printf("%p\n" , &p)   fmt.Printf("类型:%T\n" , "hello world" ) fmt.Printf("字段在内的实例的完整信息:%+v\n" , "hello world" ) fmt.Printf("字段和限定类型名称在内的实例的完整信息:%#v\n" , "hello world" ) p := point{1 , 2 } fmt.Printf("%v\n" , p) fmt.Printf("%+v\n" , p) fmt.Printf("%#v\n" , p) fmt.Printf("%T\n" , p) fmt.Printf("%e\n" , 123400000.0 ) fmt.Printf("%E\n" , 123400000.0 ) fmt.Printf("%x\n" , "hex this" ) fmt.Printf("|%6d|%6d|\n" , 12 , 345 ) fmt.Printf("|%6.2f|%6.2f|\n" , 1.2 , 3.45 ) fmt.Printf("|%-6.2f|%-6.2f|\n" , 1.2 , 3.45 ) fmt.Printf("|%6s|%6s|\n" , "foo" , "b" ) fmt.Printf("|%-6s|%-6s|\n" , "foo" , "b" ) s := fmt.Sprintf("a %s" , "string" ) fmt.Println(s) fmt.Fprintf(os.Stderr, "an %s\n" , "error" ) 
 
还有对应的 strings、strconv 包。
s := "This is an example of a string.中文"  println (strings.HasPrefix(s, "This" ))println (strings.HasSuffix(s, "string" ))println (strings.Contains(s, "a " ))println (strings.Index(s, "is" ))println (strings.LastIndex(s, "i" ))strings.Replace() strings.ToLower(s) string  strconv.Itoa(int (item))   
 
文件操作 f, err := os.Open('/etc/passwd' ) defer  f.Close()buf := make ([]byte , 10 ) f.Read(buf) r := bufio.NewReader(f) for  {    str, err := r.ReadString('\n' )     if  err == io.EOF {         break      }     fmt.Printf(str) } content, err := ioutil.ReadFile('/etc/passwd' )   ioutil.ReadDir("." ) 
 
命令执行 cmd := exec.Command("id" ) stdoutStderr, err := cmd.CombinedOutput() if  err != nil  {    log.Fatal(err) } 
 
泛型 package  mainimport  "strings" import  "fmt" func  Index (vs []string , t string )  int   {    for  i, v := range  vs {         if  v == t {             return  i         }     }     return  -1  } func  Include (vs []string , t string )  bool   {    return  Index(vs, t) >= 0  } func  Any (vs []string , f func (string )  bool ) bool   {    for  _, v := range  vs {         if  f(v) {             return  true          }     }     return  false  } func  All (vs []string , f func (string )  bool ) bool   {    for  _, v := range  vs {         if  !f(v) {             return  false          }     }     return  true  } func  Filter (vs []string , f func (string )  bool ) []string   {    vsf := make ([]string , 0 )     for  _, v := range  vs {         if  f(v) {             vsf = append (vsf, v)         }     }     return  vsf } func  Map (vs []string , f func (string )  string ) []string   {    vsm := make ([]string , len (vs))     for  i, v := range  vs {         vsm[i] = f(v)     }     return  vsm } func  main ()   {         var  strs = []string {"peach" , "apple" , "pear" , "plum" }     fmt.Println(Index(strs, "pear" ))     fmt.Println(Include(strs, "grape" ))     fmt.Println(Any(strs, func (v string )  bool   {         return  strings.HasPrefix(v, "p" )     }))     fmt.Println(All(strs, func (v string )  bool   {         return  strings.HasPrefix(v, "p" )     }))     fmt.Println(Filter(strs, func (v string )  bool   {         return  strings.Contains(v, "e" )     }))          fmt.Println(Map(strs, strings.ToUpper)) } 
 
面向“对象” 仅支持封装,不支持继承和多态
结构体和方法 用大小写来区分,大写开头 public、小写开头 private,private 只能在当前包内使用(使用工厂模式解决,即自行实现(大写开头)构造函数)
type  Books struct  {    title string      author string      book_id string  } books := Books{     title:   "Go" ,     author:  "Tim" ,     book_id: "1" , } type  Member struct  {    Id     int     `json:"id,-"`      Name   string  `json:"name"`      Email  string  `json:"email"`      Gender int     `json:"gender,"`      Age    int     `json:"age"`  } books = Books{"Go" , "Tim" , "2" } func  (book Books)  print ()   {     } func  (book *Books)  print ()   {    if  book == nil  {              } } type  Edu struct  {    Books       name string  } 
 
包和封装 同一个目录下只能有一个包,main 包下为主入口
为结构定义的方法必须放在同一个包内
可以是不同文件
import 中可以使用相对路径 ./、../ 引用包,如果没有用相对路径,go 会去 $GOPATH/src/ 目录找
扩展已有类型 定义别名 使用组合 使用内嵌来扩展已有类型 依赖管理 依赖管理 GOPATH 和 GOVENDOR gopath 和 path 一样,可以接受多个路径,路径之间用冒号分隔
go mod go: cannot find main module; see ‘go help modules’
go env -w GO111MODULE=on go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct 
 
go mod 子命令
go mod init module_name go list -m -u all  # 检查可以升级的包 go get -u need-upgrade-package  # 升级 download edit graph init tidy vendor verify why 
 
面向接口 duck typing 
“像鸭子走路,像鸭子叫,长的像鸭子,那么就是鸭子” 
描述事物的外部行为而非内部结构 
严格来说 go 属于结构化类型系统,类似 duck typing 
 
Python 中的鸭子
// 运行时才知道传入的 retriever 有没有 get 方法 // 需要注释来说明接口 def  download (retriever) :    return  retriever.get("http://qq.com" ) 
 
C++ 中的鸭子
template  <class  R >string  download (const  R & retriver ) {    return  retriver.get("http://qq.com" ) } 
 
Java 中的类似代码
<R extends Retriver> String download (R r)   {    return  r.get("http://qq.com" ) } 
 
Go
type  Retriver interface  {    Get(source string ) string  } func  download (retriver Retriver)  string   {    return  retriver.Get("http://qq.com" ) } 
 
接口的概念 接口由使用者定义
接口的实现时隐式的,只要实现里面的方法(不太理解这句话
接口本身不能创建实例,但可以指向一个实现了该接口的(自定义)类型的变量。
一个自定义类型需要将某个接口的所有方法都实现,才说这个自定义类型实现了该接口,否则编译不通过。
一个自定义类型可以实现多个接口
一个接口可以继承多个接口
interface 类型默认是一个指针
空接口 interface{} 没有任何方法,所以所有类型都实现了空接口,即可以把任何一个变量赋值给空接口。
类型断言 
var  x interface {}var  f float32  = 1.1 x = f y := x.(float32 )   if  y, ok := x.(float32 ); ok {     } 
 
继承与接口 
当 A 结构体继承了 B 结构体,那么 A 就有了 B 的所有字段和方法,并可以直接调用。
当 A 结构体需要扩展功能,同时不希望破坏继承关系,实现某个接口即可。
因此,实现接口可以看做是对继承机制的补充。
继承的价值:解决代码的复用性和可维护性。
接口的价值:设计,设计好各种规范(方法),让其它自定义类型去实现这种方法。
接口比继承更加灵活,继承是满足 is-a 的关系,而接口只需满足 like-a 的关系,在一定程度上实现了解耦。
接口的定义和实现 
实际上就是实现了多态,同样的方法在不同对象调用时表现不同的意义?
还有个典型的列子,即 sort(),需要实现三个方法就可以给自定义类型排序
 
type  geometry interface  {    area() float64      perim() float64  } type  rect struct  {    width, height float64  } type  circle struct  {    radius float64  } func  (r rect)  area  float64   {    return  r.width * r.height } func  (r rect)  perim ()  float64   {    return  (r.width + r.height) * 2  } func  (c circle)  area ()  float64   {    return  math.Pi * c.radius * c.radius } func  (c circle)  perim ()  float64   {    return  2  * math.Pi * c.radius } func  measure (g geometry)   {    fmt.Println(g)     fmt.Println(g.area())     fmt.Println(g.perim()) } func  main ()   {    r := rect{width: 3 , height: 4 }     c := circle{radius: 5 }          measure(r)     measure(c) } 
 
接口的值类型 接口的组合 常用系统接口 函数式编程 闭包 函数内的局部变量 + 匿名函数构成闭包
func  adder ()  func (int )  int   {    sum := 0      return  func (value int )  int   {     	sum += value     	return  sum     } } 
 
为函数实现接口
反射 使用反射遍历结构体字段,调用结构体的方法,并获取结构体标签的值。
定义多个函数,再定义一个适配器函数用作统一处理接口。
使用反射创建并操作结构体。
错误处理和资源管理 defer 延时机制,在 return 后再调用,先 defer 的后调用。
常常需要创建资源(数据库连接、文件句柄、锁),使用 defer 来关闭更省心。
错误处理概念 服务器统一出错处理 panic 和 recover panic 一旦出错直接终止程序。
recover 可对接收到的错误自定义处理。
defer  func ()   {    if  err := recover (); err {         println (err)     }  }() 
 
哪些不能 Recover:
Thread Limit,超过了系统的线程限制,详细参考下面的说明; 
Concurrent Map Writers,竞争条件,同时写 map,参考下面的例子。推荐使用标准库的 sync.Map 解决这个问题。 
 
最佳实践 Go 的 err 过于简单,需要增加上下文信息。
package  mainimport  (    "fmt"  ) type  Handler interface  {    Filter(err error, r interface {}) error } type  Logger interface  {    Ef(format string , a ...interface {}) } func  HandlePanic (hdr Handler, logger Logger)  error   {    return  handlePanic(recover (), hdr, logger) } type  hdrFunc func (err error, r interface {})  error func  (v hdrFunc)  Filter (err error, r interface {})  error   {    return  v(err, r) } type  loggerFunc func (format string , a ...interface {}) func  (v loggerFunc)  Ef (format string , a ...interface {})   {    v(format, a...) } func  HandlePanicFunc (hdr func (err error, r interface {})  error ,    logger  func (format string , a ...interface {}) , ) error   {    var  f Handler     if  hdr != nil  {         f = hdrFunc(hdr)     }     var  l Logger     if  logger != nil  {         l = loggerFunc(logger)     }     return  handlePanic(recover (), f, l) } func  handlePanic (r interface {}, hdr Handler, logger Logger)  error   {    if  r != nil  {         err, ok := r.(error)         if  !ok {             err = fmt.Errorf("r is %v" , r)         }         if  hdr != nil  {             err = hdr.Filter(err, r)         }         if  err != nil  && logger != nil  {             logger.Ef("panic err %+v" , err)         }         return  err     }     return  nil  } func  main ()   {    func ()   {         defer  HandlePanicFunc(nil , func (format string , a ...interface {})   {             fmt.Println(fmt.Sprintf(format, a...))         })         panic ("ok" )     }()     logger := func (format string , a ...interface {})   {         fmt.Println(fmt.Sprintf(format, a...))     }     func ()   {         defer  HandlePanicFunc(nil , logger)         panic ("ok" )     }() } 
 
问题追踪和调试 打印日志 GDB 测试与调优 TDD 先写测试  => 尝试运行测试  => 写少量代码跑起来  => 补充完整通过测试  => 重构  => 跳到第二步 
先写测试代码,编写足够的代码来使编译通过,仅此而已  。
请记住,我们要查看的是,测试是否因为合理的原因失败。
迭代!
尽你所能拆分需求是一项很重要的技能,这样你就能拥有可以工作的软件 。
在测试的支持下,将功能切分成小的功能点,并使其首尾相连顺利的运行。 
敏捷开发:让它运作,使它正确,使它快速 
过早的优化是万恶之源  —— Donald Knuth
单元测试 单元测试:保证项目工程质量的最有效办法。
可测试:意味着面向接口编程以及减少单个函数中包含的逻辑,使用『小方法』; 
组织方式:使用 Go 语言默认的 Test 框架、开源的 suite 或者 BDD 的风格对单元测试进行合理组织; 
Mock 方法:四种不同的单元测试 Mock 方法;
 
断言:使用社区的 testify  快速验证方法的返回值; 
 
命名约定 
测试用例文件名必须以 _test.go 结尾。
 
测试函数必须以 Test 开头
 
 
测试函数只接受一个 t *testing.T 参数。 
 
import  "testing" func  TestHello (t *testing.T)   {    assertCorrectMessage := func (t *testing.T, got string , want string )   {     	t.Helper()       	if  got != want {     		t.Errorf("want %q got %q" , want, got)     	}     }     t.Run("saying hello to people" , func (t *testing.T)   {     	got := Hello("wywwzj" )     	want := "Hello, wywwzjj"      	assertCorrectMessage(t, got, want)     })     t.Run("empty string defaults to 'world'" , func (t *testing.T)   {     	want := "Hello, world"      	got := Hello("" )     	assertCorrectMessage(t, got, want)     }) } func  TestFib (t *testing.T)   {    var  fibTests = []struct  {         in       int           expected int       }{         {1 , 1 },         {2 , 1 },         {3 , 2 },         {4 , 3 },         {5 , 5 },         {6 , 8 },         {7 , 13 },     }     for  _, tt := range  fibTests {         actual := Fib(tt.in)         if  actual != tt.expected {             t.Errorf("Fib(%d) = %d; expected %d" , tt.in, actual, tt.expected)         }     } } 
 
测试并发中的条件竞争
 
example 既能生成文档,也是做了一次测试。
func  Example_GetScore ()   {    score := getScore(100 , 100 , 100 , 2.1 )     fmt.Println(score)           } 
 
testing 的变量 gotest 的变量有这些:
test.short : 一个快速测试的标记,在测试用例中可以使用 testing.Short () 来绕开一些测试 
test.outputdir : 输出目录 
test.coverprofile : 测试覆盖率参数,指定输出文件 
test.run : 指定正则来运行某个 / 某些测试用例 
test.memprofile : 内存分析参数,指定输出文件 
test.memprofilerate : 内存分析参数,内存分析的抽样率 
test.cpuprofile : cpu 分析输出参数,为空则不做 cpu 分析 
test.blockprofile : 阻塞事件的分析参数,指定输出文件 
test.blockprofilerate : 阻塞事件的分析参数,指定抽样频率 
test.timeout : 超时时间 
test.cpu : 指定 cpu 数量 
test.parallel : 指定运行测试用例的并行数 
 
testing 包内的结构 
B : 压力测试 
BenchmarkResult : 压力测试结果 
Cover : 代码覆盖率相关结构体 
CoverBlock : 代码覆盖率相关结构体 
InternalBenchmark : 内部使用的结构 
InternalExample : 内部使用的结构 
InternalTest : 内部使用的结构 
M : main 测试使用的结构 
PB : Parallel benchmarks 并行测试使用结果 
T : 普通测试用例 
TB : 测试用例的接口 
 
testing 的通用方法 T 结构内部是继承自 common 结构,common 结构提供集中方法,是我们经常会用到的:
当我们遇到一个断言错误的时候,我们就会判断这个测试用例失败,就会使用到:
Fail  : case 失败,测试用例继续 FailedNow : case 失败,测试用例中断 
 
当我们遇到一个断言错误,只希望跳过这个错误,但是不希望标示测试用例失败,会使用到:
SkipNow : case 跳过,测试用例不继续 
 
当我们只希望在一个地方打印出信息,我们会用到 :
Log : 输出信息 Logf : 输出有 format 的信息 
 
当我们希望跳过这个用例,并且打印出信息 :
Skip : Log + SkipNow Skipf : Logf + SkipNow 
 
当我们希望断言失败的时候,测试用例失败,打印出必要的信息,但是测试用例继续:
Error : Log + Fail Errorf : Logf + Fail 
 
当我们希望断言失败的时候,测试用例失败,打印出必要的信息,测试用例中断:
Fatal : Log + FailNow Fatalf : Logf + FailNow 
 
基准测试 func  BenchmarkHello (t testing.B)   {    b.ResetTimer()       for  i := 0 ; i < b.N; i++ {         Hello();     } } 
 
测试 http 服务器 httptest server := httptest.NewServer(http.HandlerFunc(func (w http.ResponseWriter, r *http.Request)   {     	w.WriteHeader(http.StatusOK)     })) server.URL 
 
#### 
代码覆盖率和性能测试 pprof runtime/pprof
net/http/pprof
runtime/trace 
只需在程序执行前加上环境变量 GODEBUG=gctrace=1
GODEBUG=gctrace=1 go test -bench=. GODEBUG=gctrace=1 go run main.go 
 
生成文档和示例代码 性能调优分析指标 
Wall Time 
CPU Time 
Block Time 
Memory allocation 
GC times / time spent 
 
别让性能被锁住 高效字符串连接 字符串是不可变对象,已经生成不能改变,每次改变都是产生了一个新的字符串。
string.Builder 性能最好
  var  builder strings.Builderfor  i := 0 ; i < b.N; i++ {    builder.WriteString(strconv.Itoa(i)) } str := builder.String() 
 
 
bytes.Buffer 次之
  var  buf bytes.Bufferfor  i := 0 ; i < b.N; i++ {    buf.WriteString(strconv.Itoa(i)) } str := buf.String() 
 
 
+
 
sprintf 最慢
  var  s string for  i := 0 ; i < b.N; i++ {    s = fmt.Sprintf("%v%v" , s, i) } 
 
 
 
GC 友好 避免内存分配和复制 
复杂对象尽量传引用
 
复用内存
  slice 初始化合适的大小。
 
 
并发编程 
https://github.com/golang/go/wiki/LearnConcurrency 
 
Go 实现了 CSP(通信顺序进程,Communicaing Sequential Process)模型来作为 goroutine 间的推荐通信方式
Goroutine MPG模式 
M:操作系统的主线程(物理线程)
P:协程执行需要的上下文
G:协程
如果协程中出现了 panic,则整个程序都会崩溃。
设置运行 CPU 数量
num := runtime.NumCPU()   runtime.GOMAXPROCS(233 )   
 
channel 
不要通过共享内存来通信,而应通过通信来共享内存。
 
类似 Unix 下的双向管道,可指定单向,用于goroutine 之间通信,可传送任意数据类型。符号为 <- chan <-。
线程安全,多 goroutine 访问时,不需要加锁,即本身就是线程安全的。
func  main ()   {    channel := make (chan  string )     go  func ()   {         channel <- "Hello"      } } 
 
main 函数所处的是一个主 goroutine,由 runtime.main 启动。
main 函数一旦运行结束退出,其他的 goroutine 会被杀掉。
channel := make (chan  int , 3 )   
 
channel 默认是阻塞的,满了阻塞写,空了阻塞读。
获取 channel 内容可使用 range 遍历,但发送方 channel 要手动 close 一下。
queue := make (chan  string , 2 ) queue <- "one"  queue <- "two"  close (queue)for  elem := range  queue {    fmt.Println(elem) } 
 
Go 中的 select
for  i := 0 ; i < 2 ; i++ {    select  {         case  msg1 := <- c1:         	     	case  msg2 := <- c2:         	     } } for  {    timeout_cnt := 0      select  {         case  msg1 := <- c1:         	         case  msg2 := <- c2:         	         case  <- time.After(time.Second * 30 ):         	timeout_cnt++     }     if  time_cnt > 3  {         break      } } for  {    select  {         case  msg1 := <- c1:         	         case  msg2 := <- c2:         	         default :          	     } } close (channel)more := true  for  more {    select  {         case  msg, more = <- channel:             if  more {     			             } else  {                              }     } } 
 
var  sem = make (chan  int , MaxOutstanding)func  handle (r *Request)   {    sem <- 1          process(r)       <-sem        } func  Serve (queue chan  *Request)   {    for  {         req := <-queue         go  handle(req)       } } func  Serve (queue chan  *Request)   {    for  {         req := <-queue         sem <- 1          go  func ()   {             process(req)             <- sem         }()     } } func  Serve (queue chan  *Request)   {    for  {         req := <-queue         sem <- 1          go  func (req *Request)   {             process(req)             <- sem         }(req)     } } func  Serve (queue chan  *Request)   {    for  req := range  queue {         req := req           sem <- 1          go  func ()   {             process(req)             <- sem         }()     } } 
 
理解调度器 线程池 package  mainimport  "fmt" import  "time" func  worker (id int , jobs <-chan  int , results chan <- int )   {    for  j := range  jobs {         fmt.Println("worker" , id, "processing job" , j)         time.Sleep(time.Second)         results <- j * 2      } } func  main ()   {         jobs := make (chan  int , 100 )     results := make (chan  int , 100 )          for  w := 1 ; w <= 3 ; w++ {         go  worker(w, jobs, results)     }          for  j := 1 ; j <= 9 ; j++ {         jobs <- j     }     close (jobs)          for  a := 1 ; a <= 9 ; a++ {         <-results     } } 
 
runtime  
context 原子操作 这样的函数还有很多,参看 go 的 atomic 包文档 
imort "sync/atomic" var cnt uint32 = 0 atomic.AddUint32(&cnt, 1) cntFinal := atomic.LoadUint32(&cnt)  // 取数据 
 
互斥锁 Mutex var  memoryAccess sync.Mutexvar  value int go  func ()   {    memoryAccess.Lock()     value++     memoryAccess.Unlock() } memoryAccess.Lock() if  value == 0  {    println (value) } memoryAccess.Unlock() func  main ()   {         var  state = make (map [int ]int )          var  mutex = &sync.Mutex{}                         var  ops int64  = 0           for  r := 0 ; r < 100 ; r++ {         go  func ()   {             total := 0              for  {                                                                                     key := rand.Intn(5 )                 mutex.Lock()                 total += state[key]                 mutex.Unlock()                 atomic.AddInt64(&ops, 1 )                                                                                                      runtime.Gosched()             }         }()     }               for  w := 0 ; w < 10 ; w++ {         go  func ()   {             for  {                 key := rand.Intn(5 )                 val := rand.Intn(100 )                 mutex.Lock()                 state[key] = val                 mutex.Unlock()                 atomic.AddInt64(&ops, 1 )                 runtime.Gosched()             }         }()     }               time.Sleep(time.Second)          opsFinal := atomic.LoadInt64(&ops)     fmt.Println("ops:" , opsFinal)          mutex.Lock()     fmt.Println("state:" , state)     mutex.Unlock() } 
 
读写锁 RWMutex Once 调用无数次也只执行一次。
var  once sync.OnceonceBody := func ()   {     fmt.Println(time.Now()) } done := make (chan  bool ) for  i := 0 ; i < 10 ; i++ {    go  func ()   {         once.Do(onceBody)         done <- true      }() } for  i := 0 ; i < 10 ; i++ {    fmt.Println(<- done) } 
 
WaitGroup wg := sync.WaitGroup{} wg.Add(10 )   wg.Done()   wg.Wait() 
 
竞争条件检测 go build,go run 或者 go test 命令后面加上 -race
常用库 os 尽量多用 os 提供的方法,这里的方法都是跨平台的,少用 syscall。
 
time package  mainimport  (    "fmt"      "time"  ) func  daysBetweenDates (date1 string , date2 string )  int   {    d1, _ := time.Parse("2006-01-02" , date1)     d2, _ := time.Parse("2006-01-02" , date2)     delta := d1.Sub(d2)     return  abs(int (delta.Hours()) / 24 ) } func  main ()   {    timeString := time.Now().Format("2006-01-02 15:04:05" )       fmt.Println(timeString)     fmt.Println(time.Now().Format("2017-09-07 18:05:32" ))          p := fmt.Println          now := time.Now()     p(now)          then := time.Date(2009 , 11 , 17 , 20 , 34 , 58 , 651387237 , time.UTC)     p(then)          p(then.Year())     p(then.Month())     p(then.Day())     p(then.Hour())     p(then.Minute())     p(then.Second())     p(then.Nanosecond())     p(then.Location())          p(then.Weekday())          p(then.Before(now))     p(then.After(now))     p(then.Equal(now))          diff := now.Sub(then)     p(diff)          p(diff.Hours())     p(diff.Minutes())     p(diff.Seconds())     p(diff.Nanoseconds())          p(then.Add(diff))     p(then.Add(-diff)) } 
 
定时器 Timers timer = time.NewTimer(time.Second * 2 ) go  func ()   {    <-timer.C        }() timer.Stop()   
 
打点器 Tickers 间隔一段时间发一个信号。
ticker := time.NewTicker(time.Millisecond * 500 ) go  func ()   {    for  t := range .ticker.C {         fmt.Println("Tick at " , t)     } }() ticker.Stop() 
 
速率限制 Rate Limiting func  main ()   {              requests := make (chan  int , 5 )     for  i := 1 ; i <= 5 ; i++ {         requests <- i     }     close (requests)               limiter := time.Tick(time.Millisecond * 200 )               for  req := range  requests {         <-limiter         fmt.Println("request" , req, time.Now())     }                    burstyLimiter := make (chan  time.Time, 3 )          for  i := 0 ; i < 3 ; i++ {         burstyLimiter <- time.Now()     }               go  func ()   {         for  t := range  time.Tick(time.Millisecond * 200 ) {             burstyLimiter <- t         }     }()               burstyRequests := make (chan  int , 5 )     for  i := 1 ; i <= 5 ; i++ {         burstyRequests <- i     }     close (burstyRequests)     for  req := range  burstyRequests {         <-burstyLimiter         fmt.Println("request" , req, time.Now())     } } 
 
encoding import  b64 "encoding/base64" import  "fmt" func  main ()   {         data := "abc123!?$*&()'-=@~"                sEnc := b64.StdEncoding.EncodeToString([]byte (data))     fmt.Println(sEnc)               sDec, _ := b64.StdEncoding.DecodeString(sEnc)     fmt.Println(string (sDec))     fmt.Println()          uEnc := b64.URLEncoding.EncodeToString([]byte (data))     fmt.Println(uEnc)     uDec, _ := b64.URLEncoding.DecodeString(uEnc)     fmt.Println(string (uDec)) } 
 
container heap type  IntHeap []int func  (h IntHeap)  Len ()  int             { return  len (h) }func  (h IntHeap)  Less (i, j int )  bool   { return  h[i] < h[j] }func  (h IntHeap)  Swap (i, j int )        { h[i], h[j] = h[j], h[i] }func  (h *IntHeap)  Push (x interface {})   {    *h = append (*h, x.(int )) } func  (h *IntHeap)  Pop ()  interface  {} {    old := *h     n := len (old)     x := old[n-1 ]     *h = old[0  : n-1 ]     return  x } h := &IntHeap{2 , 1 , 5 } heap.Init(h) heap.Push(h, 3 ) heap.Pop(h) 
 
list type  Element struct  {    next, prev *Element       list *List       Value interface {}   } type  List struct  {    root Element       len   int        } 
 
package  mainimport  (    "container/list"      "fmt"  ) func  main ()   {    list := list.New()     list.PushBack(1 )     list.PushBack(2 )     fmt.Printf("len: %v\n" , list.Len())     fmt.Printf("first: %#v\n" , list.Front())     fmt.Printf("second: %#v\n" , list.Front().Next()) } 
 
type  Element    func  (e *Element)  Next ()  *Element      func  (e *Element)  Prev ()  *Element  type  List     func  New ()  *List      func  (l *List)  Back ()  *Element    // 最后一个元素     func  (l *List)  Front ()  *Element   // 第一个元素     func  (l *List)  Init ()  *List   // 链表初始化     func  (l *List)  InsertAfter (v interface {}, mark *Element)  *Element  // 在某个元素后插入     func  (l *List)  InsertBefore (v interface {}, mark *Element)  *Element   // 在某个元素前插入     func  (l *List)  Len ()  int  // 在链表长度     func  (l *List)  MoveAfter (e, mark *Element)   // 把 e  元素移动到 mark  之后     func  (l *List)  MoveBefore (e, mark *Element)   // 把 e  元素移动到 mark  之前     func  (l *List)  MoveToBack (e *Element)  // 把 e  元素移动到队列最后     func  (l *List)  MoveToFront (e *Element)  // 把 e  元素移动到队列最头部     func  (l *List)  PushBack (v interface {})  *Element   // 在队列最后插入元素     func  (l *List)  PushBackList (other *List)   // 在队列最后插入接上新队列     func  (l *List)  PushFront (v interface {})  *Element   // 在队列头部插入元素     func  (l *List)  PushFrontList (other *List)  // 在队列头部插入接上新队列     func  (l *List)  Remove (e *Element)  interface  {} 
 
ring type Ring struct {     next, prev *Ring     Value      interface{} } 
 
package  mainimport  (    "container/ring"      "fmt"  ) func  main ()   {    ring := ring.New(3 )     for  i := 1 ; i <= 3 ; i++ {         ring.Value = i         ring = ring.Next()     }          s := 0      ring.Do(func (p interface {})  {         s += p.(int )     })     fmt.Println("sum is" , s) } 
 
type  Ring    func  New (n int )  *Ring   // 初始化环     func  (r *Ring)  Do (f func (interface {}) )  // 循环环进行操作     func  (r *Ring)  Len ()  int  // 环长度     func  (r *Ring)  Link (s *Ring)  *Ring  // 连接两个环     func  (r *Ring)  Move (n int )  *Ring  // 指针从当前元素开始向后移动或者向前(n  可以为负数)     func  (r *Ring)  Next ()  *Ring  // 当前元素的下个元素     func  (r *Ring)  Prev ()  *Ring  // 当前元素的上个元素     func  (r *Ring)  Unlink (n int )  *Ring  // 从当前元素开始,删除 n  个元素 
 
regexp package  mainimport  "bytes" import  "fmt" import  "regexp" func  main ()   {         match, _ := regexp.MatchString("p([a-z]+)ch" , "peach" )     fmt.Println(match)               r, _ := regexp.Compile("p([a-z]+)ch" )               fmt.Println(r.MatchString("peach" ))          fmt.Println(r.FindString("peach punch" ))               fmt.Println(r.FindStringIndex("peach punch" ))               fmt.Println(r.FindStringSubmatch("peach punch" ))          fmt.Println(r.FindStringSubmatchIndex("peach punch" ))               fmt.Println(r.FindAllString("peach punch pinch" , -1 ))          fmt.Println(r.FindAllStringSubmatchIndex(         "peach punch pinch" , -1 ))          fmt.Println(r.FindAllString("peach punch pinch" , 2 ))                    fmt.Println(r.Match([]byte ("peach" )))               r = regexp.MustCompile("p([a-z]+)ch" )     fmt.Println(r)          fmt.Println(r.ReplaceAllString("a peach" , "<fruit>" ))          in := []byte ("a peach" )     out := r.ReplaceAllFunc(in, bytes.ToUpper)     fmt.Println(string (out)) } 
 
strings func  Compare (a, b string )  int func  Contains (s, substr string )  bool func  ContainsAny (s, chars string )  bool func  ContainsRune (s string , r rune )  bool func  Count (s, substr string )  int func  EqualFold (s, t string )  bool func  Fields (s string )  []string func  FieldsFunc (s string , f func (rune )  bool ) []string func  HasPrefix (s, prefix string )  bool func  HasSuffix (s, suffix string )  bool func  Index (s, substr string )  int func  IndexAny (s, chars string )  int func  IndexByte (s string , c byte )  int func  IndexFunc (s string , f func (rune )  bool ) int func  IndexRune (s string , r rune )  int func  Join (a []string , sep string )  string func  LastIndex (s, substr string )  int func  LastIndexAny (s, chars string )  int func  LastIndexByte (s string , c byte )  int func  LastIndexFunc (s string , f func (rune )  bool ) int func  Map (mapping func (rune )  rune , s  string ) string func  Repeat (s string , count int )  string func  Replace (s, old, new  string , n int )  string func  ReplaceAll (s, old, new  string )  string func  Split (s, sep string )  []string func  SplitAfter (s, sep string )  []string func  SplitAfterN (s, sep string , n int )  []string func  SplitN (s, sep string , n int )  []string func  Title (s string )  string func  ToLower (s string )  string func  ToLowerSpecial (c unicode.SpecialCase, s string )  string func  ToTitle (s string )  string func  ToTitleSpecial (c unicode.SpecialCase, s string )  string func  ToUpper (s string )  string func  ToUpperSpecial (c unicode.SpecialCase, s string )  string func  ToValidUTF8 (s, replacement string )  string func  Trim (s string , cutset string )  string func  TrimFunc (s string , f func (rune )  bool ) string func  TrimLeft (s string , cutset string )  string func  TrimLeftFunc (s string , f func (rune )  bool ) string func  TrimPrefix (s, prefix string )  string func  TrimRight (s string , cutset string )  string func  TrimRightFunc (s string , f func (rune )  bool ) string func  TrimSpace (s string )  string func  TrimSuffix (s, suffix string )  string type  Builder     func  (b *Builder)  Cap ()  int      func  (b *Builder)  Grow (n int )      func  (b *Builder)  Len ()  int      func  (b *Builder)  Reset ()      func  (b *Builder)  String ()  string      func  (b *Builder)  Write (p []byte )  (int , error)      func  (b *Builder)  WriteByte (c byte )  error      func  (b *Builder)  WriteRune (r rune )  (int , error)      func  (b *Builder)  WriteString (s string )  (int , error)  type  Reader     func  NewReader (s string )  *Reader      func  (r *Reader)  Len ()  int      func  (r *Reader)  Read (b []byte )  (n int , err error)      func  (r *Reader)  ReadAt (b []byte , off int64 )  (n int , err error)      func  (r *Reader)  ReadByte ()  (byte , error)      func  (r *Reader)  ReadRune ()  (ch rune , size int , err error)      func  (r *Reader)  Reset (s string )      func  (r *Reader)  Seek (offset int64 , whence int )  (int64 , error)      func  (r *Reader)  Size ()  int64      func  (r *Reader)  UnreadByte ()  error      func  (r *Reader)  UnreadRune ()  error      func  (r *Reader)  WriteTo (w io.Writer)  (n int64 , err error)  type  Replacer     func  NewReplacer (oldnew ...string )  *Replacer      func  (r *Replacer)  Replace (s string )  string      func  (r *Replacer)  WriteString (w io.Writer, s string )  (n int , err error)  
 
bytes 操作 byte slice
func  Compare (a, b []byte )  int func  Contains (b, subslice []byte )  bool func  ContainsAny (b []byte , chars string )  bool func  ContainsRune (b []byte , r rune )  bool func  Count (s, sep []byte )  int func  Equal (a, b []byte )  bool func  EqualFold (s, t []byte )  bool func  Fields (s []byte )  [][]byte func  FieldsFunc (s []byte , f func (rune )  bool ) [][]byte func  HasPrefix (s, prefix []byte )  bool func  HasSuffix (s, suffix []byte )  bool func  Index (s, sep []byte )  int func  IndexAny (s []byte , chars string )  int func  IndexByte (b []byte , c byte )  int func  IndexFunc (s []byte , f func (r rune )  bool ) int func  IndexRune (s []byte , r rune )  int func  Join (s [][]byte , sep []byte )  []byte func  LastIndex (s, sep []byte )  int func  LastIndexAny (s []byte , chars string )  int func  LastIndexByte (s []byte , c byte )  int func  LastIndexFunc (s []byte , f func (r rune )  bool ) int func  Map (mapping func (r rune )  rune , s  []byte ) []byte func  Repeat (b []byte , count int )  []byte func  Replace (s, old, new  []byte , n int )  []byte func  ReplaceAll (s, old, new  []byte )  []byte func  Runes (s []byte )  []rune func  Split (s, sep []byte )  [][]byte func  SplitAfter (s, sep []byte )  [][]byte func  SplitAfterN (s, sep []byte , n int )  [][]byte func  SplitN (s, sep []byte , n int )  [][]byte func  Title (s []byte )  []byte func  ToLower (s []byte )  []byte func  ToLowerSpecial (c unicode.SpecialCase, s []byte )  []byte func  ToTitle (s []byte )  []byte func  ToTitleSpecial (c unicode.SpecialCase, s []byte )  []byte func  ToUpper (s []byte )  []byte func  ToUpperSpecial (c unicode.SpecialCase, s []byte )  []byte func  ToValidUTF8 (s, replacement []byte )  []byte func  Trim (s []byte , cutset string )  []byte func  TrimFunc (s []byte , f func (r rune )  bool ) []byte func  TrimLeft (s []byte , cutset string )  []byte func  TrimLeftFunc (s []byte , f func (r rune )  bool ) []byte func  TrimPrefix (s, prefix []byte )  []byte func  TrimRight (s []byte , cutset string )  []byte func  TrimRightFunc (s []byte , f func (r rune )  bool ) []byte func  TrimSpace (s []byte )  []byte func  TrimSuffix (s, suffix []byte )  []byte type  Buffer     func  NewBuffer (buf []byte )  *Buffer      func  NewBufferString (s string )  *Buffer      func  (b *Buffer)  Bytes ()  []byte      func  (b *Buffer)  Cap ()  int      func  (b *Buffer)  Grow (n int )      func  (b *Buffer)  Len ()  int      func  (b *Buffer)  Next (n int )  []byte      func  (b *Buffer)  Read (p []byte )  (n int , err error)      func  (b *Buffer)  ReadByte ()  (byte , error)      func  (b *Buffer)  ReadBytes (delim byte )  (line []byte , err error)      func  (b *Buffer)  ReadFrom (r io.Reader)  (n int64 , err error)      func  (b *Buffer)  ReadRune ()  (r rune , size int , err error)      func  (b *Buffer)  ReadString (delim byte )  (line string , err error)      func  (b *Buffer)  Reset ()      func  (b *Buffer)  String ()  string      func  (b *Buffer)  Truncate (n int )      func  (b *Buffer)  UnreadByte ()  error      func  (b *Buffer)  UnreadRune ()  error      func  (b *Buffer)  Write (p []byte )  (n int , err error)      func  (b *Buffer)  WriteByte (c byte )  error      func  (b *Buffer)  WriteRune (r rune )  (n int , err error)      func  (b *Buffer)  WriteString (s string )  (n int , err error)      func  (b *Buffer)  WriteTo (w io.Writer)  (n int64 , err error)  type  Reader     func  NewReader (b []byte )  *Reader      func  (r *Reader)  Len ()  int      func  (r *Reader)  Read (b []byte )  (n int , err error)      func  (r *Reader)  ReadAt (b []byte , off int64 )  (n int , err error)      func  (r *Reader)  ReadByte ()  (byte , error)      func  (r *Reader)  ReadRune ()  (ch rune , size int , err error)      func  (r *Reader)  Reset (b []byte )      func  (r *Reader)  Seek (offset int64 , whence int )  (int64 , error)      func  (r *Reader)  Size ()  int64      func  (r *Reader)  UnreadByte ()  error      func  (r *Reader)  UnreadRune ()  error      func  (r *Reader)  WriteTo (w io.Writer)  (n int64 , err error)  
 
strconv func  AppendBool (dst []byte , b bool )  []byte func  AppendFloat (dst []byte , f float64 , fmt byte , prec, bitSize int )  []byte func  AppendInt (dst []byte , i int64 , base int )  []byte func  AppendQuote (dst []byte , s string )  []byte func  AppendQuoteRune (dst []byte , r rune )  []byte func  AppendQuoteRuneToASCII (dst []byte , r rune )  []byte func  AppendQuoteRuneToGraphic (dst []byte , r rune )  []byte func  AppendQuoteToASCII (dst []byte , s string )  []byte func  AppendQuoteToGraphic (dst []byte , s string )  []byte func  AppendUint (dst []byte , i uint64 , base int )  []byte func  Atoi (s string )  (int , error) func  CanBackquote (s string )  bool func  FormatBool (b bool )  string func  FormatFloat (f float64 , fmt byte , prec, bitSize int )  string func  FormatInt (i int64 , base int )  string func  FormatUint (i uint64 , base int )  string func  IsGraphic (r rune )  bool func  IsPrint (r rune )  bool func  Itoa (i int )  string func  ParseBool (str string )  (bool , error) func  ParseFloat (s string , bitSize int )  (float64 , error) func  ParseInt (s string , base int , bitSize int )  (i int64 , err error) func  ParseUint (s string , base int , bitSize int )  (uint64 , error) func  Quote (s string )  string func  QuoteRune (r rune )  string func  QuoteRuneToASCII (r rune )  string func  QuoteRuneToGraphic (r rune )  string func  QuoteToASCII (s string )  string func  QuoteToGraphic (s string )  string func  Unquote (s string )  (string , error) func  UnquoteChar (s string , quote byte )  (value rune , multibyte bool , tail string , err error) type  NumError     func  (e *NumError)  Error ()  string  
 
casbin jwt-go cobra urfave/cli termui viper redigo grpc-go pkg/errors notify gopherjs logrus zap excelize dig fasthttp gopsutil resty GORM gonum jsoniter gofpdf Testify 写个爬虫 
简单分布式爬虫,爬取相亲网站资料
 
工具集 开发 1)sql2go  用于将 sql 语句转换为 golang 的 struct. 使用 ddl 语句即可。 例如对于创建表的语句: show create table xxx. 将输出的语句,直接粘贴进去就行。http://stming.cn/tool/sql2go.html 
2)toml2go  用于将编码后的 toml 文本转换问 golang 的 struct.https://xuri.me/toml-to-go/ 
3)curl2go  用来将 curl 命令转化为具体的 golang 代码.https://mholt.github.io/curl-to-go/ 
4)json2go  用于将 json 文本转换为 struct.https://mholt.github.io/json-to-go/ 
5) mysql 转 ES 工具 http://www.ischoolbar.com/EsParser/ 
6)golang  模拟模板的工具,在支持泛型之前,可以考虑使用。https://github.com/cheekybits/genny 
7) 查看某一个库的依赖情况,类似于 go list 功能 https://github.com/KyleBanks/depth 
8) 一个好用的文件压缩和解压工具,集成了 zip,tar 等多种功能,主要还有跨平台。https://github.com/mholt/archiver 
9) go 内置命令  go list 可以查看某一个包的依赖关系. go vet 可以检查代码不符合 golang 规范的地方。
10) 热编译工具 https://github.com/silenceper/gowatch 
11)revive  golang 代码质量检测工具https://github.com/mgechev/revive 
12)Go Callvis  golang 的代码调用链图工具https://github.com/TrueFurby/go-callvis 
13)Realize  开发流程改进工具https://github.com/oxequa/realize 
14)Gotests  自动生成测试用例工具https://github.com/cweill/gotests 
调试 1)perf  代理工具,支持内存,cpu,堆栈查看,并支持火焰图. perf 工具和 go-torch 工具,快捷定位程序问题.https://github.com/uber-archive/go-torch https://github.com/google/gops 
2) dlv 远程调试  基于 goland+dlv 可以实现远程调式的能力.https://github.com/go-delve/delve  提供了对 golang 原生的支持,相比 gdb 调试,简单太多。
3) 网络代理工具  goproxy 代理,支持多种协议,支持 ssh 穿透和 kcp 协议.https://github.com/snail007/goproxy 
4) 抓包工具  go-sniffer 工具,可扩展的抓包工具,可以开发自定义协议的工具包。现在只支持了 http,mysql,redis,mongodb. 基于这个工具,我们开发了 qapp 协议的抓包。https://github.com/40t/go-sniffer 
5) 反向代理工具,快捷开放内网端口供外部使用。  ngrok 可以让内网服务外部调用https://ngrok.com/ https://github.com/inconshreveable/ngrok 
6) 配置化生成证书  从根证书,到业务侧证书一键生成.https://github.com/cloudflare/cfssl 
7) 免费的证书获取工具  基于 acme 协议,从 letsencrypt 生成免费的证书,有效期 1 年,可自动续期。https://github.com/Neilpang/acme.sh 
8) 开发环境管理工具,单机搭建可移植工具的利器。支持多种虚拟机后端。vagrant  常被拿来同 docker 相比,值得拥有。https://github.com/hashicorp/vagrant 
9) 轻量级容器调度工具  nomad 可以非常方便的管理容器和传统应用,相比 k8s 来说,简单不要太多.https://github.com/hashicorp/nomad 
10) 敏感信息和密钥管理工具 https://github.com/hashicorp/vault 
11) 高度可配置化的 http 转发工具,基于 etcd 配置。 https://github.com/gojek/weaver 
12) 进程监控工具 supervisor https://www.jianshu.com/p/39b476e808d8 
13) 基于 procFile  进程管理工具。相比 supervisor 更加简单。https://github.com/ddollar/foreman 
14) 基于 http,https,websocket 的调试代理工具 ,配置功能丰富。在线教育的 nohost web 调试工具,基于此开发.https://github.com/avwo/whistle 
15) 分布式调度工具 https://github.com/shunfei/cronsun/blob/master/README_ZH.md https://github.com/ouqiang/gocron 
16) 自动化运维平台 Gaia https://github.com/gaia-pipeline/gaia 
网络 
常用网站 go 百科全书: https://awesome-go.com/ 
json 解析: https://www.json.cn/ 
出口 IP: https://ipinfo.io/ 
redis 命令: http://doc.redisfans.com/ 
ES 命令首页: https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html 
UrlEncode: http://tool.chinaz.com/Tools/urlencode.aspx 
Base64: https://tool.oschina.net/encrypt?type=3 
Guid: https://www.guidgen.com/ 
常用工具: http://www.ofmonkey.com/ 
常用库 日志 https://github.com/Sirupsen/logrus https://github.com/uber-go/zap 
配置  兼容 json,toml,yaml,hcl 等格式的日志库.https://github.com/spf13/viper 
存储  mysql: https://github.com/go-xorm/xorm  es: https://github.com/elastic/elasticsearch  redis: https://github.com/gomodule/redigo  mongo: https://github.com/mongodb/mongo-go-driver  kafka: https://github.com/Shopify/sarama 
数据结构 https://github.com/emirpasic/gods 
命令行 https://github.com/spf13/cobra 
框架 https://github.com/grpc/grpc-go https://github.com/gin-gonic/gin 
并发 https://github.com/Jeffail/tunny https://github.com/benmanns/goworker  现在我们框架在用的,虽然 star 不多,但是确实好用,当然还可以更好用.https://github.com/rafaeldias/async 
工具  定义了实用的判定类,以及针对结构体的校验逻辑,避免业务侧写复杂的代码.https://github.com/asaskevich/govalidator https://github.com/bytedance/go-tagexpr 
protobuf 文件动态解析的接口,可以实现反射相关的能力。https://github.com/jhump/protoreflect 
表达式引擎工具 https://github.com/Knetic/govaluate https://github.com/google/cel-go 
字符串处理 https://github.com/huandu/xstrings 
ratelimit 工具 https://github.com/uber-go/ratelimit https://blog.csdn.net/chenchongg/article/details/85342086 https://github.com/juju/ratelimit 
golang 熔断的库  熔断除了考虑频率限制,还要考虑 qps,出错率等其他东西.https://github.com/afex/hystrix-go https://github.com/sony/gobreaker 
表格 https://github.com/chenjiandongx/go-echarts 
tail 工具库 https://github.com/hpcloud/taglshi 
最佳实践 Effective Go  ,Go 的语法不复杂,所以,Go 语言的最佳实践只需要看这篇官方文档就够了。
以下是在实际开发过程中遇到的一些问题,仅供参考: 
异常处理统一使用 error,不要使用 panic/recover 来模拟 throw…catch,最初我是这么做的,后来发现这完全是自以为是的做法。 
原生的 error 过于简单,而在实际的 API 开发过程中,不同的异常情况需要附带不同的返回码,基于此,有必要对 error 再进行一层封装。 
任何协程逻辑执行体,逻辑最开始处必须要有 defer recover () 异常恢复处理,否则 goroutine 内出现的 panic,将导致整个进程宕掉,需要避免部分逻辑 BUG 造成全局影响。 
在 Golang 中,变量 (chan 类型除外) 的操作是非线程安全的,也包括像 int 这样的基本类型,因此并发操作全局变量时一定要考虑加锁,特别是对 map 的并发操作。 
所有对 map 键值的获取,都应该判断存在性,最好是对同类操作进行统一封装,避免出现不必要的运行时异常。 
定义 slice 数据类型时,尽量预设长度,避免内部出现不必要的数据重组。 
 
代码规范 Go 语言比较常见并且使用广泛的代码规范就是官方提供的 Go Code Review Comments ,无论你是短期还是长期使用 Go 语言编程,都应该至少完整地阅读一遍这个官方的代码规范指南 ,它既是我们在写代码时应该遵守的规则,也是在代码审查时需要注意的规范。
goimports goimports  是 Go 语言官方提供的工具,它能够为我们自动格式化 Go 语言代码并对所有引入的包进行管理,包括自动增删依赖的包引用、将依赖包按字母序排序并分类。相信很多人使用的 IDE 都会将另一个官方提供的工具 gofmt  对代码进行格式化,而 goimports 就是等于 gofmt 加上依赖包管理。
golint 在基础库或者框架中使用 golint 进行静态检查(或者同时使用 golint 和 golangci-lint ),在其他的项目中使用可定制化的 golangci-lint 来进行静态检查,因为在基础库和框架中施加强限制对于整体的代码质量有着更大的收益。
目录结构 https://github.com/golang-standards/project-layout 
├── LICENSE.md ├── Makefile ├── README.md ├── api ├── assets ├── build ├── cmd ├── configs ├── deployments ├── docs ├── examples ├── githooks ├── init ├── internal ├── pkg ├── scripts ├── test ├── third_party ├── tools ├── vendor ├── web └── website 
 
模块拆分 显式与隐式 面向接口 面试题 
聊聊 GPM? 
go 的 new 和 make 区别 
go 怎么从源码编译到二进制文件 
go 的调度模型 
go 的锁如何实现,用了什么 cpu 指令 
go 的 runtime 如何实现 
看过 sql 的连接池实现吗 
c++ 的 map 和 go 的 map 的区别(红黑树和 hashtable) 
ctx 包了解吗?有什么用? 
go 什么情况下会发生内存泄漏?(他说 ctx 没有 cancel 的时候,这个真不知道) 
怎么实现协程完美退出? 
智力题:1000 瓶酒中有 1 瓶毒酒,10 只老鼠,7 天后毒性才发作,第 8 天要卖了,怎么求那瓶毒酒? 
简单 dp 题,n*n 矩阵从左上角到右下角有多少种走法(只限往下和往右走) 
用 channel 实现定时器?(实际上是两个协程同步) 
channel 的实现? 
go 为什么高并发好?讲了 go 的调度模型 
操作系统内存管理?进程通讯,为什么共享存储区效率最高 
实现一个 hashmap,解决 hash 冲突的方法,解决 hash 倾斜的方法 
c++ 的模板跟 go 的 interface 的区别 
怎么理解 go 的 interface 
go 代码运行结果(闭包函数) 
git 和 svn 区别,模型 
唯一订单号问题,并发量高的话怎么解决 
hash 表设计要注意什么问题 
数组和为 n 的数组对 
lru 实现 
多个线程读,一个线程写一个 int32 会不会有问题,int64 呢(这里面试官后来说了要看数据总线的位数,32 位的话写 int32 没问题,int64 就有问题) 
GO 语言中的协程与 Python 中的协程的区别? 主要讲解 Go 中 GMP 机制 
 
杂项 go doc json  // 输出 json 包对应的文档 
 
跨平台编译 GOOS=darwin GOARCH=amd64 go build GOOS=windows GOARCH=amd64 go build 
 
go get 被墙 export http_proxy=socks5://127.0.0.1:1080 GitHub 上有官方镜像库,如 https://github.com/golang/net 即 https://golang.org/x/net git clone https://github.com/golang/tools.git #  然后进对应目录 go install,bin 目录就有了。 go install github/xxx/xxx #  go mod 优先使用镜像 go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/ 
 
随机数 package  mainimport  "time" import  "fmt" import  "math/rand" func  main ()   {         fmt.Print(rand.Intn(100 ), "," )     fmt.Print(rand.Intn(100 ))     fmt.Println()          fmt.Println(rand.Float64())          fmt.Print((rand.Float64()*5 )+5 , "," )     fmt.Print((rand.Float64() * 5 ) + 5 )     fmt.Println()               s1 := rand.NewSource(time.Now().UnixNano())     r1 := rand.New(s1)          fmt.Print(r1.Intn(100 ), "," )     fmt.Print(r1.Intn(100 ))     fmt.Println()          s2 := rand.NewSource(42 )     r2 := rand.New(s2)     fmt.Print(r2.Intn(100 ), "," )     fmt.Print(r2.Intn(100 ))     fmt.Println()     s3 := rand.NewSource(42 )     r3 := rand.New(s3)     fmt.Print(r3.Intn(100 ), "," )     fmt.Print(r3.Intn(100 )) } 
 
Tricks 如何声明一个最大的int和uint常量? const  MaxUint = ^uint (0 )const  MaxInt = int (^uint (0 ) >> 1 )
 
如何在编译时刻决定系统原生字的尺寸? 这个技巧和Go无关。
const  Is64bitArch = ^uint (0 ) >> 63  == 1 const  Is32bitArch = ^uint (0 ) >> 63  == 0 const  WordBits = 32  << (^uint (0 ) >> 63 ) 
 
使用闭包进行调试 当您在分析和调试复杂的程序时,无数个函数在不同的代码文件中相互调用,如果这时候能够准确地知道哪个文件中的具体哪个函数正在执行,对于调试是十分有帮助的。您可以使用 runtime 或 log 包中的特殊函数来实现这样的功能。包 runtime 中的函数 Caller() 提供了相应的信息,因此可以在需要的时候实现一个 where() 闭包函数来打印函数执行的位置:
where := func ()   {     _, file, line, _ := runtime.Caller(1 )     log.Printf("%s:%d" , file, line) } where() where() where() 
 
log.SetFlags(log.Llongfile) log.Print("") var where = log.Print func func1() { where() ... some code where() ... some code where() }