Zeldman LISP

谁惧怕又大又坏的 Medium?

本文是翻译,版权归原作者所有



早在 2003 年,Douglas Bowman 就是 Twitter 的一名创意总监,他在个人/商业站点 stopdesign.com 上写设计相关的文章,发布他参与设计的项目方面的案例研究,以及分享他的摄影。

再往前一年,Douglas 录制了用 CSS 来布局 Wired.com 而在标准化圈子里一夜成名。这听起来有些荒谬,但是在 2002 年,像我这样的人仍然在奋力说服我们的 web 设计师小伙伴们要用 CSS、而不是 HTML table 来布局。主要的 web 设计师开始看到希望,突然就有了大量的博客和个人站点,它们使用 CSS 布局,它们的 HTML 标记力求有语义且通过校验。但是还没有人把 web 标准应用到大型的商业站点——在卢德运动式【注1】的 web 设计师当中引起了抗议,他们认为基于标准的设计“适合博客”,但和”真实的“ web 没有关系。

随后 Douglas 录制了使用 CSS 的 Wired.com,Mike Davidson 为 ESPN.com 做了同样的事情,所有陈旧的反对言论突然就像大元帅佛朗哥去世了一样【注2】——该竞赛是要建立一个遵循的标准,在所有内容和应用程序行业开放 web。

在帮助引领这次巨大变化的过程中,Douglas Bowman 出名了,任何人,也就是 web 设计圈当中的任何人,开始激动地阅读他的博客。但是。

但是,当 Douglas 产生了一个真正宏伟想法要分享给我们的社区时,他在 A List Apart 上发布了,这是一本”面向做网站的人“的杂志。

他这样做是因为博客死了吗?因为开放 web 遇到麻烦了?当然不是。他这样做是因为 2003 年在 A List Apart 上发布,Douglas 就可以和他的同行的最宽泛群体分享其创新性的设计技巧。

在多个地方发布不是新鲜事了。维多利亚英国时代的文学巨匠查尔斯·狄更斯【注3】就做过。(他也开创了系列交叉剪接、系列叙事、把观众反馈并入他的叙事——加入悬疑情节的技巧,《广告狂人》系列的电视叙事,和基于互联网的、电视内容调整以响应观众的反馈。但是这都是另外一些话题了,可能是更加有趣的故事。)

当 Doug Bowman 在 A List Apart 发布”CSS 滑动门“时,没人说开放 web 死了。

RSS 阅读器使得无需在浏览器地址栏敲入自发布作者的个人网站的网址,就可以检查你收藏他们的最新内容,当这变得更加容易时,没人说博客死了。

当 Mike Davidson 的 Newsvine 【注4】开始重新发布纽约时报的内容时,那些前卫的纽约时报思想家们没有抱怨;报纸代理着交易。他们害怕在他们自己的地盘给他们的文章添加评论,才把 Newsvine 做为一个完美的场所,用来检验在线读者的反馈怎样装入纽约时报的世界。

当 Cameron Koczon 注意到并定义了我们和在线内容(”未来,内容将不再固守在网站上,而是在用户周围的轨道上流动【注5】“)交互的新方式时,聪明的作家、出版商、和内容制作人都对这种想法感到欢欣,他们的话能以更多的方式传达给更多的人。的确,它意味着重新思考盈利;但是 web 上的内容盈利大多数是一种被破坏的竞次【注6】,不管怎样,究竟是谁在哀悼”web 用户每天手动访问你的网站首页以期找到新内容“这种模式的加速死亡呢?我们当中没有太多人。

当 Cameron 在 2011 年 4 月写”环绕式内容“【注6】时,差不多所有 A List Apart 和 zeldman.com 的访问都是来自于 tweet 和其它第三方文章。人们那时候在 Google 和 Twitter 、而不是 yourhomepage.com 上加书签。这很好了。如果你写了优秀内容且正确地组织了,人们将会找到它。精确地搜索你的内容的观众将直接跳到该内容,而不是在你安装好模板之前、借助过时的首页层级菜单导航。

那么,我们为什么惧怕 Medium?Medium 没有恳求或编辑大多数的内容、也没有支付它的大部分作者,这姑且不论,那么它和所有之前的、从 SlateThe Verge 之类的、 web 发布平台有什么区别?为什么在 Medium(除去你的个人站点和其它发布平台)发布内容预示了,不仅仅是博客(”Death of Blogging III: This Time It’s Personal“)的最终版-最终版-最终版的死亡,而且,甚至更让人担忧的是,开放 web 的死亡

你或许认为我夸大其词了,但是我听到过,多个受人尊敬的同僚认为在 Medium 上的发布使得我们独立内容制作人关心和表现的所有东西都无效了;它只用敲一次回车键就摧毁了我们所有优秀的作品。

甚至我自己也有过这种想法。

不过,Medium 之类的、新模式的 web 发布不正是 web 还健在、以及催生了新形式的创新和成功的、证据吗?

当个人站点的发布者为 Medium 写作时,她就真的放弃了自己的站点了吗?她不可以只是希望拥有新读者吗?

(如果她成功了,那些新读者当中,或许有一部分人会时不时地访问她的站点。)

感谢Bastian Allgeier,激发了本文的产生。

本文还发布在 Medium


  • 注1:卢德运动(英语:Luddite)是19世纪英国民间对抗工业革命、反对纺织业者的一次社会运动。在该运动中,常常发生毁坏纺织机的事件。因为工业革命运用机器大量取代人力劳作,使人们不能改善他们的生活,甚至失去职业。该运动以内德‧卢德(Ned Ludd)将军或卢德王命名,此人与生活在舍伍德森林的罗宾汉(Robin Hood)同样是虚构的人物。http://zh.wikipedia.org/wiki/%E7%9B%A7%E5%BE%B7%E9%81%8B%E5%8B%95
  • 注2:1970年代《周六夜现场》(Saturday Night Live)节目创造的一句关于西班牙独裁者弗朗西斯科-佛朗哥(Generalissimo Francisco Franco)的流行语.当年,关于佛朗哥是死是活的消息长期占据美国各大媒体的头版头条.而《周六夜现场》也不忘在最后时刻拿佛朗哥开涮.而节目中的 “Generalissimo Francisco Franco is still dead(佛朗哥还死着呢)”也成了当时的流行语.http://en.wikipedia.org/wiki/Generalissimo_Francisco_Franco_is_still_dead。此注释引用自 http://www.cnbeta.com/articles/74070.htm
  • 注3:查尔斯·约翰·赫芬姆·狄更斯(英语:Charles John Huffam Dickens,1812年2月7日-1870年6月9日),维多利亚时代英国最伟大的作家,也是一位以反映现实生活见长的作家。狄更斯的作品在其有生之年就已有空前的名声,在二十世纪时他的文学作品受到评论家和学者广泛的认可。狄更斯的小说和短篇故事继续广为流行。http://zh.wikipedia.org/wiki/%E6%9F%A5%E5%B0%94%E6%96%AF%C2%B7%E7%8B%84%E6%9B%B4%E6%96%AF
  • 注4:Newsvine是一家社交新闻网站。通过汇集和整合多个新闻来源,用户可以读到同一个故事的不同版本,Newsvine还能为用户提供读者的相关反馈,以及一个强大的,能够对用户每天所阅读的新闻进行补充的网络社区。http://www.baike.com/wiki/Newsvine
  • 注5:请参看「内容何以为王」系列之:环绕式内容,http://apple4us.com/2011/06/orbital-content-chinese/
  • 注6:竞次 (race to the bottom)一词最先使用的是一位美国法官路易斯·布兰迪斯(Louis Brandeis)。在中国,“竞次”一词是由经济学者袁剑首先用于经济学领域的。http://baike.baidu.com/view/1437000.htm 英文版请参考 http://en.wikipedia.org/wiki/Race_to_the_bottom

