标签 Python 下的文章

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

本文永久链接 – 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

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

从DevOps到日常脚本:聊聊Go语言的多面性

本文永久链接 – https://tonybai.com/2024/10/08/go-languages-versatility-from-devops-to-daily-scripts

2024年初,TIOBE编程语言排行榜上,Go再次进入了前十,并在之后又成功冲高至第七名

Go语言的排名上升,至少在Reddit Go论坛上帖子数量和在线人数上得到了体现,尽管目前与Rust热度仍有差距,但可见Go的关注度在提升:


2024年国庆节假期某天下午的实时在线数对比

随着Go语言人气的上升,论坛中的问题也变得愈发多样化。许多Gopher常常问及为何Go是DevOps语言Go适合用作脚本语言吗等问题,这些都反映了Go语言的多面性。

从最初的系统编程语言,到如今在DevOps领域的广泛应用,再到一些场合被探索用作脚本语言,Go展现出了令人惊叹的灵活性和适应性。在本篇文章中,我们将聚焦于Go语言在DevOps领域的应用以及它作为脚本替代语言的潜力,聊聊其强大多面性如何满足这些特定场景的需求。

1. Go在DevOps中的优势

随着DevOps的发展,平台工程(Platform Engineering)这一新兴概念逐渐兴起。在自动化任务、微服务部署和系统管理中,编程语言的作用变得愈发重要。Go语言凭借其高性能、并发处理能力以及能够编译成单一二进制文件的特点,越来越受到DevOps领域开发人员的青睐,成为开发DevOps工具链的重要组成部分。

首先,Go的跨平台编译能力使得DevOps团队可以在一个平台上编译,然后在多个不同的操作系统和架构上运行,结合编译出的单一可执行文件的能力,大大简化了部署流程,这也是很多Go开发者认为Go适合DevOps的第一优势:

$GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64 main.go
$GOOS=linux GOARCH=arm64 go build -o myapp-linux-arm64 main.go
$GOOS=darwin GOARCH=amd64 go build -o myapp-darwin-amd64 main.go
$GOOS=windows GOARCH=amd64 go build -o myapp-windows-amd64.exe main.go

其次,Go的标准库仿佛“瑞士军刀”,开箱即用,为DevOps场景提供了所需的丰富的网络、加密和系统操作功能库,大幅降低对外部的依赖,即便不使用第三方包生态系统,也可以满足大部分的DevOps功能需求。

此外,Go的goroutines和channels为处理高并发任务提供了极大便利,这在DevOps中也尤为重要。例如,以下代码展示了如何使用goroutines并发检查多个服务的健康状态:

func checkServices(services []string) {
    var wg sync.WaitGroup
    for _, service := range services {
        wg.Add(1)
        go func(s string) {
            defer wg.Done()
            if err := checkHealth(s); err != nil {
                log.Printf("Service %s is unhealthy: %v", s, err)
            } else {
                log.Printf("Service %s is healthy", s)
            }
        }(service)
    }
    wg.Wait()
}

并且,许多知名的DevOps基础设施、中间件和工具都是用Go编写的,如Docker、Kubernetes、Prometheus等,集成起来非常丝滑。这些工具的成功进一步证明了Go在DevOps领域的适用性。

2. Go作为脚本语言的潜力

在传统的DevOps任务中,Python和Shell脚本长期以来都是主力军,它们(尤其是Python)以其简洁的语法和丰富的生态系统赢得了DevOps社区的广泛青睐。然而,传统主力Python和Shell脚本虽然灵活易用,但在处理大规模数据或需要高性能的场景时往往力不从心。此外,它们的动态类型系统可能导致运行时错误,增加了调试难度。

随着Go的普及,它的“超高性价比”逐渐被开发运维人员所接受:既有着接近于脚本语言的较低的学习曲线与较高的生产力(也得益于Go超快的编译速度),又有着静态语言的高性能,还有单一文件在部署方面的便利性

下面是一个简单的文件处理脚本,用于向大家展示Go的简单易学:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    file, err := os.Open("input.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        if strings.Contains(line, "ERROR") {
            fmt.Println(line)
        }
    }
}

这个示例虽然要比同等功能的Python或shell代码行数要多,但由于Go的简单和直观,多数人都很容易看懂这段代码。

此外,Go的静态强类型系统可以在编译时捕获更多错误,避免在运行时的调试,提高了脚本在运行时的可靠性。

开发运维人员眼中的脚本语言,如Shell脚本和Python脚本,通常是直接基于源代码进行解释和运行的。实际上,Go语言同样可以实现这一点,而其关键工具就是go run命令。这个命令允许开发者快速执行Go代码,从而使Go源码看起来更像是“脚本”,下面我们就来看看go run。

3. go run:桥接编译型语言与脚本语言的利器

我们知道go run命令实际上是编译和运行的组合,它首先编译源代码,然后立即执行生成的二进制文件。这个过程对用户来说是透明的,使得Go程序可以像脚本一样方便地运行。这一命令也大大简化了Go程序的开发流程,使Go更接近传统的脚本语言工作流。可以说,通过go run,Go语言向脚本语言的使用体验更靠近了一步。

此外,go run与go build在编译阶段的行为并不完全相同:

  • go run在运行结束后,不保留编译后的二进制文件;而go build生成可执行文件并保留。

  • go run编译时默认不包含调试信息,以减少构建时间;而go build则保留完整的调试信息。

  • go run可以使用-exec标志指定运行环境,比如:

$go run -exec="ls" main.go
/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1742641170/b001/exe/main

我们看到,如果设置了-exec标志,那么go run -exec=”prog” main.go args编译后的命令执行就变为了”prog a.out args”。go run还支持跨平台模拟执行,当GOOS或GOARCH与系统默认值不同时,如果在\$PATH路径下存在名为”go_\$GOOS_\$GOARCH_exec”的程序,那么go run就会执行:

$go_$GOOS_$GOARCH_exec a.out args

比如:go_js_wasm_exec a.out args
  • go run通常用于运行main包,在go module开启的情况下,go run使用的是main module的上下文。go build可以编译多个包,对于非main包时只检查构建而不生成输出

  • go run还支持运行一个指定版本号的包

当指定了版本后缀(如@v1.0.0或@latest)时,go run会进入module-aware mode(模块感知模式),并忽略当前目录或上级目录中的go.mod文件。这意味着,即使你当前的项目中存在依赖管理文件go.mod,go run也不会影响或修改当前项目的依赖关系,下面这个示例展示了这一点:

$go run golang.org/x/example/hello@latest

go: downloading golang.org/x/example v0.0.0-20240925201653-1a5e218e5455
go: downloading golang.org/x/example/hello v0.0.0-20240925201653-1a5e218e5455
Hello, world!

这个功能特别适合在不影响主模块依赖的情况下,临时运行某个工具或程序。例如,如果你只是想测试某个工具的特定版本,或者快速运行一个远程程序包,而不希望它干扰你正在开发的项目中的依赖项,这种方式就很实用。

不过有一点要注意的是:go run的退出状态并不等于编译后二进制文件的退出状态,看下面这个示例:

// main.go成功退出
$go run main.go
Hello from myapp!
$echo $?
0

// main.go中调用os.Exit(2)退出
$go run main.go
Hello from myapp!
exit status 2
$echo $?
1

go run使用退出状态1来表示其运行程序的异常退出状态,但这个值和真实的exit的状态值不相等。

到这里我们看到,go run xxx.go可以像bash xxx.sh或python xxx.py那样,以“解释”方式运行一个Go源码文件。这使得Go语言在某种程度上具备了脚本语言的特性。然而,在脚本语言中,例如Bash或Python等,用户可以通过将源码文件设置为可执行,并在文件的首行添加适当的解释器指令,从而直接运行脚本,而无需显式调用解释器。这种灵活性使得脚本的执行变得更加简便。那么Go是否也可以做到这一点呢?我们继续往下看。

4. Go脚本化的实现方式

下面是通过一些技巧或第三方工具实现Go脚本化的方法。对于喜欢使用脚本的人来说,最熟悉的莫过于shebang(即解释器指令)。在许多脚本语言中,通过在文件的第一行添加指定的解释器路径,可以直接运行脚本,而无需显式调用解释器。例如,在Bash或Python脚本中,通常会看到这样的行:

#!/usr/bin/env python3

那么Go语言支持shebang吗? 是否可以实现实现类似的效果呢?我们下面来看看。

4.1 使用“shebang(#!)”运行Go脚本

很遗憾,Go不能直接支持shebang,我们看一下这个示例main.go:

#!/usr/bin/env go run 

package main

import (
    "fmt"
    "os"
)

func main() {
    s := "world"
    if len(os.Args) > 1 {
        s = os.Args[1]
    }
    fmt.Printf("Hello, %v!\n", s)
}

这一示例的第一行就是一个shebang解释器指令,我们chmod u+x main.go,然后执行该Go“脚本”:

$./main.go
main.go:1:1: illegal character U+0023 '#'

这个执行过程中,Shell可以正常识别shebang,然后调用go run去运行main.go,问题就在于go编译器视shebang这一行为非法语法!

常规的shebang写法行不通,我们就使用一些trick,下面是改进后的示例:

//usr/bin/env go run $0 $@; exit

package main

import (
    "fmt"
    "os"
)

func main() {
    s := "world"
    if len(os.Args) > 1 {
        s = os.Args[1]
    }
    fmt.Printf("Hello, %v!\n", s)
}

这段代码则可以chmod +x 后直接运行:

$./main.go
Hello, world!
$./main.go gopher
Hello, gopher!

这是因为它巧妙地结合了shell脚本和Go代码的特性。我们来看一下第一行:

//usr/bin/env go run $0 $@; exit

这一行看起来像是Go的注释,但实际上是一个shell命令。当文件被执行时,shell会解释这一行,/usr/bin/env用于寻找go命令的路径,go run \$0 \$@ 告诉go命令运行当前脚本文件(\$0)以及所有传递给脚本的参数(\$@),当go run编译这个脚本时,又会将第一行当做注释行而忽略,这就是关键所在。最后的exit确保shell在Go程序执行完毕后退出。如果没有exit,shell会执行后续Go代码,那显然会导致报错!

除了上述trick外,我们还可以将Go源码文件注册为可执行格式(仅在linux上进行了测试),下面就是具体操作步骤。

4.2 在Linux系统中注册Go为可执行格式

就像在Windows上双击某个文件后,系统打开特定程序处理对应的文件一样,我们也可以将Go源文件(xxx.go)注册为可执行格式,并指定用于处理该文件的程序。实现这一功能,我们需要借助binfmt_misc。binfmt_misc是Linux内核的一个功能,允许用户注册新的可执行文件格式。这使得Linux系统能够识别并执行不同类型的可执行文件,比如脚本、二进制文件等。

我们用下面命令将Go源文件注册到binfmt_misc中:

echo ':golang:E::go::/usr/local/bin/gorun:OC' | sudo tee /proc/sys/fs/binfmt_misc/register

简单解释一下上述命令:

  • :golang::这是注册的格式的名称,可以自定义。
  • E:::表示执行文件的魔数(magic number),在这里为空,表示任何文件类型。
  • go:::指定用于执行的解释器,这里是go命令。
  • /usr/local/bin/gorun:指定用于执行的程序路径,这里是一个自定义的gorun脚本
  • :OC:表示这个格式是可执行的(O)并且支持在运行时创建(C)。

当你执行一个Go源文件时,Linux内核会检查文件的类型。如果文件的格式与注册的格式匹配,内核会调用指定的解释器(在这个例子中是gorun)来执行该文件。

gorun脚本是我们自己编写的,源码如下:

#!/bin/bash

# 检查是否提供了源文件
if [ -z "$1" ]; then
  echo "用法: gorun <go源文件> [参数...]"
  exit 1
fi

# 检查文件是否存在
if [ ! -f "$1" ]; then
  echo "错误: 文件 $1 不存在"
  exit 1
fi

# 将第一个参数作为源文件,剩余的参数作为执行参数
GO_FILE="$1"
shift  # 移除第一个参数,剩余的参数将会被传递

# 使用go run命令执行Go源文件,传递其余参数
go run "$GO_FILE" "$@"

将gorun脚本放置带/usr/local/bin下,并chmod +x使其具有可执行权限。

接下来,我们就可以直接执行不带有”shebang”的正常go源码了:

// main.go
package main

import (
    "fmt"
    "os"
)

func main() {
      s := "world"
      if len(os.Args) > 1 {
          s = os.Args[1]
      }
      fmt.Printf("Hello, %v!\n", s)
}

直接执行上述源文件:

$ ./main.go
Hello, world!
$ ./main.go gopher
Hello, gopher!

4.3 第三方工具支持

Go社区也有一些将支持将Go源文件视为脚本的解释器工具,比如:traefik/yaegi等。

$go install github.com/traefik/yaegi/cmd/yaegi@latest
go: downloading github.com/traefik/yaegi v0.16.1
$yaegi main.go
Hello, main.go!

yaegi还可以像python那样,提供Read-Eval-Print-Loop功能,我们可以与yaegi配合进行交互式“Go脚本”编码:

$ yaegi
> 1+2
: 3
> import "fmt"
: 0xc0003900d0
> fmt.Println("hello, golang")
hello, golang
: 14
>

类似的提供REPL功能的第三方Go解释器还包括:cosmos72/gomacrox-motemen/gore等,这里就不深入介绍了,感兴趣的童鞋可以自行研究。

5. 小结

在本文中,我们探讨了Go语言在DevOps和日常脚本编写中的多面性。首先,Go语言因其高性能、并发处理能力及跨平台编译特性,成为DevOps领域的重要工具,助力于自动化任务和微服务部署。其次,随着Go语言的普及,其作为脚本语言的潜力逐渐被开发运维人员认识,Go展现出了优于传统脚本语言的高效性和可靠性。

我们还介绍了Go脚本的实现方式,包括使用go run命令,它使得Go程序的执行更像传统脚本语言,同时也探讨了一些技巧和工具,帮助开发者将Go源码文件作为可执行脚本直接运行。通过这些探索,我们可以看到Go语言在现代开发中的灵活应用及其日益增长的吸引力。

随着AI能力的飞速发展,使用Go编写一个日常脚本就是分分钟的事情,但Go的特性让这样的脚本具备了传统脚本语言所不具备的并发性、可靠性和性能优势。我们有理由相信,Go在DevOps和脚本编程领域的应用将会越来越广泛,为开发者带来更多的可能性和便利。

6. 参考资料


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