learn.lianglianglee.com/专栏/22 讲通关 Go 语言-完/20 协作开发:模块化管理为什么能够提升研发效能?.md.html
2022-05-11 19:04:14 +08:00

493 lines
24 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>20 协作开发:模块化管理为什么能够提升研发效能?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/22 讲通关 Go 语言-完/00 开篇词 Go 为开发者的需求设计,带你实现高效工作.md.html">00 开篇词 Go 为开发者的需求设计,带你实现高效工作.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/01 基础入门:编写你的第一个 Go 语言程序.md.html">01 基础入门:编写你的第一个 Go 语言程序.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/02 数据类型:你必须掌握的数据类型有哪些?.md.html">02 数据类型:你必须掌握的数据类型有哪些?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/03 控制结构if、for、switch 逻辑语句的那些事儿.md.html">03 控制结构if、for、switch 逻辑语句的那些事儿.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/04 集合类型:如何正确使用 array、slice 和 map.md.html">04 集合类型:如何正确使用 array、slice 和 map.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/05 函数和方法Go 语言中的函数和方法到底有什么不同?.md.html">05 函数和方法Go 语言中的函数和方法到底有什么不同?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/06 struct 和 interface结构体与接口都实现了哪些功能.md.html">06 struct 和 interface结构体与接口都实现了哪些功能.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/07 错误处理:如何通过 error、deferred、panic 等处理错误?.md.html">07 错误处理:如何通过 error、deferred、panic 等处理错误?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/08 并发基础Goroutines 和 Channels 的声明与使用.md.html">08 并发基础Goroutines 和 Channels 的声明与使用.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/09 同步原语sync 包让你对并发控制得心应手.md.html">09 同步原语sync 包让你对并发控制得心应手.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/10 Context你必须掌握的多线程并发控制神器.md.html">10 Context你必须掌握的多线程并发控制神器.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/11 并发模式Go 语言中即学即用的高效并发模式.md.html">11 并发模式Go 语言中即学即用的高效并发模式.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/12 指针详解:在什么情况下应该使用指针?.md.html">12 指针详解:在什么情况下应该使用指针?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/13 参数传递:值、引用及指针之间的区别?.md.html">13 参数传递:值、引用及指针之间的区别?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/14 内存分配new 还是 make什么情况下该用谁.md.html">14 内存分配new 还是 make什么情况下该用谁.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/15 运行时反射:字符串和结构体之间如何转换?.md.html">15 运行时反射:字符串和结构体之间如何转换?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/16 非类型安全:让你既爱又恨的 unsafe.md.html">16 非类型安全:让你既爱又恨的 unsafe.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/17 SliceHeaderslice 如何高效处理数据?.md.html">17 SliceHeaderslice 如何高效处理数据?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/18 质量保证Go 语言如何通过测试保证质量?.md.html">18 质量保证Go 语言如何通过测试保证质量?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/19 性能优化Go 语言如何进行代码检查和优化?.md.html">19 性能优化Go 语言如何进行代码检查和优化?.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/22 讲通关 Go 语言-完/20 协作开发:模块化管理为什么能够提升研发效能?.md.html">20 协作开发:模块化管理为什么能够提升研发效能?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/21 网络编程Go 语言如何玩转 RESTful API 服务?.md.html">21 网络编程Go 语言如何玩转 RESTful API 服务?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/22 网络编程Go 语言如何通过 RPC 实现跨平台服务?.md.html">22 网络编程Go 语言如何通过 RPC 实现跨平台服务?.md.html</a>
</li>
<li>
<a href="/专栏/22 讲通关 Go 语言-完/23 结束语 你的 Go 语言成长之路.md.html">23 结束语 你的 Go 语言成长之路.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>20 协作开发:模块化管理为什么能够提升研发效能?</h1>
<p>任何业务,都是从简单向复杂演进的。而在业务演进的过程中,技术是从单体向多模块、多服务演进的。技术的这种演进方式的核心目的是<strong>复用代码、提高效率</strong>,这一讲,我会为你介绍 Go 语言是如何通过模块化的管理,提升开发效率的。</p>
<h3>Go 语言中的包</h3>
<h4>什么是包</h4>
<p>在业务非常简单的时候,你甚至可以把代码写到一个 Go 文件中。但随着业务逐渐复杂,你会发现,如果代码都放在一个 Go 文件中,会变得难以维护,这时候你就需要抽取代码,把相同业务的代码放在一个目录中。在 Go 语言中,这个目录叫作包。</p>
<p>在 Go 语言中,一个包是通过<strong>package 关键字定义</strong>的,最常见的就是<strong>main 包</strong>,它的定义如下所示:</p>
<pre><code>package main
</code></pre>
<p>此外,前面章节演示示例经常使用到的 fmt 包,也是通过 package 关键字声明的。</p>
<p>一个包就是一个<strong>独立的空间</strong>,你可以在这个包里<strong>定义函数</strong><strong>结构体</strong>等。这时,我们认为这些函数、结构体是属于这个包的。</p>
<h4>使用包</h4>
<p>如果你想使用一个包里的函数或者结构体,就需要先<strong>导入这个包</strong>,才能使用,比如常用的 fmt包代码示例如下所示。</p>
<pre><code>package main
import &quot;fmt&quot;
func main() {
fmt.Println(&quot;先导入fmt包才能使用&quot;)
}
</code></pre>
<p>要导入一个包,需要使用 import 关键字;如果需要同时导入多个包,则可以使用小括号,示例代码如下所示。</p>
<pre><code>import (
&quot;fmt&quot;
&quot;os&quot;
)
</code></pre>
<p>从以上示例可以看到,该示例导入了 fmt 和 os 这两个包,使用了小括号,每一行写了一个要导入的包。</p>
<h4>作用域</h4>
<p>讲到了包之间的导入和使用,就不得不提作用域这个概念,因为只有满足作用域的函数才可以被调用。</p>
<ul>
<li><strong>Java 语言</strong>中,通过 public、private 这些修饰符修饰一个类的作用域;</li>
<li>但是在<strong>Go 语言</strong>中,并没有这样的作用域修饰符,它是通过首字母是否大写来区分的,这同时也体现了 Go 语言的简洁。</li>
</ul>
<p>如上述示例中 fmt 包中的<strong>Println 函数</strong></p>
<ul>
<li>它的首字母就是大写的 P所以该函数才可以在 main 包中使用;</li>
<li>如果 Println 函数的首字母是小写的 p那么它只能在 fmt 包中被使用,不能跨包使用。</li>
</ul>
<p>这里我为你总结下 Go 语言的作用域:</p>
<ul>
<li>Go 语言中,所有的定义,比如函数、变量、结构体等,如果首字母是大写,那么就可以被其他包使用;</li>
<li>反之,如果首字母是小写的,就只能在同一个包内使用。</li>
</ul>
<h4>自定义包</h4>
<p>你也可以自定义自己的包,通过包的方式把相同业务、相同职责的代码放在一起。比如你有一个 util 包,用于存放一些常用的工具函数,项目结构如下所示:</p>
<pre><code>ch20
├── main.go
└── util
└── string.go
</code></pre>
<p>在 Go 语言中,一个包对应一个文件夹,上面的项目结构示例也验证了这一点。在这个示例中,有一个 util 文件夹,它里面有一个 string.go 文件,这个 Go 语言文件就属于 util 包,它的包定义如下所示:</p>
<p><em>ch20/util/string.go</em></p>
<pre><code>package util
</code></pre>
<p>可以看到Go 语言中的包是代码的一种<strong>组织形式</strong>,通过包把相同业务或者相同职责的代码放在一起。通过包对代码进行归类,便于代码维护以及被其他包调用,提高团队协作效率。</p>
<h4>init 函数</h4>
<p>除了 main 这个特殊的函数外Go 语言还有一个特殊的函数——init通过它可以<strong>实现包级别的一些初始化操作</strong></p>
<p>init 函数没有返回值,也没有参数,它<strong>先于 main 函数执行</strong>,代码如下所示:</p>
<pre><code>func init() {
fmt.Println(&quot;init in main.go &quot;)
}
</code></pre>
<p>一个包中可以有多个 init 函数,但是它们的执行顺序并不确定,所以如果你定义了多个 init 函数的话,要确保它们是<strong>相互独立</strong>的,一定<strong>不要有顺序上的依赖</strong></p>
<p>那么 init 函数作用是什么呢? 其实就是在导入一个包时,可以对这个包做一些必要的初始化操作,比如数据库连接和一些数据的检查,确保我们可以正确地使用这个包。</p>
<h3>Go 语言中的模块</h3>
<p>如果包是比较低级的代码组织形式的话,那么模块就是更高级别的,在 Go 语言中,一个模块可以包含很多个包,所以模块是相关的包的集合。</p>
<p>在 Go 语言中:</p>
<ul>
<li>一个模块通常是<strong>一个项目</strong>,比如这个专栏实例中使用的 gotour 项目;</li>
<li>也可以是<strong>一个框</strong>架,比如常用的 Web 框架 gin。</li>
</ul>
<h4>go mod</h4>
<p>Go 语言为我们提供了 go mod 命令来创建一个模块(项目),比如要创建一个 gotour 模块,你可以通过如下命令实现:</p>
<pre><code>➜ go mod init gotour
go: creating new go.mod: module gotour
</code></pre>
<p>运行这一命令后,你会看到已经创建好一个名字为 gotour 的文件夹,里面有一个 go.mod 文件,它里面的内容如下所示:</p>
<pre><code>module gotour
go 1.15
</code></pre>
<ul>
<li>第一句是该项目的<strong>模块名</strong>,也就是 gotour</li>
<li>第二句表示要编译该模块至少需要<strong>Go 1.15 版本的 SDK</strong></li>
</ul>
<blockquote>
<p>小提示:模块名最好是以自己的域名开头,比如 flysnow.org/gotour这样就可以很大程度上保证模块名的唯一不至于和其他模块重名。</p>
</blockquote>
<h4>使用第三方模块</h4>
<p>模块化为什么可以提高开发效率?最重要的原因就是<strong>复用了现有的模块</strong>Go 语言也不例外。比如你可以把项目中的公共代码抽取为一个模块,这样就可以供其他项目使用,不用再重复开发;同理,在 Github 上也有很多开源的 Go 语言项目,它们都是一个个独立的模块,也可以被我们直接使用,提高我们的开发效率,比如 Web 框架 <a href="https://github.com/gin-gonic/gin">gin-gonic/gin</a></p>
<p>众所周知,在使用第三方模块之前,需要先设置下 Go 代理,也就是 GOPROXY这样我们就可以获取到第三方模块了。</p>
<p>在这里我推荐 goproxy.io 这个代理,非常好用,速度也很快。要使用这个代理,需要进行如下代码设置:</p>
<pre><code>go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct
</code></pre>
<p>打开终端,输入这一命令回车即可设置成功。</p>
<p>在实际的项目开发中,除了第三方模块外,还有我们<strong>自己开发的模块</strong>,放在了公司的 GitLab上这时候就要把公司 Git 代码库的域名排除在 Go PROXY 之外,为此 Go 语言提供了GOPRIVATE 这个环境变量帮助我们达到目的。通过如下命令即可设置 GOPRIVATE</p>
<pre><code># 设置不走 proxy 的私有仓库,多个用逗号相隔(可选)
go env -w GOPRIVATE=*.corp.example.com
</code></pre>
<p>以上域名只是一个示例,实际使用时你要改成自己<strong>公司私有仓库的域名</strong></p>
<p>一切都准备好就可以使用第三方的模块了,假设我们要使用 Gin 这个 Web 框架,首先需要安装它,通过如下命令即可安装 Gin 这个 Web 框架:</p>
<pre><code>go get -u github.com/gin-gonic/gin
</code></pre>
<p>安装成功后,就可以像 Go 语言的标准包一样,通过 import 命令导入你的代码中使用它,代码如下所示:</p>
<pre><code>package main
import (
&quot;fmt&quot;
&quot;github.com/gin-gonic/gin&quot;
)
func main() {
fmt.Println(&quot;先导入fmt包才能使用&quot;)
r := gin.Default()
r.Run()
}
</code></pre>
<p>以上代码现在还<strong>无法编译通过</strong>,因为还没有同步 Gin 这个模块的依赖也就是没有把它添加到go.mod 文件中。通过如下命令可以添加缺失的模块:</p>
<pre><code>go mod tidy
</code></pre>
<p>运行这一命令,就可以把缺失的模块添加进来,同时它也可以移除不再需要的模块。这时你再查看 go.mod 文件,会发现内容已经变成了这样:</p>
<pre><code>module gotour
go 1.15
require (
github.com/gin-gonic/gin v1.6.3
github.com/golang/protobuf v1.4.2 // indirect
github.com/google/go-cmp v0.5.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)
</code></pre>
<p>所以我们不用手动去修改 go.mod 文件,通过 Go 语言的工具链比如 go mod tidy 命令,就可以帮助我们自动地维护、自动地添加或者修改 go.mod 的内容。</p>
<h3>总结</h3>
<p>在 Go 语言中,包是同一目录中,编译在一起的源文件的集合。包里面含有函数、类型、变量和常量,不同包之间的调用,必须要首字母大写才可以。</p>
<p>而模块又是相关的包的集合,它里面包含了很多为了实现该模块的包,并且还可以通过模块的方式,把已经完成的模块提供给其他项目(模块)使用,达到了代码复用、研发效率提高的目的。</p>
<p>所以对于你的项目(模块)来说,它具有<strong>模块 ➡ 包 ➡ 函数类型</strong>这样三层结构,同一个模块中,可以通过包组织代码,达到代码复用的目的;在不同模块中,就需要通过模块的引入,达到这个目的。</p>
<p>编程界有个谚语:不要重复<strong>造轮子</strong>,使用现成的轮子,可以提高开发效率,降低 Bug 率。Go 语言提供的模块、包这些能力,就可以很好地让我们使用现有的轮子,在多人协作开发中,更好地提高工作效率。</p>
<p>最后,为你留个作业:基于模块化拆分你所做的项目,提取一些公共的模块,以供更多项目使用。相信这样你们的开发效率会大大提升的。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/22 讲通关 Go 语言-完/19 性能优化Go 语言如何进行代码检查和优化?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/22 讲通关 Go 语言-完/21 网络编程Go 语言如何玩转 RESTful API 服务?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996dd1fde83d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>