最新公告
  • 欢迎您光临欧资源网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入我们
  • 现代模板引擎Go的标准库有两个模板包和html/template

    作者 | 塞思·瓦戈翻译 | 弯月面

    出品 | CSDN(ID:CSDNnews)

    虽然 Go 是我最喜欢的编程语言之一,但它远非完美。在过去的 10 年里,我使用 Go 构建了很多小型个人项目和大型应用程序。自从 2009 年第一个版本发布以来,Go 已经发生了很大的变化,但是我想通过这篇文章表达一些我认为 Go 仍然需要改进的地方。

    在此之前,让我声明一下:我并不是在批评 Go 开发团队或个人的贡献。我的目标是让 Go 成为最好的编程语言。

    现代模板引擎

    Go 的标准库有两个模板包:text/template 和 html/template。两者都使用大致相同的语法,但 html/template 处理实体转义和一些其他特定于 Web 的构造。不幸的是,对于一些高级用例,这两个库都不够强大,仍然需要大量开发。

    {{ range $a, $b := .Items }} // [$a = 0,$b = “foo”]

    for a, b := range items { // [a = 0, b = “foo”]

    但是,当只有一个参数时,模板引擎返回值,Go 返回索引:

    {{ range $a := .Items }} // [$a = “foo”]

    对于 a := range items { // [a = 0]

    Go 的模板包应该符合标准库。

    在编写完 Consul Template() 之后,我很清楚标准的 Go 模板功能不足以满足用户的需求。超过一半的问题与使用 Go 的模板语言有关。如今,Consul Template 拥有超过 50 个“助手”功能,其中绝大多数应该由标准模板语言提供。

    不只是我有这个问题,Hugo 有一个广泛的帮助函数列表(),其中绝大多数应该由标准模板语言提供。即使在我最近的一个项目中,也无法避免使用反射。

    Go 的模板语言确实需要更广泛的函数集。

    {{ if (and $foo $foo.Bar) }}

    虽然代码看起来不错,但 和 条件都需要计算,这意味着表达式中没有短路逻辑。如果 $foo 为 nil,则抛出运行时异常。

    要解决此问题,您必须拆分条件子句:

    {{ if $foo }} {{ if $foo.Bar }}{{ end }}

    Go 的模板语言应该表现得像标准库,在遇到第一个真值条件后停止。

    范围的改进:不要复制值

    虽然文档很好,但是 range 子句中的值被意外复制了。例如,考虑以下代码:

    类型 Foo 结构 {bar 字符串}

    func main() {list :=[]Foo{{“A”}, {“B”}, {“C”}}

    cp := make([]*Foo,len(list))for i, value := rangelist {cp[i] = &value}

    fmt.Printf(“list:%qn”, list)fmt.Printf(“cp:%qn”, cp)}

    cp的价值是多少?[ABC]?对不起,你错了。其实cp的值是:

    [CCC]

    这是因为在 Go 的 range 子句中使用了值的副本,而不是值本身。在 Go 2.0 中,range 子句应该通过引用传递。另外,我对 Go 2.0 有一些建议,包括改进 for 循环,在每次迭代时重新定义作用域循环变量。

    确定的选择

    在 select 语句中,如果多个条件为真,则不确定将执行哪个语句。这种微小的差异可能会导致错误,并且问题比类似使用的 switch 语句更明显,因为 switch 语句是按照它们编写的顺序一个一个地计算的。

    考虑下面的代码,我们想要的行为是:如果系统停止,什么也不做。否则等待 5 秒,然后超时。

    for {select {case returncase thing :=// … long-runningoperationcasereturnfmt.Errorf(“timeout”)}}

    对于 select 语句,如果多个条件为真(例如 doneCh 关闭超过 5 秒),则最后执行的语句是未定义的行为。所以我们不得不添加冗长的取消代码:

    for {// 在此处检查以防我们长时间受到 CPU 限制,我们需要// 检查优雅停止器风险返回超时错误。select {case returndefault:}

    select {case returncase thing :=// 即使 thiscase 赢了,我们仍然可能会被停止。select {case returndefault:}// …default// 即使 thiscase 赢了,我们仍然可能会被停止。select {case returndefault:}return fmt.Errorf(“timeout”)}}

    如果您可以将 select 语句更改为确定性,则原始代码(更简单且更易于编写)按预期工作。但是,由于 select 的非确定性,我们必须不断检查占主导地位的条件。

    另外,我想看看“如果分支通过条件win7 试图共享时出现错误 函数不正确,执行下面的代码,否则继续下一个分支”的简写语法。当前的语法很冗长:

    选择 {case returndefault:}

    我希望看到更简洁的检查,如下所示:

    选择

    结构化日志接口

    Go 的标准库包含 log 包,可以用来处理基本操作。但是,大多数生产系统都需要结构化日志记录,而 Go 中也不乏结构化日志记录库:

    ● 顶点/原木

    ● go-kit/日志

    ● golang/glog

    ● hashcorp/go-hclog

    ● 不可切碎/log15

    ● rs/zerolog

    ● sirupus/logrus

    ● 优步/zap

    由于 Go 在这方面没有给出明确的意见,因此导致了这些包的泛滥,其中大多数具有不兼容的功能和签名。因此,库作者不可能发出结构化日志。例如,我希望能够在 go-retry、go-envconfig 或 go-githubactions 中发出结构化日志,但这样做会与其中一个库紧密耦合。理想情况下,我希望图书馆的用户自己选择结构化的日志记录解决方案,但缺乏通用界面使得选择非常困难。

    Go 标准库需要定义一个结构化的日志接口,现有的上游包可以选择实现。那么,作为库作者,我可以选择接受 log.StructuredLogger 接口,实现者可以选择:

    func WithLogger(l log.StructuredLogger) 选项 {return func(f *Foo) *Foo{f.logger = lreturn f}}

    我很快整理了一个潦草的界面:

    // StructuredLogger 是结构化 logging.type StructuredLogger interface 的接口{// Log 记录一个 message.Log(message string, fields…LogField)

    // LogAt 在提供的级别记录一条消息。也许我们也可以拥有// Debugf、Infof 等,但我认为这对于标准// library.LogAt(level LogLevel,message string, fields … LogField) 可能过于局限

    // LogEntry 记录一个完整的日志条目。默认值见LogEntry if//缺少任何字段。日志条目(条目*日志条目)}

    // LogLevel 是底层的日志级别。type LogLevel uint8

    // LogEntry 表示单个日志条目。type LogEntry struct {// Level 是日志级别。如果没有提供级别,则使用// LevelError的默认级别。Level LogLevel

    // Message 是实际的日志 message.Message 字符串

    // Fields 是结构化日志记录字段的列表。如果两个字段具有相同的// 名称,则后一个优先。字段 []*LogField}

    // LogField 是命名字段(字符串)及其底层值的元组。type LogField struct {Name stringValue interface{}}

    围绕具体的接口,如何最小化资源分配,最大化兼容性有很多讨论,但目标是定义一个其他日志库可以轻松实现的接口。

    回到我的 Ruby 开发时代,有一段时间 Ruby 版本管理器激增,每个版本管理器都有不同的配置文件名和语法。Fletcher Nichol 写了一个要点,成功说服所有 Ruby 版本管理器维护者对 .ruby-version 进行标准化。我希望 Go 社区以类似的方式处理结构化日志记录。

    多重错误处理

    在许多情况下,尤其是后台作业或周期性任务,系统可能会并行处理多个任务或采用错误继续策略。在这些情况下,返回多个错误可能会有所帮助。标准库中没有对处理错误集合的内置支持。

    Go 社区可以围绕多个错误处理构建一个清晰简洁的标准库,这不仅可以统一社区,还可以降低错误处理错误的风险,就像它们被打包和解包一样。

    错误的 JSON 序列化

    说到错误,你知道如果你将错误类型嵌入到一个结构体字段中,然后对该结构体进行 JSON 序列化,“错误”将被序列化为 {}?

    // 主包

    导入(“编码/json”“fmt”)

    类型 Response1 结构 {Err 错误`json:”error”`}

    func main() {v1 :=&Response1{Err: fmt.Errorf(“oops”)}b1, err :=json.Marshal(v1)if err != nil {panic(err)}

    // 得到:{“error”:{}}// 想要:{“error”: “oops”}fmt.Println(string(b1))}

    至少对于内置的 errorString 类型,Go 应该序列化 .Error() 的结果。或者在 Go 2.0 中,尝试序列化错误类型时,如果没有定义序列化逻辑,则返回错误。

    标准库中不再有公共变量

    仅举一个例子,http.DefaultClient 和 http.DefaultTransport 都是具有共享状态的全局变量。http.DefaultClient 没有超时,所以很容易引起 DOS 攻击win7 试图共享时出现错误 函数不正确,造成瓶颈。许多软件包修改了 http.DefaultClient 和 http.DefaultTransport,导致开发人员浪费数天时间来追踪错误。

    Go2.0 应该将这些全局变量设为私有,并通过返回唯一分配变量的函数调用公开它们。或者,Go 2.0 也可以实现一种不能被其他包修改的“冻结”全局变量。

    从软件供应链的角度来看,这些问题也让我很担心。如果我开发了一个秘密修改 http.DefaultTransport 的包,然后使用自定义 RoundTripper 将所有流量转发到我的服务器,那就麻烦了。

    对缓冲渲染器的本机支持

    有些问题是因为它们是未知的或未记录的。大多数示例,包括 Go 文档中的示例,对于 JSON 序列化或通过 Web 请求呈现 HTML 的行为应如下所示:

    func toJSON(w http.ResponseWriter, i interface{}) {if err :=json.NewEncoder(w).Encode(i); 错误!= nil {http.Error(w,”oops”, http.StatusInternalServerError)} }

    func toHTML(w http.ResponseWriter, tmpl string, i interface{}) {if err :=templates.ExecuteTemplate(w, tmpl, i); 错误!= nil {http.Error(w,”oops”, http.StatusInternalServerError )}}

    但是,对于上面的两段代码,如果 i 足够大,在发送第一个字节(和 200 状态码)后,编码/执行可能会失败。此时,请求不可恢复,因为无法更改响应代码。

    为了解决这个问题,广泛接受的解决方案是先渲染,然后复制到 w。此解决方案仍有可能引发错误(由于连接问题而无法写入 w),但可确保在发送第一个字节之前编码/执行成功。但是,为每个请求分配一个字节切片可能会很昂贵,因此通常使用缓冲池。

    这种方法非常冗长,并且将许多不必要的复杂性推给了实施者。相反,如果 Go 可以使用像 EncodePooled 这样的函数来自动处理这个缓冲池管理,那就太好了。

    总结

    Go 是我最喜欢的编程语言之一,这就是为什么我愿意说一些我的批评。与其他编程语言一样,Go 也在不断发展。您同意本文​​提出的问题吗?请在下方留言。

    《新程序员003》正式上线,50多位技术专家合着,云原生和数字开发者的技术选书。内容既有发展趋势,又有方法论结构,有华为、阿里巴巴、字节跳动、网易、快手、微软、亚马逊、英特尔、西门子、施耐德等30多家知名企业的云原生和数字化第一手经验!

    站内大部分资源收集于网络,若侵犯了您的合法权益,请联系我们删除!
    欧资源网 » 现代模板引擎Go的标准库有两个模板包和html/template

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    欧资源网
    一个高级程序员模板开发平台

    发表评论