分类 技术志 下的文章

写出Go标准库级别文档注释的十个细节

本文永久链接 – https://tonybai.com/2024/10/27/ten-details-when-using-documentation-comments

Go语言以其优秀的工具链、“开箱即用”的标准库和相对完善的文档生态而闻名。Go通过代码中的文档注释(Doc Comments)生成相关包、类型、函数以及方法的说明文档。Go标准库中的文档注释不仅为使用者提供了清晰的指引,更为广大Go开发人员树立了高质量技术文档的标杆。

然而,在日常开发中,很多Go开发者往往只注意到文档注释的基本格式要求,而忽略了一些能让文档质量更上一层楼的细节。这些细节虽小,但却是区分专业级别和业余水平代码的重要标志。

在这篇文章中,我就挑出十个在编写Go文档注释时容易被忽视的关键细节,和大家说说,希望能帮助大家提升Go代码文档注释的level。

1. 注释缩进的陷阱

很多开发者在写多行注释时会不经意产生缩进问题,例如:

// TODO Revisit this design. It may make sense to walk those nodes
//      only once.  // 错误示例:第二行有缩进

这种缩进会使第二行被解析为代码块。正确的做法是保持未缩进:

// TODO Revisit this design. It may make sense to walk those nodes
// only once.

2. 并发安全性说明

对于类型(Type)的文档注释,默认情况下读者会认为该类型仅适用于单个goroutine。如果类型支持并发访问,应该显式地明确注释说明:

// Regexp is the representation of a compiled regular expression.
// A Regexp is safe for concurrent use by multiple goroutines,
// except for configuration methods, such as Longest.
type Regexp struct {
    ...
}

3. 零值行为说明

如果类型支持零值可用或零值具有特殊含义,应当在注释中显式说明:

// Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
    ...
}

4. 避免实现细节

函数的文档注释应该关注其行为和返回值,而不是实现细节。除非是性能关键的场景需要说明算法复杂度,否则应该避免在注释中描述算法实现:

// 好的示例:关注行为
// Sort sorts data in ascending order as determined by the Less method.
// It makes O(n*log(n)) calls to data.Less and data.Swap.

// 不好的示例:暴露实现细节
// Sort uses quicksort algorithm to sort data...

5. 返回布尔值的函数注释惯例

对于返回布尔值的函数,按惯例最好使用”reports whether”的描述方式,避免使用”or not”:

// HasPrefix reports whether the string s begins with prefix.
func HasPrefix(s, prefix string) bool

6. “构造函数”在文档中的位置

Go自身没有构造函数的专有语法,但当一个包中包含返回类型T或指针*T(包括伴随返回一个error的情况)的包顶层函数时,这些函数会被视为“构造函数”。这些构造函数在godoc中会被显示在T类型的下面,看起来像是T类型的方法,这的确容易“误导”一些Go新手:

$go doc time
... ...
type Duration int64
    func ParseDuration(s string) (Duration, error)
    func Since(t Time) Duration
    func Until(t Time) Duration
... ...

在pkg.go.dev中,这些“构造函数”在文档中会自动显示在类型T类型旁边:

这意味着我们在写“构造函数”的文档时应当注意与类型文档的一致性

// NewReader creates a new Reader reading from r.
// It is similar to NewReaderSize with the default buffer size.
func NewReader(r io.Reader) *Reader

// Reader implements buffering for an io.Reader object.
type Reader struct {
    // ...
}

7. 顶层函数的并发安全性说明

对于顶层函数(包级别的导出函数),默认情况下是假定它们是并发安全的,因此不需要显式说明这一点:

// 不必要的说明
// Parse parses the regular expression and returns a Regexp object.
// This function is safe for concurrent use.  // 这行是多余的
func Parse(expr string) (*Regexp, error)

// 正确的做法
// Parse parses the regular expression and returns a Regexp object.
func Parse(expr string) (*Regexp, error)

8. 方法的并发安全性说明

与包的顶层函数不同,类型的方法则是默认被认为仅限单个goroutine使用,即不是并发安全的。如果某些方法支持并发调用,应当在方法的文档注释中显式给予说明:

// Load returns the value stored in the map for a key.
// It is safe for concurrent use by multiple goroutines.
func (m *Map) Load(key interface{}) (value interface{}, ok bool)

9. 方法接收器命名一致对文档展示的影响

在编写类型的多个方法时,应该使用统一的接收器(receiver)命名,这样可以提高文档的一致性和可读性,避免不必要的命名变化:

// 不好的示例:接收器命名不一致
func (buffer *Buffer) Read(p []byte) (n int, err error)
func (b *Buffer) Write(p []byte) (n int, err error)
func (buf *Buffer) Cap() int

// 好的示例:统一使用b作为接收器名
func (b *Buffer) Read(p []byte) (n int, err error)
func (b *Buffer) Write(p []byte) (n int, err error)
func (b *Buffer) Cap() int

通过下图,我们也可以看到一致的方法receiver参数命名在文档中体现出的一致性,这种一致性不仅让文档看起来更专业,也让使用者在阅读文档时能更专注于方法的功能本身,而不是被不同的命名所分散注意力:

此外,选择方法接收器名称时的建议使用简短的命名(通常是类型名的第一个小写字母,比如上面的b),避免使用this、self等其他语言常用的命名。即使是单个方法,也要遵循这个命名约定,为后续可能的方法扩展做准备。

10. 废弃标记的使用

当需要标记某个API为废弃时,应该及时使用”Deprecated:”前缀予以标记,并提供替代方案,如下面的strings.Title函数:

// Title returns a copy of the string s with all Unicode letters that begin words
// mapped to their Unicode title case.
//
// Deprecated: The rule Title uses for word boundaries does not handle Unicode
// punctuation properly. Use golang.org/x/text/cases instead.
func Title(s string) string {
    ... ...
}

这些细节虽小,但都会影响到文档的可读性和代码的可维护性。良好的文档习惯需要在日常编码中持续积累和保持。


Go团队将代码的可读性和可维护性放到至关重要的位置上,而编写高质量的文档注释就是提升代码可读可维护性的重要实践。从注释的缩进、并发安全性说明,到零值行为、构造函数文档等细节,这些看似微小的考量都在传递着重要的信息。通过遵循这些最佳实践,我们不仅能让文档更加清晰易懂,还能帮助团队减少沟通成本,提高开发效率。更重要的是,这些实践能帮助我们培养更专业的编码习惯,写出更加规范的代码,让我们在日常开发中持续积累这些好的习惯,让代码文档更接近Go标准库的专业水准^_^。

要了解更多关于Go文档注释的细节,可以阅读Go Doc Comments


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily
  • Gopher Daily Feed订阅 – https://gopherdaily.tonybai.com/feed

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

认知负荷对编程语言选择和学习的影响

本文永久链接 – https://tonybai.com/2024/10/24/cognitive-load-impact-on-programming-language-choice-and-study

在《Go语言精进之路:从新手到高手的编程思想、方法和技巧》两卷书出版后,我收到了一些读者的反馈。其中一位读者提到:“为什么作者如此偏爱使用心智负担这个词?”当时我对此并未给予太多关注。然而,近期我阅读了一些关于认知心理学和脑科学的著作后,才意识到读者的反馈不仅仅是对该词频繁使用的关注,更可能暗示了用词不当的问题。

“心智负担”(Mental Load)指的是在处理多任务或日常生活安排时所需耗费的心理资源和精力,包括记忆、计划、组织以及应对各种任务所带来的精神压力。然而,在学习、思考和理解的情境中,特别是在编程语言的学习中,使用“认知负荷”(Cognitive Load)这一术语可能更为恰当。

认知负荷理论最初由澳大利亚新南威尔士大学的认知心理学家约翰·斯威勒(John Sweller)于1988年首先提出来的,旨在解释学习过程中的认知资源分配。认知负荷是指在学习、思考或解决问题时,大脑在处理信息和执行任务时所承受的负担。在选择编程语言时,认知负荷是一个至关重要的因素,指的是人们在学习和使用某种编程语言时,为理解语法、掌握工具和解决问题所需付出的心理负担和精力。

那么,在面对众多主流编程语言时,在不考虑市场需求与公司或组织强制学习的情况下,认知负荷究竟如何影响开发人员对编程语言的选择呢?在这篇文章中,我将进行一些不那么严谨,也非专业的粗略探讨,希望能够为大家带来一些启发。

1. 认知负荷在编程语言中的体现

认知负荷理论发展到今天,其总体被分为三种类型:

  • 内在认知负荷(Intrinsic Cognitive Load)

内在认知负荷,也称为固有负荷,是由学习材料本身的复杂性所决定的,它与学习任务的本质和内容密切相关。例如,编程语言的语法规则、数据类型内存管理并发模型等都是内在负荷的一部分。学习这些概念的难易程度主要取决于编程语言本身的设计和复杂度。

  • 外在认知负荷(Extraneous Cognitive Load)

