“设计之变”— 从iPhone应用到iPad应用

 

在做APP的iPad版本设计时,我们常常需要考虑:如何在延续iPhone版本设计特色和优点同时,充分利用iPad的特性更好地进行设计。本文从iPad和iPhone的差异性入手,试图总结这一设计过程中需要注意的问题。

一、视图之变

屏幕尺寸的差异直接影响到用户可见的视图范围和应用的信息布局。
iPhone5: 屏幕4.0英寸 分辨率1136×640; New iPad:屏幕9.7英寸, 分辨率 2048×1536

1.1全屏切换与视觉稳定性。

由于屏幕空间限制,iPhone上不同内容之间的切换通常通过全屏切换(full-screen transitions)来实现,但全屏切换会带来视觉稳定性的降低和位置感的丢失。而iPad的屏幕尺寸和独有的UI框架则可以很方便地解决这一问题。
在iPad应用设计时要善用浮动窗口(popover)、页面分割(split view)等UI元素。他们能能减少全屏切换,方便用户在主要界面中快捷地完成任务。
在苹果设计指南中也特别提到:
“When you perform fewer full-screen transitions, your application has greater visual stability, which helps people keep track of where they are in their task.”(摘自iOS人机交互指南)
最经典的案例莫过于系统日历、备忘录、Evernote和Mobile RSSS的例子。通过页面分割和浮动窗口,用户可以在一个页面内完成多封邮件或文章的操作。
More

Mobile App 七大 UX 設計經驗

 

leonlu 1圖片來源:uxpassion

投稿作者:Leon Lu(Leon.LLH@gmail.com)。新鮮活運動!喜歡研究軟體架構的美,雖然每次架構設計還是常有不足;喜歡專案管理方法論的妙,雖然每回專案時程還是常 Delay;喜歡與三五好友大談移動科技的樂,天馬行空是我們的樂趣!

產品使用的第一印象是至關重要的,因為使用者在意的是產品使用過程中是否和當初的 First Impression 一樣的令人樂不釋手。這觀念小編很是認同,因為 2012 年時我大部份時間都在進行 Mobile 相關的專案開發,當時偶然下拜讀了 Ivo Weevers 在他的 Blog 分享 Mobile UX 的設計觀點(註一),雖然已是一年多前的看法,但相較於現今的 Mobile 設計及市場發展其實大同小異。

Ivo 強調的是設計上的原則,而原則是基於團隊在開發 Mobile Product 的經驗累積,嚴格來說這些並不是 Design Guideline,但小編認為這些觀點很值得提供予 Mobile「產品開發」者自我省思,重新思考產品 UX 思維。

七大 UX 經驗法則

自過去四年來,Mobility 上發生了很大的變化(e.g. UI、Screen、Processors 和輸入模式等)。但是 Mobile 產品設計的任務究竟是?相信就產品設計而言,建立產品與 End User 間的關聯性,從而推至對產品或品牌的支持,是大家一致認同的目標。然而最困難的就是如何滿足使用者的全部期待,因此確定產品特色中的 Key Driver 就顯得特別重要(E.g. Performance有助於品牌的差異化)。
More

如何创建自适应系统来增强用户体验

译者注: 普适运算这个概念已经在计算机行业提出了很久。 在设计领域, 随着便携式智能设备的发展与普及, 越来越多的设计师也将这个理念融入到产品设计中去。 本文作者通过几个实例生动的讲解了在设计自适应系统时可以利用的设备与设计理念。 希望能让读者受益 。

原文地址: Creating An Adaptive System To Enhance UX

———————————————————————————————————————————————

在计算机科学中,“自适应系统”特指能够根据所收集的用户信息, 使用情景来调整其行为的交互系统。

它一直是学术领域关注的焦点, 这个理念也启发了大批的计算机科学家。 但从来没有一个时期能像今天这样让人憧憬未来计算机系统与人之间的交互所蕴含的前景。

今天的网络信息科技得以让我们创造丰富和个性化的体验并追踪用户的交互行为加以实时分析。 辅以我们日常生活中随身携带的智能设备和各种传感器所收集的数据, 让我们有机会在设计中添加适应性得以提供更透明和贴切的用户体验。 More

