温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Go命令行工具项目结构最佳实践方法教程

发布时间:2021-10-19 16:52:13 来源:亿速云 阅读:126 作者:iii 栏目:web开发

这篇文章主要介绍“Go命令行工具项目结构最佳实践方法教程”,在日常操作中,相信很多人在Go命令行工具项目结构最佳实践方法教程问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go命令行工具项目结构最佳实践方法教程”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

用良好的package设计构建项目

第一个最佳实践是,项目中任何可重用的代码都要做成一个package。如何设计package结构和有关package的最佳时间就需要单独写篇文章,我做过一次关于这个内容的分享,ppt连接在下面:

https://go-pkg-structure.dev/

把代码放进一个个package要比仅仅重用代码的好处大得多。从项目结构的角度来说,把代码放进独立package有助于把一个个拥有独立功能的代码进行分组,这样更方便其他参与开发的开发者维护代码,这对开源项目来说意义重大。

独立成一个个package的做法能让项目测试起来更容易。把功能独立成一个个package,就能用更少的依赖对一个个功能进行测试。

在创建和重构项目时,我第一件事就是写好项目需要的package,甚至会在写代码之前创建好基本项目结构。

把应用程序逻辑和接入层逻辑分开

另一个我看到用的比较多的最佳实践是把应用代码和接入层代码分离开,这里接入代码指的是main包和main()方法。

Go和其他语言一样,应用的接入层代码是main方法,当应用开始运行时就是最先执行的一部分逻辑,很可能就把所有初始化逻辑都只写在main方法里了。把各自初始化逻辑放在app包内实现是比全写在main方法里更好的做法。

把初始化逻辑放到各自package下是更好的做法,这样也更方便做测试。比如把Start() Stop() Shutdown()方法都放到app包内,写测试代码时就可以在当前包中调用启动停止这些功能了。

下面是一个app包内的实现例子:

package app  import (  "fmt"  )  var ErrShutdown = fmt.Errorf("application was shutdown gracefully")  func Start(...) error {  // Application runtime code goes here  }  func Shutdown() {  // Shutdown contexts, listeners, and such  }

如果你的命令行工具项目里,既有服务端代码也有客户端代码,在一个app文件夹内实现的逻辑就能被服务端和客户端共享。

然而这个做法对简单的命令行应用不友好,这些应用可能是启动-执行-停止的模式。但我依然选择使用把逻辑放到app目录下的做法,这样可以把运行时逻辑都放在一起,降低了其他开发者对这个项目的理解难度。

main package里该放些什么?

把我们所有应用都放到app包里之后,也要考虑main包里有什么。很简单,main包里只有很少内容。

总的来说,我会把main包限制为“只放与用户交互的代码”。例如,如果我的项目里既有cli又有服务端逻辑, 我通常会将命令行参数解析的逻辑放入main包中。服务端和客户端cli编译的二进制文件会包含不同包,通过解析主程序包中的参数,就可以为不同cli创建独立的选项。

其他需要和用户交互的命令行应用,我也倾向于放进main包,例如:

  • 命令行参数的解析

  • 用户输入(很简单的输入、不参与核心逻辑)

  • 解析配置文件

  • 退出逻辑

  • 处理信号

下面的代码是一个main方法例子:

// main runs the command-line parsing and validations. This function will also start the application logic execution.  func main() {  // Parse command-line arguments  var opts options  args, err := flags.ParseArgs(&opts, os.Args[1:])  if err != nil {  os.Exit(1)  }  // Convert to internal config  cfg := config.New()  cfg.Verbose = opts.Verbose  // more taking command line options and putting them into a config struct.  if opts.Pass {  // ask the user for a password  }  // Run the App  err = app.Run(cfg)  if err != nil {  // do stuff  os.Exit(1)  }  }

一种推荐的项目结构

有种被推荐了很多的一种项目结构如下:

  • internal/app - 仅在内部使用的核心应用功能

  • internal/pkg/ - 仅在内部使用的package

  • pkg/ - 需要和外部代码进行分享的package

  • cmd/<app_name> - 把main package放在带有app名称的这个目录下

这个推荐结构的一个重点在于把核心代码放在internal/app、入口代码放进cmd/<app_name>。这种结构对于一次编译出好几个二进制文件的项目来说非常友好,比如一次编译出server和cli的项目。cmd/<app_name>应当包含cli的main方法,cmd/<app_name>-server目录下放服务端的main方法。这两者可以在internal/app目录下共享其他代码。

总的来说这也是个不错的目录结构,但是这个目录结构对我不适用,看看我是怎么改的吧。

我把package放到了其他路径下

我做的与上一段的推荐结构不同的是package的路径。应用程序项目结构的子目录太多,这与独立项目结构不同,我也不喜欢用应用程序项目结构组织代码。我认为,太多子目录阻碍开发者找到功能实现的代码。

子目录多,对代码量很大的重量级项目来可能比较有必要,但最好不要对小型中型项目使用这种项目结构。

我选择把所有package都放在代码根目录这一层,例如我有个Parser包,它的路径就是parser/,ssh包的路径就是ssh/,app包路径是app/。

这个做法使找包和功能都很容易,因为包和代码都在项目第一层。再次强调下,把所有包都放在目录第一层的做法适用于包数量不大的项目,如果项目包数量变多,那还是把包放到pkg/路径下靠谱。

我没有采用internal和pkg模式

我并不觉得把代码放进internal/或者pkg/这种实践好,主要原因在于这种实践是针对app内部包。但是关于app内部包并没有明确的“内”“外”划分。对于仅在内部使用的包,很多开发者就会因为"没有其他人使用这些包"所以根本也没有用最佳实践。

我也不希望开发者在pkg路径下像维护一个个独立项目一样维护代码。实际开发中,这些包内的接口可能和一个个独立项目一样做变动,那么如果这些逻辑真的是一个个分离开的,还不如放到独立的项目里实现。

对我来说把我所有项目内部代码都放到同一个文件夹下更合理。要么是放在顶层目录下要么是放在pkg/目录下。

我没有把所有文件都放进cmd/目录

cmd/目录不适用于我的项目。我这个项目里有一个简单的CLI应用,这个应用要方便使用者下载安装。最快最方便的安装办法是使用go get命令安装:

$ go get -u github.com/madflojo/efs2

我想要用户只需要用go get加项目url就能安装,但是如果用了cmd目录就需要让用户在url基础上增加/cmd/<app_name>才能安装:

$ go get -u github.com/madflojo/efs2/cmd/efs2

这个url格式比较乱,用户还需要知道我项目结构是怎么样的才能安装。我希望项目结构能让别人更方便而不是更麻烦。所以我就把这个小应用的main.go文件放到了项目的顶层文件夹下,这样用户就可以直接通过go get命令安装应用了,另把应用的功能实现都放在app包内。

到此,关于“Go命令行工具项目结构最佳实践方法教程”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

go
AI