外在负荷是由学习环境和教学方式引起的负担,通常是由于无关信息或低效的学习方法造成的。比如,配置开发环境、学习非必要的工具或被复杂的IDE界面困扰,都可能增加外在负荷。在编程语言学习中,清晰的文档和易于理解的教程可以显著减少外在负荷。

虽然外在负荷不是由编程语言语法本身决定的,但它会影响新手的学习体验。如果学习资源和工具太复杂或不直观,即使是简单的编程语言也会让人感到困难。

  • 相关认知负荷(Germane Cognitive Load)

相关认知负荷是指学习过程中专门用于理解、整合和构建知识结构的认知努力。它与思维加工、模式识别、知识内化等过程有关。在编程中,相关认知负荷指的是学习者在掌握编程思想、设计模式和编程习惯时所付出的努力。例如,理解如何在实际项目中应用编程概念,如何优化代码设计,以及如何解决编程中的复杂问题,这些过程都会增加相关认知负荷。这种负担是积极的,因为它有助于深入理解和长期记忆。

下面这张图来自网络,可以帮助我们进一步理解三类认知负荷(只是出发点来自教学角度):

由此可见,对于新手来说,学习一门编程语言时,外在认知负荷是第一道门槛,它决定了是否能坚持学习,还是选择“Hello and Bye”;内在认知负荷则是基础,是核心;相关认知负荷则是进阶挑战,决定了可以达到的高度

接下来,我们将针对一些主流编程语言,沿着新手入门学习编程语言的认知负荷先后顺序进行粗略对比。希望这能为大家提供在编程语言选择方面的有用信息,同时帮助不同阶段的学习者针对各自的认知负荷水平做好心理准备。

2. 主流编程语言的认知负荷对比

在探讨主流编程语言的认知负荷时,我们需要从外在认知负荷、内在认知负荷以及相关认知负荷这三个维度进行深入分析。这种分析不仅能帮助我们理解不同语言的特点,更能为选择合适的编程语言提供参考依据。

注:笔者是后端程序员出身,对前端语言比如Javascript、Typescript等了解有限,因此这里将使用像Go、Rust、C++等主流后端语言作为分析和对比的参考对象。

2.1 外在认知负荷的影响

在编程语言学习的初始阶段,外在认知负荷往往是最先遇到的挑战

Python在这方面表现出色,它简单的环境搭建流程让初学者能够快速开始编程之旅。只需安装一个解释器,新手就能立即开始编写代码。虽然在使用pip管理依赖时可能遇到一些包冲突的问题,但整体来说,在环境搭建、工具使用等外在认知负荷方面对初学者相当友好。

Go语言同样提供了令人称道的开发体验。它的工具链安装过程直观明了,跨平台支持也十分完善。特别值得一提的是,自从Go 1.11引入go modules以来,依赖管理变得更加自动化和直观。虽然对新手来说,理解版本控制可能需要一些时间。此外,Go团队也给出了Go项目布局的官方建议,为开发者进行代码组织提供了清晰的参考。

相比之下,C++的环境搭建则显得较为复杂。开发者需要安装编译器,配置IDE,这些步骤对新手来说都构成了不小的挑战。加上缺乏统一的包管理工具(尽管vcpkgconan等工具正在改变这一现状),以及灵活但缺乏标准的项目结构,都让C++的外在认知负荷明显高于其他语言。

Rust通过其官方工具链安装工具rustup提供了相对简便的环境搭建方式。它的Cargo包管理器集成度高,使用便捷,而且项目结构的标准化程度高,这些特点都有效降低了外在认知负荷。

Java则介于两个极端之间。它需要安装JDK并配置环境变量(如JAVA_HOME、CLASS_PATH等),这个过程对新手来说可能有些繁琐。虽然Maven和Gradle这样的依赖管理工具功能强大,但学习曲线较陡峭。不过,Java严格的项目布局规范在初期可能显得死板,但从长远来看反而有助于培养良好的工程习惯。

过了环境安装、工具使用和项目布局这些“外在认知负荷”的关卡后,语言自身的复杂性便会成为新手面前的更大的挑战。

2.2 内在认知负荷考量

谈到语言本身的复杂性,Python的设计理念“简单胜于复杂”使其成为认知负荷最低的选择之一。它的语法接近自然语言,几乎不需要特别的学习就能读懂基本的代码结构。这种简洁性使得Python特别适合编程初学者,以至于主流的儿童编程教学大多使用Python(当然一些启蒙教学使用的是scratch)。