Webkit for Android分析

网上有许多webkit的分析文章,其中针对android porting的一篇文章WebKit – WebKit For Android,写的非常好,分析得非常深入。不过这篇文章针对的Android版本比较老(具体版本无从考究),因此本文将在这篇文章的基础上,加入android 4.0 webkit porting的一些内容。

一、Android WebKit简介

Webkit是一个开源的浏览器排版和渲染引擎,包含WebCore和JavascriptCore。WebKit有众多的实现(Qt、Gtk, windows, chromium, android, etc)。Android 4.0平台的Web引擎框架采用了WebKit中的WebCore,javascript引擎则是采用google的V8引擎。Android 4.0的webkit采用了和chromium 12.0.742.130中webkit相同的codebase,webkit版本为534.30。

二、Android WebKit模块框架

Android平台的WebKit上层由Java语言封装,并且作为API提供给Android应用开发者,而底层使用WebKit核心库(WebCore)进行网页排版。WebKit模块分为两个部分: Java层和C层(webkit库)。Java层和C层通过JNI相互调用,如图1所示:

AndroidWebKitArchitecture

图1 Android WebKit模块框架

在webkit其它平台的移植中,webkit层就是封装WebCore,为上层应用提供接口的。Android的平台具有一定的特殊性,需要提供Java API接口,应用程序框架也是基于Java的,所以在Android的移植中,webkit层实际上被拆成两部分,Java部分和C++部分,它们之间通过JNI接口进行通讯。JNI是一种双向通讯机制,Java代码可以调用C/C++代码,C/C++代码也可以调用Java代码。

通常,WebCore中回调Java的代码都位于WebKit(Android Implementation)层,但有一个例外,就是Source/WebCore/platform/android/GeolocationServiceBridge.cpp,该文件也包含回调到Java的代码。

  More

社区讨论:Android的架构设计

最近,开发者在知乎社区中就Android的架构设计展开了讨论

有人问“Android 架构设计的思想与原则是什么?”:

最近工作中遇到了Android中的权限问题,发现Android确实是开源的,但并不开放,比如权限控管就相当严格,限制做很多事情,这一点得益于Linux内核。这也勾起来对其架构研究的兴趣,不知到哪位能够深度剖析下Android架构设计的思想与原则?

rlei分析了Android的设计哲学:

理解好Intent,就可以理解Android哲学(“所有应用生来平等”)的一部分。举个简单的例子,iOS里面应用要集成SNS如facebook/twitter/sina weibo等,都需要应用自己实现(iOS5也只是集成twitter一家);Android上只需要广播一个share内容的intent。从理解Intent如何工作开始,你就在慢慢理解Activity Manager, Package Manager, Services这些Android的重要组件是如何工作的。

另外Binder是Android架构里非常核心的一块。Android基于Intent的消息传递和组件/应用解耦,下面的基础都是Binder IPC。在这一点上,Android实际上是光荣的传承了BeOS和Palm OS 6(悲催的OS6…)未能发扬光大的一部分。

MVC(Content Provider, Activity, Layout, Adapters)这个比较基础,也不算Android特有的。

Content Provider对数据访问的抽象也是比较有意思的一块。理想情况下,content provider可以让客户端用URI以语义化的方式访问数据(URI本身即表示数据层次结构和查询条件),而下面数据库表的结构可以任意变动,不影响客户端代码。当然实做的时候content provider还是会被各种复杂的where子句暴露出SQL的实现细节……

至于Android的权限管理,其实比较简单,主要是利用现成的Linux安全模型,进程之间相互隔离。API级别的权限管理和JVM类似。

Billy Cui重点解析了权限系统的设计:

Android的权限系统是基于Linux,但又增加了很多自己的控制模块。

总体上来说,其分为以下几部分权限系统:

1. userid : 继承于linux,对于多个app,通过shareuid的方式可以使用同一个userid,主要承担一些目录访问权限之类的工作,比如私有目录只能由同一uid应用访问。

2. 安装level:system level or app level,这个是根据应用的安装位置决定的,在system/app下安装的应用就是system level,在权限访问中会得到更多的权限,比如静默安装应用的权限等。

