20210103继续深入思考type所导致的类型别名与类型定义之间的区别,同时明确相关的书写规范 - ziyouzy/2021blog GitHub Wiki

首先在v1.9后已经明确,type拥有两种方式,分别是类型别名与类型定义:

类型别名:  type newi = int         //程序编译时只会存在int                        //打印 type: %T时显示均为int
类型定义:  type newi int            //程序编译时会同时存在newi与int     //打印 type: %T时分别显示int与main.newI

此外不能给别名定义方法,原因可以从“main.newI”这一结果可以看出,如果真正的实体存在于本地(同一个包),才可以为其定义方法,而别名的本体肯定不会存在于程序员当前所操作的包,所以并不符合最基本的语法规则

回到http包的HandlerFunc函数类型,由于需要为他定义方法,所以只能采用类型定义的方式,而不能时类型别名,实操测试结果如下:

type testHandlerFunc =func(int, string)
func (p testHandlerFunc)testf(){fmt.Println("test")}
编译无法通过

type testHandlerFunc func(int, string)
func (p testHandlerFunc)testf(){fmt.Println("test")}
编译可以通过并顺利执行打印"test"

这些规则确实稍有混乱(包括结构体的特殊性)

但是golang也给了程序员解决方案,那就是var与:=之间的灵活性,type是否需要加=的灵活性

程序员可以自己约束自己的编码规范,从而实现整体上思维逻辑与代码逻辑的合理化,标准化封装:

type规则:

如果需要type一个旧数据类型的同时为其设计方法(如遇到http包server.go中type HandlerFunc func(ResponseWriter, *Request)这样的情况)则需要通过type进行类型定义

补充再说下server.go中type HandlerFunc func(ResponseWriter, *Request)设计者之所以这么做是为了方便使用者把自己的函数转化为一个handler,但是无论如何如果确实type后需要设计方法,那么就一定不能是类型别名的思路了,设计的出发点多种多样(server.go只是其中只一),但是只要结果是需要给type后的东西再去设计方法,那么他都要遵循类型定义的规则!

而类型定义后的新类型与旧类型,虽然有些可以直接进行相互赋值的操作,但是禁止直接进行赋值与传参的操作,因为虽然代码可行,但是逻辑上是不合理的 
如果必须这么做,那么必须先定义一个新类型变量容器在去接收他,如:
type newSL []int
func main(){
    var newsl1 newSL
    newsl1 =newSL([]int{1,2,3})//书写代码时,需要能看出、体现出思维逻辑上明确区分了旧类型与新类型的不同

    newsl2 :=newI([]int{1,2,3})//书写代码时,需要能看出、体现出思维逻辑上明确区分了旧类型与新类型的不同

    sl :=[]int{1,2,3}//书写代码时,需要能看出、体现出思维逻辑上明确区分了旧类型与新类型的不同
    newsl3 :=newI(sl)

    /*--------------------*/

    wrongsl :=[]int{1,2,3}
    var wrongnewsl newSL
    wrongnewsl =wrongsl//虽然可以但是禁止这样写,因为无法看出、体现出思维逻辑上明确区分了旧类型与新类型的不同
    wrongsl =wrongnewsl//虽然可以但是禁止这样写,因为无法看出、体现出思维逻辑上明确区分了旧类型与新类型的不同
    
    wrongnewsl =newSL(wrongsl)//正确的逻辑产生的正确的书写方式
    wrongsl =[]int(wrongnewsl)//正确的逻辑产生的正确的书写方式

    /*--------------------*/
    作为参数表时:
    sl := []int{1,2,3}
    newsl :=newSL([]int{1,2,3})

    func testf1(arr []int){fmt.Println("test1")}
    func testf2(arr newSL){fmt.Println("test2")}

    testf1(newsl)//虽然可以但是禁止这样
    testf1([]int(newsl))//这才是正确的表达方式

    testf2(sl)//虽然可以但是禁止这样
    testf2(newSL(sl))//这才是正确的表达方式
}

/-------------------------------------------/

如果只是为了像视觉上清晰,那就是另一种情况:纯粹的将一个旧类型起个小名

这种情况硬性规定如下:

1.自己定义了一个结构类,内部包含的某个结构类的字段,从容起个别名,从而让代码从视觉上可以更为直观的理解
2.再比如type NeedCRC =bool这样的别名,他可以用来和const ISBIGENDDIAN = true定义常量的操作配合使用,从而让代码逻辑更为清晰
1和2本质上是一回事,本质都是为了当某个旧结构的含义比较宽泛,如bool,则可以用这种方式在某些细分的功能环境内,明确这个旧结构体的含义(bool->是否需要CRC校验)

硬性规范了这一唯一的别名的使用场景

总结,其实至此能够总结出,type的规范问题是一个目的导向问题,虽然书写方式灵活,但是你以后会遇到各种各样的场景,有时会需要做类型定义,有时会需要起类型别名,在操作过程中代码自身虽然怎么写都能编译通过,但是书写的时候需要遵顼思路清晰的原则即可,或者说,这是必需的!