关于可访问性的 7 个地方

关于可访问性的 7 个地方(For 每个人)

本文是翻译,版权归原作者所有



可访问性使有障碍的人能够对网络进行感知、理解、导航、交互以及贡献。设想这样一个世界,开发人员了解可访问性的方方面面。你设计,他们开发……非常完美。在那个世界里,只有设计本身使有障碍的人在使用产品上遇到困难。

为了让你的产品成为符合 Section 508Web 内容无障碍指南(简称 WCAG)【注1】的最小化标准的“设计准备”,这些指南将包含你需要知道的主要方面。剩下的就取决于开发和质量测试了。

1.可访问性不是创新的阻碍

可访问性不会迫使你做出丑陋、单调或杂乱的产品。在你考虑设计时,它将引入一组需加以考虑的约束。这些设计约束将给你新的想法,以探索面向所有用户的、通往更好产品的道路。

当你通读这些指南时,要记得我们不是为了设计同行而设计。

this-is-so-meta

为将要和你的产品交互的、形形色色的用户而设计。

为每个人设计

用户包含了盲人、色盲或弱视,他们有聋子或听力困难的人,临时或永久的移动性障碍的人,或者是认知障碍的人。为年轻人、老人、有影响力的人、普通用户和只享受有质量体验的人设计。

拥抱这些可访问性指南,还有设计约束。要创造让人尖叫的产品,它们就是挑战的一部分。

2.不要把颜色做为信息传送的唯一视觉方式

对于那些不能从一种颜色区分另一种颜色或有困难的人们,是有帮助的。这包含了色盲(12 名男性中有 1 名,200 名女性中有 1 名)、弱视(30 个人中有 1 个)或盲人(188 人中有 1 个)。

使用颜色高亮或补充已经可见的东东。

这个例子以灰度模式呈现,你能说出错误状态下有几个表单域吗?

灰度显示的错误状态表单

看到该灰度模式下的人说的答案是 1 个,“人类验证”表单域。这是因为其内部带有叹号的三角标记标示出了错误的地方。

现在看看以颜色呈现出来的同样屏幕。在错误状态下有多少个表单域?

颜色显示的错误状态表单

有了颜色,答案变成了,“共有 4 个”。

有很多可接受的方式使得这个表单视觉上具备可访问性。你可以把红色的三角图标放到所有的错误表单域里。你可以使用文本来说明或解释为什么某个表单域有错误。你可以使用提示、粗边框、粗体文本、下划线、斜体等。选择是无限的,但是唯一的规则是不要只用颜色。

你是如何设计注册表单,以致于颜色不是呈现错误表单域的唯一视觉方式?

3.在文本和背景之间保证有足够的对比。

根据 WCAG 指出的,文本及其背景的对比度至少在 4.5 到 1 之间。如果你的字体至少在 24 px 或 19 px 粗体,那么最小可降到 3 到 1。

该指导有助于弱视、色盲或恶化视力的人阅读你屏幕上的文本。

这意味着,当文本是 24 px、19 px 粗体、或更大时,你能在白色背景上使用的最小灰度是 #95959。

大号字体

对于小一些的字体,你能在白色背景上使用的最小灰度是 #76767。如果你有一个灰色的背景,文本需要更深一些。

正常的字体

有一些不错的、包括 Color Safe 在内的工具,帮你找到用于设计的可访问性调色板。还有 WebAIM’s Color Contrast Checker,用来测试你已经选中的颜色。

处于禁用状态的 Logo 或元素不在此标准之列。这包括不可点击的按钮或菜单项。用于表单域的占位符或 ghost 文本应该排除在该标准之外。

下面是来自某个流行博客站点的例子,其文本对比度低于标准。只有巨大的字母”M“的对比度满足标准。

medium的对比度演示

下面 BBC 的例子显示了及格的色彩组合的UI。既然它们的最小灰度是 #76767,那么你能分清它们正积极地通过了对比度吗?

BBC通过的颜色组合

我和 Salesforce 设计系统团队一起工作,并以此为荣,他们在 Salesforce 移动应用上遵循了色彩对比指南。

Sailesforce通过的颜色

探索使用高对比度的色彩组合。做过这种训练的设计师常常惊叹于他们是多么地喜欢高对比度设计。我非常自信你也想找到使用最小化可允许的对比,它不会分散对你产品的注意力。

更多关于色彩对比的信息,可参看 Projectors Don’t LieAccessible Interface Design

4.为键盘焦点提供视觉焦点标示

让我们先感谢一下已经给予了现代 web 设计师的 reset 样式表和所有的工具集。没有 reset 样式表,在不同设备和浏览器上创建一致性体验就变得非常困难。

让我们再谴责一下 reset 样式表,因为它对于在互联网传播最广的可用性错误负有责任。

:focus {outline: 0;}

这一行 CSS 使得视力正常的用户几乎不可能只使用键盘访问网站。幸运的是,自从最初的 reset CSS 发布之后,包括 Eric Meyer 在内的很多知名网站已经更新了,去掉了不加样式的 :focus 伪类。

没有样式的焦点意图是足够高尚的:移除默认的焦点样式,是为了设计师和开发人员用他们网页样式的视觉和配套来取代。足以容易到不喜欢 IE 或 Firefox 下的灰色虚线边框、或 Chrome 下的蓝色光晕。

IE、Firefox、Chrome下的焦点样式

问题是,大多数网站没有创建他们自己的焦点样式。这些焦点指示符,对于键盘用户的成功有着基础作用,却在 web 上严重缺席了。

做为体验一个常常没有提供视觉焦点的网站的、一种快速练习,打开一个标签,访问为公司制作移动站点的网页。重复按下 TAB 键浏览这个页面。当你浏览时,你能看到一个视觉焦点指示器吗?或许你在页面上的某些链接上看到了而不是全部?考虑下这种效果是如何影响那些只使用键盘和 web 交互的人们的。