3. permission : 这个是最主要的权限控制,一般开发者开发应用主要是接触这个部分,在这部分中,会根据应用在AndroidManifest.xml中声明的use-permission而在访问相应api或资源时判断其是否有访问权限,比如常用的android.permission.INTERNET等。

4. signature: 签名,是Android权限系统中的重要组成部分,对于系统签名的应用,会有一些特殊的功能,而shareuid等特性也是需要同一签名作为基础。此外,permission在设置/自定义其权限时也经常会使用到签名,比如控制只有我自己的应用才可以访问我自己定义的公开API。

除此以外,其实Android在uid的里面设置了一些预定义有特殊功能的uid,比如system/media等,在配置其system level的services的时候会用到。

董兆辉则认为Android主要是基于组件搭配思想:

Android是开源的,不过开源不意味着可以乱来,开源只是告诉你我是怎么做的,至于为什么这么做,就是另外一个问题了。任何一个系统如果没有权限管理,那不是乱套了么?我倒是觉得Android的权限管理设计的还不错,有些地方比iOS好,也较灵活,不过同样是因为这一点,容易被利用。

说到Android架构的设计思想和原则,按我的理解主要是组件搭配,即在用户看来,所有的module或者组件,都是可以重复利用和简单组合的。想法是好的,不过有得必有失,或者说Android现在做的还不够好,在性能方面是很低的,否则的话Android也不会推出补丁(NDK之类的,dalvik的不断升级)。

我觉得所有Framework或者平台或者语言都想给应用开发者最方便使用的接口,最人性化的体验,同时又要争取最大的性能,两者权衡折中吧。不过随着硬件速度的飞速增长,性能的权重会变低。

范怀宇还谈到了资源体系:

Android架设在Linux之上,因此,继承了Linux可移植性、用户管理机制、文件系统,等等。

Android的核心在Framework层,本质上,这是一个基于组件的应用开发系统,组件间通过消息(Intent)进行通信。一方面,Intent是通信信息的载体,另一方面,Intent也定义了Android组件的通信协议。

Android可以对组件所运行的进程做托管,在Android中,进程概念相当薄弱。依赖于进程托管,Android可以轻松支撑多任务多进程的应用模型。

除了组件,资源体系也是Android中比较特色的一块,它提供了完整的资源支持,可以用来描述一切与UI相关的内容,并实现多设备的适配。

 

iOS应用性能调优的25个建议和技巧

性能对 iOS 应用的开发尤其重要,如果你的应用失去反应或者很慢,失望的用户会把他们的失望写满App Store的评论。然而由于iOS设备的限制,有时搞好性能是一件难事。开发过程中你会有很多需要注意的事项,你也很容易在做出选择时忘记考虑性能影响。

这正是我写下这篇文章的原因。这篇文章以一个方便查看的核对表的形式整合了你可以用来提升你app性能的25条建议和技巧。

请耐心读完这篇文章,为你未来的app提个速!

注意:每在优化代码之前,你都要注意一个问题,不要养成”预优化”代码的错误习惯。时常使用Instruments去profile你的代码来发现需要提升的方面。Matt Galloway写过一篇很棒的如何利用Instruments来优化代码的文章

还要注意的是,这里列出的其中一些建议是有代价的,所建议的方式会提升app的速度或者使它更加高效,但也可能需要花很多功夫去应用或者使代码变得更加复杂,所以要仔细选择。

 

目录

我要给出的建议将分为三个不同的等级: 入门级、 中级和进阶级:

入门级(这是些你一定会经常用在你app开发中的建议)

  • 1. 用ARC管理内存
  • 2. 在正确的地方使用reuseIdentifier
  • 3. 尽可能使Views透明
  • 4. 避免庞大的XIB
  • 5. 不要block主线程
  • 6. 在Image Views中调整图片大小
  • 7. 选择正确的Collection
  • 8. 打开gzip压缩

