CookBook

这里会记录一些Nace经常碰到的一些不容易记住的编程相关的 专业术语小Tips ;

About Code

位运算符

位运算符 语义/Semantics e.g.
& 按位与/AND 0101 & 0011 = 0001
I 按位或/OR 0101 I 0011 = 0111
^ 按位异或/XOR 0101 ^ 0011 = 0110
^& 位清除/AND NOT 0110 &^ 1011 = 0100
<< 位左移/LEFT SHIFT 0001 << 3 = 1000
>> 位右移/Right SHIFT 1000 >> 3 = 0001

TIPS: 位清除(bit clear/AND NOT) 与 异或(XOR) 虽然结果表现的一致 但是 实际表述并不一样。

位移操作的右操作数必须要是uint类型

b=:23
x:=1 << b  //panic: (shift count type int,must be unsigned integer)
//Right Code
var t uint
t = 2
x:=1 << t
println(x) // 4

About Arch

Best Practice in Golang

  • 类型转换

在一些高性能场景中,类型转换不能使用type(foo)的形式,可以考虑用unsafe包 从底层存储方式进行转换 会高效很多:

func toString(foo []byte)string{
  return *(*string)(unsafe.Pointer(&foo))
}
//其他转换同理
  • 构建字符串

字符串相加在数量很多的场景下,性能会变得非常差,因为每次都会给新的string重新分配内存。所以,在需要相加的字符串很多的情况下,用strings包的Join()或者bytes包的WriteString()/WriteBytes(),一次性分配好需要分配的内存就会让性能提升很多.

func enlargeString(foo string,boo string)string{
  var buf bytes.Buffer
  buf.WriteString(foo)
  for (i:=0;i<aLargeNumber;i++){
    buf.WriteString(boo)
  }
  return buf.String()
}

  • 避免Goroutine中的延迟求值

在日常的开发中 我们往往需要在一个LOOP中实现并发 这时候往往这么写:

  //foos is a array slice or else which can be ranged
  for _,foo:=range foos{
    go func(){
      //do sth with foo
    }()
  }

但是这样就会遇到一个问题:得到的foo永远都是index==len(foos)-1的foo值,这是因为goroutine的延迟求值,这里每次go func的匿名方法并不会被马上执行,当LOOP 执行完的时候才会去执行展开其中的Goroutine.所以正确的写法应该是每次执行go func的时候把foo带进去,让goroutine去保存这个状态。具体的代码如下:

  //foos is a array slice or else which can be ranged
  for _,foo:=range foos{
    go func(foo TYPE){
      //do sth with foo
    }(foo)
  }
  • 关于反射的一些知识

反射是在Runtime层读取对象的内存信息与类型;但是由于没有对象的头指针,无法做到靠其自身来解析对象的类型。依靠interface{}来实现反射,i interface{}可以保存参数的类型数据

这里的interface{}只是对象的一个复制,并且是不可寻址的,所以,要改变对象的信息只能传入指向此对象的指针

a:=100
reflect.Value(a) //canset:false canaddress:false
reflect.Value(&a) //canset:true canaddress:true

利用reflect来导出不可导出类型 任何pkg相对于reflect来说都是外部包。

  • Golang中的AOP(面向切面编程)

什么是AOP?

在Golang中,反射(reflect)很好地切合了AOP的主题。通过reflect在Runtime的时候动态执行方法,当我们用AOP的思想抽象出一个切面之后,也许这个切面是一个日志模块,或是一个审计模块(这些模块具有一个共同点:永远不需要也不可能被需要耦合到业务代码中去)。也可以把这些模块看成一个代码hook,这些模块在不用更改业务代码的同时调用业务代码中的逻辑。一次简单的尝试如下:

  type LogModule struct{}
  func (lg LogModule)logsth()(string,error){
    //log sth ...
  }
  func main(){
    logm:=new(LogModule)
    reflectLog:=reflect.ValueOf(logm)
    m:=reflectLog.MethodByName("logsth")
    in:=[]reflect.Value{}
    //Call logsth in Runtime
    out:=m.Call(in)
    //res
    for _,res:=range out{
      println(res)
    }
  }
  • 在Golang中使用Set

首先,Golang中是没有built-inset关键字的。所以,要实现类似set的数据结构,需要使用mapkey来组合成一个set

这里的set更多的指的是一个FIFO的map也就是一个ordered-map。

比如,我们现在有一个map:

foo:=make(map[string]string)
foo["go"]="go"
foo["js"]="js"
foo["php"]="php"

然后,我们发现遍历这个map的时候,key的顺序有可能是会被更换的:

for key,value:=range foo{
  //key,value的值并不唯一
  println(key,value)
}

这是为什么呢?其实仔细想一想就知道,我们为什么要指望map来向set一样来工作呢?这本来就是两个数据结构啊!所以,我们要的是什么?在这种场景下,我们需要的只是key按一定顺序排列的map中对应的值,经过这层思考之后,就可以很快的得出代码:

  //按你想要的顺序遍历map吧..
  keys:=[]string{"go","js","php"}
  for _,key:=range keys{
    println(key,foo[key])
  }

再反过来想想 我们为什么希望map按照FIFO顺序来呢?终究是对map的理解出现了误解。 这时候静下来 好好读读Go blog的说明,就会有豁然开朗的感觉了吧:)