如果你是 Mac 用户,你可能需要开启 系统偏好设置 > 键盘 > 快捷键 下的全键盘控制。它位于底部。

Mac下的全键盘设置

如果你移除了默认焦点,请用一些“更好的”样式取代你的浏览器所提供的样式。

在下面的例子,BBC 使用一个色条来指示哪个部分的链接拥有焦点。

BBC的导航焦点色条

Twitter 使用默认焦点和显示键盘焦点提示的组合。icon 也从灰色变成了绿色。这是三种独立的视觉,为键盘用户标示了焦点。

twitter的焦点样式

在提供你自己的焦点状态时,要确保移除了默认状态,这样你就不会出现下面例子中的情况,Chrome 的蓝色矩形覆盖掉了该菜单的蓝色区块。

5.当心表单。

最近几年我们已经体验了表单域的退化。由于更加极简的方式,现代设计已经抛弃了传统的识别属性和用于交互的承担特征【注2】。今天的表单,缺乏两种特定项,它们对于可访问性是至关重要的:清晰定义的边界和可见的标签。

没有边框的表单

下面是一个传统文本输入框的例子。它有着定义好的边界的矩形。它还填充了颜色,但是不必去填写。还有一个可见标签,在该例子中,它位于输入框左侧。

表单的输入框

明确定义了边界的表单域,对于移动性障碍和认知障碍的人是重要的。明白点击目标的位置和尺寸,对于使用标准的或自适应指示设备的人也是重要的。存在认知障碍的用户,对于没有普通视觉指示的表单域,可能难以找到和交互。

下面的例子是来自于一款流行的记录笔记的应用。

流行的笔记应用

屏幕上只有一个输入框。你能够猜出表单域的边界吗?点击“search notes”这句话的某个地方,才会把你放入输入框里面。

下面的例子来自于一个流行博客平台的、没有边界的表单域。这个页面有两个输入框。我为了进入“Tell your story…“文本域,我应该点击屏幕哪个地方呢?

Medium的添加文章表单

下面是同样的屏幕,为了显示文本域边界,而添加了蓝色矩形。如果你点击这个区域的下面区域,什么也不会发生。

Medium的添加文章表单(焦点)

下面是另一个记录笔记的设计例子。它没有使用传统的输入框视觉,而给有障碍的用户提供了更多的信息。笔记的标题位于两条水平线之间,用户点击底部两条线之间的任何地方,就可以开始敲入他们的笔记。

非标准但好用的表单

你能够为这些设计师们想到其它方案吗?你是怎样设计记录笔记或博客发布的应用的?

没有标签的表单

标签用于告诉用户对应域的目的,当焦点位于输入框内部时,说明它们的用处,甚至在填完输入框之后,仍然保持着。占位符文本对于视觉化标签而言,属于表现不够好的替代。

它们通常具有低对比度。下面的 7 个例子,只有 1 个例子有着足够的对比度,符合我们需要的 4:5:1 的比率。

对比度的7个例子

占位符文本消失了。在下面的例子,我应该在文本输入框填写什么呢?对于 JetBlue【注3】的例子,我应该输入用户名、邮箱地址、还是我的 TrueBlue 号码?对于 Caviar 的例子,我应该通过敲入我喜欢的食物、首选餐厅还是我的邮箱,才能“Get Started”呢?价格域是最小的和最大的、上和下、或者前后颠倒?

Trueblue的注册表单

和上面呈现的价格域相比,下面是更具可访问性的设计方式。我们将看到标签、最小和最大、甚至在我们填好之后。

价格输入框

6.避免组件识别危机

Q: 一个菜单在什么时候就不再是菜单了?
A:当它是一个非模态对话时。

这个问题是当今最大的 web 可访问性方面的问题核心。为了完全理解这一点,请思考 W3C 对于设计模式的 Authoring Pratices。对于今天普通的、包括表单、模型、自动完成、树、标签页集合等在内的设计模式,这是一份如何开发一种可访问性版本的指导。

这些模式的每一种,都有一个具体的、期望的 HTML 语义、键盘行为和可访问的富因特网应用程序(ARIA)属性用法的集合。这些 ARIA 属性指导屏幕阅读器用户,当使用键盘时,该怎样与组件交互。当用户和组件互动时,它们还提供了状态更新。例如,它们指导用户通过使用方向键在列表中向上、向下移动来实现交互。

满足不起眼的自动补全输入提示。

自动补全

下面是相同的输入提示,但是每个列表项前面有个 icon。

输入提示

这些都是必要的、完全相同的 UI 模式。用户敲入一个输入框。经过输入文字过滤后的一组结果出现在下面。然后用户能够使用方向键或鼠标,从列表中定位或选择其中一项。

下面是带有识别危机的自动补全的例子。用户不仅可以从列表中过滤或选择一项,而且他们能通过点击铅笔或垃圾箱图标来选择编辑或删除每一项。这两个按钮的添加让这里的自动补全产生了识别危机。

非模态对话的菜单

这部分地引起了可访问性问题,因为它破坏了自动补全的、标准键盘交互模型。辅助技术常常不能传达识别、操作和混乱组件的状态,因为 W3C 的 Web 可访问性倡议(WAI)还没有对传播这种 UI 定义一个说明。

对于菜单也有同样的规则。下面的例子来自 Virgin America,视觉上非常类似,只有右侧的下拉列表是一个真正的“菜单”。左边的下拉列表是一个非模态对话。

非模态对话菜单对比

菜单是给用户提供一个选择列表的小工具。如果我们在每行提供了多种选择,正如左侧的那个例子,那么它就不再是菜单了。键盘交互从使用方向键变成了使用 TAB 键。它改变了键盘焦点的处理方式,以及当下拉列表关闭后该何去何从。

和上面自动补全的例子不一样,幸运的是,非模态对话有可访问性。了解它们之间的区别和对用户体验的影响。理解少量的设计变化是怎样导致用户交互模型变化的。这使你能够为产品选择合适的模式。

7.不要让人们悬停才能找到东西

这个原则主要面向运动障碍相关的人群。它包含具有视力的、只使用键盘的用户、和使用诸如 Dragon NaturallySpeaking 语音识别工具【注4】和网页交互的人们。键盘用户和 Dragon 之类的辅助技术,依靠于屏幕上可见的可操作项。如果一个链接或按钮不能被 Dragon 看到,它也将不能被口头上“点击”。如果只使用键盘的用户不能看到页面上存在的按钮,我们怎能期望他们导航到最终出现的空白区域呢?

下面是一个来自于带有 Dragon NaturallySpeaking 的 Gmail 截屏,显示了覆盖着带有供识别的数字的超链接。用户现在能够大声说出一个数字来激活一个链接。如果链接只有在区域被悬停时才会出现,那该怎么办?我们将有数字出现在空白旁边。

Dragon NaturallySpeaking 的 Gmail 截屏

我理解,在悬停状态下隐藏可操作项的实践是怎样流行起来的。它是计算机科学家艾伦·凯【注5】提出的、精心构建的启发式可用性的解决方案。

