前言

今天开始,会经常写一些小的代码片段,放在这个“远离枯燥的golang”系列里面。众所周知,golang是一门语法非常简单的语言,其实每天用一门这样的语言写代码,功能实现很快,但是可能会渐渐失去写代码的乐趣,感觉每天都在写if err != nil。曾经就有个笑话说,连java程序员都写完代码了,golang程序员还在写if err != nil。所以就会把工作中突发奇想想到的一些我自认为有点意思的golang代码的小片段记录在这里。

base64解码

一般来说,用到base64最频繁的场景就是,有一个base64编码过的string,然后将其解码成另一个string,用最简单的方式操作大概就是这样:

package main
import "base64"
import "fmt"
func main(){
	b64Str := "dGhpcyBpcyBhIGV4YW1wbGU="
	var res string
	if bytes ,err := base64.StdEncoding.DecodeString(b64Str);err != nil {
		fmt.Println("err!")
	} else {
		res = string(res)
	}
}

这种平平无奇的代码写多就会想吐呢!今天我想了下,至少写一点审美情趣不一样的代码吧。我首先就是不想看到这个多值返回!而且他喵的返回值是个字节数组,真是讨厌

回调替代多值返回

于是,我先写出了如下代码:


package main
import "base64"
import "fmt"

func myb64Decode(f func([]bytes),b64Str string) error {
	 if bytes ,err := base64.StdEncoding.DecodeString(b64Str) ;err != nil {
		 	return error
	 } else {
		 f(bytes)
	 }
}
func main(){
	b64Str := "dGhpcyBpcyBhIGV4YW1wbGU="
	var res string
	if err := myb64Decode(func (bytes []byte){
			res = (string)bytes
		},b64Str) ;err != nil {
		fmt.Println("err!")
	}
}

看上去其实并没有太好。(但是用上了闭包就看起来厉害一点了啊!)至少不用处理我个人非常不爱的多值返回了。

改进那个闭包

再想想,其实这个回调本质只是想要一个从[]bytestring的setter函数。不如写成固定的实现好了。

package main
import "base64"
import "fmt"

func myb64Decode(f func([]bytes),b64Str string) error {
	 if bytes ,err := base64.StdEncoding.DecodeString(b64Str) ;err != nil {
		 	return error
	 } else {
		 f(bytes)
	 }
}

func setter(str *string) func([]byte) {
	return func(bytes []byte) {
		*str = string(bytes)
	}
}

func main(){
	b64Str := "dGhpcyBpcyBhIGV4YW1wbGU="
	var res string
	if err := myb64Decode(setter(&res),b64Str);err != nil {
		fmt.Println("err!")
	}
}

业务代码好像变得看起来好一点了!当然,其实setter是可以隐藏在函数内部直接复制的,让他看上去行为就是从一个base64的string,解码并复制到了另一个string的引用中去。但是我懒得打字了。

最终我的写法

其实写成上一节的话呢,已经可以达到目的了。但是我今天写的功能里面有一个特殊的地方,解码完成以后,是替换掉原先的变量的值的,也就是说,如果我写到上一节为止,我的代码会是这样的


myb64Decode(setter(&b64Str),b64Str)

作为一个重度懒癌,我拒绝同一个变量写两次!于是我又套了一层,写了一个不通用的实现。

package main
import "base64"
import "fmt"

func myb64Decode(f func([]bytes),b64Str string) error {
   if bytes ,err := base64.StdEncoding.DecodeString(b64Str) ;err != nil {
      return error
   } else {
     f(bytes)
   }
}

func setter(str *string) func([]byte) {
  return func(bytes []byte) {
    *str = string(bytes)
  }
}

func b64ToSelf(str * string) error {
  return myb64Decode(setter(str),*str)
}

func main(){
  b64Str := "dGhpcyBpcyBhIGV4YW1wbGU="
  if err := b64ToSelf(&b64Str);err != nil {
    fmt.Println("err!")
  }
}

结语

最终大概就是这样!最近新写的系统是rest api,api里解出来的参数基本都是base64编码过的,所以会频繁地操作解码,这样写大概会舒服一点!

本文的代码都是按照记忆重新写的,均没有经过测试!

结语的结语:为什么我不喜欢多值返回

首先,多值返回让原本很好用的golang类型推导运算符:=变得非常痛苦。

当我们写出


if x, err := function() ; err != nil {

}

就意味着,这里的x是一个作用域非常狭窄的变量,大多数时候我们都不希望函数返回值太过短暂;如果不是真的需要这样,可能需要另一个赋值语句把结果保存到生命周期更长的变量里,简直就是脱裤子放屁!

那么,我们就得写出

x, err := function()
if err != nil {

}

或者

var x interface{}
var err error
if x, err = function() ; err != nil {

}

这两种写法都恶心的地方在于,为了x ,我必须拥有一个生命周期一样长的error!所以多值返回的问题在于,多值返回的变量会享有同样的生命周期,我控制不了!虽然看上去没什么大不了,但是学c++出身的人!受!不!了!(教练!我想写rust!)

所以上文所说的,通过引用赋值规避多值返回,能控制住error类型变量的周期在很短的语句里!也不需要看到结果变量被赋值来赋值去了!