Go语言同样以简洁著称,它的语法设计注重一致性和可读性。虽然保留了指针这样的底层特性,可能会让某些初学者感到困惑,但整体而言,Go的学习曲线相当平缓。值得注意的是,Go 1.18引入泛型后,虽然提升了语言的表达能力,但也增加了一定的复杂性。至于Go是否适合作为从零开始编程的新手,也是见仁见智。

C++的内在认知负荷则明显较高。它支持多种编程范式,包括面向过程、面向对象、模板编程等,这些范式和特性固然强大,但对初学者来说往往构成了较大的认知负担。特别是在处理多态、模板元编程等高级特性时,学习曲线会变得异常陡峭。

Rust的内在认知负荷同样不低,但事实证明其复杂性是有意义的。它的所有权系统和借用检查器虽然增加了学习难度,但这些机制对于理解系统编程的本质非常有帮助,同时提高了程序在运行时的安全性。新手在最初接触这些概念时可能会感到困惑,但掌握后会对内存安全有深刻的理解。

Java的内在认知负荷介于中等水平。它的面向对象语法虽然比Python或Go略显繁琐,但整体而言还算直观。Java的复杂性主要体现在面向对象设计模式、泛型和异常处理等特性上,这些概念需要时间来消化和掌握。

2.3 相关认知负荷的深入分析

在实际应用知识解决问题时,各种语言呈现出不同的特点。

Python的优势在于它能让学习者快速将知识付诸实践。其丰富的标准库和生态、简洁的语法使得从学习到应用的过程异常顺畅。无论是数据科学还是Web开发,Python都能让新手快速看到成果。它支持多种编程范式,并且社区的PEP 8规范为代码风格提供了清晰的指导。

Go语言在知识应用方面同样表现出色。它的工具链完善,容易将所学付诸实践。特别是在服务器端开发领域,Go的并发模型和简洁的语法让新手能够相对轻松地构建高效的后端服务。虽然Go不像传统的面向对象语言那样依赖继承体系,但其接口机制和组合方式为代码设计提供了优雅的解决方案。

C++的相关认知负荷较高,主要体现在将理论知识转化为实践时面临的挑战。内存管理和性能优化这些概念需要大量实践才能真正掌握。它支持多种编程范式,这种灵活性虽然强大,但对初学者来说往往是一把双刃剑。由于缺乏统一的编码规范,新手可能在选择最佳实践时感到困惑。

Rust在这方面呈现出独特的特点。它的所有权系统要求开发者在实践中深入思考内存管理问题,这个过程虽然充满挑战,但却能培养扎实的系统编程思维。Rust社区提供的编码规范和工具链都很完善,有助于形成良好的编程习惯。

Java则以其企业级开发的特点著称。它要求开发者深入理解面向对象编程的核心概念,这个过程需要较长时间的积累。Java的设计模式体系完备,社区的编码规范成熟,这些特点有助于培养专业的工程思维,但对新手来说可能需要更多的时间和耐心。

2.4 综合评估

通过以上分析,我们可以看出不同语言在认知负荷方面的特点。

Python以其全方位的低认知负荷成为初学者的理想选择。

Go语言通过简洁的设计和完善的工具链在降低认知负荷方面做出了显著成效。

Java虽然相对繁琐,但其成熟的生态系统和规范的开发流程为长期发展提供了良好基础。

Rust和C++的学习曲线较陡,但它们在系统编程和性能优化方面的深度让投入的学习成本变得有价值。

在理解了编程语言的认知负荷特点后,我们不妨再从心理学的角度,特别是借助三脑理论的视角,来探讨初学者是如何在面对不同编程语言时做出选择的。

3. 初学者的编程语言学习决策过程

三脑理论(Triune Brain Theory)由Paul D. MacLean于1970年提出的理论假说,该理论将人脑分为三个层次,如下图所示:


来自维基百科

  • 爬虫脑(Reptilian Brain):也称原始脑,负责基本生存反应,包括对威胁的快速反应和本能行为。
  • 情绪脑(Limbic System):处理情绪和动机,影响记忆形成和社交行为。
  • 理性脑(Neocortex):负责高级认知功能,如逻辑思考、语言处理和复杂决策。

注:三脑理论提出较早,如今有新的理论认为三脑理论毫无依据。不过这里我们假定这个理论是正确和适用的。

三脑理论影响初学者的编程学习决策的过程是怎样的呢?这个过程往往涉及本能反应(爬虫脑主导)、情感体验(情绪脑主导)和理性思考(理性脑主导)三个层面的互动。我们继续往下看。

3.1 初学阶段的决策历程