“简单的东西就要有简单的设计,而碰到复杂的情况时也应该能应付。”
——艾伦·凯

我是这种启发式的忠实信奉者,但是它应该以一种让复杂成为用户可能应付的方式,包括那些有障碍的人们。不幸的是,对于可访问性,很多东西都变成了下面的句子,它不是引用艾伦·凯的话:

“主要的东西应该是可见的,第二位的东西应该悬停时才可见。”

不要在悬停状态隐藏操作和信息,而要探索更多可包含的替代方式。

  • 把第二位的操作放在菜单里(或非模态对话),不要使用悬停状态隐藏这种触发。
  • 降低第二位图标的对比度,在悬停时要加深它们。
  • 对于较大的悬停,使用矩形项做为触发。对于启动充满内容的悬停,信息图标比起空白,算是更好的触发项。

举个 LinkedIn 的例子。下面是我的个人首页截屏。

LinkedIn个人主页名片-1

下面是我把鼠标放到个人卡片之后的情形:

LinkedIn个人主页名片-2

突然有了视觉上的指示,我可以单个编辑这个页面上的域,包括我的名字、头衔、之前的工作、教育、甚至我的首页照片。做进一步体验,当我悬停到我的头衔上时,文本变成了蓝色,说明我打算点击了。

LinkedIn个人主页名片-3

这种设计可能引起某些用户群体的可访问性问题,下面是解决方案。我让更小的铅笔出现在域的旁边。它们总是可见的。

LinkedIn个人主页名片-4

当我在一个域上悬停时,蓝色才出现。

LinkedIn个人主页名片-5

 

当出现这种解决方案时,设计师做出的反应是问:

“有点儿多,不是吗?”

或许吧,不过它是这个问题的唯一可能的解决方案。而且,它只出现在我自己的个人首页里。一个人要花多少时间看自己的 LinkedIn 首页?这种级别的“量级”对于通用的可访问性,是公平的折衷吗?如果我们不喜欢这个铅笔的特定使用,那么我们还能提供其它的承担特征吗?

下面是来自 Evernote 的另一个例子。这是笔记的列表视图。当用户的鼠标悬停一行时,四个可操作的图片出现了。

evernote的笔记列表

对于这个例子,我愿意让设计师探索总是显示这四个图标的情况。一种可能的解决方案将是一直显示这些图标。它们可以是白色行里的绿色,或反过来。

evernote的笔记列表-2

这种解决方案也可以称作“多”,但请记住我们不是为设计师而设计。我们是为形形色色的人设计,他们有着不同的需求和使用计算机的不同技能。


从表面看,把这些限制用到你对组件、悬停状态和视觉设计上,好像限制了你的创新。如果有一些的话,这些指南将推动你创新的限制,因为你会发现视觉上令人愉悦的设计能够适用于更加宽泛的用户群体。

有了正确的关注,你将发现任何设计挑战都是以满足你的主管、市场团队、Dribbble 关注者和所有用户的方式来达成,包括有障碍的人们。


作者简介

Jesse Hausler 是可访问性领域、有着 12 年经验的老兵。他目前是 Salesforce 的重要可访问性专家,在这里已经四年了。特别感谢 Cordelia McGee-Tubb,是他才有了本文用到的让人尖叫的插图。


  • 注1:网页亲和力(又称网络无障碍、网络可达性或网络可用性)旨于确保任何人都有办法获取放在网页上的媒体内容——无论人们是否遭遇了身体、心理或技术上的障碍,都不会妨碍人们接收作者所发布的信息。也就是让网上的内容“易于亲近”,易于获取、利用。http://zh.wikipedia.org/wiki/網頁親和力
  • 注2:环境赋使(affordance),或称为预设用途、可操作暗示、支应性、示能性等,指一件物品实际上用来做何用途,或被认为有什么用途。也就是说在物品的某个方面,具有让人明显知道该如何使用它的特性。例如门提供“打开”的功能,椅子提供“支撑”的功能。人们得知如何使用物品有一部分来自认知心理学,另一部分来自物品的外形。http://zh.wikipedia.org/wiki/%E6%89%BF%E6%93%94%E7%89%B9%E8%B3%AA
  • 注3:捷蓝航空是美国一家廉价航空公司,由捷蓝航空公司(JetBlue Airways Corporation,NASDAQ:JBLU)拥有。主要营运美国内陆航线和来往加勒比海、巴哈马和百慕大的国际航线。http://zh.wikipedia.org/wiki/%E6%8D%B7%E8%97%8D%E8%88%AA%E7%A9%BA
  • 注4:Dragon NaturallySpeaking是一个语音识别软件包。Dragon NaturallySpeaking让忙碌的专业人士创建文档、报告、电子邮件、填写表格和工作流程,而这一切只需要说就可以完成。http://baike.baidu.com/view/8438264.htm
  • 注5:艾伦·科提斯·凯伊(英语:Alan Curtis Kay,1940年5月17日-),美国计算机科学家,在面向对象编程和窗口式图形用户界面方面作出了先驱性贡献,他是Smalltalk的最初设计者。http://zh.wikipedia.org/wiki/%E8%89%BE%E4%BC%A6%C2%B7%E5%87%AF

软件工程原则

软件工程原则

本文是翻译,版权归原作者所有



我经常思考,对于我在开发的各种机器人而言,合适的软件方法论应该是什么样子。我的想法随着时间的推移而演化,我看到了它们的作用。由于我没有受过正式的软件工程培训,这些都是我曾经看到过、听到过、阅读过的通用原则,我深信不疑(我写到这里,已经是凌晨 2 点了)。

免责:大多数建议来自其它资源。请查看附加的链接以查看详情。

既然写代码是最基础的,我就从如何编写代码开始。然后我们进入软件工程基础,它需要让软件合理地运转。

通用编程规则

