将 for-select 语法结构封装成函数

如果需要中断 for-select 语法结构,通常是需要使用标签来实现的,示例如下:

func main() {

L:
    for {
        select {
        case <-time.After(time.Second):
            fmt.Println("hello")
        default:
            break L
        }
    }

    fmt.Println("ending")
}

如你所见,在 for-select 语法结构中,需要结合标签的使用来实现中断。这是一种常见的用法,但是我并不喜欢。因为示例中的循环结构看起来很简短明了,这样使用并不显得有太大的问题,但在实际中通常要比这个复杂得多,并且追踪中断情形会显得十分冗长繁琐。

因此如果我需要在 for-select 语法结构中实现中断,我通常会将其封装成一个函数来实现:

func main() {
    foo()
    fmt.Println("ending")
}

func foo() {
    for {
        select {
        case <-time.After(time.Second):
            fmt.Println("hello")
        default:
            return
        }
    }
}

这就简洁优雅多了,当然你也可以在这个函数中返回错误(或任意其它值)来帮助函数调用者完善业务逻辑的处理,例如这样:

// blocking
if err := foo(); err != nil {
    // 处理错误
}
将 slice 或 map 定义成自定义类型可以让你的代码更容易维护

假设有一个 Server 类型和一个返回服务器列表的函数:

type Server struct {
    Name string
}

func ListServers() []Server {
    return []Server{
        {Name: "Server1"},
        {Name: "Server2"},
        {Name: "Foo1"},
        {Name: "Foo2"},
    }
}

现在假设需要获取某些特定名字的服务器。需要对 ListServers () 做一些改动,增加筛选条件:

// ListServers 返回服务器列表。只会返回包含 name 的服务器。空的 name 将会返回所有服务器。
func ListServers(name string) []Server {
    servers := []Server{
        {Name: "Server1"},
        {Name: "Server2"},
        {Name: "Foo1"},
        {Name: "Foo2"},
    }

    // 返回所有服务器
    if name == "" {
        return servers
    }

    // 返回过滤后的结果
    filtered := make([]Server, 0)

    for _, server := range servers {
        if strings.Contains(server.Name, name) {
            filtered = append(filtered, server)
        }
    }

    return filtered
}

现在可以用这个来筛选有字符串 Foo 的服务器:

func main() {
    servers := ListServers("Foo")

    // 输出: "servers [{Name:Foo1} {Name:Foo2}]"
    fmt.Printf("servers %+vn", servers)
}

显然这个函数能够正常工作。不过它的弹性并不好。如果你想对服务器集合引入其他逻辑的话会如何呢?例如检查所有服务器的状态,为每个服务器创建一个数据库记录,用其他字段进行筛选等等……

现在引入一个叫做 Servers 的新类型,并且修改原始版本的 ListServers () 返回这个新类型:

type Servers []Server

// ListServers 返回服务器列表
func ListServers() Servers {
    return []Server{
        {Name: "Server1"},
        {Name: "Server2"},
        {Name: "Foo1"},
        {Name: "Foo2"},
    }
}

现在需要做的是只要为 Servers 类型添加一个新的 Filter () 方法:

// Filter 返回包含 name 的服务器。空的 name 将会返回所有服务器。
func (s Servers) Filter(name string) Servers {
    filtered := make(Servers, 0)

    for _, server := range s {
        if strings.Contains(server.Name, name) {
            filtered = append(filtered, server)
        }

    }

    return filtered
}

现在可以针对字符串 Foo 筛选服务器:

func main() {
    servers := ListServers()
    servers = servers.Filter("Foo")
    fmt.Printf("servers %+vn", servers)
}

哈!看到你的代码是多么的简单了吗?还想对服务器的状态进行检查?或者为每个服务器添加一条数据库记录?没问题,添加以下新方法即可:

func (s Servers) Check() 
func (s Servers) AddRecord() 
func (s Servers) Len()
不要在String()方法里面调用涉及String()方法的方法

它会导致意料之外的错误,比如下面的例子,它导致了一个无限迭代(递归)调用(TT.String()调用fmt.Sprintf,而fmt.Sprintf又会反过来调用TT.String()...),很快就会导致内存溢出:

type TT float64

func (t TT) String() string {
    return fmt.Sprintf("%v", t)
}
t. String()