中级(这些是你可能在一些相对复杂情况下可能用到的)

  • 9. 重用和延迟加载Views
  • 10. Cache, Cache, 还是Cache!
  • 11. 权衡渲染方法
  • 12. 处理内存警告
  • 13. 重用大开销的对象
  • 14. 使用Sprite Sheets
  • 15. 避免反复处理数据
  • 16. 选择正确的数据格式
  • 17. 正确地设定Background Images
  • 18. 减少使用Web特性
  • 19. 设定Shadow Path
  • 20. 优化你的Table View
  • 21. 选择正确的数据存储选项

进阶级(这些建议只应该在你确信他们可以解决问题和得心应手的情况下采用)

  • 22. 加速启动时间
  • 23. 使用Autorelease Pool
  • 24. 选择是否缓存图片
  • 25. 尽量避免日期格式转换

无需赘述,让我们进入正题吧~ More

Linux Netcat命令:网络工具中的瑞士军刀

netcat是网络工具中的瑞士军刀,它能通过TCP和UDP在网络中读写数据。通过与其他工具结合和重定向,你可以在脚本中以多种方式使用它。使用netcat命令所能完成的事情令人惊讶。

netcat所做的就是在两台电脑之间建立链接并返回两个数据流,在这之后所能做的事就看你的想像力了。你能建立一个服务器,传输文件,与朋友聊天,传输流媒体或者用它作为其它协议的独立客户端。

下面是一些使用netcat的例子.

[A(172.31.100.7) B(172.31.100.23)]

Linux netcat 命令实例:

1,端口扫描

端口扫描经常被系统管理员和黑客用来发现在一些机器上开放的端口,帮助他们识别系统中的漏洞。

1
$nc -z -v -n 172.31.100.7 21-25

可以运行在TCP或者UDP模式,默认是TCP,-u参数调整为udp.

z 参数告诉netcat使用0 IO,连接成功后立即关闭连接, 不进行数据交换(谢谢@jxing 指点)

v 参数指使用冗余选项(译者注:即详细输出)

n 参数告诉netcat 不要使用DNS反向查询IP地址的域名

这个命令会打印21到25 所有开放的端口。Banner是一个文本,Banner是一个你连接的服务发送给你的文本信息。当你试图鉴别漏洞或者服务的类型和版本的时候,Banner信息是非常有用的。但是,并不是所有的服务都会发送banner。

一旦你发现开放的端口,你可以容易的使用netcat 连接服务抓取他们的banner。

1
$ nc -v 172.31.100.7 21

netcat 命令会连接开放端口21并且打印运行在这个端口上服务的banner信息。

Chat Server

假如你想和你的朋友聊聊,有很多的软件和信息服务可以供你使用。但是,如果你没有这么奢侈的配置,比如你在计算机实验室,所有的对外的连接都是被限制的,你怎样和整天坐在隔壁房间的朋友沟通那?不要郁闷了,netcat提供了这样一种方法,你只需要创建一个Chat服务器,一个预先确定好的端口,这样子他就可以联系到你了。

Server

1
$nc -l 1567

netcat 命令在1567端口启动了一个tcp 服务器,所有的标准输出和输入会输出到该端口。输出和输入都在此shell中展示。

Client

1
$nc 172.31.100.7 1567

不管你在机器B上键入什么都会出现在机器A上。

3,文件传输

大部分时间中,我们都在试图通过网络或者其他工具传输文件。有很多种方法,比如FTP,SCP,SMB等等,但是当你只是需要临时或者一次传输文件,真的值得浪费时间来安装配置一个软件到你的机器上嘛。假设,你想要传一个文件file.txt 从A 到B。A或者B都可以作为服务器或者客户端,以下,让A作为服务器,B为客户端。

Server

1
$nc -l 1567 < file.txt

Client

1
$nc -n 172.31.100.7 1567 > file.txt

这里我们创建了一个服务器在A上并且重定向netcat的输入为文件file.txt,那么当任何成功连接到该端口,netcat会发送file的文件内容。

在客户端我们重定向输出到file.txt,当B连接到A,A发送文件内容,B保存文件内容到file.txt.

没有必要创建文件源作为Server,我们也可以相反的方法使用。像下面的我们发送文件从B到A,但是服务器创建在A上,这次我们仅需要重定向netcat的输出并且重定向B的输入文件。

B作为Server

Server

1
$nc -l 1567 > file.txt