在编码时,如果没有基本规则供你遵循,你的代码注定要失败且不可依赖。其它工作都基于此。很多人开始编程,想使用疯狂的模板魔法,每个地方都继承其它某些地方,然而,你必须停下来,编写易于理解和测试的简单代码。

  • 控制结构简单——限制你的分支,不要有长长的 if-else 嵌套、循环语句,不要有递归魔法。
  • 无限循环——如果你不是真想永远困在那儿,就不要使用没有边际的循环(比如主循环)。必须有一个上/下边界用于终止循环。loop 循环要比 while 循环更要慎重。
  • 没有动态内存——忘了 malloc 的存在。它能够引起程序在每次运行时有着不同的行为,还能增加内存泄露,这是难以检测到的。
  • 断言【注1】——断言有助于你实施、对每个函数有帮助的、合适的事情。我通常不喜欢默认的断言函数,它们只是简单地引起一个终止;有时候这是合适的,但是对于大部分情况,你应该尝试从错误中恢复而非只是终止。你需要评估在你的应用程序中,什么才是更安全的方式。
  • 保持作用域——尽量在一个尽可能小的范围内赋值或初始化变量、结构等。这包括了限制全局变量的使用。
  • 指针——有可能时,要限制你的指针使用。你应该真正避免多级指针。如果你引用了不应该接触的内存地址,它们追踪起来将非常混乱,且导致各种问题。
  • 检查来自所有(非 void)函数的返回值——如果一个函数能够(应该能够)返回错误的值,请确保捕获它并做相应处理。检查返回值的边界而非盲目地相信它,也是不错的。
  • 打开所有的编译器警告——这样你能尽早捕获一些情况。对于格式化文本、内存相关以及位拆裂【注2】,尤为重要。
  • 使用圆括号控制运行顺序——我见过很多人臆测或忘记了操作符的正确顺序。使用圆括号可以澄清和加强想要的结果。
  • 硬编码数字——请为任何值使用变量,它可能需要被另一名程序员修改。如果你有一个非常不明显的数字,要就该数字的来源以及如何派生写个注释。
  • 预处理器——限制预处理器为“简单的”东东,比如 #define#if。使用复杂的宏将导致难以理解的代码、难以移植、和 bug 增加的几率。
  • 标准——使你的代码保持一致性。这包含命名、空格、你的花括号放在哪里。这使得沿用代码变得容易。如果你编辑已有代码,就使用已有代码的风格。
  • 当心循环中的 NOOP 【注3】或 empty——它们应该被避免,因为它们在不同的系统运行方式不同。这主要是针对嵌入式系统的。
  • 给你的代码写文档——需要我说更多吗?

有帮助的链接

软件代码库

对于每个程序员和编程团队而言,拥有一个代码库是必须的。有很多可用的免费工具,因此没有理由不用它们。

有了软件代码库,你就用在各个点保存代码,你就能回滚都先前的工作版本。代码库还让你在每个文件记录修改了什么、被谁修改了的日志。这些工具是和团队分享代码的优秀方式——没有必要来来回回用邮件发送代码。你还能把文档放入代码库,这样它就一直是可用的。

有很多可选项,cvs、svn、mercurial、git、bitkeeper 等。挑一个!

最近我在用一种托管的代码库服务,名叫 github.com——我喜欢与这个网站上的 git 打包的所有工具。bitbucket.org 是另一个类似选择。

我通常喜欢在根目录下按照如下目录创建代码库:

  • bin——所有二进制文件编译后放置的地方。默认情况下,这个目录不应该签入代码库。
  • config——如果有很多 config 文件,我把它们都放在这个目录。
  • doc——系统文档(数据表、图表、笔记等)
  • externals——来自于外部的源代码和资源库(比如,你不想编写它们了)
  • includes——多个程序想在编译时访问的头文件。
  • libs——所有资源库编译之后存放的地方。默认情况下,这个文件夹也不要签入代码库。
  • src——这里应该是我们写代码的地方。
  • tools——不适用于其它地方的、比较随意的“工具”。

除了以上目录,我通常把项目的主 make file 和 README 放在根目录。

链接

Bug/问题追踪

用一个中央数据库追踪 bug,对于开发没有 bug 的优秀代码是非常重要的。任何人发现了 bug,用这种方式来报告,就不会丢失。仅仅告诉开发人员或给他们发邮件是不够的。人们忘性大。理想情况下,你不想让开发人员关闭 bug;你想让某人报告 bug,开发人员修复它,然后发现该 bug 的那个人确认它被正确地修复了。

一个 bug 报告需要三个条件:

  1. 你期望发生什么
  2. 实际发生了什么
  3. 重现可观察到的行为(比如 bug)的步骤。尽量最小化需要重现错误的步骤数。包括你在用的代码版本。

链接

自动化测试

你的每个过程/函数都应该有一组测试(经常叫做“单元测试”)以验证它们的功能。这些测试确保函数能够应付各种输入极端和组合。理想情况下,你应该针对一个函数内的每种可能控制提供测试。

比如,如果你在测试函数“sum(number1, number2)”,可以有下列测试文件:

int main () {
  assert( 2 = sum(1,1));
  assert( 0 = sum(-1,1));
  assert( NULL = sum(1,NULL)); // or whatever you want it to be doing…
  assert( 0 = sum(0,0));
  assert( 1999998 = sum(999999,999999));
  assert( 5 = sum(4.2,1)); // assuming integers and that floats are rounded.

  RETURN (TRUE);
}

在这个例子中,断言将要在失败和终止的地方打印出错误信息。(这只是我的小例子,一定可以被扩展和改善。它还可以修改为在编译时失败,这样你就无需“运行”它了。)

自动化(每天)构建系统

拥有一台构建服务器,在有人提交代码、或最小化地、每个晚上时构建,对于识别引入系统的不经意的 bug 和自动化运行测试,是非常有价值的。

你对一个模块做了修改,经常引起另一个模块的失败。但是作为开发人员,我们聚焦于我们的模块,倾向于尽量不重新编译所有代码来确保我们没有破坏其它地方。早些发现你搞坏的地方,常常是更好的。

某些修改倾向于让其它模块破坏更多的模块。例如,修改一个使用广泛的头文件或消息定义文件,会引起其它问题。

它还让程序员仅把编译好的代码提交到代码库而不破坏代码。如果他们提交了有问题的代码,构建工具可以/应该发送邮件,通知人们这次构建失败了(以及这是谁的错误)。

你可以弄一个快速脚本来签出代码库、构建、分析输出和邮件发送结果;也可以找一个合适的构建系统,比如 Jenkins。通常地,使用其他人的产品更加可靠,也可以有效利用你的时间。

链接

代码审查

代码审查是艰难的,但是它们也是减少代码中 bug 数量的最好办法。传统的代码审核——会议室里一个人站起来和其他人讨论所有代码(这个人从没见过该代码)——不会起作用!让人们熟悉程序的运行或许是有好处的,但是这不是代码审查和找到问题的最好办法。

为了做代码审查,你应该让该程序员给 1-3 名其他软件工程师发送审查请求。这个审查请求应该包含代码总览、流程图和访问代码的说明。工程师的数量和他们的经验应该符合代码的关键程度。每个参与审查的人应该是:

  1. 审查代码(比较明显)——不要拘泥于形式,你要珍惜自己的时间。
  2. 整理一份关于代码的问题清单。
  3. 编写一些测试,确保他们通过了(它们可以被提交,常常运行在每日构建)。

