标签 Channel 下的文章

Go 作为第一门编程语言:天才之选还是糟糕开端?

本文永久链接 – https://tonybai.com/2025/10/11/go-is-a-good-first-programming-language

大家好,我是Tony Bai。

近日,在 r/golang 社区,一个初学者的真诚提问,再次点燃了一场关于 Go 是否适合作为入门语言的激烈辩论。他很困惑:“为什么很多经验丰富的开发者说 Go 不适合作为第一门编程语言,而很多大学却用与之相似的 C 语言作为第一门编程语言呢?”

这个问题,如同一块探针,深入到了编程教育的核心分歧之中,并迅速将社区观点分裂为两大阵营。一方认为,Go 能从第一天起就培养严谨的工程思维,堪称“天才之选”。另一方则认为,它的定位不上不下,对初学者而言是一个“糟糕的开端”

那么,真相究竟为何?为了厘清思路,让我们深入这场辩论,分别听取两大阵营的观点,并审视其背后的根本分歧:我们学习编程,到底是为了什么?

观点一:Go 是一个“糟糕的开端”

这一方的核心论点是:Go 语言陷入了一个尴尬的“中间地带”,对于编程教育的两个主要目标,它都未能完美胜任。

论据一:Go 不够底层,无法胜任“计算机科学基础教育”

这一方的支持者指出,大学 CS 教育的首要目标,是培养学生对计算机工作原理的深刻理解。在这个目标下,C 语言之所以是“黄金标准”,恰恰在于它的“不友好”:

  • 直面内存:手动 malloc/free 和危险的指针算术,迫使学生直面内存布局、栈与堆等核心概念。
  • 最小化抽象:学生必须从零开始构建数据结构,这个过程能让他们对算法的理解建立在物理实现之上。

而Go 的垃圾回收 (GC) 机制,虽然是工程上的巨大进步,但在教育上却成了一个“黑盒”,完全隐藏了内存管理的复杂性。它让学生“知其然”,却无法“知其所以然”,因此无法胜任传授底层原理的重任。

论据二:Go 不够“温柔”,无法胜任“快速入门与兴趣培养”

接着,这一方展示了另一个极端——以 Python 为代表的“实战派”入门语言。这类语言的目标是让初学者尽快体验到编程的乐趣和效用。

  • 语法“温柔”:Python 的语法接近伪代码,极大地降低了入门的认知门槛。
  • 快速反馈:作为解释型语言,其“编写即运行”的交互式体验,对维持初学者的学习热情至关重要。

尽管 Go 也以简单著称,但其静态类型、编译周期、以及对项目结构的规范要求,都为纯粹的初学者制造了不必要的“摩擦力”。与 Python 相比,它不够“温柔”,可能会在入门阶段就劝退一部分学习者。

由此来看,Go 既不像 C 那样能让你深入底层,又不像 Python 那样能让你轻松起步。它是一个尴尬的“中间派”,对于任何一个明确的教学目标来说,都有比它更好的选择。因此,它是一个“糟糕的开端”。

观点二:Go 是一个“天才之选”

另一方的核心论点是:观点一中所说的“中间地带”并非尴尬,而是一个经过深思熟虑、精心设计的“甜蜜点” (sweet spot)。Go 的目标,不是培养纯粹的理论家或业余爱好者,而是从第一天起,就为培养专业的“软件工程师”奠定基础

论据一:Go 教授的是“更重要”的底层原理

观点二的支持者承认 Go 隐藏了手动内存管理的细节,但他们认为,在 2025 年的今天,这部分细节的教学价值正在下降。相反,Go 教授了更现代、更重要的底层概念:

  • 安全的指针哲学:Go 保留了指针,让学生能够深刻理解“引用 vs. 值”这一核心概念,这是理解程序性能和行为的关键。同时,它通过移除指针算术,杜绝了 C 语言中最常见的一类安全漏洞。
  • 并发是第一性原理:他们强调,现代计算的核心是并发。Go 将 goroutine 和 channel 作为内建特性,让学生能够以一种前所未有的简洁方式,去接触和理解并发这一现代计算机科学的基石。