Client

1
nc 172.31.100.23 1567 < file.txt

4,目录传输

发送一个文件很简单,但是如果我们想要发送多个文件,或者整个目录,一样很简单,只需要使用压缩工具tar,压缩后发送压缩包。

如果你想要通过网络传输一个目录从A到B。

Server

1
$tar -cvf – dir_name | nc -l 1567

Client

 

1
$nc -n 172.31.100.7 1567 | tar -xvf -

这里在A服务器上,我们创建一个tar归档包并且通过-在控制台重定向它,然后使用管道,重定向给netcat,netcat可以通过网络发送它。

在客户端我们下载该压缩包通过netcat 管道然后打开文件。

如果想要节省带宽传输压缩包,我们可以使用bzip2或者其他工具压缩。

Server

 

1
$tar -cvf – dir_name| bzip2 -z | nc -l 1567

通过bzip2压缩

Client

 

1
$nc -n 172.31.100.7 1567 | bzip2 -d |tar -xvf -

使用bzip2解压

5. 加密你通过网络发送的数据

如果你担心你在网络上发送数据的安全,你可以在发送你的数据之前用如mcrypt的工具加密。

服务端

1
$nc localhost 1567 | mcrypt –flush –bare -F -q -d -m ecb > file.txt

使用mcrypt工具加密数据。

客户端

1
$mcrypt –flush –bare -F -q -m ecb < file.txt | nc -l 1567

使用mcrypt工具解密数据。

以上两个命令会提示需要密码,确保两端使用相同的密码。

这里我们是使用mcrypt用来加密,使用其它任意加密工具都可以。

6. 流视频

虽然不是生成流视频的最好方法,但如果服务器上没有特定的工具,使用netcat,我们仍然有希望做成这件事。

服务端

1
$cat video.avi | nc -l 1567

这里我们只是从一个视频文件中读入并重定向输出到netcat客户端

1
$nc 172.31.100.7 1567 | mplayer -vo x11 -cache 3000 -

这里我们从socket中读入数据并重定向到mplayer。

7,克隆一个设备

如果你已经安装配置一台Linux机器并且需要重复同样的操作对其他的机器,而你不想在重复配置一遍。不在需要重复配置安装的过程,只启动另一台机器的一些引导可以随身碟和克隆你的机器。

克隆Linux PC很简单,假如你的系统在磁盘/dev/sda上

Server

1
$dd if=/dev/sda | nc -l 1567

Client

1
$nc -n 172.31.100.7 1567 | dd of=/dev/sda

dd是一个从磁盘读取原始数据的工具,我通过netcat服务器重定向它的输出流到其他机器并且写入到磁盘中,它会随着分区表拷贝所有的信息。但是如果我们已经做过分区并且只需要克隆root分区,我们可以根据我们系统root分区的位置,更改sda 为sda1,sda2.等等。

8,打开一个shell

我们已经用过远程shell-使用telnet和ssh,但是如果这两个命令没有安装并且我们没有权限安装他们,我们也可以使用netcat创建远程shell。

假设你的netcat支持 -c -e 参数(默认 netcat)

Server

1
$nc -l 1567 -e /bin/bash -i

Client

1
$nc 172.31.100.7 1567

这里我们已经创建了一个netcat服务器并且表示当它连接成功时执行/bin/bash

假如netcat 不支持-c 或者 -e 参数(openbsd netcat),我们仍然能够创建远程shell

Server

1
2
$mkfifo /tmp/tmp_fifo
$cat /tmp/tmp_fifo | /bin/sh -i 2>&1 | nc -l 1567 > /tmp/tmp_fifo

这里我们创建了一个fifo文件,然后使用管道命令把这个fifo文件内容定向到shell 2>&1中。是用来重定向标准错误输出和标准输出,然后管道到netcat 运行的端口1567上。至此,我们已经把netcat的输出重定向到fifo文件中。

说明:

从网络收到的输入写到fifo文件中

cat 命令读取fifo文件并且其内容发送给sh命令

sh命令进程受到输入并把它写回到netcat。

netcat 通过网络发送输出到client