在一次大型审查之前的代码审查时,审查者应该关注的地方有:

  • 代码做了它应该做的事情吗(不要做它不应该做的)?
  • 代码/函数应该用更有效的方式编写吗?
  • 控制流程是简单/整洁/优秀的吗(ifelsereturn 等)?
  • switch 语句有默认的 catch 来捕获所有异常吗?
  • 代码注释够多吗?
  • 变量是在合适的作用域内(最小化全局)声明的吗?
  • 有内存泄露吗?
  • 如果返回了 NULL,会发生什么?
  • 错误/异常处理
  • 缓冲区溢出(用 charint 等做不合适的事情,内存位置)
  • 指针和基于 math 的指针
  • 数组索引
  • 关闭所有句柄(文件、端口等)
  • 宏的使用
  • 64 位和 32 位的修正
  • 多线程(有必要?线程数量、计分点、死锁、数据传递、优先级倒置等)
  • 操作符(优先级和正确的用法[&和&&、=和==])
  • 来自用户和函数的输入项在使用之前是否检查了

(上面的这个清单和优秀编程实践有些类似,你注意到了吗?)

做了上面步骤后,就可以组织一次大会,人们坐下来,通读和审查代码;虽然这不是严格必需的。

审查代码应该是艰难的。让你的程序员习惯阅读代码和审查代码,有助于加强他们自己的编码。设置一名专门的 QA 人员审查代码也是有用的。

链接

编译系统

你应该使用一种编译工具,而不用手动编译每个文件。很可能最常用的工具是 make。

每个 make 文件应该至少有如下指令:

  • all——编译所有的生产代码(production code)
  • clean——删除二进制和目标文件。使得下一次编译是完全干净的。
  • install——把二进制和资源库安装到正确的路径。这可以是如下的过程:
    • 拷贝旧的二进制文件到 <binaryName>_<archivedDateTime>
    • 拷贝新的二进制文件到带有活动进程的位置。
  • test——编译一组单元/系统测试,校验操作,确认没有引入 bug。

我相信,你应该能用一个命令来编译、安装、测试或清理你的整个系统了。正常情况下,我将为每个进程生成一个 make 文件,然后创建一个主 make 文件,来调用所有单独的 make 文件。让一个单个 make 文件编译所有文件,有利于安装新系统,也使得配置每日构建更加容易。

总结

  • 编写优秀代码。
  • 使用一个代码库服务(假定不是非常敏感的数据),比如 GitHub 或 Bitbucket。给了你代码库、问题追踪和轻松做代码审查的能力。
  • 配置一台服务器,编译你的代码库、运行测试,一天至少一次。

为了让你的软件更加可靠,你还有其它喜爱的工具吗?请在下面留言。


  • 注1:在程序设计中,断言(assertion)是一种放在程序中的一阶逻辑(如一个结果为真或是假的逻辑判断式),目的是为了标示与验证程序开发者预期的结果-当程序运行到断言的位置时,对应的断言应该为真。若断言不会真时,程序会中止运行,并出现错误消息。http://zh.wikipedia.org/wiki/%E6%96%B7%E8%A8%80_(%E7%A8%8B%E5%BC%8F)
  • 注2:位拆裂 (Bit Banging):一种利用微控制器的通用端口仿真串行接口标准(I²C、SPI等)的技术。http://en.wikipedia.org/wiki/Bit_banging
  • 注3:计算机科学中,NOP或NOOP(No Operation或No Operation Performed的缩写,意为无操作)是汇编语言的一个指令,一系列编程语句,或网络传输协议中的表示不做任何有效操作的命令。http://zh.wikipedia.org/wiki/NOP

译文:软件工程原则 》| 腊八粥
为什么注释是愚蠢的

一个实例:为什么注释是愚蠢的

本文是翻译,版权归原作者所有



当我写一篇文章、或制作一个 YouTube 视频,提及大部分情况下注释不是必需的以及实际上弊大于利时,没有什么比这更能激起宗教式讨论了。

在我读《代码大全》第二版时,我首先转到这个讨论的侧面。

在那本书,Steve McConnell 相当详实整洁地给我阐明我在代码放入如此多注释的原因:

  • 我的命名没有采用让解释型注释成为不必要的方式。
  • 我的方法和函数过于庞大,而需要额外的解释。

我写代码的方式发生了巨大的变化。

我相信我过去做得还不错,通过写注释解释我的代码、让下一个开发人员更容易地理解它们,使我成为一名本分的程序员。

但是,当我开始应用代码大全里学到的东西,开始编写经常比我之前写的注释还要整洁的代码时,我意识到,我给那些接手我代码的人所帮的忙,要比单纯地写注释更有益处。我正在让我的代码更加整洁。

当我读 Uncle Bob 【注1】的《代码整洁之道》一书时,我的观点进一步加强了。

Uncle Bob 不仅仅说我们应该避免注释,还说了——并且演示了——注释多半是怎样在表达代码意图上失败的。

Bob 让我意识到我仍然在深入学习注释,我需要进一步提高我的命名,努力让我的代码无需外在帮助就能交流意图。

演示真实的移除注释的例子

这是我自己的个人发展——我不期望每个人都和我有同样的经历,或看到同样的方式——但是,我觉得当我提起更多的会沟通的代码(communicative code)、注释通常应该避免时,还会有大量的无知要吐出来。

过了一会儿,我觉得有必要支撑一下我要说的东东。

对我而言,赞美整洁、会沟通代码的价值是一回事,而像 Uncle Bob 所定义的“整洁”,相较于过度注释的、同等“优秀”的代码,清晰的代码是多么地更容易被人理解和维护。

起初我打算编造一些代码例子向你演示这种情况。

我打算写一些代码,有着不好的命名、充满了注释,然后向你演示我是怎样重构代码以除掉这些注释并真正增加清晰程度。

但是,据我经验看,这是一个陷阱。

很多人将吐槽、指责我设立了一个“稻草人”,它无法代表真实世界的代码。

幸运的是,我意识到一个绝佳机会正向我走来。

重构来自 .NET 框架的真实代码

自从微软决定开源整个 .NET 代码库后,我决定不去挑选一段不太糟糕的真实代码示例,而要挑选可以被重构以消除代码、且仍然让代码清晰的例子——据我看,要更清晰。

我决定“挑选”的这段代码位于这里

我仅仅摘出这段代码里的一个方法,SplitDirectoryFile,来阐明我的观点。

我不知道这段代码的作者,甚至不认为这段代码是糟糕的。我只是用它作为示例,请记住这一点。

我挑选一个相当小的方法,这样容易被理解,并适合本文,但是我的总体方法可以应用到更大型的代码样本里。

在我重构之前,让我们先看看这段代码:

internal static void SplitDirectoryFile(string path, out string directory, out string file)
{
    directory = null;
    file = null;
 
    // assumes a validated full path
    if (path != null)
    {
        int length = path.Length;
        int rootLength = GetRootLength(path);
 
        // ignore a trailing slash
        if (length > rootLength && EndsInDirectorySeparator(path))
            length--;
 
        // find the pivot index between end of string and root
        for (int pivot = length - 1; pivot >= rootLength; pivot--)
        {
            if (IsDirectorySeparator(path[pivot]))
            {
                directory = path.Substring(0, pivot);
                file = path.Substring(pivot + 1, length - pivot - 1);
                return;
            }
        }
 
        // no pivot, return just the trimmed directory
        directory = path.Substring(0, length);
    }
    return;
}

