Golang简历代码审查与优化建议

Golang简历代码审查与优化建议 大家好, 我正在寻求代码审查,以帮助找出我目前知识上的不足之处。

我希望(有朝一日)能找到一份使用Go语言进行开发的工作,并确保我在GitHub上托管的代码不会让人看着不舒服。

任何反馈都将不胜感激,因为我希望不断进步,并能够展示我在Go编程语言方面的知识。

我的代码库可以在以下位置找到:

GitHub

jrswab/Go-Status

用Go语言编写的状态栏。通过创建GitHub账户为jrswab/Go-Status的开发做出贡献。

感谢!


更多关于Golang简历代码审查与优化建议的实战教程也可以访问 https://www.itying.com/category-94-b0.html

14 回复

谢谢,我下班后会研究这些内容,看看能否整理得更清晰。

更多关于Golang简历代码审查与优化建议的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


啊,是的,我现在记起来了。谢谢 @geosoft1

catacombs:

在两个不同地方维护代码有点麻烦,不是吗?

我只是提交到Github的主分支,并且只在那里存放内容,因为很多人都在使用它。我所有进行中的工作和分支都只推送到Gitlab。

我改用了一个定时器。

我还移除了 defer 语句,直接关闭了 HTTP 请求。这样做的话,下次调用该库的 goroutine 时它会重新打开吗?还是说我实际上阻止了任何更新?(反正温度变化也不频繁,哈哈)

谢谢!我接下来会实现这些功能。

这里的目的是向未来的面试官证明我能够用Go语言编程。而且练习Go语言的这些部分从长远来看只会对我有所帮助。

一个小观察:在 Go 语言中,不需要显式地将变量初始化为它们的零值。

var i int = 0

可以简化为:

var i int

参考链接:https://tour.golang.org/basics/12

更新:
我选择在作为 Go 协程运行的库中实现定时器,效果非常好。

再次感谢所有为我改进 Go 示例提出建议的人。迫不及待想开始下一个项目了 🙂

附注:我仍然欢迎对这个仓库提出建议,欢迎大家分享想法。该仓库同时托管在 GitLab 和 GitHub 上。

🙏💙

@catacombs@heatbr 我为自定义包添加了简单测试,并设置了两个新的错误消息通道(考虑后续将错误输出到日志文件)。新代码已上传至主分支。

@imatmati 这个思路很合理,如果其他部分出现问题,确实没有必要保持某些资源开启。

再次感谢大家的帮助! 本周末我将继续实施 heatbr 提出的更多建议。

你好 jsrwab,

我没有时间深入了解你的实现细节,所以我的反馈更多是基于感觉而非具体分析。我认为你应该以不同的方式使用通道。在 main.go 中,你应该使用 for select 代码块。这样会更优雅且更符合标准做法。wttr.go 中的循环让我感到困惑,为什么不使用 Ticker 来替代这种复杂的 i 值管理呢?

请记住,你的 defer 语句会在函数返回时执行!在循环的情况下,它永远不会被执行。你这里存在潜在的内存泄漏风险:https://golang.org/ref/spec#Defer_statements

祝你今天愉快。

关于项目结构的一些建议:

1 - 使用依赖项创建项目。有时你会需要依赖项。 2 - 添加测试,如果你熟悉Golang测试,你就领先一步了。 3 - 可以使用自定义类型代替普通的string通道返回。type weather string。 5 - 使用错误通道,不要只用一个通道来处理数据/错误。 4 - 添加接口(你必须熟悉)并使用它们。

5 - 大多数库使用cmd/binname/main.go作为编译和生成可执行文件的入口点。

type weather string

你的 goroutine 每五秒启动一次,每小时执行的任务也可以受益于使用定时器。 实际上,这些定时器似乎可以移到 goroutine 内部。在你编写的没有循环的新版本中,现在使用 defer 会很完美,因为可能会出现某些错误或提前返回……你有两个选择:

  • 使用 for 循环 -> 内部不使用 defer
  • 不使用 for 循环 -> 使用 defer 很完美 这两者是互斥的。

resp.Body.Close 实际上是关闭响应而非请求。确实,不关闭必然会在某些地方产生不良影响,因为 Body 字段的文档要求:调用者有责任关闭 Body。如果你不关闭响应流,可能意味着连接保持打开状态。那么在某个时刻,由于资源耗尽,你将耗尽可用连接。

感谢所有的建议!

catacombs: 我认为你可以将 var i uint16 = 0 写成 i := 0。这样更简短易读,不需要显式声明为无符号16位整数。

我之前以为使用 i := 0 会默认转为 int64,因为我的系统是64位的(不确定是从哪里听说的),所以想尽量保持整数类型的最小化。如果实际情况不是这样,那么使用 var 声明确实过于冗长了。

啊对了!Sprintf 我完全忽略了!哈哈哈

谢谢,我会研究一下 DWM 的自动启动补丁。我已经有一段时间没有使用补丁了,想看看从 i3wm 中我需要/怀念哪些功能。

很好知道!我会再多尝试一下,看看能有什么发现。

再次感谢所有的帮助!你们查看这段代码让我能更深入地学习Go语言的方方面面。

你是指应该像下面这样使用defer来关闭HTTP请求,而不是在通道传递数据后手动关闭吗?

