简单

Go语言的设计者们在语言设计之初,就拒绝了走语言特性融合的道路,选择了“做减法”,选择了“简单”,但Go语言的自身实现并不容易,这些复杂性被Go语言的设计者们“隐藏”了。

  • 仅有25个关键字;
  • 内置垃圾收集,降低开发人员内存管理的心智负担;
  • 首字母大小写决定可见性,无需通过额外关键字修饰;
  • 变量初始为类型零值,避免以随机值作为初值的问题;
  • 内置数组边界检查,极大减少越界访问带来的安全隐患;
  • 内置并发支持,简化开发程序设计;
  • 内置接口类型,为组合的设计哲学奠定基础;
  • 原生提供完善的工具链,开箱即用

    ……

显示

首先,先来瞅瞅“隐式”代码的行为特征
在Java中,下面这段代码可以正常编译输出正确结果,因为Java在编译过程中将变量类型做了隐式转换:

  1. package com.xiaofeng;
  2. public class test {
  3. public static void main(String[] args) {
  4. short a = 1;
  5. int b = 1;
  6. long c ;
  7. c = a + b;
  8. System.out.println(c);
  9. }
  10. }

接下来将上面代码转换为Go代码

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a int16 = 1
  5. var b int = 1
  6. var c int64
  7. c = a + b
  8. fmt.Println(c)
  9. }

如果运行这段程序,就会得到报错:“invalid operation: a + b (mismatched types int16 and int)”。
Go不允许不同类型的整型变量进行混合计算,它同样也不会对其进行隐式的自动转换。
因此,如果要让这段代码通过编译,就需要对a和b进行显示转型,如下:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a int16 = 1
  5. var b int = 1
  6. var c int64
  7. c = int64(a) + int64(b)
  8. fmt.Println(c)
  9. }

这就是Go语言显示设计哲学的一个体现。

组合

C++、Java等主流面向对象语言通过庞大的自上而下的类型体系、继承、显示接口实现等机制将程序的各个部分耦合起来,但在Go语言中找不到经典的面向对象语法元素、类型体系和继承机制,Go推崇的是组合的设计哲学。

在Go语言设计层面,Go设计者为开发者们提供了正交的语法元素,以供后续组合使用,包括:

  1. Go语言无类型层次体系,各类型之间是相互独立的,没有子类型概念;
  2. 每个类型都可以有自己的方法集合,类型定义与方法实现是正交独立的;
  3. 接口与其实现之间隐式关联;实现某个接口时,无需像Java那样采用特定关键字修饰;
  4. 包之间是相对独立的,没有子包的概念。

类型嵌入 —— 垂直组合
Go语言提供的最为直观的组合的语法元素是类型嵌入。通过类型嵌入,可以将已经实现的功能嵌入新类型中,以快速满足新类型的功能需求。这种方式类似面向对象语言中的继承机制,但是在原理上与其完全不同,这是Go设计者们精心设计的语法糖。

interface —— 水平组合
interface是Go语言的一个创新设计,它只是方法集合,且与实现者之间的关系是隐式的,它让程序各个部分之间的耦合降至最低,同时是连接程序各部分的纽带。水平组合的模式有很多,一种常见的方法是通过接受interface类型参数的普通函数进行组合。

组合是构建Go程序骨架的主要方式,它可以大幅度降低程序元素间的耦合,提高程序的可扩展性和灵活性。

并发

“并发”这个设计的出现有它的背景,CPU都是靠提高主频来改进性能的,但是现在这个做法已经遇到了瓶颈。主频提高导致CPU的功耗和发热量剧增,反过来制约了CPU性能的进一步提高。2007年开始,处理器厂商的竞争焦点从主频转向了多核。

在这种大背景下,Go的设计者在决定去创建一门新语言的时候,果断将面向多核、原生支持并发作为了新语言的设计原则之一。并且Go放弃了传统的基于操作系统线程的并发模型,而采用了用户层轻量级线程,Go将其称为goroutine。

goroutine占用的资源非常小,Go运行时默认为每个goroutine分配的栈空间仅2KB。goroutine调度的切换也不用陷入(trap)操作系统内核层完成,代价很低。因此,一个Go程序中可以创建成千上万个并发的goroutine。而且,所有的Go代码都在goroutine中执行,哪怕是go运行时代码也不例外。

在提供了开销较低的goroutine的同时,Go还在语言层面内置了辅助并发设计的原语:channel和select。开发者可以通过语言内置的channel传递消息或实现同步,并通过select实现多路channel的并发控制。相较于传统复杂的线程并发模型,Go对并发的原生支持将大大降低开发人员在开发并发程序的心智负担。

此外,并发的设计哲学不仅仅让Go在语法层面提供了并发原语支持,其对Go应用程序设计的影响更为重要。并发是一种程序结构设计的方法,它似的并行称为可能。

而且,并发与组合的哲学是一脉相承的,并发是一个更大的组合的概念,它在程序设计的全局层面对程序进行拆解组合,再映射到程序执行层面上:goroutine各自执行特定的工作,通过channel+select将goroutines组合连接起来。并发的存在鼓励程序员在程序设计时进行独立计算的分解,而对并发的原生支持让Go语言也更适应现代计算机环境。

面向工程

Go语言设计的初衷,就是面向解决真实世界中Goole内部大规模软件开发存在的各种问题,为这些问题提供答案,这些问题包括:程序构建慢、依赖管理失控、代码难于理解、跨语言构建难等。Go语言最初设计阶段就将解决工程问题作为Go的设计原则之一去考虑Go语法,在这种面向工程设计哲学的驱使下,Go在语法设计细节上做了精心的打磨,比如:

  • 重新设计编译单元和目标文件格式,实现Go源码快速构建,让大工程的构建时间缩短到类似动态语言的交互式解释的编译速度;
  • 如果源文件导入它不使用的包,则程序将无法编译。这可以充分保证任何Go程序的依赖树是精确的。这也可以保证在构建程序时不会编译额外的代码,从而最大限度的缩短编译时间;
  • 去除包的循环依赖,循环依赖就在大规模的代码中引发问题,因为他们要求编译器同时处理更大的源文件集,这会减慢增量构建;
  • 包路径是唯一的,而包名不必唯一。导入路径必须唯一标识要导入的包,而名称只是包的使用者如何引用其内容的约定。“包名称不必是唯一的”这个约定,大大降低了开发人员给包起唯一名字的心智负担;
  • 故意不支持默认函数参数。因为在规模工程中,很多开发者利用默认函数参数机制,向函数添加过多的参数以弥补函数的API的设计缺陷,这会导致函数拥有太多的参数,降低清晰度和可读性。
  • 增加类型别名,支持大规模代码库的重构。

参考:
《Go语言精进之路》—— 白明