正如我上面说的,这段代码还不错。

它相当清晰地表达了要实现的功能。

注释甚至更加明确。

但是,我们能够消除所有注释并真正让代码更加易懂吗?

咱们从第一行注释开始。它貌似非常无辜。

// assumes a validated full path

在这段代码里,存在我们可以讨论的方式吗?

用更好的参数名取代注释

如果我们把变量名从 path 改成 validatedFullPath,会怎样呢?

internal static void SplitDirectoryFile(string validatedFullPath, out string directory, out string file)
{
    directory = null;
    file = null;
 
    if (validatedFullPath != null)
    {
        int length = validatedFullPath.Length;
        int rootLength = GetRootLength(validatedFullPath);
 
        // ignore a trailing slash
        if (length > rootLength && EndsInDirectorySeparator(validatedFullPath))
            length--;
 
        // find the pivot index between end of string and root
        for (int pivot = length - 1; pivot >= rootLength; pivot--)
        {
            if (IsDirectorySeparator(validatedFullPath[pivot]))
            {
                directory = validatedFullPath.Substring(0, pivot);
                file = validatedFullPath.Substring(pivot + 1, length - pivot - 1);
                return;
            }
        }
 
        // no pivot, return just the trimmed directory
        directory = validatedFullPath.Substring(0, length);
    }
 
    return;
}

没有太大差异——太琐碎——但是,我们不但消除了一行注释,我们也让调用这个方法的人能够根据变量名辨认出,它们不只是传递过来的 path,而是验证过的 full path。

重申,这貌似是个相当小的修改,但是,我相信它让代码更清晰了。

稍微大点儿的改动……

继续,我们可以看到下面的注释:

// ignore a trailing slash

现在,我们有了处理这种情况的一些方法。

我们可以用一种方法简单地取代注释,因此有了:

if (ShouldIgnoreTrailingSlash(length, rootLength, validatedFullPath)

    length–;

我们还可以创建简单的布尔变量来取代注释:

bool ShouldIgnoreTrailingSlash = length > rootLength && EndsInDirectorySeparator(validatedFullPath);

if(ShouldIgnoreTrailingSlash)

    length–;

另外,或许更好的可能性就是下面的方法:

length = LengthWithoutTrailingSlash(length, rootLength, validatedFullPath);

不过,我看到了一个机会,那就是改善整体阅读性和代码结构,并去掉注释。

这次该怎样重构呢?

public class DirectoryFileSplitter
{
    private readonly string validatedFullPath;
    private int length;
    private int rootLength;
 
    public DirectoryFileSplitter(string validatedFullPath)
    {
        this.validatedFullPath = validatedFullPath;
    }
 
    public void Split(out string directory, out string file)
    {
        directory = null;
        file = null;
 
        if (validatedFullPath != null)
        {
            length = validatedFullPath.Length;
            rootLength = GetRootLength(validatedFullPath);
 
            IgnoreTrailingSlash();
 
            // find the pivot index between end of string and root
            for (int pivot = length - 1; pivot >= rootLength; pivot--)
            {
                if (IsDirectorySeparator(validatedFullPath[pivot]))
                {
                    directory = validatedFullPath.Substring(0, pivot);
                    file = validatedFullPath.Substring(pivot + 1, length - pivot - 1);
                    return;
                }
            }
 
            // no pivot, return just the trimmed directory
            directory = validatedFullPath.Substring(0, length);
        }
 
        return;
    }
 
    private void IgnoreTrailingSlash()
    {
        if (length > rootLength && EndsInDirectorySeparator(validatedFullPath))
            length--;
    }
}

或许做得过度了,但是我认为把原来的静态方法放到一个实际的类里,要清晰很多。

然后我们可以非常清晰地说明该类应该做什么,并利用类的状态来简化代码。

尽量整洁地移除代码,导致我重构了这个方法本身,因为我发现,表达这个意图的最清晰方式就是将其封装。

现在,你或许认同了这种特定的重构——还不错——但是我确信,你仍然想看到我们是怎样通过更清晰地表达代码意图来轻松地去除注释的。

现在容易了

去除剩下的注释就有些琐碎了:

// find the pivot index between end of string and root

我们所有不得不要做的修改就是将其改成刚才提到的方法。

public class DirectoryFileSplitter
{
    private readonly string validatedFullPath;
    private int length;
    private int rootLength;
    private bool pivotFound;
    public string Directory { get; set; }
    public string File { get; set; }
 
    public DirectoryFileSplitter(string validatedFullPath)
    {
        this.validatedFullPath = validatedFullPath;
        length = validatedFullPath.Length;
        rootLength = GetRootLength(validatedFullPath);
    }
 
    public void Split()
    {
        if (validatedFullPath != null)
        {
            IgnoreTrailingSlash();
 
            FindPivotIndexBetweenEndOfStringAndRoot();
 
            // no pivot, return just the trimmed directory
            if(!pivotFound)
                Directory = validatedFullPath.Substring(0, length);
        }
 
        return;
    }
 
    private void FindPivotIndexBetweenEndOfStringAndRoot()
    {
        for (int pivot = length - 1; pivot >= rootLength; pivot--)
        {
            if (IsDirectorySeparator(validatedFullPath[pivot]))
            {
                Directory = validatedFullPath.Substring(0, pivot);
                File = validatedFullPath.Substring(pivot + 1, length - pivot - 1);
                pivotFound = true;
            }
        }
    }
 
    private void IgnoreTrailingSlash()
    {
        if (length > rootLength && EndsInDirectorySeparator(validatedFullPath))
            length--;
    }
}

我仍然忍不住继续修改这个结构。

我把 DirectoryFile 移到了类的属性,而不是方法的参数。

但是,这没有移除注释和用方法取代它重要,然而在逻辑之上提供了一种抽象。

我还把找到 pivot 的方式挪到了变量里,因此我们可以显式地使用其状态,而不是依靠之前的 return。(甚至都不用一行注释来解释)

现在意图更加清晰了。如果我们找不到 pivot,就只返回 trim 过的目录。

最终重构

我们来到了最终的重构:

public class DirectoryFileSplitter
{
    private readonly string validatedFullPath;
    private int length;
    private int rootLength;
    private bool pivotFound;
    public string Directory { get; set; }
    public string File { get; set; }
 
    public DirectoryFileSplitter(string validatedFullPath)
    {
        this.validatedFullPath = validatedFullPath;
        length = validatedFullPath.Length;
        rootLength = GetRootLength(validatedFullPath);
    }
 
    public void Split()
    {
        if (validatedFullPath != null)
        {
            IgnoreTrailingSlash();
 
            FindPivotIndexBetweenEndOfStringAndRoot();
 
            if(!pivotFound)
                TrimDirectory();
        }
    }
 
    private void TrimDirectory()
    {
        Directory = validatedFullPath.Substring(0, length);
    }
 
    private void FindPivotIndexBetweenEndOfStringAndRoot()
    {
        for (int pivot = length - 1; pivot >= rootLength; pivot--)
        {
            if (IsDirectorySeparator(validatedFullPath[pivot]))
            {
                Directory = validatedFullPath.Substring(0, pivot);
                File = validatedFullPath.Substring(pivot + 1, length - pivot - 1);
                pivotFound = true;
            }
        }
    }
 
    private void IgnoreTrailingSlash()
    {
        if (length > rootLength && EndsInDirectorySeparator(validatedFullPath))
            length--;
    }
}

我们简单地抽出了 trim 过的目录,放到一个可以确切表明意义的方法里。

现在,据我看,这段代码已经非常清晰了,并且零注释。

我快速地浏览这段代码,就理解了它的功能。

这种结构和变量、方法的命名,比原来代码里的注释所要表达的意义,更加精确和清晰。

比较和对比

看下原来的代码和这份重构的代码,你认为哪一个版本更易于理解和维护。

尤其要比较实现每种功能的主要方法:

internal static void SplitDirectoryFile(string path, out string directory, out string file)
{
    directory = null;
    file = null;
 
    // assumes a validated full path
    if (path != null)
    {
        int length = path.Length;
        int rootLength = GetRootLength(path);
 
        // ignore a trailing slash
        if (length > rootLength && EndsInDirectorySeparator(path))
            length--;
 
        // find the pivot index between end of string and root
        for (int pivot = length - 1; pivot >= rootLength; pivot--)
        {
            if (IsDirectorySeparator(path[pivot]))
            {
                directory = path.Substring(0, pivot);
                file = path.Substring(pivot + 1, length - pivot - 1);
                return;
            }
        }
 
        // no pivot, return just the trimmed directory
        directory = path.Substring(0, length);
    }
 
    return;
}
public void Split()
{
    if (validatedFullPath != null)
    {
        IgnoreTrailingSlash();
 
        FindPivotIndexBetweenEndOfStringAndRoot();
 
        if(!pivotFound)
            TrimDirectory();
    }
}

是的,我修改了代码结构,让其变成了类,我用属性取代了输出参数,但是我达到了类似的结果,消除注释、用更有表现力的代码和变量命名取代了注释。(尽管如此,在特定情况下,我将承认输出参数会增加代码难度。)

没有注释的代码更容易维护

然而,我要说的是,尽量用代码表达的方式编写代码而非依靠注释,使你以更好的方式构造代码,这会因此产生更加内敛的类和更好的总体结构。

不管你是否认为我这样做得更好,你或许同意的一个地方就是,我肯定没有使其变得更糟。

如果你把有注释的代码重构为没有注释的代码、且没有损失清晰度,在我看来就是最大的胜利,因为代码被更新了,注释却没有。(至少它们经常没有)

记住,这是一些已经得体的代码示例,这里的注释不是非常不相关的。

我见过的大部分注释的代码看起来根本不是这样。

代码中的变量已经有着非常好的命名,注释描述了逻辑要做的事情,而不是它是怎样做到的。

我见过的大部分带注释的代码,使用注释做为代码的支撑,一点都不清晰。

因此,在某种意义上说,我挑了一个糟糕的例子。

我没有挑选带有糟糕注释的丑陋代码,并通过移除它们让代码更好——这应该也不难。

不管怎么说,我静候着愤怒评论家们的猛烈攻击(没有别的意思)。

你是怎么看的?

我已经完全说服你了吗?

你至少看到了编写没有注释的代码不是懒惰、而是真正有益处的——或者至少是一种偏好?



哦数据,你在哪儿?

本文是翻译,版权归原作者所有



当然,你掌控着自己的数据……是吗?哦,不一定。你同意每个保存、上传或用法术语,你可能放弃了对自己数据的掌控,甚至都意识不到。

数据安全的担忧

回想你最后一次用手机拍照。图片存放在哪里?当然,你将其放在了手机里,但是你知道它或许位于的所有其它地方吗?使用操作系统提供你的自动上传功能,是相当普通的。然而,为了方便,你授权这些有用的应用和服务访问你的手机却忘记了这些,也一样普通。Dropbox、iCloud、Google Photos 和不计其数的其它应用可以抓取你已经拍下来的每张图片的拷贝,因为你曾给过他们授权。你的手机拷贝它们并上传到云服务,经常没有告诉你。你不清楚每一次拷贝,也就失去了对自己文件的控制。

一张图片只是一个例子,因为该问题可以延伸到你的所有数据,包括文档、web 浏览器历史、财务和银行详情、位置传感器、SMS 和邮件信息,甚至还有地址簿联系人。

Facebook 的数据访问

知识就是力量。不清楚你的数据被存放、发送或分享到了哪些地方,也就意味着失控。也不清楚保护你自己的数据隐私是非常难的(几乎不可能)。因为我们经常使用的软件、应用和服务都不是直观的、透明的,你的数字财产很难有所有权。

保护你的数字财产隐私不应该这么复杂。数字隐私应该和你保护线下财产隐私一样简单:关上柜子抽屉,你存放了财务文档,或者关上你的壁橱门,那儿有你的珠宝盒子。

当你离开屋子时,你锁上前门,但是如果你的窗户仍然开着,将是毫无意义的。在你的数字生活中,你经常做着同样的事情。你不知道、或不记得你留下的所有开着的窗户,更不用说怎样去关上了。

你最终从手机上删除了图片,认为它们已经消失了,而实际上它们仍然存放在你自动上传到的多个地方。对于你的东西的每一份拷贝、真正的所有权、控制和隐私,变得越来越不直观了。

对你而言,要点还不是恐慌。要点在于你去回想你把数据放在哪里了、以及哪些应用可以访问。如果你想完全控制,或许就该关闭所有自动助手功能了。如果你不想这样,那么找到这些应用,明确地不要集成其它任何功能,故而当你决定有必要控制时,你就真的知道数据在哪儿了。

我们开发 KeepSafe,为你的数字贵重物品创建一个安全场所,把控制交给你。我们的目标是让数字隐私变得容易和直观。然而,移除混淆和歧义应该是技术行业中每个人的职责。你的所有服务需要确保你知道,你的数据发生了什么,要一直这样。最终,只有在你知道数据去哪儿了,你才能真正掌控隐私的钥匙。


关于作者:

Zouhair Belkoura 创办了 KeepSafe,因为他意识到我们对数字内容掌控很少。在他自己的生活中,他意识到个人和专业之间的界线正变得越来越模糊。他打开智能手机分享办公室的白板,但是当他滚动相册时,却在这个过程意外地暴露了隐私图片。对于 KeepSafe,目标就是让每个人都能轻松控制他们的数字财产。KeepSafe 目前在解决一种非常具体的用例;Android 和 iPhone 应用为隐藏图片和视频提供了一个保险库。