func Local(cWttr chan string) {
        // get temp(%t) and wind direction/speed (%w)
        // for exact location add postal code - wttr.in/~15222?format...
        // for more wttr options see https://wttr.in/:help
        resp, err := http.Get("https://wttr.in/?format=%t+%w")
        if err != nil {
                errMessage := "wttr connection issue"
                cWttr <- errMessage
        }
        defer resp.Body.Close() // close http request

        // convert responce to string for return
        bodyData, _ := ioutil.ReadAll(resp.Body)
        weather := fmt.Sprintf("%s | ", strings.TrimSpace(string(bodyData)))
        cWttr <- weather
}

以下是对你的Go代码仓库(jrswab/go-status)的代码审查与优化建议。我会从代码结构、Go语言最佳实践、性能和可维护性等方面进行分析,并提供具体示例。

1. 代码结构改进

你的项目结构相对简单,但可以进一步优化以符合Go标准。当前缺少清晰的包组织和模块化设计。建议将功能拆分为独立的包,例如:

  • cmd/ 目录用于存放可执行文件入口(如 main.go)。
  • pkg/ 目录用于存放库代码(如状态栏逻辑)。
  • internal/ 目录用于内部代码(如果适用)。

示例结构:

go-status/
├── cmd/
│   └── main.go
├── pkg/
│   └── statusbar/
│       ├── statusbar.go
│       └── config.go
├── go.mod
└── README.md

pkg/statusbar/statusbar.go 中,你可以封装状态栏逻辑:

package statusbar

import (
    "fmt"
    "time"
)

type StatusBar struct {
    // 定义状态栏字段
}

func (s *StatusBar) Update() string {
    // 实现状态更新逻辑
    return fmt.Sprintf("Status at %s", time.Now().Format("15:04:05"))
}

cmd/main.go 中调用:

package main

import (
    "github.com/jrswab/go-status/pkg/statusbar"
    "log"
)

func main() {
    sb := &statusbar.StatusBar{}
    log.Println(sb.Update())
}

2. 错误处理改进

你的代码中错误处理可以更一致和详细。Go鼓励显式错误处理,避免忽略错误。例如,在文件操作或网络请求中,始终检查并处理错误。

当前代码中(如 main.go),你可能直接调用了函数而没有处理潜在错误。优化示例:

func readConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("failed to read config: %w", err)
    }
    var config Config
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("failed to parse config: %w", err)
    }
    return &config, nil
}

main 函数中:

func main() {
    config, err := readConfig("config.json")
    if err != nil {
        log.Fatalf("Error loading config: %v", err)
    }
    // 使用 config
}

3. 使用Go模块和依赖管理

确保你的项目使用Go模块进行依赖管理。检查 go.mod 文件是否最新,并定期更新依赖。运行 go mod tidy 来清理未使用的依赖。

如果尚未初始化,可以运行:

go mod init github.com/jrswab/go-status

4. 并发处理优化

如果状态栏涉及并发操作(如定期更新),使用Go的goroutine和channel来安全处理。避免数据竞争,使用 sync 包或channel进行同步。

示例使用goroutine定期更新状态:

func (s *StatusBar) StartUpdate(interval time.Duration, updates chan<- string) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()
    for {
        select {
        case <-ticker.C:
            update := s.Update()
            updates <- update
        }
    }
}

func main() {
    updates := make(chan string)
    sb := &statusbar.StatusBar{}
    go sb.StartUpdate(5*time.Second, updates)
    
    for update := range updates {
        fmt.Println(update)
    }
}

5. 代码测试和文档

添加单元测试以覆盖关键功能。使用 testing 包编写测试,并运行 go test 验证。同时,为函数和类型添加Godoc注释以提高可读性。

示例测试文件 pkg/statusbar/statusbar_test.go

package statusbar

import (
    "testing"
    "time"
)

func TestStatusBar_Update(t *testing.T) {
    sb := &StatusBar{}
    result := sb.Update()
    expected := "Status at " + time.Now().Format("15:04:05")
    if result != expected {
        t.Errorf("Expected %s, got %s", expected, result)
    }
}

为函数添加文档:

// Update generates a status string with the current time.
// It returns a formatted string representing the status.
func (s *StatusBar) Update() string {
    // 实现
}

6. 性能优化

避免在热路径中不必要的内存分配。使用基准测试(benchmark)来识别瓶颈。例如,如果状态栏涉及字符串操作,使用 strings.Builder 来高效构建字符串。

示例基准测试:

func BenchmarkStatusBar_Update(b *testing.B) {
    sb := &StatusBar{}
    for i := 0; i < b.N; i++ {
        _ = sb.Update()
    }
}

运行基准测试:

go test -bench=.

7. 配置和可移植性

如果状态栏依赖外部配置(如文件路径或API密钥),使用环境变量或配置文件,并确保代码可跨平台运行。避免硬编码值。

示例使用环境变量:

func getAPIKey() string {
    key := os.Getenv("STATUS_API_KEY")
    if key == "" {
        log.Fatal("STATUS_API_KEY environment variable not set")
    }
    return key
}

总结

通过以上优化,你的代码将更符合Go语言习惯,提高可读性、可维护性和性能。继续练习并参与开源项目,以加深对Go的理解。如果你有具体代码片段需要进一步审查,请分享细节。

回到顶部