Go 并非不教底层,而是有选择地教授那些在现代软件工程中依然至关重要的底层概念,同时将那些日益自动化、易出错的细节(如手动内存管理)抽象掉。

论据二:Go 的“摩擦力”恰恰是良好工程习惯的开端

观点二的支持者认为,观点一所说的“摩擦力”,实际上是宝贵的“纪律训练”:

  • 静态类型:不是负担,而是一张安全网,它教会学生思考数据的结构和契约。TypeScript逐步超越JavaScript就是一个静态类型取得胜利的明证。
  • 显式错误处理:if err != nil 不是样板代码,而是对健壮性最深刻的、日复一日的训练。它让学生明白,失败是程序中正常的一部分,必须被认真对待
  • 编译周期:不是障碍,而是专业开发流程的预演,教会学生区分构建时和运行时。

Go 的设计,完美地平衡了抽象与细节。它既能让学生快速构建出实际的应用(比如一个简单的 Web 服务器),又在整个过程中不断地、潜移默化地向他们灌输专业的工程思想。它不是在教“编程”,而是在教“软件工程”。因此,对于立志成为专业工程师的学习者来说,它是一个“天才之选”

小结:目标决定了最佳路径

至此,辩论的脉络已经清晰。这场争论没有绝对的赢家,因为双方的论点都建立在各自合理的目标之上。

最终的结论是:这取决于你的目标。

  • 如果你的目标是成为一名计算机科学家,深入理解机器的每一个齿轮如何运转,那么从 C 开始的“苦修”或许无法绕开。
  • 如果你的目标是快速体验编程的乐趣、尽快构建应用,那么 Python 或 JavaScript 可能会为你提供一条更平坦、更愉悦的道路。
  • 而 Go,则为那些从一开始就立志于成为一名专业、高效、能构建并发系统的现代软件工程师的学习者,提供了一条无与伦比的捷径。

它或许不是最完美的“第一站”,但对于目标明确的人来说,它是一个能让你赢在起跑线上的“天才之选”。它将“学习编程”与“成为一名软件工程师”这两个阶段,以前所未有的方式紧密地结合在了一起。

资料链接:https://www.reddit.com/r/golang/comments/1nvbrv8/im_confused_as_to_why_experienced_devs_say_go_is/


想系统学习Go,构建扎实的知识体系?

我的新书《Go语言第一课》是你的首选。源自2.4万人好评的极客时间专栏,内容全面升级,同步至Go 1.24。首发期有专属五折优惠,不到40元即可入手,扫码即可拥有这本300页的Go语言入门宝典,即刻开启你的Go语言高效学习之旅!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

Go 考古:Slice 的“隐秘角落”——只读切片与扩容策略的权衡

本文永久链接 – https://tonybai.com/2025/10/02/go-archaeology-slice

大家好,我是Tony Bai。

slice(切片),可以说是 Go 语言中最重要、也最常用的数据结构,没有之一。我们每天都在使用它,尤其是 append 函数,它就像一个魔术师,总能“恰到好处”地为我们管理好底层数组的容量,让我们几乎感受不到内存分配的烦恼。

但你是否想过,这份“恰到好处”的背后,隐藏着怎样的代价与权衡?append 的扩容策略,是简单的“翻倍”吗?如果不是,那它遵循着怎样一条精密的数学公式?

更进一步,slice 的设计真的是完美的吗?它有一个与生俱来的“危险”——共享底层数组。一个不经意的函数调用,就可能导致意想不到的数据修改,引发难以追踪的 bug。Go 团队是否考虑过一种更“安全”的切片?如果考虑过,它又为何最终没有出现在我们今天的 Go 语言中?

理解这些位于“隐秘角落”历史问题,不仅能让你写出性能更好、更安全的代码,更能让你洞悉 Go 语言设计的核心哲学——在简单性、性能和安全性之间,那永恒的、精妙的平衡艺术