至于为什么会成功是因为管道使命令平行执行,fifo文件用来替代正常文件,因为fifo使读取等待而如果是一个普通文件,cat命令会尽快结束并开始读取空文件。

在客户端仅仅简单连接到服务器

Client

1
$nc -n 172.31.100.7 1567

你会得到一个shell提示符在客户端

反向shell

反向shell是指在客户端打开的shell。反向shell这样命名是因为不同于其他配置,这里服务器使用的是由客户提供的服务。

服务端

1
$nc -l 1567

在客户端,简单地告诉netcat在连接完成后,执行shell。

客户端

1
$nc 172.31.100.7 1567 -e /bin/bash

现在,什么是反向shell的特别之处呢
反向shell经常被用来绕过防火墙的限制,如阻止入站连接。例如,我有一个专用IP地址为172.31.100.7,我使用代理服务器连接到外部网络。如果我想从网络外部访问 这台机器如1.2.3.4的shell,那么我会用反向外壳用于这一目的。

10. 指定源端口

假设你的防火墙过滤除25端口外其它所有端口,你需要使用-p选项指定源端口。

服务器端

1
$nc -l 1567

客户端

1
$nc 172.31.100.7 1567 -p 25

使用1024以内的端口需要root权限。

该命令将在客户端开启25端口用于通讯,否则将使用随机端口。

11. 指定源地址

假设你的机器有多个地址,希望明确指定使用哪个地址用于外部数据通讯。我们可以在netcat中使用-s选项指定ip地址。

服务器端

1
$nc -u -l 1567 < file.txt

客户端

1
$nc -u 172.31.100.7 1567 -s 172.31.100.5 > file.txt

该命令将绑定地址172.31.100.5。

这仅仅是使用netcat的一些示例。

其它用途有:

  •     使用-t选项模拟Telnet客户端,
  •     HTTP客户端用于下载文件,
  •     连接到邮件服务器,使用SMTP协议检查邮件,
  •     使用ffmpeg截取屏幕并通过流式传输分享,等等。其它更多用途。

简单来说,只要你了解协议就可以使用netcat作为网络通讯媒介,实现各种客户端。

参考文档

Netcat手册

 

英文原文: Linux Netcat command – The swiss army knife of networking,编译:oschina

从何说起——正确方式学Android

老婆大人最近想学Android开发,向我寻求帮助。所以我觉得为了避免老婆处罚并且让她在Android应用样式风格和界面一致性方面从一开始就在正确的方向上、确保让她更清楚认识到参考Google提供的UI设计规范是如此重要,我最好能收集一些学习资源。

下面是我找到的非常好的资源,也希望能帮助到大家:

先说最重要的,学习Java语言

如果你不知道Java编程语言或者说你是从其他语言转过来的,需要熟悉下Java的话,下面这些是很好的让你开始了解Java的学习资源。

Java 教程 (内有大量教程)
http://www.tutorialspoint.com/java/index.htm

开始学Java – “Java初学者入门”
http://www.javacoffeebreak.com/tutorials/gettingstarted/

Java语言编程介绍 – Lars Vogel
http://www.vogella.com/articles/JavaIntroduction/article.html

教你用Java编程 (麻省理工学院)
http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-092-introduction-to-programming-in-java-january-iap-2010/index.htm

视频 -学习Java: 第一部分: 基础介绍
http://www.youtube.com/watch?v=3MZIkY55fS0

免费Java在线互动教程(译者注:提供了在线编辑器)
http://www.learnjavaonline.org/

Java初学者教程
http://www.javabeginner.com/

当你学好了Java,你已经准备好正式开始学习Android开发了

** 每一个Android开发者都应该知道的资源 **
http://www.bongizmo.com/blog/android-resources-each-developer-should-know/

注: 即使你不打算看下面的,不过上面这个一定要看(译者注:上面这个真的很干货,一定要看)

开始Android开发 – 作者 Ray Wenderlich
http://www.raywenderlich.com/5527/getting-started-with-android-development

开始Android开发 – 作者 Chris Lacy
https://plus.google.com/104649936579980037256/posts/UQD4e1jgLS1

Support Library (用于支持旧版本Android设备)
http://developer.android.com/tools/extras/support-library.html