在首次接触编程语言时,学习者的反应往往是多层次的。本能层面的反应最为直接,面对像C++这样认知负荷较高的语言时,很多人会本能地产生畏惧感。这种反应不是简单的怯懦,而是大脑对复杂性的自然防御机制。相反,Python这类认知负荷较低的语言则较少触发这种应激反应,使得学习者能够保持相对轻松的心态。

情感层面的体验则更为复杂。当成功运行第一个程序时,无论使用什么语言,都会带来成就感。但随着学习的深入,不同语言带来的情感体验会产生分化。举个例子,我在早期学习Java时,仅仅是配置环境变量这样的基础工作就带来了挫折感,这种负面情绪很容易影响学习的积极性。而Rust虽然入门门槛较低,但一旦进入到所有权系统的学习,很多人会因为频繁的编译错误而感到沮丧。

理性思考则是决策过程中最后但也是最重要的环节。这包括对语言应用领域的评估、职业发展前景的考虑,以及个人学习时间和精力投入的权衡。这个阶段的决策通常更加慎重,也更具有长期性。

3.2 深入学习阶段的转变

随着学习的深入,最初的决策依据往往会发生改变。原本令人望而生畏的特性可能转变为吸引力的来源。这种转变在Rust的学习过程中特别明显,当开发者逐渐理解了所有权系统的价值,最初的困惑可能转化为对语言设计的欣赏

在这个阶段,情感体验也往往变得更加丰富。克服困难带来的成就感可能超越了简单的编程快感,这也解释了为什么一些看似“难学”的语言反而能够培养出更加忠实的用户群体。Rust连续多年在最受欢迎编程语言榜单上位居前列,很大程度上就源于这种深层的技术认同感

理性思考在这个阶段会更加全面,不再局限于语言本身的特性,而是扩展到整个技术生态系统的考量。开发者会更多地思考语言的性能特点、社区活跃度、工具链完善程度等因素。

3.3 认知负荷与学习效果

从短期来看,低认知负荷的语言确实能够提供更平缓的学习曲线,让入门过程更加顺畅。Python和Go在这方面的优势明显,它们能让学习者快速进入实践阶段,建立信心。但这种便利性有时也会带来一个意想不到的问题:学习者可能在掌握了基础语法后陷入平台期,难以实现质的突破。这也是为什么经常有读者询问如何才能在Go语言编程中更进一步

相比之下,高认知负荷的语言虽然入门较难,但往往能够培养更深入的编程思维。比如Rust的所有权系统,虽然增加了学习难度,但这种设计迫使开发者深入思考内存管理的问题,从而建立更扎实的系统编程基础。C++的模板元编程虽然复杂,但掌握后能够大大提升代码的抽象能力和复用效率。

不过,我们也要警惕过高的认知负荷带来的风险。如果学习过程中的挫折感持续累积,很容易导致半途而废。每年入门一次Rust的真实案例也屡见不鲜。这就要求我们在选择编程语言时,既要考虑个人的学习能力和时间投入,也要权衡职业发展的需求,找到一个适合自己的平衡点。

4. 小结

在探讨了认知负荷对编程语言学习的影响后,我们可以得出一些粗浅的见解:编程语言的学习绝非简单的语法掌握过程,而是一个涉及多个认知维度的复杂历程。从开发环境的搭建到语言特性的理解,从基础概念的掌握到工程实践的应用,每个阶段都会给学习者带来不同程度的认知压力。理解这些认知负荷的本质,有助于我们做出更明智的编程语言学习的选择。

对于编程新手来说,像Python和Go这样在各个维度都尽量降低认知负荷的语言,无疑是入门的理想选择。但我们也要认识到,较高的认知负荷未必就是缺点。就像Rust和C++这样的语言,它们的学习曲线虽然陡峭,但这种”困难”往往蕴含着宝贵的学习机会。通过克服这些认知挑战,开发者能够建立起更深入的系统编程认知,形成更扎实的技术功底。

选择合适的编程语言,某种程度上就像选择一位长期相处的伙伴。这个选择不仅要考虑语言本身的特点,还要权衡个人的学习能力、职业规划和时间投入。认知负荷理论为我们提供了一个有价值的分析框架,但最终的选择还是要回归到个人的实际需求和发展目标。正如没有完美的编程语言一样,也没有放之四海而皆准的学习路径。找到适合自己的平衡点,或许才是最务实的学习策略。

最后,在人工智能编码辅助技术飞速发展的今天,开放的学习心态和持续学习的能力,可能比选择某个特定的编程语言更为重要。毕竟唯一不变的可能就是变化本身。

5. 参考资料


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily
  • Gopher Daily Feed订阅 – https://gopherdaily.tonybai.com/feed

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats