Go 代码格式化该用 gofmt 还是 goimports?一文讲清选择与集成方式

江岸
江岸
发布时间:2026-08-25 | 87人浏览过
原创

前言

写 Go 时,最常见却也最打断节奏的,往往不是业务逻辑本身,而是没删干净的 import、漏写的依赖和反复手动整理的格式问题。相比只负责排版的 gofmt,goimports 把 import 的增删、合并和分组也一起接管。本文就从两者差异、命令行用法到编辑器、Git 钩子和 CI 集成,梳理一套更适合日常开发和团队协作的落地方案。

写 Go 时,很多人最容易被两类小问题打断:一类是忘了删掉没用的包,结果编译直接失败;另一类是代码里已经用了某个包,却还得回头手动补 import。表面上只是几秒钟的整理动作,但在日常开发、多人协作和提交检查里,这些重复劳动会不断累积。与其在 gofmt 之后继续手修 import,不如直接把 goimports 放进日常流程里。下面就按“它和 gofmt 到底差在哪、怎么在本地用起来、怎么接进提交和 CI”这几个问题,梳理一套更适合项目发布和团队协作的用法。

gofmt 和 goimports 到底差在哪

很多 Go 开发者都熟悉 gofmt。它是 Go 官方提供的格式化工具,主要负责代码排版,比如缩进、括号位置,以及同一个 import 块内部的字母排序。

对比 gofmt 与 goimports 在 import 管理能力上的差异,用卡片和箭头展示格式化范围变化。
gofmt 与 goimports 能力对比把“只做排版”和“连 import 一起管理”的区别直接视觉化,适合读者快速建立判断。

goimports 可以理解为 gofmt 的增强版,或者更直接一点说,是一个覆盖面更完整的日常格式化工具。它不仅会做常规格式化,还会自动管理 import 语句的增删、合并和分组。

功能特性 gofmt goimports
代码格式化 ✓ ✓
import块内按字母排序 ✓ ✓
按标准库/第三方库分组 ✗ ✓
合并多个import声明 ✗ ✓
自动添加缺失的import ✗ ✓
自动删除未使用的import ✗ ✓

先看一个最常见的例子。假设代码如下:

package main

import "os"
import "fmt"

func main() {
    fmt.Println("hello")
}

这段代码里,os 没有被使用,而且 import 还是拆开的两个声明。

运行 gofmt 后,结果保持不变,因为它不会主动合并独立的 import 声明,也不会删除未使用的包:

package main

import "os"
import "fmt"

func main() {
    fmt.Println("hello")
}

如果运行的是 goimports,它会做三件事:合并 import、按字母排序、删除未使用的 os:

package main

import "fmt"

func main() {
    fmt.Println("hello")
}

再看分组的区别。下面这段代码同时包含标准库和第三方包:

package main

import (
    "github.com/sirupsen/logrus"
    "fmt"
    "os"
)

func main() {
    fmt.Println("hello")
    logrus.Info("test")
    os.Exit(0)
}

gofmt 只会按字母顺序整理,不会区分包来源:

import (
    "fmt"
    "github.com/sirupsen/logrus"
    "os"
)

goimports 则会把标准库和第三方库分开,中间保留空行,阅读起来更清楚:

import (
    "fmt"
    "os"

    "github.com/sirupsen/logrus"
)

更关键的一点是,如果你已经在代码里使用了某个包,但漏写了对应的 import,goimports 还能自动补上。这也是很多团队最终把它作为默认格式化方案的原因:它保留了 gofmt 的排版能力,同时把 import 管理也一起接管了,而且几乎不增加额外使用成本。

命令行怎么用,适合什么场景

如果你还没有安装 goimports,可以先执行:

go install golang.org/x/tools/cmd/goimports@latest

针对单个文件,最直接的用法是:

goimports -w yourfile.go

这里的 -w 表示直接写回文件。如果不带这个参数,通常更适合做查看或比对;带上 -w 则更符合日常开发习惯,执行后文件会被就地修正。

如果想一次处理项目里的多个 Go 文件,可以批量运行:

find . -name "*.go" -exec goimports -w {} ;

这种方式适合几个典型场景:

  • 第一次把旧项目切换到 goimports;
  • 从别的编辑器迁移过来,想先统一一遍仓库;
  • CI 检查失败后,需要本地快速修复整批文件。

不过,命令行手动执行更像是补救手段,而不是最佳实践。因为只要依赖“记得运行一次”,就还是会有人遗漏。更稳妥的做法,是把它接入编辑器、提交钩子和 CI,让格式化变成自动动作。

编辑器里如何自动执行 goimports

对大多数开发者来说,最省事的接入方式就是“保存时自动运行”。这样写完代码按一次保存,格式和 import 都会同时整理。

VS Code

VS Code 用户需要先安装 Go 扩展,然后在 settings.json 中配置:

展示 goimports 接入开发流程的分层结构,从保存自动整理到提交钩子再到 CI 检查。
goimports 的三层接入方式突出本地、提交前、流水线三层兜底关系,帮助读者判断该把自动化放到哪一层。
{
    "[go]": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "golang.org/x/tools/gopls"
    },
    "gopls": {
        "formatting.gofumpt": false
    }
}

这里有一个容易混淆的点:gopls 默认使用的是 gofmt。如果你希望获得 goimports 那套 import 管理能力,还需要在 gopls 相关设置里启用对应选项,或者直接在 Go 扩展设置中选择格式化工具。也就是说,只打开 formatOnSave 还不够,关键是确认保存动作最后调用的是哪套格式化能力。

GoLand

GoLand 可以通过 Tools -> File Watchers 添加 watcher,把文件保存动作和 goimports 关联起来:

Program: goimports
Arguments: -w $FilePath$

这种方式的好处是改动范围明确,保存当前文件时就会触发处理,不需要每次手动开终端执行命令。

Vim

如果你使用 Vim,可以在 .vimrc 中加入:

let g:go_fmt_command = "goimports"
let g:go_fmt_autosave = 1

这样保存文件时就会自动调用 goimports。对于键盘流工作方式很重的用户来说,这通常是最低干扰、也最容易长期坚持的方案。

无论你使用哪种编辑器,核心原则都一样:尽量把格式化前置到“写代码的当下”,而不是留到提交前再集中补救。越早自动处理,越不容易把 import 问题带进后续环节。

提交前和 CI 里怎么兜底

编辑器自动格式化解决的是个人开发效率问题,但团队协作还需要兜底机制。最常见的两层保障分别是 Git 钩子和 CI/CD 检查。

pre-commit 钩子

通过 pre-commit,可以在提交前自动格式化暂存区里的 Go 文件:

#!/bin/sh
gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$')
[ -z "$gofiles" ] && exit 0

goimports -w $gofiles
git add $gofiles

这段脚本的逻辑很直接:

  • 先找出当前已暂存、并且后缀为 .go 的文件;
  • 如果没有 Go 文件,就直接退出;
  • 如果有,就执行 goimports -w;
  • 最后重新 git add,把格式化后的结果放回暂存区。

它的价值在于,即便有人本地编辑器没有正确配置,也能在真正提交前把明显的格式问题拦下来。

GitHub Actions 检查

在持续集成里加入检查,是面向仓库质量的最后一道防线。以 GitHub Actions 为例,可以直接使用下面的步骤:

- name: Check formatting
  run: |
    go install golang.org/x/tools/cmd/goimports@latest
    if [ -n "$(goimports -l .)" ]; then
      echo "Code is not formatted"
      exit 1
    fi

这里的思路不是自动改文件,而是用 goimports -l . 列出所有未按规范整理的内容。只要输出不为空,CI 就报错并终止流程。这样做能保证进入主分支的代码至少在格式和 import 管理上保持一致。

Makefile 集成

如果项目本身通过 Makefile 管理常用命令,也可以把格式化和检查单独封装出来:

.PHONY: fmt check-fmt

fmt:
    goimports -w .

check-fmt:
    @if [ -n "$(shell goimports -l .)" ]; then 
        echo "Code is not formatted. Run 'make fmt'"; 
        exit 1; 
    fi

这样团队里就会形成统一入口:本地修复执行 make fmt,流水线检查执行 make check-fmt。命令统一以后,文档、脚本和 CI 配置也更容易同步。

实际项目里该怎么选

如果只回答“gofmt 还是 goimports”,结论其实很明确:在绝大多数项目里,优先用 goimports 更合适。

原因并不复杂。gofmt 解决的是排版一致性,而 Go 开发里的高频摩擦点,很多都集中在 import 管理上。未使用的包、漏导入的依赖、多个 import 声明未合并、标准库和第三方库混排,这些问题单看都不大,但它们恰好是最容易重复出现、也最适合交给工具处理的一类问题。

goimports 的价值就在这里:它不是替代 Go 代码风格,而是在 gofmt 的基础上,把开发者最常手动收尾的那部分一起自动化。对于个人项目,它能减少保存后的修修补补;对于团队项目,它能降低代码风格争议,并减少因 import 问题导致的编译失败或无意义提交。

更实际的落地顺序可以是这样:

  1. 先安装 goimports 并在编辑器里启用保存自动整理;
  2. 再根据团队习惯增加 pre-commit 钩子,降低漏网概率;
  3. 最后在 CI/CD 中加入检查,把规范真正变成门槛。

做到这三步之后,goimports 基本就不再是一个“偶尔手动执行的工具”,而会变成 Go 项目默认的一部分。对大多数团队来说,这比单独使用 gofmt 更完整,也更接近日常开发的真实需求。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表