今天,就让我们扮演一次“Go 语言考古学家”,带上放大镜和洛阳铲,深入 Go 官方的设计文档和 CL (Change List) 的历史尘埃中,去挖掘 slice 背后那两个鲜为人知的故事:一个是被遗弃的“只读切片”提案,另一个是 append 扩容策略的“精益求精”。

失落的“伊甸园”:Read-Only Slice 提案

我们先从一个几乎所有 Gopher 都遇到过,或者未来一定会遇到的“坑”开始。看下面这段代码:

func processData(data []int) {
    // 假设我们只是想读取 data,但某个“新手”在这里修改了它
    data[0] = 100
}

func main() {
    metrics := []int{10, 20, 30}
    processData(metrics)
    fmt.Println("Original metrics:", metrics) // 输出: Original metrics: [100 20 30]
}

在 main 函数中,我们期望 metrics 切片在调用 processData 后保持不变。但事与愿违,它的第一个元素被意外地修改了。这就是 slice 的“原罪”——它只是底层数组的一个“视图”(指针、长度、容量)。当我们将 slice 作为参数传递时,我们传递的是这个视图的副本,但它指向的底层数组却是同一个。

这个特性虽然带来了极高的性能(无需拷贝大量数据),但也打开了“副作用”的潘多拉魔盒。为了解决这个问题,早在 2013 年 5 月,Go 核心开发者 Brad Fitzpatrick(memcached、Go HTTP/2 等库的作者)正式提交了一份名为 “Read-only slices” 的语言变更提案

这份提案的目标非常明确:在语言层面引入一种新的、受限的切片类型,它在编译期就保证了其内容不可被修改。

提案的蓝图:一个更安全的 io.Writer

Brad Fitzpatrick 在提案中设想了一种 [].T 的新语法(他本人也说语法可以再讨论),并将其与 Go 中已有的“只收/只发 channel”进行类比:

c := make(chan int)    // 可读可写
var rc <-chan int = c  // 只读 channel
var sc chan<- int = c  // 只写 channel

// 设想中的未来
t := make([]T, 10) // 可读可写 slice
var vt [].T = t    // 只读 slice

一旦一个切片被转换为只读切片 [].T,它将失去修改自身元素的能力。这意味着,对 vt[i] = x 的赋值操作,甚至获取元素地址 &vt[i],都将在编译期被禁止。

这个提案的“杀手级应用”是什么?Brad 指向了标准库中最核心的接口之一:io.Writer。

// 今天的 io.Writer
type Writer interface {
    Write(p []byte) (n int, err error)
}

Write 方法接收一个 []byte,但没有任何机制能阻止 Write 的实现去修改 p 的内容。这其实是一种安全隐患。如果有了只读切片,io.Writer 的定义将变得更加安全和清晰:

// 设想中的 io.Writer
type Writer interface {
    Write(p [].byte) (n int, err error)
}

接收一个只读的 [].byte,明确地告诉调用者:“我保证不会修改你的数据”。

更妙的是,这个改动还能顺带解决 string 和 []byte 之间长期存在的“重复 API”问题。由于 string 本质上是不可变的字节序列,它可以被零成本地转换为只读的 [].byte。这意味着:

  1. io.WriteString 这个为了避免 string 到 []byte 转换开销而存在的辅助接口,将变得多余。我们可以直接写 writer.Write(“hello”)。
  2. strings 和 bytes 包中大量功能重复的函数(如 Index, Contains, HasPrefix 等)可以被合并,统一接收 [].byte。

这个蓝图看起来如此美好:更高的安全性、更少的 API 冗余、更好的性能。它似乎解决了 Go 切片设计中所有令人不安的“小瑕疵”。

然而,仅仅两周后,Go 团队的技术负责人 Russ Cox 发表了一份详尽的评估报告,以一种冷静、深刻、几乎无可辩驳的方式,最终否决了这个提案。

Russ Cox 的“灵魂拷问”:一个看似简单的改动,如何引发系统性崩溃?

Russ Cox 的评估报告,是 Go 设计哲学的一次完美展示。他没有停留在提案美好的愿景上,而是通过亲手实现一个原型,去系统性地评估这个改动对整个语言生态带来的连锁反应。

他的结论是:只读切片解决了一些问题,但引入了至少同样多、甚至更棘手的新问题。

以下是他提出的几个核心论点:

1. 问题一:从“重复”到“三倍重复”

提案希望消除 string 和 []byte 的重复函数,但 Russ Cox 指出,这只对“纯输入”的函数有效。对于那些需要返回其输入类型子切片的函数(如 TrimSpace),问题就来了。

func bytes.TrimSpace(s []byte) []byte
func strings.TrimSpace(s string) string

你无法用一个 func TrimSpace(s readonly []byte) readonly []byte 来统一它们。因为调用者通常需要一个明确的 []byte(用于后续修改)或 string(用于比较、拼接),一个只读的 readonly []byte 对它们来说“不够用”。所以,这两个函数必须保留。

更糟糕的是,现在我们有了一个新的只读类型,那么我们还需要为它提供一套完整的操作函数!于是,我们可能需要 robytes.TrimSpace。重复不仅没有消除,反而变成了三倍。

2. 问题二:性能的“隐形杀手”——局部不可变 vs. 全局不可变

提案的一个动机是提升性能,避免 string 和 []byte 之间的拷贝。但 Russ Cox 指出了一个更深层次的陷阱。

string 的内容是全局不可变 (globally immutable) 的。这意味着,一旦创建,它的内容在程序的任何地方、任何时间都不会改变。编译器和开发者都可以完全信赖这一点。

而 readonly []byte 只是局部不可变 (locally immutable)。持有 readonly []byte 的函数不能修改它,但程序的其他地方可能持有同一个底层数组的可写 []byte 别名,并随时修改它!

这个根本性的差异,导致了意想不到的性能退化:

  • 错误处理中的拷贝: 当一个函数(如 os.Open)接收 readonly []byte 路径并遇到错误时,它不能像接收 string 那样直接把路径存到 error 对象里。因为它无法保证这块 []byte 的内容在未来不会被修改,所以必须进行一次防御性拷贝
  • 优化的丧失: string 的全局不可变性允许编译器做很多优化。例如,strings.Replace 在发现没有子串需要替换时,可以直接返回原始 string,零成本。但如果输入是 readonly []byte,由于无法保证其全局不变性,这个优化就不能安全地进行了。

3. 问题三:接口的“分裂”与泛用性的丧失

Russ Cox 还指出了一个对 Go 生态破坏性极大的问题:接口的分裂。以 sort.Interface 为例:

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

IsSorted 函数只需要 Len 和 Less,而 Sort 函数则需要全部三个方法。如果我们要对一个 readonly []int 进行排序检查,我们就无法将它转换为 sort.Interface,因为它无法实现可写的 Swap 方法。

解决方案是什么?可能需要定义一个新的 sort.ReadOnlyInterface,然后让 IsSorted 接收这个新接口。这会导致标准库的接口体系大规模分裂,代码的泛用性大大降低。一个简单的改动,最终波及了整个生态。

最终的裁决:保持简单,相信开发者

在评估报告的最后,Russ Cox 给出了明确的结论:

“It does solve some problems, but it introduces at least as many new problems… I think we should keep going with the current type system.”
(它确实解决了一些问题,但也引入了至少同样多的新问题……我认为我们应该继续使用当前的类型系统。)

这场关于只读切片的深刻辩论,最终以维持现状告终。Go 团队的决策,深刻地体现了其核心设计哲学:

  • 系统性思考: 一个语言特性的价值,必须放在整个生态系统的背景下进行评估。任何可能导致“三倍重复”或“接口分裂”的改动,都必须被极度审慎地对待。
  • 简单性高于一切: 增加一个新的只读类型体系,会极大地增加语言的认知负担,这违背了 Go 的初衷。
  • 约定优于强制: Go 最终选择相信开发者。一个行为良好的 Go 函数,不应该修改它不拥有的数据。这是一种代码约定 (Convention),而非编译器强制 (Compiler Enforcement)

只读切片,这个失落的“伊甸园”,成为了 Go 语言发展史上一块极其珍贵的化石。它告诉我们,语言设计中没有完美的“银弹”,只有在无数个约束条件下的、充满智慧的权衡与取舍

append 的“进化论”:从“粗暴”到“平滑”的扩容策略

现在,让我们把目光从“安全(只读slice)”转向“性能”,来挖掘 append 函数背后的扩容秘密。

我们都知道,当 append 发现底层数组容量不足时,会分配一个更大的新数组,并将旧数据拷贝过去。

那么,“更大”是多大呢?一个最简单的想法是容量翻倍。这在很多场景下工作的不错,但当切片变得很大时,会造成可观的内存浪费。

Go 团队是如何选择的呢?通过考古Go团队和社区的历史讨论、CL 347917 的提交记录以及 runtime/slice.go 的源码演进,我们可以清晰地看到一条“进化”的轨迹。

早期(Go 1.18 之前)的策略:硬阈值下的“突变”

在很长一段时间里,Go 的扩容策略是一个简单明了的分段函数,其分界点设在 1024:

  • 当切片容量小于 1024 时,直接翻倍 (newCap = oldCap * 2)。 这种策略保证了小切片能够快速成长,减少早期阶段的分配次数。
  • 当切片容量大于等于 1024 时,以 1.25 倍的系数持续增长 (newCap = oldCap * 1.25)。 这种策略旨在当切片变大后,避免因翻倍而导致的巨大内存浪费。

这个策略在大部分情况下都工作的很好,但它有一个“不优美”的地方,正如 CL 347917 的提交日志中所指出的那样——它不是单调的 (monotonic)。这意味着,在阈值附近,一个更大的初始容量,经过一次扩容后,其新容量反而可能小于一个更小的初始容量扩容后的结果。

更重要的是,在 1024 这个阈值点,增长行为会发生一次“突变”。一个容量为 1023 的切片,下次会扩容到 2046;而一个容量为 1024 的切片,下次只会扩容到 1280。这种不连续性,虽然不是 bug,但对于追求优雅和可预测性的 Go 团队来说,显然还有优化的空间。

现代(Go 1.18 及之后)的策略:平滑过渡的艺术

在 CL 347917 中,Go 团队对这个算法进行了一次精心的“平滑”处理,旨在解决上述问题。新的策略将突变的阈值点从 1024 下调到了 256,并引入了一个全新的、逐渐衰减的增长公式。

让我们直接来看 Go 1.24 中 runtime/slice.go 里的 nextslicecap 函数核心实现:

// nextslicecap computes the next appropriate slice length.
func nextslicecap(newLen, oldCap int) int {
    newcap := oldCap
    doublecap := newcap + newcap
    if newLen > doublecap {
        return newLen
    }

    const threshold = 256
    if oldCap < threshold {
        return doublecap
    }
    for {
        // Transition from growing 2x for small slices
        // to growing 1.25x for large slices. This formula
        // gives a smooth-ish transition between the two.
        newcap += (newcap + 3*threshold) >> 2

        if uint(newcap) >= uint(newLen) {
            break
        }
    }
    // ... (overflow check)
    return newcap
}

这段代码揭示了现代扩容策略的秘密:

  1. 新阈值:256

    • 当旧容量 oldCap 小于 256 时,策略依然是简单高效的翻倍
    • CL 347917 的日志解释了为什么选择 256:这是为了在最终扩容到一个非常大的切片时,新旧算法所需的总重分配次数大致相当,是一个精心计算的平衡点。
  2. 平滑过渡公式:newcap += (newcap + 3*threshold) >> 2

    • 当 oldCap 大于等于 256 时,Go 进入一个 for 循环,反复应用这个公式来增加容量,直到新容量 newcap 足够容纳所需的 newLen。
    • 这个公式 newcap += (newcap / 4) + (3 * 256 / 4),可以看作是 newcap *= 1.25 的一个变体,但增加了一个与阈值相关的固定量。它的精妙之处在于,当 newcap 刚刚超过 256 时,增长因子接近 2;而当 newcap 变得非常大时,增长因子则会逐渐趋近于 1.25。

CL 347917 的提交日志中,给出了几个关键点的实际增长因子,让我们能更直观地感受这种“平滑”:

可以看到,增长因子不再是断崖式地从 2.0 跌到 1.25,而是在 [256, +∞) 这个区间内,像一条平滑的曲线一样逐渐下降。

最后一道工序:内存对齐

这还没完。runtime 计算出的期望容量 newcap,还必须经过内存分配器的“打磨”。Go 的内存分配器是按一系列的规格 (size classes) 来组织内存的。growslice 函数在最后,会将计算出的 newcap 转换为所需的字节数,并向上取整到最接近的一个 size class。

这意味着,即使扩容算法算出来需要 130 个字节,内存分配器可能最终会给你一块 144 字节的内存块。这进一步展示了语言特性(切片扩容)与底层 runtime(内存分配)之间的紧密协作。

综上可以看出:append 的扩容策略,从一个简单的、带有“突变”的分段函数,演进到一个阈值更低、过渡更平滑、数学上更优美的算法,这正是 Go 团队数据驱动、精益求精的工程文化的完美体现。

这个看似微小的改动,实际上解决了旧算法的“非单调性”问题,并让切片的内存增长行为变得更加平滑和可预测。

所以,下一次当你的同事随口说出“Go 切片扩容是翻倍”时,你就可以微笑着,把 256、1.25 和那条平滑下降的增长曲线,娓娓道来。而这正是“Go 考古”的魅力所在。技术的每一个细节,都值得我们深入探索。

小结:从“隐秘角落”看 Go 的设计哲学

今天,我们的“考古”之旅暂告一段落。通过深入 slice 的两个“隐秘角落”,我们挖掘出的不仅仅是技术细节,更是一部关于 Go 语言设计哲学的微缩史。

  • 在“失落的伊甸园”中,我们看到了一份看似完美的只读切片提案,是如何在 Russ Cox 系统性的、基于原型的评估下,暴露出其可能引发的“API 三倍重复”、“性能隐形退化”和“接口生态分裂”等深层问题。它告诉我们,任何语言特性的价值,都必须在整个生态系统的宏大背景下进行审视。

  • 在“append 的进化论”里,我们则见证了一场精益求精的工程优化。Go 团队并非满足于一个“够用就好”的分段函数,而是为了解决“非单调性”和“突变”等细微的“不优美”,通过 CL 347917 引入了一个阈值更低 (256)、过渡更平滑的数学公式。这完美地诠释了 Go 语言数据驱动、持续打磨的务实品格。

这两个故事,一“舍”一“取”,共同描绘出了 Go 设计哲学的核心画像:极度审慎地对待语言复杂性的增加,同时又对核心实现的性能与优雅报以永不满足的追求。

而这,正是“Go 考古”的魅力所在。技术的每一个细节,都值得我们深入探索。

参考资料

  • Read-only slice proposal – https://docs.google.com/document/d/1UKu_do3FRvfeN5Bb1RxLohV-zBOJWTzX0E8ZU1bkqX0/edit?tab=t.0#heading=h.2wzvdd6vdi83
  • Evaluation of read-only slices – https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A/edit?tab=t.0
  • slices grow at 25% after 1024 but why 1024? – https://groups.google.com/g/golang-nuts/c/UaVlMQ8Nz3o
  • runtime: make slice growth formula a bit smoother (cl347917)- https://go-review.googlesource.com/c/go/+/347917

你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


想系统学习Go,构建扎实的知识体系?

我的新书《Go语言第一课》是你的首选。源自2.4万人好评的极客时间专栏,内容全面升级,同步至Go 1.24。首发期有专属五折优惠,不到40元即可入手,扫码即可拥有这本300页的Go语言入门宝典,即刻开启你的Go语言高效学习之旅!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 AI原生开发工作流实战 Go语言精进之路1 Go语言精进之路2 Go语言第一课 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