下面是视频教程

Android 训练营视频教程
Tutorial: Android Application Development – A 9,000 foot overview

下面是常用的第三方库

Android Asset Studio
http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html

ActionBarSherlock
http://actionbarsherlock.com/

AndroidSideMenu
https://github.com/dmitry-zaitsev/AndroidSideMenu

SlidingMenu
https://github.com/jfeinstein10/SlidingMenu

ViewPagerIndicator
http://viewpagerindicator.com/

UnifiedPreference
https://github.com/saik0/UnifiedPreference

Pull To Refresh Views for Android
https://github.com/chrisbanes/Android-PullToRefresh

SwipeListView
https://github.com/47deg/android-swipelistview

让你的应用APP轻松支持平板设备 – 学习使用Fragment

开发中如何使用 Fragments
http://mobile.tutsplus.com/tutorials/android/android-sdk_fragments/

Android中使用Fragment – 教程
http://www.vogella.com/articles/AndroidFragments/article.html

**当开发Android应用的时候,理解Java、界面布局和Fragment使用很重要。
不过理解Android UI设计规范并在你自己的APP 设计中保证体验一致性也同等重要,遵循这些规范能让用户体验更好。

希望上面提到的资源对你开始Android开发有所帮助。如果你已经做Android一段时间了,也许你也能在这些资源中发现一些干货让你进一步提升。
**
最后,祝大家开发愉快 :-)

原文链接

via eoe

备受开发者青睐的13款热门开源项目

摘要:本文我们总结了国外13款热门级的开源项目,包括ProjectLibre、OpenBravo POS、Impress.JS、Reveal.JS、Diaspora等,这些项目都是备受开发者青睐的开源项目,希望你会喜欢。

开源是一项伟大的高科技技术产业。如今各大企业都在积极拥抱开源,企业在花费了大量的人力、物力后像似下一盘赌注,因为你不确定该项目是否受到开发者的青睐。本文我们总结了国外13款热门级的开源项目,希望你能喜欢。

开源项目Top 13如下:

1. ProjectLibre

ProjectLibre软件项目管理是仿照LibreOffice项目进行补充的,是一个Microsoft Project的仿制品,它为团队管理提供了一个完整的工具集,旨在维护任务列表,追踪标记完成的任务情况。该项目采用分布式CPAL ( Common Public Attribution License)许可证。

2. OpenBravo POSUnicenta

OpenBravo是一种类似ERP软件,在企业里颇为流行。该工具常被运用在在商店和餐馆。你可以在此基础上添加库,该软件还可追踪客户购买的东西。OpenBravo POS允许开发者清理旧代码,添加新鲜的视觉设计,该工具采用GPL 3.0许可证。

3. Impress.JS

Impress.js是一款开源版本的Prezi商业包。你无需创建以往的矩形图,只需将信息放置在一个大型的画布中,然后点击滚动、旋转、放大即可,提供自然的交互方式来表现高层次的抽象和低级别的细节。该项目采用MIT或 GPL许可证。

4. Reveal.JS  

Reveal.JS是一款基于HTML的演示系统,以3D的形式进行数据和动画转换。其一大特性就是你可以通过不同的路径设置幻灯片版面。该项目基于MIT许可证。

5. Diaspora

如果你想设计一款类似Facebook的分布式社交网络,把控制权交还给用户手中,那么Diaspora就是你首选的开源项目。该项目基于Gnu AGPL下发行。

6. D3.js 

尽管图片无法胜过千言万语的话语,但它看起来总是让人感到愉悦。如果你想让数据在华丽的交互式图表中显示,这又有何不可呢?D3.js就是JavaScript最新的集合工具,包含多种复杂的图表。该项目基于BSD许可证。

7. CodeMirror

许多开发者仍然希望坚持使用台式机和笔记本,这是因为他们想要快速的连接和控制另一台台式机和笔记本。然而这一现状正慢慢被迁移到云端中,相信在不久的将来集成开发环境也会存储在云中,以便开发者可以快速的创建和存储更多的代码。

CodeMirror是一款基于浏览器的代码编辑器,支持高亮和自定义几十个最流行的编程语言。该项目基于MIT许可证下发行。

