精华内容
下载资源
问答
  • 一、关于详细设计说明书 最近在准备一个项目的详细设计说明书。编制详细设计说明书的目的,在于说明一个软件系统各个层次中的每一个程序 (每个模块或子程序)的设计。详细设计基于需求调研分析成果和概要设计成果...

    唐杨烱.唐昭武校尉曹君神道碑有这样的记载:“托无愧之铭,跋涉载劳於千仞,访他山之石,东西向逾万里。”诚所谓:它山之石,可以攻玉。

    clip_image001[4]

     

    一、关于详细设计说明书

    最近在准备一个项目的详细设计说明书。编制详细设计说明书的目的,在于说明一个软件系统各个层次中的每一个程序 (每个模块或子程序)的设计。详细设计基于需求调研分析成果和概要设计成果编制,是系统开发、测试、验收等项目过程的重要指导文件。也不难理解,为什么在电视剧生产量最大的中国,很多导演或演员总会叹息:没有好本子。确实,一个好本子,可以让一个名不见经传的演员一炮走红;一个垃圾本子,则可能让一个很红的实力派演员被人称为奶油小生。

    自然,也就会归结到我们的系列话题:互联网产品设计。详细设计说明书用来指导设计人员、开发人员、工程人员、测试人员更好地完成相关工作。文档的颗粒度越高,越有利于直接指导开发人员进行实现。

    详细设计是考验技术专家设计思维的重要关卡,详细设计说明书应当把具体的模块以最“干净”的方式(黑箱结构)提供给编码者,使得系统整体模块化达到最大。一份好的详细设计说明书,可以使编码的复杂性减低到最低,实际上,严格的讲详细设计说明书应当把每个函数的每个参数的定义都精精细细的提供出来,从需求分析到概要设计到完成详细设计说明书,一个软件项目就应当说完成了一半了。换言之,一个大型软件系统在完成了一半的时候,其实还没有开始一行代码工作。当然,“胸中有丘壑”者例外。

    做产品设计之前,可以在浏览器中输入http://file.baidu.com,在出现的浏览器界面中,再输入“详细设计说明书”,你会有许多意想不到的收获,没准你会轻松理解某个大型社区的详细设计。

    一个典型的参考目录

    这是一个典型的参考目录。

    1. 综述... 4

    1.1. 文档说明... 4

    1.2. 编写目的... 4

    1.3. 适用范围... 4

    1.4. 名词、术语、缩略语定义... 5

    1.5. 参考资料... 5

    2. 项目概述... 5

    2.1 项目背景... 5

    2.2 项目名称... 6

    2.3 项目单位... 6

    2.4 项目目标... 6

    3. 任务概述... 7

    3.1 设计目标... 7

    3.2 系统环境... 7

    3.3 需求概述... 7

    3.4 条件与限制... 8

    3.5 详细设计的基本任务... 9

    4. 总体设计... 9

    4.1 设计方法论... 9

    4.2 系统总体结构... 9

    4.3 逻辑架构... 9

    4.4 软件架构... 9

    4.5 部署结构... 10

    5. 功能设计... 10

    5.1 功能划分... 10

    5.2 子系统名称A.. 11

    5.3 子系统名称B. 12

    6. 级联设计... 13

    6.1 系统级联概述... 13

    6.2 级联应用场景... 13

    6.3 级联设计方案... 13

    7. 栏目与页面设计... 13

    7.1 桌面栏目设计... 13

    7.2 系统页面设计... 13

    8. 外部接口设计... 14

    8.1 平台内部接口... 14

    8.2 第三方产品接口... 14

    8.3 业务应用系统接口... 14

    9. 程序结构描述... 15

    9.1 程序目录结构... 15

    9.2 代码目录规则... 15

    9.3 数据命名规范... 15

    10. 异常处理机制... 15

    三、简明扼要的书写

    说明书确实要尽量全,但不能因为“全”而忽略了条理性。篇幅所限,这里列举一个比较直观的实例。

    为了说明“学生信息查询模块”,可以遵循以下步骤。

    clip_image003[4]

    第一,功能流程图。如下图所示。

    clip_image005[6]

    第二,功能描述。如下所示。

    (1) 功能类型:查询数据

    (2) 功能概述:显示查询结果

    (3) 前提业务:无

    (4) 后继业务:

    (5) 功能约束:没有约束;

    (6) 约束描述:

    (7) 操作权限:面向所有用户

    第三,界面中的动作说明,如下所示。

    动作编号

    动作名称

    动作描述

    A01

    确定

    点击按钮 提交数据到图书查询数据处理.jsp页面

    A02

    图书信息查询

    点击按钮 将页面转至图书信息查询.jsp页面

    第四,模块内部逻辑,如下所示。

    clip_image007[4]

    01. Search.jsp用于显示界面的内容,给用户显示一个查询接口

    02. Index.jsp用来调度所有的页面,它根据传入的参数来决定包含哪一个jsp页面来显示内容;

    在index.jsp页面中,

    它根据传入的参数来决定包含哪一个jsp页面来显示内容;则可以通过<jsp:include page=”<%= ……%>”/>,利用jsp:include标签来被动态加载发送到相应页面;

    03. List.jsp 用来显示查询到的结果列表。

    04. View.jsp用来显示查询到的学生的各项属性。

    第五,存储分配。以学生文件为例。

    序号

    字段

    字段名

    类型

    长度

    精度

    小数位数

    默认值

    允许空

    主键

    说明

    1

    XSID

    学生编号

    Int

    10

      

    1

     

    自动编号

    2

    Name

    姓名

    nvarchar

    10

          

    3

    Sex

    性别

    nvarchar

    2

       

      

    4

    ZJH

    证件号码

    nvarchar

    25

       

      

    5

    LXDH

    联系电话

    nvarchar

    40

       

      

    6

    DJRQ

    登记日期

    datetime

    8

       

      

    7

    YXQZ

    有效期至

    datetime

    8

       

      

    8

    YJSS

    已借书数

    smallint

    2

       

      

    9

    RuleID

    学生规则ID

    int

    10

          

    10

    ZT

    是否挂失

    nvarchar

    2

       

      

    参考资料:http://file.baidu.com

    展开全文
  • 国军标-Gjb软件设计说明书模板

    万次阅读 2016-06-29 17:47:55
    设计说明应包括以下内容(若适用),任何适合于所提供的信息的顺序提供,并应从接口实体角度说明这些特性之间的区别(例如关于数据元素的大小、频率或其他特性的不同期望值): A 接口实体分配给该接口的...
     模板下载地址::http://www.trufun.net/uploads/soft/160629/1_1025508321.docx
    

    一、Trufun服务目标

    规范软件开发过程     优化软件开发流程

    保证软件开发质量     提高软件开发效率

        西安楚凡科技有限公司(Trufun)是全球领先的软件开发行业应用生命周期管理(ALM)CASE工具解决方案提供商,倡导"实用、简洁"的产品理念,为企业实现产品开发与服务支持间的规范化应用平台,在管理软件研发全过程的同时,支持当前各种规范标准,实现企业的战略目标




    Gjb软件需求规格说明书

    1. 范围

    1.1. 标识

        本条应描述本文档使用系统和软件的完整标识,适用时,包括其标识号、名称、缩略名、版本号和发布号。

    1.2. 系统概述

        本条应概述本文档所适用的系统和软件的用途。它还应描述系统与软件的一般特性;概述系统开发、运行和维护的历史;标识项目的需方、用户、开发方和保障机构等;标识当前和计划的运行现场;列出其他有关文档。

    1.3. 文档概述

        本条应概述本文档的用途和内容,并描述与它的使用有关的保密性方面的要求。

    2. 引用文档

    本章应列出引用文档的编号、标题、编写单位、修订版及日期,还应标识不能通过正常采购活动得到的文档的来源。

    3. CSCI级设计决策

    本章应根据需要分条给出CSCI级设计决策,即CSCI行为设计的决策(忽略其内部实现,从用户角度出发描述系统将怎样运转以满足需求)和其他影响组成该CSCI的软件单元的选择与设计的决策。如果在需求中所有这些决策是明确的,或者这些决策要推迟到CSCI的软件单元的设计时指出,则本章应如实陈述。针对关键性需求(例如对安全性或保密性关键的需求)做出的设计决策,应在专门的章条中加以叙述。如果设计决策依赖于系统状态或方式,则应指明这一依赖性。本条应给出或引用理解这些设计所需的设计约定。CSCI级设计决策的例子如下:
    a. 关于CSCI将接收的输入和将产生的输出的设计决策,包括与其他系统、HWCI、CSCI和用户的接口(本文档的4.3.X条支出本说明要考虑的主题)。如果这一信息的全部或部分已在接口设计说明(IDD)中给出,则可以直接引用。
    b. 有关响应每个输入或条件的CSCI行为的设计决策,包括CSCI要执行的动作、响应时间和其他性能特性,模型化的物理系统的说明,选定的方程式/算法/规则,以及对不允许的输入或条件进行的处理。
    c. 有关数据库/数据文件如何呈现给用户的设计决策(本文档的4.3.X条标识了本说明要考虑的主题)。如果这一信息的全部或部分在数据库设计说明(DBDD)中给出,则可直接引用。
    d. 为满足安全性和保密性需求所选择的方法。
    e. 为满足需求所做的其他CSCI级设计决策,例如为提供所需的灵活性、可用性和可维护性所选择的方法。

    4. CSCI体系结构设计

    本章应分为以下几条描述CSCI体系结构设计。如果设计的全部或部分依赖于系统的状态方式,此依赖性应予指明。如果设计信息在多于一个条中出现,它只需被提供一次,而在其他条中引用。本条应提供或引用为了理解设计所需要的设计约定。

    4.1. CSCI部件

    本条应描述:
    a. 构成该CSCI的所有软件单元。应赋予每个软件单元一个项目唯一的标识符。
    注:软件单元是CSCI设计中的一个元素;例如,CSCI的一个主要分支,该主要分支的一个组成部分,一个类、对象、模块、函数、例程或数据库。软件单元可以出现在层次结构的不同层上,且又可以由其他软件单元组成。在设计中,软件单元与实现它们的代码和数据实体(例程,过程,数据库,数据文件等),或与包含这些实体的计算机文件之间,可以有、也可以没有一对一的对应关系。一个数据库可被处理为一个CSCI也可被处理为一个软件大院。SDD可以采用与所使用设计方法相一致的任何名字来称呼软件单元。
    b. 软件单元的静态(如,由。。。组成)关系。根据所选择的软件设计方法学,可以给出多种关系(例如,采用面向对象的设计方法时,本条既可以给出类和对象结构,也可给出CSCI的模块和过程结构)。
    c. 每个软件单元的用途,指明分配给它的CSCI需求和CSCI级设计或软件、再工程的已有的设计或软件、为重用而要开发的软件等,为构建版N计划的软件)。针对现有的设计或软件,本说明应提供标识信息,例如名字、版本、文档引用、库等。
    d. CSCI(若适用,针对每个软件单元)计划适用的计算机硬件资源(例如处理机能力,内存能力,输入/输出设备能力,辅存能力以及通信/网络设备能力)。本说明应覆盖CSCI的资源适用需求中、影响该CSCI的系统级资源分配中、以及在软件开发计划(SDP)的资源适用测量策划中包含的全部计算机硬件资源。如果针对指定计算机硬件资源的所有使用数据都在同一处提供,例如在SDD中提供,那么本条可直接引用。对每个计算机硬件资源,应包括如下信息:
    1. 得到满足的CSCI需求或系统级资源分配;
    2. 使用数据基于的假设和条件(例如,典型用法、最坏情况用法、特定事件的假定);
    3. 影响使用的特殊考虑(例如,虚存、覆盖、多处理器的使用情况,操作系统、库软件的开销或其他实现开销的影响);
    4. 所使用的测度的单位(例如,处理器能力的百分比、周期/秒、内存字节数、千字节/妙);
    5. 进行评估或测量的几笔(例如,软件单元,CSCI或者可执行程序)。
    e. 标识实现每个软件单元的软件放置在哪个程序库中。

    4.2. 执行方案

    本条应说明软件单元间的执行方案,可采用图表和描述,来说明软件单元间的动态关系,即CSCI运行期间软件单元间的互相作用情况,(若适用)应包括执行控制流程、数据流、动态控制序列、状态转换图、时序图、单元间的优先关系、中断处理、时序/排序关系、例外处理、并发执行、动态分配与去除分配、对象/进程/任务的动态创建/删除、以及动态行为的其他方面。

    4.3. 接口设计

    4.3.1. 接口标识和接口图

    本条应说明赋予每个借口的项目唯一的标识符,(若适用)应通过名称、编号、版本及文档引用来标识接口实体(软件单元、系统、配置项、用户等)。该标识应说明那些实体具有固定的接口特性(从而把接口需求分配给这些接口实体);说明哪些实体正在开发或修改(这些实体已有各自的接口需求)。(若适用)应通过接口图来描述这些接口。

    4.3.2. X(接口的项目唯一的标识符)

    本条(从4.3.2开始)应通过项目唯一的标识符来标识接口,应简要地标识接口实体,根据需要可分条描述单方或双方接口实体的特性。如果一指定的接口实体未包含在本SDD中(例如,一个外部系统),而描述接口实体需要提到其接口特性时,这些特性应作为假设予以陈述、或以“当【未涵盖的实体】这样做时,【所指定的实体】将…”的形式描述。本条可引用其他文档(例如数据字典、协议标准、用户接口标准)代替在此所描述的信息。本设计说明应包括以下内容(若适用),以任何适合于所提供的信息的顺序提供,并应从接口实体角度说明这些特性之间的区别(例如关于数据元素的大小、频率或其他特性的不同期望值):
    A 接口实体分配给该接口的优先级。
    B 对实现的接口类型的要求(例如实时数据传送、数据的储存和检索等)。
    C 接口实体将提供、储存、发送、存取、接受的各个数据元素所要求的特征,例如:
    1) 名称/标识符:
    a. 项目唯一的标识符;
    b. 非技术(自然语言)名称;
    c. 数据元素名称;
    d. 技术名(如在代码或数据库中的变量名或字段名);
    e. 缩略名或同义名。
    2) 数据类型(字母、数字、整数等)。
    3) 大小和格式(如:字符串的长度和标点符号)。
    4) 计量单位(如:m等)。
    5) 可能值的范围或枚举(如:0-99)。
    6) 准确性(正确程度)和精度(有效数字位数)。
    7) 优先级别、定时、频率、容量、序列以及其他约束条件(例如数据元素是否可以被更新、业务规则是否适用)。
    8) 保密性约束。
    9) 来源(建立/发送实体)和接收者(使用/接收实体)。
    D 接口实体将提供、存储、发送、访问、接收的数据元素组合体(记录、消息、文件、数组、显示、报表等)所要求的特征,例如:
    1) 名称/标识符:
    a. 项目唯一的标识符;
    b. 非技术(自然语言)名称;
    c. 技术名(如在代码或数据库中的变量名或字段名);
    d. 缩略名或同义名。
    2) 数据元素组合体中的数据元素和其结构(编号、顺序和组成情况)。
    3) 介质(例如磁盘)以及在介质上数据元素/包的结构。
    4) 显示和其他输出的视听特性(例如颜色、布局、字体、图标和其他显示元素、蜂鸣音和亮度)。
    5) 数据自合体之间的关系,如排序/存取特性。
    6) 优先级、时序、频率、容量、序列及其他约束,例如包是否可以被更新、业务规则是否适用。
    7) 保密性约束。
    8) 来源(建立/发送实体)和接收者(使用/接收实体)。
    E 接口实体用于接口的通信方法的特征。如:
    1) 项目唯一的标识符;
    2) 通信链接/带宽/频率/介质及其特征;
    3) 消息格式;
    4) 控制流(如:序列编号和缓冲区分配);
    5) 周期/非周期传送的数据传送速率,传输间隔;
    6) 路由、寻址、命名约定;
    7) 传输服务,包括:优先权和等级;
    8) 安全性/保密性考虑,如:加密、用户鉴别、隔离和审核。
    F 接口实体用于接口的协议所要求的特征,如:
    1) 项目唯一的标识符;
    2) 协议的优先级别/层次;
    3) 打包,包括拆包和重新打包、路由和寻址;
    4) 合法性检查、错误控制和恢复过程;
    5) 同步,包括建立连接、保持和终止;
    6) 状态、标识及任何其他报告的特性。
    G 其他特征,例如接口实体的物理兼容性(尺寸、公差、负荷和接插件的兼容性等)。

    5. CSCI详细设计

    5.1. X(软件单元的项目唯一的标识符,或者一组软件单元的标识符)

    本条应通过项目唯一的标识符来标识软件单元,并对该单元进行说明。(若适用)该说明应包括下列信息。本条也可以指定一组软件单元,软后再分小条对他们分别进行标识和说明,包含其他软件单元的软件单元可引用那些软件单元的说明,而无需在此重复。
    A. (若有)单元设计决策,例如所使用的算法(如果此前尚未选定)。
    B. 该软件单元设计中的任何约束、限定或非常规特征。
    C. 如果使用的编程语言不同于该CSCI所指定的语言,则应支出并说明使用它的理由。
    D. 如果该软件单元博阿含过程性命令或由过程性命令组成(例如数据库管理系统DBMS查询,用于代码自动生成的图形用户接口(GUI)构造器的输入,操作系统的命令或Shell脚本),应列出这些过程性命令,并引用解释它们的用户手册或其他文档。
    E. 如果该软件单元包含、接收或输出数据,(若适用)应对它的输入、输出及其他数据元素和数据元素组合体进行说明。本文档的4.3.X条提供了(若适用)应包括的主题。软件单元的局部数据应与软件单元的输入或输出数据分开来描述。如果该软件单元是一个数据库,应引用响应的数据库设计说明(DBDD);接口特性可以在这里提供、也可以引用第4章或相应的接口设计说明(IDD)。
    F. 如果该软件单元包含逻辑,则给出该软件单元所用到的逻辑,(若适用)应包括:
    1) 该软件单元执行启动时,其内部起作用的条件。
    2) 将控制传递给其他软件单元的条件。
    3) 对每个输入的响应以及响应时间,包括数据转换、重命名以及数据传输操作。
    4) 在软件单元运行期间的操作顺序和动态控制序列,包括:
    a. 顺序控制的方法;
    b. 该方法的逻辑和输入条件,例如时序编译、优先级分配等;
    c. 进出内存的数据传输;
    d. 对离散输入信号的感知,以及该软件单元内中断操作之间的时序关系。
    5) 异常和错误处理。

    6. 需求可追踪性

    本章应包含:
    1) 从本SDD所标识的每个软件单元,到分配给它的CSCI需求的可追踪性(这一可追踪性也可以在4.1条中提供)。
    2) 从每个CSCI需求,到被分配这些需求的软件单元的可追踪性。

    7. 注释

    本章应包括有助于了解文档的所有信息(例如:背景、术语、缩略语或公式)。
    展开全文
  • 设计社会做什么

    千次阅读 2012-04-25 16:18:15
    连续两天,接受媒体的采访,忽然发现大家开始关心一个新的话题设计到底在社会做了些什么?这不是设计师提出的问题,是代表民意的记者提出的,这说明设计在慢慢地受到了社会的关注了,这是多么好的一件事情呀。...
    连续两天,接受媒体的采访,忽然发现大家开始关心一个新的话题:设计到底在为社会做了些什么?这不是设计师提出的问题,是代表民意的记者提出的,这说明设计在慢慢地受到了社会的关注了,这是多么好的一件事情呀。但是有多少设计师考虑过这个问题呢?换以言之就是设计到底为别人做了些什么呢。
    

    这个问题也给我带来了许多思考,于是觉得有必要写一篇文章,一起探讨一下设计的意义。

    难道设计师是在为自己创作吗?就像许多设计师畅想的自由创作状态,可是比较一下通过作品不断塑造着个人品牌的艺术家们你会发现,设计师并不能通过自己的作品给自己留名,道理很简单,我们数数那些耳熟能详的著名设计作品,有多少人能说出那些设计背后的作者的名字呢?销售了3000多万台的iphone手机是谁设计的?风靡世界的保时捷911跑车是谁设计的?人见人爱的耐克标志是谁设计的?你们家的电视机是谁设计的?你身上穿的衣服是谁设计的?在设计师中最容易留名的要数建筑设计师了,可是面对社会大众而言,有谁会关心中央电视台的大裤衩是谁设计的?鸟巢是谁设计的?……可见设计师的工作不是为了自己。除了满足了个人成就感之外,我看起码从狭义上讲设计师的工作不是为了自己和自己的创作。

    我和曾经做了5年摩托罗拉设计部门经理的邱丰顺聊天时,曾发自内心地赞扬过他那款“明”的设计,因为那个设计实在是取得了巨大的商业业绩。可是邱丰顺说:“在大公司里,好设计都不是设计师设计出来的,那些畅销的设计都是市场营销人员调查和预测以及推销的结果,只有他们最清楚市场需要什么。单凭设计师的喜好,很容易设计出市场不喜欢的产品,因为设计师的喜好往往特别小众。”

    既然畅销的好设计都是市场调研的结果,那么设计师究竟为社会创造了什么呢?突然发现这是一个多么哲学的命题。正如哲学家永远也探讨不清楚的那些问题:我们是谁,我们从哪里来,我们为什么而来……呵呵!

    回归设计的原点,我们发现设计都是发生在事物开始之前,当房地产商拥有了一块可以建筑大楼的土地的时候,他们会想到:我未来的大楼要盖成什么样子呢?当手机制造商正在热销手头的商品时,他们会想:我的下一款产品怎样还能如此受到欢迎?当某人欣然创业的时候,望着崭新的办公室的时候,他们回想:我未来伟大的公司应该叫个什么名字,她的标志应该是什么样呢?当市场营销经理看到下滑的业绩,他们会想:我的网站是不是应该更新了呢?当一个人买到了一套新的房子,他们自然会幻想:未来自己的家应该怎样漂亮的时候……接下来他们都会马上就会想到一个人——设计师。

    于是设计师被召唤过来,他们一开始听到的话绝对不是“你自己去创作吧”,而是一大堆业主的理想,不是吗?所以我觉得设计师的职业可以这样认为,当别人有了梦想的时候,他们找到了拥有专业技能的设计师,帮助他们实现他们自己的梦想。

    能做一个帮助别人圆梦的人,不是很幸福吗?我觉得这个意义已经很大了。

    另外,在交谈中我们还谈到了审美,因为设计师的工作离不开审美,没有人会说:“来给我设计一辆世界上最丑陋的汽车……”

    那么我们想一想社会上每个人的审美都是怎样形成的吗?当然不是在课堂上学习来的,因为你想想你在你接受教育的生涯里上过审美课吗?即使你是美术学院毕业的专业人事,你上过审美课吗?我想肯定没有,别立刻就抱怨我们的教育体系,我跟你说国外大多数学校里也没有审美课,我们古代文明中那么璀璨的审美硕果也不是私塾里传授的。而是传成于一间一间的作坊,一代一代的手工艺人。

    所有美学教育家都承认,人们的审美来自耳闻目染,来自家庭熏陶,来自社会影响。想想为什么我们今天社会的审美偏差那么大吧?为什么我们今人甚至读不懂祖先的审美遗产。很简单那是因为100年的战争和后来的文化大革命把我们祖先的审美体系都打乱了,否定了,抛弃了……在过去几十年的时间里,我们整个社会人人都穿类似军装一样丑陋的衣服,我们人人家里以穷途四壁为荣没有任何装饰品,我们的城市一片灰暗……我们的民族整整有两代人成长在这样的丑陋的环境里,他们的基因里哪里还有什么审美呢?

    那么我们民族的审美秩序将靠谁来重建呢?是政府吗?是领袖吗?是学校吗?是银行吗?我觉得都不是,想想倒述千百年我们古代中国的审美是谁塑造的呢?无疑是那些默默无闻,没有留下任何名姓的古代工匠们,他们有一个统一的“职称”——鲁班,鲁班就是设计师。

    我们的审美就建立在从小长大的历程里,在于你的妈妈对于家庭的布置、家门前的花园或场院、街道的气息,还有车站、汽车、餐厅、食物、座椅、餐具、你我的衣服……对了最重要的是:我们彼此的音容笑貌。这些都是设计师的工作,无疑设计师(鲁班)在重塑着我们民族的审美。这不是一个人的工作,不是通过一些伟大的设计完成的,也不是短短几年能够完成的,但是这一切毕竟正在进行着。你看我们今天的城市不是比十年前漂亮多了吗?你看80后90后不是越来越美丽了吗?

    设计师对于社会的贡献不是一个作品几个人能够体现的,是一群人通过他们点点滴滴的工作,经过漫长时间才能体现出来的。就像亚当·斯密在《国富论》里所述,每个人在“卑鄙”地追逐着私利的同时,无意识地贡献着社会。

    所以我说:设计师别无知地傲慢也别卑微地看小自己,我们的工作很有趣,只需要换个角度看她。快乐地追逐自己的小小成绩吧!也别想着把自己的设计公司做的多大垄断市场,那些都是无知的徒劳和贪婪的欲望,它不是设计。
    展开全文
  • 来源:http://blog.jobbole.com/36480/编译:oschina,原文:Go at Google: Language... 摘要(本文是根据Rob Pike于2012年10月25日在Tucson, Arizona举行的SPLASH 2012大会上所做的主题演讲进行修改后所撰写的。)针对我


    来源:http://blog.jobbole.com/36480/


    编译:oschina,原文:Go at Google: Language Design in the Service of Software Engineering

    1. 摘要

    (本文是根据Rob Pike于2012年10月25日在Tucson, Arizona举行的SPLASH 2012大会上所做的主题演讲进行修改后所撰写的。)

    针对我们在Google公司内开发软件基础设施时遇到的一些问题,我们于2007年末构思出Go编程语言。当今的计算领域同创建如今所使用的编程语言(使用最多的有C++、Java和Python)时的环境几乎没什么关系了。由多核处理器、系统的网络化、大规模计算机集群和Web编程模型带来的编程问题都是以迂回的方式而不是迎头而上的方式解决的。此外,程序的规模也已发生了变化:现在的服务器程序由成百上千甚至成千上万的程序员共同编写,源代码也以数百万行计,而且实际上还需要每天都进行更新。更加雪上加霜的是,即使在大型编译集群之上进行一次build,所花的时间也已长达数十分钟甚至数小时。

    之所以设计开发Go,就是为了提高这种环境下的工作效率。Go语言设计时考虑的因素,除了大家较为了解的内置并发和内存垃圾自动回收这些方面之外,还包括严格的依赖管理、对随系统增大而在体系结构方面发生变化的适应性、跨组件边界的健壮性(robustness)。

    本文将详细讲解在构造一门轻量级并让人感觉愉悦的、高效的编译型编程语言时,这些问题是如何得到解决的。讲解过程中使用的例子都是来自Google公司中所遇到的现实问题。

    2. 简介

    Go语言开发自Google,是一门支持并发编程和内存垃圾回收的编译型静态类型语言。它是一个开源的项目:Google从公共的代码库中导入代码而不是相反。

    Go语言运行效率高,具有较强的可伸缩性(scalable),而且使用它进行工作时的效率也很高。有些程序员发现用它编程很有意思;还有一些程序员认为它缺乏想象力甚至很烦人。在本文中我们将解释为什么这两种观点并不相互矛盾。Go是为解决Google在软件开发中遇到的问题而设计的,虽然因此而设计出的语言不会是一门在研究领域里具有突破性进展的语言,但它却是大型软件项目中软件工程方面的一个非常棒的工具。

    3. Google公司中的Go语言

    为了帮助解决Google自己的问题,Google设计了Go这门编程语言,可以说,Google有很大的问题。

    硬件的规模很大而且软件的规模也很大。软件的代码行数以百万计,服务器软件绝大多数用的是C++,还有很多用的是Java,剩下的一部分还用到了Python。成千上万的工程师在这些代码上工作,这些代码位于由所有软件组成的一棵树上的“头部”,所以每天这棵树的各个层次都会发生大量的修改动作。尽管使用了一个大型自主设计的分布式Build系统才让这种规模的开发变得可行,但这个规模还是太大 了。

    当然,所有这些软件都是运行在无数台机器之上的,但这些无数台的机器只是被看做数量并不多若干互相独立而仅通过网络互相连接的计算机集群。

    简言之,Google公司的开发规模很大,速度可能会比较慢,看上去往往也比较笨拙。但很效果。

    Go项目的目标是要消除Google公司软件开发中的慢速和笨拙,从而让开发过程更加高效并且更加具有可伸缩性。该语言的设计者和使用者都是要为大型软件系统编写、阅读和调试以及维护代码的人。

    因此,Go语言的目的不是要在编程语言设计方面进行科研;它要能为它的设计者以及设计者的同事们改善工作环境。Go语言考虑更多的是软件工程而不是编程语言方面的科研。或者,换句话说,它是为软件工程服务而进行的语言设计。

    但是,编程语言怎么会对软件工程有所帮助呢?下文就是该问题的答案。

    4. 痛之所在

    当Go刚推出来时,有人认为它缺乏某些大家公认的现代编程语言中所特有的特性或方法论。缺了这些东西,Go语言怎么可能会有存在的价值?我们回答这个问题的答案在于,Go的确具有一些特性,而这些特性可以解决困扰大规模软件开发的一些问题。这些问题包括:

    • Build速度缓慢
    • 失控的依赖关系
    • 每个程序员使用同一门语言的不同子集
    • 程序难以理解(代码难以阅读,文档不全面等待)
    • 很多重复性的劳动
    • 更新的代价大
    • 版本偏斜(version skew)
    • 难以编写自动化工具
    • 语言交叉Build(cross-language build)产生的问题

    一门语言每个单个的特性都解决不了这些问题。这需要从软件工程的大局观,而在Go语言的设计中我们试图致力于解决所有这些问题。

    举个简单而独立的例子,我们来看看程序结果的表示方式。有些评论者反对Go中使用象C一样用花括号表示块结构,他们更喜欢Python或Haskell风格式,使用空格表示缩进。可是,我们无数次地碰到过以下这种由语言交叉Build造成的Build和测试失败:通过类似SWIG调用的方式,将一段Python代码嵌入到另外一种语言中,由于修改了这段代码周围的一些代码的缩进格式,从而导致Python代码也出乎意料地出问题了并且还非常难以觉察。 因此,我们的观点是,虽然空格缩进对于小规模的程序来说非常适用,但对大点的程序可不尽然,而且程序规模越大、代码库中的代码语言种类越多,空格缩进造成的问题就会越多。为了安全可靠,舍弃这点便利还是更好一点,因此Go采用了花括号表示的语句块。

    5.C和C++中的依赖

    在处理包依赖(package dependency)时会出现一些伸缩性以及其它方面的问题,这些问题可以更加实质性的说明上个小结中提出的问题。让我们先来回顾一下C和C++是如何处理包依赖的。

    ANSI C第一次进行标准化是在1989年,它提倡要在标准的头文件中使用#ifndef这样的”防护措施”。 这个观点现已广泛采用,就是要求每个头文件都要用一个条件编译语句(clause)括起来,这样就可以将该头文件包含多次而不会导致编译错误。比如,Unix中的头文件<sys/stat.h>看上去大致是这样的:

    1
    2
    3
    4
    5
    /* Large copyright and licensing notice */
    #ifndef _SYS_STAT_H_
    #define _SYS_STAT_H_
    /* Types and other definitions */
    #endif

    此举的目的是让C的预处理器在第二次以及以后读到该文件时要完全忽略该头文件。符号_SYS_STAT_H_在文件第一次读到时进行定义,可以“防止”后继的调用。

    这么设计有一些好处,最重要的是可以让每个头文件能够安全地include它所有的依赖,即时其它的头文件也有同样的include语句也不会出问题。 如果遵循此规则,就可以通过对所有的#include语句按字母顺序进行排序,让代码看上去更整洁。

    但是,这种设计的可伸缩性非常差。

    在1984年,有人发现在编译Unix中ps命令的源程序ps.c时,在整个的预处理过程中,它包含了<sys/stat.h>这个头文件37次之多。尽管在这么多次的包含中有36次它的文件的内容都不会被包含进来,但绝大多数C编译器实现都会把”打开文件并读取文件内容然后进行字符串扫描”这串动作做37遍。这么做可真不聪明,实际上,C语言的预处理器要处理的宏具有如此复杂的语义,其势必导致这种行为。

    对软件产生的效果就是在C程序中不断的堆积#include语句。多加一些#include语句并不会导致程序出问题,而且想判断出其中哪些是再也不需要了的也很困难。删除一条#include语句然后再进行编译也不太足以判断出来,因为还可能有另外一条#include所包含的文件中本身还包含了你刚刚删除的那条#include语句。

    从技术角度讲,事情并不一定非得弄成这样。在意识到使用#ifndef这种防护措施所带来的长期问题之后,Plan 9的library的设计者采取了一种不同的、非ANSI标准的方法。Plan 9禁止在头文件中使用#include语句,并要求将所有的#include语句放到顶层的C文件中。 当然,这么做需要一些训练 —— 程序员需要一次列出所有需要的依赖,还要以正确的顺序排列 —— 但是文档可以帮忙而且实践中效果也非常好。这么做的结果是,一个C源程序文件无论需要多少依赖,在对它进行编译时,每个#include文件只会被读一次。当然,这样一来,对于任何#include语句都可以通过先拿掉然后在进行编译的方式判断出这条#include语句到底有无include的必要:当且仅当不需要该依赖时,拿掉#include后的源程序才能仍然可以通过编译。

    Plan 9的这种方式产生的一个最重要的结果是编译速度比以前快了很多:采用这种方式后编译过程中所需的I/O量,同采用#ifndef的库相比,显著地减少了不少。

    但在Plan 9之外,那种“防护”式的方式依然是C和C++编程实践中大家广为接受的方式。实际上,C++还恶化了该问题,因为它把这种防护措施使用到了更细的粒度之上。按照惯例,C++程序通常采用每个类或者一小组相关的类拥有一个头文件这种结构,这种分组方式要更小,比方说,同<stdio.h>相比要小。因而其依赖树更加错综复杂,它反映的不是对库的依赖而是对完整类型层次结构的依赖。而且,C++的头文件通常包含真正的代码 —— 类型、方法以及模板声明 ——不像一般的C语言头文件里面仅仅有一些简单的常量定义和函数签名。这样,C++就把更多的工作推给了编译器,这些东西编译起来要更难一些,而且每次编译时编译器都必须重复处理这些信息。当要build一个比较大型的C++二进制程序时,编译器可能需要成千上万次地处理头文件<string>以了解字符串的表示方式。(根据当时的记录,大约在1984年,Tom Cargill说道,在C++中使用C预处理器来处理依赖管理将是个长期的不利因素,这个问题应该得到解决。)

    在Google,Build一个单个的C++二进制文件就能够数万次地打开并读取数百个头文件中的每个头文件。在2007年,Google的build工程师们编译了一次Google里一个比较主要的C++二进制程序。该文件包含了两千个文件,如果只是将这些文件串接到一起,总大型为4.2M。将#include完全扩展完成后,就有8G的内容丢给编译器编译,也就是说,C++源代码中的每个自己都膨胀成到了2000字节。 还有一个数据是,在2003年Google的Build系统转变了做法,在每个目录中安排了一个Makefile,这样可以让依赖更加清晰明了并且也能好的进行管理。一般的二进制文件大小都减小了40%,就因为记录了更准确的依赖关系。即使如此,C++(或者说C引起的这个问题)的特性使得自动对依赖关系进行验证无法得以实现,直到今天我们仍然我发准确掌握Google中大型的C++二进制程序的依赖要求的具体情况。

    由于这种失控的依赖关系以及程序的规模非常之大,所以在单个的计算机上build出Google的服务器二进制程序就变得不太实际了,因此我们创建了一个大型分布式编译系统。该系统非常复杂(这个Build系统本身也是个大型程序)还使用了大量机器以及大量缓存,藉此在Google进行Build才算行得通了,尽管还是有些困难。 即时采用了分布式Build系统,在Google进行一次大规模的build仍需要花几十分钟的时间才能完成。前文提到的2007年那个二进制程序使用上一版本的分布式build系统花了45分钟进行build。现在所花的时间是27分钟,但是,这个程序的长度以及它的依赖关系在此期间当然也增加了。为了按比例增大build系统而在工程方面所付出的劳动刚刚比软件创建的增长速度提前了一小步。

    6. 走进 Go 语言

    当编译缓慢进行时,我们有充足的时间来思考。关于 Go 的起源有一个传说,话说正是一次长达45分钟的编译过程中,Go 的设想出现了。人们深信,为类似谷歌网络服务这样的大型程序编写一门新的语言是很有意义的,软件工程师们认为这将极大的改善谷歌程序员的生活质量。

    尽管现在的讨论更专注于依赖关系,这里依然还有很多其他需要关注的问题。这一门成功语言的主要因素是:

    • 它必须适应于大规模开发,如拥有大量依赖的大型程序,且又一个很大的程序员团队为之工作。
    • 它必须是熟悉的,大致为 C 风格的。谷歌的程序员在职业生涯的早期,对函数式语言,特别是 C家族更加熟稔。要想程序员用一门新语言快速开发,新语言的语法不能过于激进。
    • 它必须是现代的。C、C++以及Java的某些方面,已经过于老旧,设计于多核计算机、网络和网络应用出现之前。新方法能够满足现代世界的特性,例如内置的并发。

    说完了背景,现在让我们从软件工程的角度谈一谈 Go 语言的设计。

    7. Go 语言的依赖处理

    既然我们谈及了很多C 和 C++ 中依赖关系处理细节,让我们看看 Go 语言是如何处理的吧。在语义和语法上,依赖处理是由语言定义的。它们是明确的、清晰的、且“能被计算的”,就是说,应该很容易被编写工具分析。

    在包封装(下节的主题)之后,每个源码文件都或有至少一个引入语句,包括 import 关键词和一个用来明确当前(只是当前)文件引入包的字符串:

    1
    import "encoding/json"

    使 Go 语言规整的第一步就是:睿智的依赖处理,在编译阶段,语言将未被使用的依赖视为错误(并非警告,是错误)。如果源码文件引入一个包却没有使用它,程序将无法完成编译。这将保证 Go 程序的依赖关系是明确的,没有任何多余的边际。另一方面,它可以保证编译过程不会包含无用代码,降低编译消耗的时间。

    第二步则是由编译器实现的,它将通过深入依赖关系确保编译效率。设想一个含有三个包的 Go 程序,其依赖关系如下:

    • A 包 引用 B 包;
    • B 包 引用 C 包;
    • A 包 引用 C 包

    这就意味着,A 包对 C 包的调用是由对 B 包的调用间接实现的;也就是说,在 A 包的代码中,不存在 C 包的标识符。例如,C 包中有一个类型定义,它是 B 包中的某个为 A 包调用的结构体中的字段类型,但其本身并未被 A 包调用。具一个更实际的例子,设想一下,A 包引用了一个 格式化 I/O 包 B,B 包则引用了 C 包提供的缓冲 I/O 实现,A 包本身并没有声明缓冲 I/O。

    要编译这个程序,首先 C 被编译,被依赖的包必须在依赖于它们的包之前被编译。之后 B 包被编译;最后 A 包被编译,然后程序将被连接。

    当 A 包编译完成之后,编译器将读取 B 包的目标文件,而不是代码。此目标文件包含编译器处理 A 包代码中

    1
    import "B"

    语句所需的所有类型信息。这些信息也包含着 B 包在编译是所需的 C 包的信息。换句话说,当 B 包被编译时,生成的目标文件包含了所有 B 包公共接口所需的全部依赖的类型信息。

    这种设计拥有很重要的意义,当编译器处理 import 语句时,它将打开一个文件——该语句所明确的对象文件。当然,这不由的让人想起 Plan 9 C (非 ANSI C)对依赖管理方法,但不同的是,当 Go 代码文件被编译完成时,编译器将写入头文件。同 Plan 9 C 相比,这个过程将更自动化、更高效,因为:在处理 import 时读取的数据只是“输出”数据,而非程序代码。这对编译效率的影响是巨大的,而且,即便代码增长,程序依然规整如故。处理依赖树并对之编译的时间相较于 C 和 C++ 的“引入被引用文件”的模型将极大的减少。

    值得一提的是,这个依赖管理的通用方法并不是原始的;这些思维要追溯到1970年代的像Modula-2和Ada语言。在C语言家族里,Java就包含这一方法的元素。

    为了使编译更加高效,对象文件以导出数据作为它的首要步骤,这样编译器一旦到达文件的末尾就可以停止读取。这种依赖管理方法是为什么Go编译比C或C++编译更快的最大原因。另一个因素是Go语言把导出数据放在对象文件中;而一些语言要求程序员编写或让编译器生成包含这一信息的另一个文件。这相当于两次打开文件。在Go语言中导入一个程序包只需要打开一次文件。并且,单一文件方法意味着导出数据(或在C/C++的头文件)相对于对象文件永远不会过时。

    为了准确起见,我们对Google中用Go编写的某大型程序的编译进行了测算,将源代码的展开情况同前文中对C++的分析做一对比。结果发现是40倍,要比C++好50倍(同样也要比C++简单因而处理速度也快),但是这仍然比我们预期的要大。原因有两点。第一,我们发现了一个bug:Go编译器在export部分产生了大量的无用数据。第二,export数据采用了一种比较冗长的编码方式,还有改善的余地。我们正计划解决这些问题。

    然而,仅需作50分之1的事情就把原来的Build时间从分钟级的变为秒级的,将咖啡时间转化为交互式build。

    Go的依赖图还有另外一个特性,就是它不包含循环。Go语言定义了不允许其依赖图中有循环性的包含关系,编译器和链接器都会对此进行检查以确保不存在循环依赖。虽然循环依赖偶尔也有用,但它在大规模程序中会引入巨大的问题。循环依赖要求编译器同时处理大量源文件,从而会减慢增量式build的速度。更重要的是,如果允许循环依赖,我们的经验告诉我们,这种依赖最后会形成大片互相纠缠不清的源代码树,从而让树中各部分也变得很大,难以进行独立管理,最后二进制文件会膨胀,使得软件开发中的初始化、测试、重构、发布以及其它一些任务变得过于复杂。

    不支持循环import偶尔会让人感到苦恼,但却能让依赖树保持清晰明了,对package的清晰划分也提了个更高的要求。就象Go中其它许多设计决策一样,这会迫使程序员早早地就对一些大规模程序里的问题提前进行思考(在这种情况下,指的是package的边界),而这些问题一旦留给以后解决往往就会永远得不到满意的解决。 在标准库的设计中,大量精力花在了控制依赖关系上了。为了使用一个函数,把所需的那一小段代码拷贝过来要比拉进来一个比较大的库强(如果出现新的核心依赖的话,系统build里的一个test会报告问题)。在依赖关系方面保持良好状况要比代码重用重要。在实践中有这样一个例子,底层的网络package里有自己的整数到小数的转换程序,就是为了避免对较大的、依赖关系复杂的格式化I/O package的依赖。还有另外一个例子,字符串转换package的strconv拥有一个对‘可打印’字符的进行定义的private实现,而不是将整个大哥的Unicode字符类表格拖进去, strconv里的Unicode标准是通过package的test进行验证的。

    8. 包

    Go 的包系统设计结合了一些库、命名控件和模块的特性。

    每个 Go 的代码文件,例如“encoding/json/json.go”,都以包声明开始,如同:

    1
    package json

    “json” 就是“包名称”,一个简单的识别符号。通常包名称都比较精炼。
    要使用包,使用 import 声明引入代码,并以 包路径 区分。“路径”的意义并未在语言中指定,而是约定为以/分割的代码包目录路径,如下:

    1
    import "encoding/json"

    后面用包名称(有别于路径)则用来限定引入自代码文件中包的条目。

    1
    var dec = json.NewDecoder(reader)

    这种设计非常清晰,从语法(Namevs.pkg.Name)上就能识别一个名字是否属于某个包(在此之后)。

    在我们的示例中,包的路径是“encoding/json”而包的名称是 json。标准资源库以外,通常约定以项目或公司名作为命名控件的根:

    1
    import "google/base/go/log

    确认包路径的唯一性非常重要,而对包名称则不必强求。包必须通过唯一的路径引入,而包名称则为引用者调用内容方式的一个约定。包名称不必唯一,可以通过引入语句重命名识别符。下面有两个自称为“package log”的包,如果要在单个源码文件中引入,需要在引入时重命名一个。

    1
    2
    import "log"                          // Standard package
    import googlelog "google/base/go/log" // Google-specific package

    每个公司都可能有自己的 log 包,不必要特别命名。恰恰相反:Go 的风格建议包名称保持简短和清晰,且不必担心冲突。

    另一个例子:在 Google 代码库中有很多server 库。

    9. 远程包

    Go的包管理系统的一个重要特性是包路径,通常是一个字符串,通过识别 网站资源的URL 可以增加远程存储库。

    下面就是如何使用储存在 GitHub 上的包。go get 命令使用 go 编译工具获取资源并安装。一旦安装完毕,就可以如同其它包一样引用它。

    1
    2
    3
    4
    5
    $ go get github.com/4ad/doozer // Shell command to fetch package
     
    import "github.com/4ad/doozer" // Doozer client's import statement
     
    var client doozer.Conn         // Client's use of package

    这是值得注意的,go get 命令递归下载依赖,此特性得以实现的原因就是依赖关系的明确性。另外,由于引入路径的命名空间依赖于 URL,使得 Go 相较于其它语言,在包命名上更加分散和易于扩展。

    10. 语法

    语法就是编程语言的用户界面。虽然对于一门编程语言来说更重要的是语意,并且语法对于语意的影响也是有限的,但是语法决定了编程语言的可读性和明确性。同时,语法对于编程语言相关工具的编写至关重要:如果编程语言难以解析,那么自动化工具也将难以编写。

    Go语言因此在设计阶段就为语言的明确性和相关工具的编写做了考虑,设计了一套简洁的语法。与C语言家族的其他几个成员相比,Go语言的词法更为精炼,仅25个关键字(C99为37个;C++11为84个;并且数量还在持续增加)。更为重要的是,Go语言的词法是规范的,因此也是易于解析的(应该说绝大部分是规范的;也存在一些我们本应修正却没有能够及时发现的怪异词法)。与C、Java特别是C++等语言不同,Go语言可以在没有类型信息或者符号表的情况下被解析,并且没有类型相关的上下文信息。Go语言的词法是易于推论的,降低了相关工具编写的难度。

    Go 语法不同于 C 的一个细节是,它的变量声明语法相较于 C 语言,更接近 Pascal 语言。声明的变量名称在类型之前,而有更多的关键词很:

    1
    2
    var fn func([]int) int
    type T struct { a, b int }

    相较于 C 语言

    1
    2
    int (*fn)( int []);
    struct T { int a, b; }

    无论是对人还是对计算机,通过关键词进行变量声明将更容易被识别。而通过类型语法而非 C 的表达式语法对词法分析有一个显著的影响:它增加了语法,但消除了歧义。不过,还有一个:你可以丢掉 var 关键词,而只在表达式用使用变量的类型。两种变量声明是等价的;只是第二个更简短且共通用:

    1
    2
    var buf *bytes.Buffer = bytes.NewBuffer(x) // 精确
    buf := bytes.NewBuffer(x)                  // 衍生

    golang.org/s/decl-syntax 是一篇更详细讲解 Go 语言声明语句以及为什么同 C 如此不同的文章。

    函数声明语法对于简单函数非常直接。这里有一个 Abs 函数的声明示例,它接受一个类型为 T 的变量 x,并返回一个64位浮点值:

    1
    func Abs(x T) float64

    一个方法只是一个拥有特殊参数的函数,而它的 接收器(receiver)则可以使用标准的“点”符号传递给函数。方法的声明语法将接收器放在函数名称之前的括号里。下面是一个与之前相同的函数,但它是 T 类型的一个方法:

    1
    func (x T) Abs() float64

    下面则是拥有 T 类型参数的一个变量(闭包);Go 语言拥有第一类函数和闭包功能:

    1
    negAbs := func(x T) float64 { return -Abs(x) }

    最后,在 Go 语言中,函数可以返回多个值。通用的方法是成对返回函数结果和错误值,例如:

    1
    2
    3
    4
    func ReadByte() (c byte, err error)
     
    c, err := ReadByte()
    if err != nil { ... }

    我们过会儿再说错误。

    Go语言缺少的一个特性是它不支持缺省参数。这是它故意简化的。经验告诉我们缺省参数太容易通过添加更多的参数来给API设计缺陷打补丁,进而导致太多使程序难以理清深圳费解的交互参数。默认参数的缺失要求更多的函数或方法被定义,因为一个函数不能控制整个接口,但这使得一个API更清晰易懂。哪些函数也都需要独立的名字, 使程序更清楚存在哪些组合,同时也鼓励更多地考虑命名–一个有关清晰性和可读性的关键因素。一个默认参数缺失的缓解因素是Go语言为可变参数函数提供易用和类型安全支持的特性。

    11. 命名

    Go 采用了一个不常见的方法来定义标识符的可见性(可见性:包使用者(client fo a package)通过标识符使用包内成员的能力)。Go 语言中,名字自己包含了可见性的信息,而不是使用常见的private,public等关键字来标识可见性:标识符首字母的大小写决定了可见性。如果首字母是大写字母,这个标识符是exported(public); 否则是私有的。

    • 首字母大写:名字对于包使用者可见
    • 否则:name(或者_Name)是不可见的。

    这条规则适用于变量,类型,函数,方法,常量,域成员…等所有的东西。关于命名,需要了解的就这么多。

    这个设计不是个容易的决定。我们挣扎了一年多来决定怎么表示可见性。一旦我们决定了用名字的大小写来表示可见性,我们意识到这变成了Go语言最重要特性之一。毕竟,包使用者使用包时最关注名字;把可见性放在名字上而不是类型上,当用户想知道某个标示符是否是public接口,很容易就可以看出来。用了Go语言一段时间后,再用那些需要查看声明才知道可见性的语言就会觉得很麻烦。

    很清楚,这样再一次使程序源代码清晰简洁的表达了程序员的意图。

    另一个简洁之处是Go语言有非常紧凑的范围体系:

    • 全局(预定义的标示符例如 int 和 string)
    • 包(包里的所有源代码文件在同一个范围)
    • 文件(只是在引入包时重命名,实践中不是很重要)
    • 函数(所有函数都有,不解释)
    • 块(不解释)

    Go语言没有命名空间,类或者其他范围。名字只来源于很少的地方,而且所有名字都遵循一样的范围体系:在源码的任何位置,一个标示符只表示一个语言对象,而独立于它的用法。(唯一的例外是语句标签(label)-break和其他类似跳转语句的目标地址;他们总是在当前函数范围有效)。

    这样就使Go语言很清晰。例如,方法总是显式(expicit)的表明接受者(receiver)-用来访问接受者的域成员或者方法,而不是隐式(impliciti)的调用。也就是,程序员总是写

    1
    rcvr.Field

    (rcvr 代表接受者变量) 所以在词法上(lexically),每个元素总是绑定到接受者类型的某个值。 同样,包命修饰符(qualifier)总是要写在导入的名字前-要写成io.Reader而不是Reader。除了更清晰,这样Reader这种很常用的名字可以使用在任何包中。事实上,在标准库中有多个包都导出Reader,Printf这些名字,由于加上包的修饰符,这些名字引用于那个包就很清晰,不会被混淆。

    最终,这些规则组合起来确保了:除了顶级预先定义好的名字例如 int,每一个名字(的第一个部分-x.y中的x)总是声明在当前包。

    简单说,名字是本地的。在C,C++,或者Java名字 y 可以指向任何事。在Go中,y(或Y)总是定义在包中, x.Y 的解释也很清晰:本地查找x,Y就在x里。

    这些规则为可伸缩性提供了一个很重要的价值,因为他们确保为一个包增加一个公开的名字不会破坏现有的包使用者。命名规则解耦包,提供了可伸缩性,清晰性和强健性。

    关于命名有一个更重要的方面要说一下:方法查找总是根据名字而不是方法的签名(类型) 。也就是说,一个类型里不会有两个同名的方法。给定一个方法 x.M,只有一个M在x中。这样,在只给定名字的情况下,这种方法很容易可以找到它指向那个方法。这样也使的方法调用的实现简单化了。

    12. 语意

    Go语言的程序语句在语意上基本与C相似。它是一种拥有指针等特性的编译型的、静态类型的过程式语言。它有意的给予习惯于C语言家族的程序员一种熟悉感。对于一门新兴的编程语言来说,降低目标受众程序员的学习门槛是非常重要的;植根于C语言家族有助于确保那些掌握Java、JavaScript或是C语言的年轻程序员能更轻松的学习Go语言。

    尽管如此,Go语言为了提高程序的健壮性,还是对C语言的语意做出了很多小改动。它们包括:

    • 不能对指针进行算术运算
    • 没有隐式的数值转换
    • 数组的边界总是会被检查
    • 没有类型别名(进行type X int的声明后,X和int是两种不同的类型而不是别名)
    • ++和–是语句而不是表达式
    • 赋值不是一种表达式
    • 获取栈变量的地址是合法的(甚至是被鼓励的)
    • 其他

    还有一些很大的改变,同传统的C 、C++ 、甚至是JAVA 的模型十分不同。它包含了对以下功能的支持:

    • 并发
    • 垃圾回收
    • 接口类型
    • 反射
    • 类型转换

    下面的章节从软件工程的角度对 Go 语言这几个主题中的两个的讨论:并发和垃圾回收。对于语言的语义和应用的完整讨论,请参阅 golang.org 网站中的更多资源。

    13. 并发

    运行于多核机器之上并拥有众多客户端的web服务器程序,可称为Google里最典型程序。在这样的现代计算环境中,并发很重要。这种软件用C++或Java做都不是特别好,因为它们缺在与语言级对并发支持的都不够好。

    Go采用了一流的channel,体现为CSP的一个变种。之所以选择CSP,部分原因是因为大家对它的熟悉程度(我们中有一位同事曾使用过构建于CSP中的概念之上的前任语言),另外还因为CSP具有一种在无须对其模型做任何深入的改变就能轻易添加到过程性编程模型中的特性。也即,对于类C语言,CSP可以一种最长正交化(orthogonal)的方式添加到这种语言中,为该语言提供额外的表达能力而且还不会对该语言的其它用它施加任何约束。简言之,就是该语言的其它部分仍可保持“通常的样子”。

    这种方法就是这样对独立执行非常规过程代码的组合。

    结果得到的语言可以允许我们将并发同计算无缝结合都一起。假设Web服务器必须验证它的每个客户端的安全证书;在Go语言中可以很容易的使用CSP来构建这样的软件,将客户端以独立执行的过程来管理,而且还具有编译型语言的执行效率,足够应付昂贵的加密计算。

    总的来说,CSP对于Go和Google来说非常实用。在编写Web服务器这种Go语言的典型程序时,这个模型简直是天作之合。

    有一条警告很重要:因为有并发,所以Go不能成为纯的内存安全(memory safe)的语言。共享内存是允许的,通过channel来传递指针也是一种习惯用法(而且效率很高)。

    有些并发和函数式编程专家很失望,因为Go没有在并发计算的上下文中采用只写一次的方式作为值语义,比如这一点上Go和Erlang就太象。其中的原因大体上还是在于对问题域的熟悉程度和适合程度。Go的并发特性在大多数程序员所熟悉的上下文中运行得很好。Go让使得简单而安全的并发编程成为可能,但它并不阻止糟糕的编程方式。这个问题我们通过惯例来折中,训练程序员将消息传递看做拥有权限控制的一个版本。有句格言道:“不要通过共享内存来通信,要通过通信来共享内存。”

    在对Go和并发编程都是刚刚新接触的程序员方面我们经验有限,但也表明了这是一种非常实用的方式。程序员喜欢这种支持并发为网络软件所带来的简单性,而简单性自然会带来健壮性。

    14. 垃圾回收

    对于一门系统级的编程语言来说,垃圾回收可能会是一项非常有争议的特性,但我们还是毫不犹豫地确定了Go语言将会是一门拥有垃圾回收机制的编程语言。Go语言没有显式的内存释放操作,那些被分配的内存只能通过垃圾回收器这一唯一途径来返回内存池。

    做出这个决定并不难,因为内存管理对于一门编程语言的实际使用方式有着深远的影响。在C和C++中,程序员们往往需要花费大量的时间和精力在内存的分配和释放上,这样的设计有助于暴露那些本可以被隐藏得很好的内存管理的细节;但反过来说,对于内存使用的过多考量又限制了程序员使用内存的方式。相比之下,垃圾回收使得接口更容易被指定。

    此外,拥有自动化的内存管理机制对于一门并发的面向对象的编程语言来说很关键,因为一个内存块可能会在不同的并发执行单元间被来回传递,要管理这样一块内存的所有权对于程序员来说将会是一项挑战。将行为与资源的管理分离是很重要的。

    垃圾回收使得Go语言在使用上显得更加简单。

    当然,垃圾回收机制会带来很大的成本:资源的消耗、回收的延迟以及复杂的实现等。尽管如此,我们相信它所带来的好处,特别是对于程序员的编程体验来说,是要大于它所带来的成本的,因为这些成本大都是加诸在编程语言的实现者身上。

    在面向用户的系统中使用Java来进行服务器编程的经历使得一些程序员对垃圾回收顾虑重重:不可控的资源消耗、极大的延迟以及为了达到较好的性能而需要做的一大堆参数优化。Go语言则不同,语言本身的属性能够减轻以上的一些顾虑,虽然不是全部。

    有个关键点在于,Go为程序员提供了通过控制数据结构的格式来限制内存分配的手段。请看下面这个简单的类型定义了包含一个字节(数组)型的缓冲区:

    1
    2
    3
    4
    type X struct {
         a, b, c int
         buf [256]byte
    }

    在Java中,buffer字段需要再次进行内存分配,因为需要另一层的间接访问形式。然而在Go中,该缓冲区同包含它的struct一起分配到了一块单独的内存块中,无需间接形式。对于系统编程,这种设计可以得到更好的性能并减少回收器(collector)需要了解的项目数。要是在大规模的程序中,这么做导致的差别会非常巨大。

    有个更加直接一点的例子,在Go中,可以非常容易和高效地提供二阶内存分配器(second-order allocator),例如,为一个由大量struct组成的大型数组分配内存,并用一个自由列表(a free list)将它们链接起来的arena分配器(an arena allocator)。在重复使用大量小型数据结构的库中,可以通过少量的提前安排,就能不产生任何垃圾还能兼顾高效和高响应度。

    虽然Go是一种支持内存垃圾回收的编程语言,但是资深程序员能够限制施加给回收器的压力从而提高程序的运行效率(Go的安装包中还提供了一些非常好的工具,用这些工具可以研究程序运行过程中动态内存的性能。)

    要给程序员这样的灵活性,Go必需支持指向分配在堆中对象的指针,我们将这种指针称为内部指针。上文的例子中X.buff字段保存于struct之中,但也可以保留这个内部字段的地址。比如,可以将这个地址传递给I/O子程序。在Java以及许多类似的支持垃圾回收的语音中,不可能构造象这样的内部指针,但在Go中这么做很自然。这样设计的指针会影响可以使用的回收算法,并可能会让算法变得更难写,但经过慎重考虑,我们决定允许内部指针是必要的,因为这对程序员有好处,让大家具有降低对(可能实现起来更困难)回收器的压力的能力。到现在为止,我们的将大致相同的Go和Java程序进行对比的经验表明,使用内部指针能够大大影响arena总计大型、延迟和回收次数。

    总的说来,Go是一门支持垃圾回收的语言,但它同时也提供给程序员一些手段,可以对回收开销进行控制。

    垃圾回收器目前仍在积极地开发中。当前的设计方案是并行的边标示边扫描(mark-and-sweep)的回收器,未来还有机会提高其性能甚至其设计方案。(Go语言规范中并没有限定必需使用哪种特定的回收器实现方案)。尽管如此,如果程序员在使用内存时小心谨慎,当前的实现完全可以在生产环境中使用。

    15. 要组合,不要继承

    Go 采用了一个不寻常的方法来支持面向对象编程,允许添加方法到任意类型,而不仅仅是class,但是并没有采用任何类似子类化的类型继承。这也就意味着没有类型体系(type hierarchy)。这是精心的设计选择。虽然类型继承已经被用来建立很多成功的软件,但是我们认为它还是被过度使用了,我们应该在这个方向上退一步。

    Go使用接口(interface), 接口已经在很多地方被详尽的讨论过了 (例如 research.swtch.com/interfaces ), 但是这里我还是简单的说一下。

    在 Go 中,接口只是一组方法。例如,下面是标准库中的Hash接口的定义。

    1
    2
    3
    4
    5
    6
    7
    type Hash interface {
         Write(p []byte) (n int, err error)
         Sum(b []byte) []byte
         Reset()
         Size() int
         BlockSize() int
    }

    实现了这组方法的所有数据类型都满足这个接口;而不需要用implements声明。即便如此,由于接口匹配在编译时静态检查,所以这样也是类型安全的。

    一个类型往往要满足多个接口,其方法的每一个子集满足每一个接口。例如,任何满足Hash接口的类型同时也满足Writer接口:

    1
    2
    3
    type Writer interface {
         Write(p []byte) (n int, err error)
    }

    这种接口满足的流动性会促成一种不同的软件构造方法。但在解释这一点之前,我们应该先解释一下为什么Go中没有子类型化(subclassing)。

    面向对象的编程提供了一种强大的见解:数据的行为可以独立于数据的表示进行泛化。这个模型在行为(方法集)是固定不变的情况下效果最好,但是,一旦你为某类型建立了一个子类型并添加了一个方法后,其行为就再也不同了。如果象Go中的静态定义的接口这样,将行为集固定下来,那么这种行为的一致性就使得可以把数据和程序一致地、正交地(orthogonally)、安全地组合到一起了。

    有个极端一点的例子,在Plan 9的内核中,所有的系统数据项完全都实现了同一个接口,该接口是一个由14个方法组成的文件系统API。即使在今天看来,这种一致性所允许的对象组合水平在其它系统中是很罕见的。这样的例子数不胜数。这里还有一个:一个系统可以将TCP栈导入(这是Plan 9中的术语)一个不支持TCP甚至以太网的计算机中,然后通过网络将其连接到另一台具有不同CPU架构的机器上,通过导入其/proctree,就可以允许一个本地的调试器对远程的进程进行断点调试。这类操作在Plan 9中很是平常,一点也不特殊。能够做这样的事情的能力完全来自其设计方案,无需任何特殊安排(所有的工作都是在普通的C代码中完成的)。

    我们认为,这种系统构建中的组合风格完全被推崇类型层次结构设计的语言所忽略了。类型层次结构造成非常脆弱的代码。层次结构必需在早期进行设计,通常会是程序设计的第一步,而一旦写出程序后,早期的决策就很难进行改变了。所以,类型层次结构这种模型会促成早期的过度设计,因为程序员要尽力对软件可能需要的各种可能的用法进行预测,不断地为了避免挂一漏万,不断的增加类型和抽象的层次。这种做法有点颠倒了,系统各个部分之间交互的方式本应该随着系统的发展而做出相应的改变,而不应该在一开始就固定下来。

    因此,通过使用简单到通常只有一个方法的接口来定义一些很细小的行为,将这些接口作为组件间清晰易懂的边界, Go鼓励使用组合而不是继承

    上文中提到过Writer接口,它定义于io包中。任何具有相同签名(signature)的Write方法的类型都可以很好的同下面这个与之互补的Reader接口共存:

    1
    2
    3
    type Reader interface {
         Read(p []byte) (n int, err error)
    }

    这两个互补的方法可以拿来进行具有多种不同行为的、类型安全的连接(chaining),比如,一般性的Unix管道。文件、缓冲区、加密程序、压缩程序、图像编码程序等等都能够连接到一起。与C中的FILE*不同,Fprintf格式化I/O子程序带有anio.Writer。格式化输出程序并不了解它要输出到哪里;可能是输出给了图像编码程序,该程序接着输出给了压缩程序,该程序再接着输出给了加密程序,最后加密程序输出到了网络连接之中。

    接口组合是一种不同的编程风格,已经熟悉了类型层次结构的人需要调整其思维方式才能做得好,但调整思维所得到的是类型层次结构中难以获得的具有高度适应性的设计方案。

    还要注意,消除了类型层次结构也就消除了一种形式的依赖层次结构。接口满足式的设计使得程序无需预先确定的合约就能实现有机增长,而且这种增长是线性的;对一个接口进行更改影响的只有直接使用该接口的类型;不存在需要更改的子树。 没有implements声明会让有些人感觉不安但这么做可以让程序以自然、优雅、安全的方式进行发展。

    Go的接口对程序设计有一个主要的影响。我们已经看到的一个地方就是使用具有接口参数的函数。这些不是方法而是函数。几个例子就应该能说明它们的威力。ReadAll返回一段字节(数组),其中包含的是能够从anio.Reader中读出来的所有数据:

    1
    func ReadAll(r io.Reader) ([]byte, error)

    封装器 —— 指的是以接口为参数并且其返回结果也是一个接口的函数,用的也很广泛。这里有几个原型。LoggingReader将每次的Read调用记录到传人的参数r这个Reader中。LimitingReader在读到n字节后便停止读取操作。ErrorInjector通过模拟I/O错误用以辅助完成测试工作。还有更多的例子。

    1
    2
    3
    func LoggingReader(r io.Reader) io.Reader
    func LimitingReader(r io.Reader, n int64) io.Reader
    func ErrorInjector(r io.Reader) io.Reader

    这种设计方法同层次型的、子类型继承方法完全不同。它们更加松散(甚至是临时性的),属于有机式的、解耦式的、独立式的,因而具有强大的伸缩性。

    16. 错误

    Go不具有传统意义上的异常机制,也就是说,Go里没有同错误处理相关的控制结构。(Go的确为类似被零除这样的异常情况的提供了处理机制。 有一对叫做panic和recover的内建函数,用来让程序员处理这些情况。然而,这些函数是故意弄的不好用因而也很少使用它们,而且也不像Java库中使用异常那样,并没有将它们集成到库中。)

    Go语言中错误处理的一个关键特性是一个预先定义为error的接口类型,它具有一个返回一个字符串读到Error方法,表示了一个错误值。:

    1
    2
    3
    type error interface {
         Error() string
    }

    Go的库使用error类型的数据返回对错误的描述。结合函数具有返回多个数值的能力, 在返回计算结果的同时返回可能出现的错误值很容易实现。比如,Go中同C里的对应的getchar不会在EOF处返回一个超范围的值,也不会抛出异常;它只是返回在返回读到的字符的同时返回一个error值,以error的值为nil表示读取成功。以下所示为带缓冲区的I/O包中bufio.Reader类型的ReadByte方法的签名:

    1
    func (b *Reader) ReadByte() (c byte, err error)

    这样的设计简单清晰,也非常容易理解。error仅仅是一种值,程序可以象对其它别的类型的值一样,对error值进行计算。

    Go中不包含异常,是我们故意为之的。虽然有大量的批评者并不同意这个设计决策,但是我们相信有几个原因让我们认为这样做才能编写出更好的软件。

    首先,计算机程序中的错误并不是真正的异常情况。例如,无法打开一个文件是种常见的问题,无需任何的特殊语言结构,if和return完全可以胜任。

    1
    2
    3
    4
    f, err := os.Open(fileName)
    if err != nil {
         return err
    }

    再者,如果错误要使用特殊的控制结构,错误处理就会扭曲处理错误的程序的控制流(control flow)。象Java那样try-catch-finally语句结构会形成交叉重叠的多个控制流,这些控制流之间的交互方式非常复杂。虽然相比较而言,Go检查错误的方式更加繁琐,但这种显式的设计使得控制流更加直截了当 —— 从字面上的确如此。

    毫无疑问这会使代码更长一些,但如此编码带来的清晰度和简单性可以弥补其冗长的缺点。显式地错误检查会迫使程序员在错误出现的时候对错误进行思考并进行相应的处理。异常机制只是将错误处理推卸到了调用堆栈之中,直到错过了修复问题或准确诊断错误情况的时机,这就使得程序员更容易去忽略错误而不是处理错误了。

    17. 工具

    软件工程需要工具的支持。每种语言都要运行于同其它语言共存的环境,它还需要大量工具才能进行编译、编辑、调试、性能分析、测试已经运行。

    Go的语法、包管理系统、命名规则以及其它功能在设计时就考虑了要易于为这种语言编写工具以及包括词法分析器、语法分析器以及类型检测器等等在内的各种库。

    操作Go程序的工具非常容易编写,因此现在已经编写出了许多这样的工具,其中有些工具对软件工程来讲已经产生了一些值得关注的效果。

    其中最著名的是gofmt,它是Go源程序的格式化程序。该项目伊始,我们就将Go程序定位为由机器对其进行格式化, 从而消除了在程序员中具有争议的一大类问题:我要以什么样的格式写代码?我们对我们所需的所有Go程序运行Gofmt,绝大多数开源社区也用它进行代码格式化。 它是作为“提交前”的例行检查运行的,它在代码提交到代码库之前运行,以确保所有检入的Go程序都是具有相同的格式。

    Go fmt 往往被其使用者推崇为Go最好的特性之一,尽管它本身并属于Go语言的一个部分。 存在并使用gofmt意味着,从一开始社区里看到的Go代码就是用它进行格式化过的代码,因此Go程序具有现在已为人熟知的单一风格。同一的写法使得代码阅读起来更加容易,因而用起来速度也快。没有在格式化代码方面浪费的时间就是剩下来的时间。Gofmt也会影响伸缩性:既然所有的代码看上去格式完全相同,团队就更易于展开合作,用起别人的代码来也更容易。

    Go fmt 还让编写我们并没有清晰地预见到的另一类工具成为可能。Gofmt的运行原理就是对源代码进行语法分析,然后根据语法树本身对代码进行格式化。这让在格式化代码之前对语法树进行更改成为可能,因此产生了一批进行自动重构的工具。这些工具编写起来很容易,因为它们直接作用于语法分析树之上,因而其语义可以非常多样化,最后产生的格式化代码也非常规范。

    第一个例子就是gofmt本身的a-r(重写)标志,该标志采用了一种很简单的模式匹配语言,可以用来进行表达式级的重写。例如,有一天我们引入了一段表达式右侧缺省值:该段表达式的长度。整个Go源代码树要使用该缺省值进行更新,仅限使用下面这一条命令:

    1
    gofmt -r 'a[b:len(a)] -> a[b:]'

    该变换中的一个关键点在于,因为输入和输出二者均为规范格式(canonical format),对源代码的唯一更改也是语义上的更改

    采用与此类似但更复杂一些的处理就可以让gofmt用于在Go语言中的语句以换行而不再是分号结尾的情况下,对语法树进行相应的更新。

    gofix是另外一个非常重要的工具,它是语法树重写模块,而且它用Go语言本身所编写的,因而可以用来完成更加高级的重构操作。 gofix工具可以用来对直到Go 1发布为止的所有API和语言特性进行全方位修改,包括修改从map中删除数据项的语法、引入操作时间值的一个完全不同的API等等很多更新。随着这些更新一一推出,使用者可以通过运行下面这条简单的命令对他们的所有代码进行更新

    1
    gofix

    注意,这些工具允许我们即使在旧代码仍旧能够正常运行的情况下对它们进行更新。 因此,Go的代码库很容易就能随着library的更新而更新。弃用旧的API可以很快以自动化的形式实现,所以只有最新版本的API需要维护。例如,我们最近将Go的协议缓冲区实现更改为使用“getter”函数,而原本的接口中并不包含该函数。我们对Google中所有的Go代码运行了gofix命令,对所有使用了协议缓冲区的程序进行了更新,所以,现在使用中的协议缓冲区API只有一个版本。要对C++或者 Java库进行这样的全面更新,对于Google这样大的代码库来讲,几乎是不可能实现的。

    Go的标准库中具有语法分析包也使得编写大量其它工具成为可能。例如,用来管理程序构建的具有类似从远程代码库中获取包等功能的gotool;用来在library更新时验证API兼容性协约的文档抽取程序godoc;类似还有很多工具。

    虽然类似这些工具很少在讨论语言设计时提到过,但是它们属于一种语言的生态系统中不可或缺的部分。事实上Go在设计时就考虑了工具的事情,这对该语言及其library以及整个社区的发展都已产生了巨大的影响。

    18. 结论

    Go在google内部的使用正在越来越广泛。

    很多大型的面向用户的服务都在使用它,包括youtube.comanddl.google.com(为chrome、android等提供下载服务的下载服务器),我们的golang.org也是用go搭建的。当然很多小的服务也在使用go,大部分都是使用Google App Engine上的内建Go环境。

    还有很多公司也在使用Go,名单很长,其中有一些是很有名的:

    • BBC国际广播
    • Canonical
    • Heroku
    • 诺基亚
    • SoundCloud

    看起来Go已经实现了它的目标。虽然一切看起来都很好,但是现在就说它已经成功还太早。到目前为止我们还需要更多的使用经验,特别是大型的项目(百万航代码级),来表明我们已经成功搭建一种可扩展的语言。

    相对规模比较小,有些小问题还不太对,可能会在该语言的下一个(Go 2?)版本中得以纠正。例如,变量定义的语法形式过多,程序员容易被非nil接口中的nil值搞糊涂,还有许多library以及接口的方面的细节还可以再经过一轮的设计。

    但是,值得注意的是,在升级到Go版本1时,gofix和gofmt给予了我们修复很多其它问题的机会。今天的Go同其设计者所设想的样子之间的距离因此而更近了一步,要是没有这些工具的支持就很难做到这一点,而这些工具也是因为该语言的设计思想才成为可能的。

    不过,现在不是万事皆定了。我们仍在学习中(但是,该语言本身现在已经确定下来了。)

    该语言有个最大的弱点,就是它的实现仍需进一步的工作。特别是其编译器所产生的代码以及runtime的运行效率还有需要改善的地方,它们还在继续的改善之中。现在已经有了一些进展;实际上,有些基准测试表明,同2012年早期发布的第一个Go版本1相比,现在开发版的性能已得到双倍提升。

    19. 总结

    软件工程指导下的Go语言的设计。同绝大多数通用型编程语言相比,Go语言更多的是为了解决我们在构建大型服务器软件过程中所遇到的软件工程方面的问题而设计的。 乍看上去,这么讲可能会让人感觉Go非常无趣且工业化,但实际上,在设计过程中就着重于清晰和简洁,以及较高的可组合性,最后得到的反而会是一门使用起来效率高而且很有趣的编程语言,很多程序员都会发现,它有极强的表达力而且功能非常强大。

    造成这种效果的因素有:

    • 清晰的依赖关系
    • 清晰的语法
    • 清晰的语义
    • 偏向组合而不是继承
    • 编程模型(垃圾回收、并发)所代理的简单性
    • 易于为它编写工具(Easy tooling )(gotool、gofmt、godoc、gofix)

    如果你还没有尝试过用Go编程,我们建议你试一下。

    http://golang.org


    展开全文
  • --------------定义交互设计--------------简单说来,交互设计,是产品、环境、系统行为,以及传达这些行为的各项正式元素的定义和设计。不同于传统的设计学科,过去专注于形式,近来更侧重于内容和意义,交互设计...
  • 它将向您介绍最重要的术语和概念,帮助您入门,并帮助您在设计中应用色彩时做出更好的决策。 主题 与这些指南一样,我们将涵盖很多内容,包括: 颜色? 色调 色调,阴影和色调 值 饱和度/色度 ...
  • 高效设计三步曲

    2021-03-01 22:57:13
    设计本就是不局限章法的事情,追求百花齐放,那么灵感的来源自然也就不受限制了,各有各的道理。但是设计不=艺术,要的是众乐乐....举个浅显的例子,任何一个比赛都是要先说明主题和缘由,为了什么而比赛,不然就变成了
  • 文章目录论述自然语言处理的技术范畴(基本全貌) 一、前言二、主要技术范畴1、语音合成(Speech Synthesis)2、语音识别(Speech Recognition)3、中文自动分词4、词性标注5、句法分析7、文本分类7、文本挖掘8、信息抽取9...
  • 领域驱动战略设计实践

    千次阅读 2018-11-26 13:16:48
    课程介绍 国内关于领域驱动设计(Domain Driven Design,DDD)的原创书籍少之又少,甚至可以说没有,作者...本系列课程拆分两个课程,即《领域驱动战略设计实践》和《领域驱动战术设计实践》,分别对应领域驱动设...
  • 自然语言处理中的N-Gram模型详解

    万次阅读 多人点赞 2016-04-29 21:32:23
    N-Gram(有时也称为N元模型)是自然语言处理中一个非常重要的概念,通常在NLP中,人们基于一定的语料库,可以利用N-Gram来预计或者评估一个句子是否合理。另外一方面,N-Gram的另外一个作用是用来评估两个字符串之间...
  • 经典设计模式实战演练

    千次阅读 2018-07-03 02:45:11
    为了实现以上目的,前辈们从实践中总结出了一套可套用的武功招式,这就是设计模式。使用设计模式可以让你写出一手令人赏心悦目的代码。 我认为每一个后端开发者都应该学习设计模式,它是代码的精华,是程序发展的...
  • 本文是指导“赤峰学院第四届‘挑战杯’大学生课外学术科技作品竞赛”的网页设计―――物理与电子信息工程系网建站例谈谈网页设计的相关问题。首先说明了系网站的页面设计与制作主题、站点的规划以及网页设计的...
  • MVC框架设计思想与设计理念

    千次阅读 2011-08-18 14:10:07
    尽管MVC早已不是什么新鲜话题了,但是从近些年一些优秀MVC框架的设计上,我们还是会发现MVC在架构设计上的一些新亮点。本文将对传统MVC架构中的一些弊病进行解读,了解一些优秀MVC框架是如何化解这些问题的,揭示...
  • 设计模式笔记--设计模式比较

    千次阅读 2016-01-15 16:10:58
    设计模式笔记--设计模式比较
  • 自然语言处理】浅谈语料库

    万次阅读 多人点赞 2018-11-05 10:19:29
    文章目录【自然语言处理】浅谈语料库前言一、浅谈语料库1、语料和语料库2、语料库语言学3、 建议语料库的意义二、语料库深入了解1、语料库划分与种类2、语料库构建原则3、语料标注的优缺点三、自然语言处理工具包:...
  • 很明显通过前面的八篇文章的介绍,并不能覆盖负载均衡层的所有技术,但是可以作为一个引子,告诉各位读者一个学习和使用负载均衡技术的思路。...接上篇:架构设计:负载均衡层设计方案(8)——负载均衡层总结上篇
  • 前端框架设计理念

    万次阅读 2016-07-02 17:06:03
    写在前面 接下去的一段时间要集中对React和ReactNative的项目做进一步的组件化。我这里也先吹个牛,虽然是小团队,...十大原则『好设计比差设计更难发现』,因为好设计是如此的自然,帮助用户轻松的完成目标,至于
  • 面向对象的设计是比较困难的,一个设计应该是具有针对性的且对于未来有一定通用性,先找到相关的对象在适当的粒度对它们归类,在对每个类定义接口和继承层次,最后确定每个对象之间的联系。有经验的设计者的确可以...
  • 重构设计模式_重构设计

    千次阅读 2020-06-28 13:03:58
    在“ 测试驱动的设计,第1部分 ”和“ 测试驱动的设计,第2部分 ”中,我介绍了测试如何新项目带来更好的设计。 在“ 组合方法和SLAP ”中,我谈到了两种关键模式-组合方法和单一级别的抽象原理-您的代码结构...
  • 工业的发展被视自强自立的必要手段,发展了一种强调机器效率的工业设计风格,把生产 的重点放在技术产品上 美国、德国、瑞士和日本 德国式刻板的机器美学 艺术性 创造美好生活的社会理想来描绘自己国家的未来...
  • 扁平化设计

    千次阅读 2013-06-06 17:21:10
    扁平化是当前很多设计师讨论的话题(包括我),尤其是不断有消息说苹果将会放弃过分的纹理化设计,转而使用更加扁平化的设计风格,设计师们也抛出了自己关于iOS扁平化设计的概念。   不过,今天我们不说iOS 7的扁平...
  • 从MVC框架看MVC架构的设计

    万次阅读 多人点赞 2011-08-16 09:57:37
    从MVC框架看MVC架构的设计尽管MVC早已不是什么新鲜话题了,但是从近些年一些优秀MVC框架的设计上,我们还是会发现MVC在架构设计上的一些新亮点。本文将对传统MVC架构中的一些弊病进行解读,了解一些优秀MVC框架是...
  • 从生活中领悟设计模式(Python)

    千次阅读 2018-07-01 03:45:55
    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、无数工程师实践的代码设计经验的总结,它是面向对象思想的高度提炼和模板化。使用设计模式将会让代码具有更高的可重用性、更好的灵活性和可拓展性、更...
  • 自然语言处理(NLP)简介

    千次阅读 多人点赞 2020-05-30 00:00:29
    简单地说,自然语言处理就是用计算机来处理、理解以及运用人类语言(如中文、英文等),它属于人工智能的一个分支,是计算机科学与语言学的交叉学科,又常被称为计算语言学。由于自然语言是人类区别于其他动物的根本...
  • python课程设计爬虫篇

    万次阅读 多人点赞 2019-08-10 13:40:30
    最近发现一个秘密,用python可以完成批量的诸如课程设计、学年设计、毕业论文模版,即从数据采集,数据分析,数据可视化,GUI界面等不同模块组合。下面介绍我自己写的一个简单例子。 一 摘要 定向网络爬虫可以...
  • 如何从生活中领悟设计模式

    千次阅读 2017-12-01 14:19:38
    作者简介罗伟富,CSDN 博客专家,ABC360 客户端资深工程师。...课程介绍设计模式(Design pattern)是一套被反复使用、多数人知晓的、无数工程师实践的代码设计经验的总结,它是面向对象思想的高度提炼和模板
  • 一个出色的桌面应用程序功能强大,同时也很简单。...这些目标之间存在着自然的紧张关系,但这种紧张关系远非不可调和。通过精心平衡的功能选择和演示,您可以实现功能和简单性。 什么使应用程序功能强大...
  • 自然语言处理怎么最快入门?

    千次阅读 2020-12-29 08:36:10
    自然语言处理是计算机科学和人工智能(artificial intelligence,AI)的一个研究领域,它关注自然语言(如英语或汉语普通话)的处理。这种处理通常包括将自然语言转换成计算机能够用于理解这个世界的数据(数字)。...
  • 但真正好的设计,是用独特的视角和智慧不断修正生活里各式各样的漏洞,生活提供美感,当然,也能帮助公司塑造品牌。设计师米奇·考波尔在那本1990年出版的《软件设计宣言》里说: “设计师之所以受人爱戴,是因为...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 57,017
精华内容 22,806
关键字:

以自然为主题的设计说明