8. CasperJS PhantomJS

CasperJS提供一个实用的功能集合,方便你通过网页创建浏览器,提供包括填写表格、点击链接、捕捉截图等功能。将CasperJS和PhantomJS集合在一起,以Webkit为核心,模拟Chrome、Safari以及iPhone。该项目采用MIT许可证。

9. GeoServer 

GeoServer是基于地理数据调整的数据库,你可以使用GeoServer检查正确的数据,搜索并且可提供映射层格式。该项目是用Java编写的,采用GPL 2.0许可证。

10. Less CSS

LESS是一种动态的样式语言。LESS将CSS赋予了动态语言的特性,如变量,继承,运算,函数。LESS既可以在客户端上运行 (支持IE 6+, Webkit, Firefox),也可以借助Node.js或者Rhino在服务端运行。

11. Simutrans

这个项目看起来像个游戏,与SimCity相类似。但Simutrans是为了测试不同的运输方案,满足人们的日常需求。这是一款模拟现实生活的游戏,目的在于让你成为好公民。该项目采用Artistic License许可证。

12. Linux MultiMedia Studio

如果你正在播放音乐并且想把它录制下来,也许会花费你很长时间。而LMMS(Linux MultiMedia Studio)允许你添加节拍并与其他音频技术结合在一起从而产生一首新旋律的歌曲。该项目基于GPL2.0。

13. TeXnic

这是一款伟大的WYSIWYG编辑器,但严谨的科学家和程序员更加青睐TeX的强大。TeXnic是最新版本的客户端用于创建集成开发环境。这是一款可视化的,让你永远不会失去编程的趣味性,该项目基于GPL。

英文出自:Infoworld

via CSDN

Go在谷歌:以软件工程为目的的语言设计

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 /* Large copyright and licensing notice */
2 #ifndef _SYS_STAT_H_
3 #define _SYS_STAT_H_
4 /* Types and other definitions */
5 #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 关键词和一个用来明确当前(只是当前)文件引入包的字符串:

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 包代码中

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”,都以包声明开始,如同:

package json

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

import "encoding/json"

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

var dec = json.NewDecoder(reader)

这种设计非常清晰,从语法(Namevs.pkg.Name)上就能识别一个名字是否属于某个包(在此之后)。
在我们的示例中,包的路径是“encoding/json”而包的名称是 json。标准资源库以外,通常约定以项目或公司名作为命名控件的根:

import "google/base/go/log"

确认包路径的唯一性非常重要,而对包名称则不必强求。包必须通过唯一的路径引入,而包名称则为引用者调用内容方式的一个约定。包名称不必唯一,可以通过引入语句重命名识别符。

下面有两个自称为“package log”的包,如果要在单个源码文件中引入,需要在引入时重命名一个。

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

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

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

9. 远程包

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

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

$ 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 语言。声明的变量名称在类型之前,而有更多的关键词很:

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

相较于 C 语言

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

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

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

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

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

func Abs(x T) float64

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

func (x T) Abs() float64

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

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

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

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)的调用。也就是,程序员总是写

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为程序员提供了通过控制数据结构的格式来限制内存分配的手段。请看下面这个简单的类型定义了包含一个字节(数组)型的缓冲区:

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接口的定义。

type Hash interface {
    Write(p []byte) (n int, err error)
    Sum(b []byte) []byte
    Reset()
    Size() int
    BlockSize() int
}

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

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

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接口共存:

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

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

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

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

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

func ReadAll(r io.Reader) ([]byte, error)
封装器 —— 指的是以接口为参数并且其返回结果也是一个接口的函数,用的也很广泛。这里有几个原型。LoggingReader将每次的Read调用记录到传人的参数r这个Reader中。LimitingReader在读到n字节后便停止读取操作。ErrorInjector通过模拟I/O错误用以辅助完成测试工作。还有更多的例子。
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方法,表示了一个错误值。:

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

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

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

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

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源代码树要使用该缺省值进行更新,仅限使用下面这一条命令:

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

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

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

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

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

 

英文原文:Go at Google: Language Design in the Service of Software Engineering

via 开源中国

参与翻译(7人)