如何写好程序(上)

这个标题有语带双关的意味,是如何 “写好” 程序?还是如何写 “好程序”?如果是问如何 “写好” 程序,有很多人可能会下意识地说:很简单啊,就是把源代码打好,按下运行程序、没有问题就 “写好” 了啊!但是,“写好” 了就是 “好程序” 吗?

好程序具体的标准

“好程序” 的标准其实很主观,因为程序好不好最终还是要回归到是否有符合用户的需求,毕竟程序开发的目的是要给 “人” 使用的,所以使用的人主观觉得好用就是好程序。所谓的 “台上一分钟,台下十年功”,要怎么样让用户觉得程序好用,在开发的过程之中就隐藏着许多的细节,这些细节牵动着用户对程序的观感。如果要把整个开发的过程,也就是软件的生命周期 (SDLC) 中相关细节都列入文章的内容,所涵盖的范围太过广泛。所以在这篇文章中先撇除需求和设计间落差的问题,单就程序设计来看如何写 “好程序” 。

程序设计通常都被安排在需求分析的活动之后进行,毕竟总要先知道用户要的是什么样的功能才有办法着手写程序。需求确立后,大多是经过软件架构软件设计程序编码等过程,也就是要先制作蓝图、才开始盖房子。只是在实际操作上,如果是一个规模不大的团队,把这些过程混在一起、且战且走、边写边设计的也还不在少数。

由于是接在需求分析之后的活动,在程序设计这个阶段对需求并没有太多左右的能力,说的不值钱一点,这个阶段就只是把需求文件里用自然语言描述的内容转译为编程语言。当然最终产出的标的物就是程序本身,代表着用户的期盼、结案的关键、收钱的依据!

翻译三原则

自然语言的翻译中有 “信、达、雅” 三大原则用来衡量翻译结果的良窳。照刚才不值钱的说法,写程序也勉强算得上是翻译事业,是不是也适用这三大原则?符合这三大原则精神的程序是不是就是好程序?

首先,以第一项 “信” 的原则来看,就如同这项原则的精神,翻译的结果应该要忠实地呈现原文的意思。换句话来说,按图施工本来就是放诸四海皆准的道理,若是不按图施工又振振有词、一堆借口,会被贴上 “无良” 的标签应该也是意料中之事。所以,无庸置疑地程序写出来的结果本来就应该要符合需求的叙述,不然就等着被 PM、用户、业主打枪,最后落得收不到钱、做白工的下场。

看起来第一项 “信” 的原则还满适用的,接下来第二项 “达” 的原则是期望翻译的结果能够语句通顺。虽然源代码不像自然语言有各式各样的变化,充其量不过是一大堆的 if-else 判断式和回圈的组合,要求通顺似乎没有太大的意义。但不管是早期的程序式或是现今流行的面向对象程序设计风格,适切地分割与配置源代码区块,让运行的顺序合理、顺畅一点,以便能增加运行效率,是编写源代码的一项重要工作。像是面向对象程序中的 Design Pattern 就是为了让开发人员在遭遇已知的设计情境时,借由其他人的经验快速地决定源代码最佳分配方案。以此来看,似乎 “达” 的精神也可以用在好程序的标准上。

最后一项 “雅” 的原则是期望选用的词句能够尽可能地优雅,这对缮打源代码这项工作来说就有点难度了。会来写程序当工程师大多不是文青的料,在进度压力之下应该也没有兴致对着机器附庸风雅。况且编程语言写来写去也就那几个保留字,能够用来展现优雅的空间非常有限。有人可能会引申 “雅” 就是让源代码愈简洁愈好,源代码能够缩就缩、能够少打就绝不占版面。但我却不是很赞同这种评断程序好坏的观点,我倒觉得源代码应该要能清楚地表达运行上的意图,所以不应该是字愈少愈好。当然也不是无意义地增加源代码来充版面,毕竟源代码字数的多寡不是重点,而是能不能提供足够的资讯让阅读的人了解程序运作的过程。在之前发表的 “软件开发团队管理杂谈” 文章中有提到相关的内容,有兴趣的人可以参考一下。所以 “雅” 要成为好程序的标准应该要解释为:源代码的写作风格是否够优雅。

“信、达、雅” 这三个原则看下来好像都可以成为好程序的标准,但仔细想想又似乎有一些不足,没有办法精确地掌握软件开发工作的一些特性。就像之前提到的,整个程序设计的过程并不是只有把源代码打进去,还包含了许多在 “翻译” 前周边的构思、规划与设计等等的工作,以便让需求翻译结果能够符合 “信、达、雅” 。这些工作虽然不像源代码能直接影响程序运作,但也和程序有一定程度的关连,像是:软件架构、介面的配置、操作动线的规划、数据库的调校、资讯安全考量以及非功能性的需求等等。

好程序的四项原则

所以我依据过去的工作经验和对于这个产业特性的了解,归纳出了好程序应该具备有 “稳、引、速、谐” 四项原则。

  • “稳” 就是让程序不容易出现错误。
  • “引” 就是让程序可以直觉地、方便地被操作。
  • “速” 就是让程序有效率地运行。
  • “谐” 就是让程序看起来不觉得碍眼。

我在设定这四项原则时,是依据取舍的优先次序来排序的,也就是当原则间有冲突时、没有办法同时套用,则舍弃次序较后面的原则,以次序较前面的原则为主。举例来说,在实际进行开发工作的过程中经常会出现:如果采用了 “增加检查点” 之类比较保险的作法,但是却会因要运行的检查动作增加、让程序在运行时的效率变得比较差,或是程序在操作时变得比较不直觉。这时应以确保程序不容易出错为优先、再来是考量如何让程序的操作更容易了解、最后才是针对前二项考量的结果对程序进行效能上的调整。

接下来就分别对这四项原则做更进一步的说明。

稳:让程序不容易出现错误

这是四个原则中最优先的原则,不论程序的功能再强大、画面再华丽、运算再神速,如果程序一碰就崩溃,岂不是根本没有人可以体验这些程序带来的优点!

这个原则如同字面上的意义 - 不容易出错,对于不可预期的情况要有适当的处理,避免程序无预警的中止, “程序无预警的中止” 用移动平台的说法就是 “闪退” 。在使用经验上最让人生气的大概就是数据输入到一半程序消失、所有的内容全部不见,然后要再输入一次。尤其是要输入的数据数量众多、输入很费时耗神之下,程序瞬间消失在眼前的情况一出现,实在很难不让人理智断线。

要让程序不容易出错,讲起来很轻巧,但却是个很复杂的课题,这是要靠一连串对细节的坚持所累积出来的结果。程序开发是一种讲求精确的活动,这主要是因为目前程序最基本的运作基础就是二进位,不是零就是一,没有第三种可能。而程序的逻辑判断也只有 “是” 与 “否” ,所以在写程序时一定要明确的区分各种数据的状态并做对应处置。一但情况超出程序可处理的范围就会产生例外、影响程序的判断,最终极的结果就是被操作系统中断程序的运行、出现闪退的情况。

没闪退就没事吗?其实,程序被操作系统中止感觉上很严重,但却是最幸运的结果,最少错误没有持续地扩大。在出现了不可预期的情况、程序仍然继续运作,代表没被考虑到的数据一直在运算、储存着,对程序和数据的影响更是难料。当运作的是无关紧要的数据,顶多只是垃圾进垃圾出,等有人看到再校正就没事了。但如果是商业价值很高的资讯呢?再加上经过很长一段时间才被发现呢?数据的错误可能因为连锁反应,已被延伸、扩展到许多的周边数据中,形成一个错综复杂的结构、完全无法修正或回复。

说得太虚无飘渺,感觉不到严重性吗?假设今天开发的是加班系统,你写的程序没有考虑到某个工时组合,于是在这种组合出现的情况之下会把应该是 2 倍率的部份全部误以 1.5 来计算。原本是个很罕见的工时组合,但在工作型态的改变之下开始持续出现。问题一直到几个月之后才终于被某个锱铢必较的雇员发现,加班费竟然少给了!是变相苛扣吗?不是的话,当然是要重算清楚再补发!但麻烦的是,发现的时间点已跨过会计年度,年度结算已经完成、上年度会计数据已经被涷结、主管提报数据已依据错误的加班信息送出、叙薪晋用奖惩都已底定,更糟糕的是财报已经被公布...一堆远比补算加班费要麻烦的问题待处理。

光是只以修正错误的直接成本来看,如果是负责写程序的人应该会觉得:为什么程序当初不直接当掉就算了!这个例子对负责的开发人员来说是不可承受之重,但在规模上不过就只是一个企业茶壶内的风暴,随着程序使用的范围加大,不难想象还有可能会有更严重的结果。

所以 “稳” 是指程序的可靠度,也就是质量,而不仅仅只是程序运作时的可用性。然而,稳就是可靠度愈高愈好,最好达到百分之一百吗?理想上是!但现实上,有负责过测试工作的人应该可以理解,不是不愿意,绝大部份瓶颈都是高层不同意投入达成百分之百可靠度所需的成本。也的确,要达成百分之百可靠度的成本很有可能会远高于收益,是非常不符合经济效益的一种做法。

可靠度通常都是妥协之下的结果,在可接受的成本下让可靠度达到最大化。而要提升程序的可靠度可分二个层面来看,一是开发工作的质量以维持程序运作时的可用性,一是源代码的可维护性以延长程序的生命力。

开发工作的质量包含了开发人员需具备足够的知识来预测并回避可能出现的问题,并且对需求内容要有足够的自主意识,而不是照单全收,再来就是看测试和验证的功夫。这里所谓的知识并不是光指会使用异常处理的语法,有时候一知半解是很可怕的,盲目地增加异常处理,有的时候反而可能是造成程序崩溃的原因。更何况拦到异常并不是处理问题的结束,而是处理问题的开始。而自主意识是指对于需求的内容要有反思、辨证的能力,写需求的也是人,哪有吃芝麻不掉烧饼的。这句谚语并不是记反了而是真的发生过,我就曾遇过好几份需求内容是对芝麻小事钜细靡遗的描述,唯独就漏了烧饼。

源代码的可维护性与程序的生命周期息息相关,如果把程序看成是有生命的个体,当程序被需要、有在使用才算是活的。但人的需求是会改变的,如果程序没有办法跟上需求的变化做出调整,程序就逐渐变得难以使用,代表着程序开始走向死亡。一直到最后因为所有的功能都不符合需求而被下线,成为生命终结的程序。

在需求变迁的过程中,源代码的可维护性就成了左右程序生命的关键。源代码的可维护性愈低,跟上需求变化的成本就愈高,背离需求的可能性就愈大。当碰到变量命名毫无逻辑、排版混乱、源代码次序怪异、没有或错误注释的程序时,修改的人如果不是原作者,就需要花很多的心神、像看推理小说一般地,去抽丝剥茧读懂盘根错结的程序。或是类似的源代码片段到处都有,像是有人施展了多重影分身之术,修正一个问题要改上数次的源代码,还完全不知道程序改了会出现什么副作用。有改过这种程序的人都应该有共同的心情,会有一股想打掉重练的冲动,但碍于进度的压力落入了进退维谷的窘境,不打掉揪心、但重练却会伤肝。

有人会以 “写出别人看不懂的程序” 为荣,但我觉得 “写出让人容易了解的源代码” 境界更高。要不要做取决于心态,能做到什么程度端看个人修为。但也并非无迹可循,开发人员间的默契是增加源代码可维护性的方法之一。默契应该就是一种相互预测意图的互动过程,明文规定则是最有效形成默契的方式,这也就是为什么需要有 Coding Conventions。接下来要做的就是在团队 Coding Conventions 框架下,以命名、注释、排版等方式来建立 “雅” 所提到的撰写风格。

基本上可以朝以下几个方向来进行:

  • 为增加阅读上的便利性,源代码应保持简洁、一致性、可预测性,让阅读者能以合理的逻辑来推演,避免过于跳跃式的逻辑。
  • 延续上一点,在撰写源代码前,先思考如何配置源代码,将源代码的重用性及扩充性在合理的成本下,达到最大的利用范围。
  • 源代码最好能明确地表达程序的意图,减少使用过于精简、合并或隐晦的语法,多辅以注释说明。
  • 源代码注释应该是明确、有效的,不只是要补充说明程序运行了什么动作,也应该要解释程序设计背后的原因。

就以上的几点,我觉得撰写源代码其实和管理团队很像,跳脱不了把源代码分配到适当的地方,让其各司其职、避免重工等等的考量。

引:让程序可以直觉地、方便地被操作

“引” 指的是导引,用户界面是用户接触程序的第一线,所以用户在评价程序好坏时,往往用户界面占了很大的因素。最完美的用户界面设计应该是让用户不需要靠任何外在文件的说明,在看到画面时就能够直觉地反应要进行什么样的动作,以便驱使程序达成想要的结果。也就是说程序不只是要开发功能,同时也要思考如何引导用户来操作功能,才能让程序发挥出功效。就像是前一个原则提到的,程序的功能再强大,如果不能被使用,那这些功能的意义不过就是一堆机械码罢了。

不过,“最完美的界面” 定义上要附加一个但书,那就是用户要具备操作软件的对应知识。只有开发程序的人和用户在共同的知识基础下,所谓最完美的界面才有意义。否则,再怎么调校,都不可能会有 “不需要靠任何外在文件的说明就可以直觉地使用” 这种情况出现。像是要一个没有会计背景的人去操作会计软件,就算是在画面中加上各式各样的使用指引,要能顺利地操作基本上是天方夜谭。

在画面上配置输入或显示的控件和室内设计的概念很接近,有些室内设计在规划时往往太着重主观的视觉效果来讨好业主,却牺牲了生活中动线的细节。往往要等到施工完成之后入住了,才发现原来就只是好看而已,在使用上却是另外一回事。像是冰箱开门的方向没考虑好,冰箱打开后的门会挡在流理台和冰箱之间,以致于拿取食材时要先把冰箱的门关上之后,才能把食材放到流理台上,食材一多就要反覆的动作,使用上非常地不方便。

操作的动线和排列画面的潜规则

所以导引的第一个重点应该是规划用户操作的动线,在现今主流 Window 操作系统的制约之下,大家都习惯由窗口的左上往右下开始了解画面的构成。所以比较常见的动线规划手法都是重要的画面元素排在靠上、靠左的位置,并按重要性依序排列,动作置于靠下、靠右的位置,也就是按钮通常都摆在这样的位置上。借此来导引用户,先把重要的资讯输入完成之后,再来选择要触发的动作。

在排列画面元素的时候,可以增加使用 Frame 或分隔线区分不同特性的内容,让关连性高的资讯形成一个群组。这样的方式可以导引用户归纳画面的资讯,快速地在心里形成一个操作步骤的概念,达到直觉使用的目标。

如果要输入的数据数量较多的时候,采用分页的方式也很常见。比较重要的数据放在较前的页次,逐页地导引用户进行信息的输入,并且在触发动作前提供检视的画面,来供用户确认是否输入正确。有在使用线上购物的人,对于这样的操作模式应该不陌生才对。

类似的技巧五花八门,在此就不一一列举。但左上右下这个模式是绝对的吗?我想不是,就我所知道的,阿拉伯语系的用户界面就不是,是一个和我们熟悉的习惯完全左右相反的世界。这也带出另外一个议题是用户界面设计不应该是一体适用,而是要依据使用的对象来量身订做,像是考虑到使用对象的生理状况、操作习惯、生活背景、成长文化、使用情境等因素来做适当的调整。

有一个全世界很常用的程序可以用来做为负面的例子,那就是拒老的 Facebook Android App。也许是 Facebook 产品规划人员自诩这是年轻人使用的软件,所以完全不体谅年长者的生理需求,显示的字型大小预设都是年长者没有办法顺利阅读的大小。这就算了,竟然还限制程序不随着系统的字型设定而改变,App 本身又没有调整的功能(也许是藏在一个我找不到的位置),最后都只能是放弃使用 App 寻找替代方案来解决年长者的不便。

Facebook 的这个生理状况类型的问题如果是在 Windows 平台上,也许就不是大问题,最少还可以调个分辨率或是买个无敌大的屏幕。同样地,在使用情境的类型上,界面的平台不同,设计的细节就必须要再调整。例如网站有一阵子很流行使用 Hover 的效果,就是让鼠标的光标移到特定的位置就会有一些画面上的特效,像是跳出菜单供进一步点选。但想要让这样的网站也能在移动平台上浏览,就可能会造成很大的使用障碍,除非是你有气功,才有可能让触控屏幕侦测试你的手指悬空移到了可以 Hover 的位置。

曾经还有遇过一个和操作习惯有关的案例,现在的人如果是由 Windows 开始接触电脑,大概都很习惯鼠标的操作模式,所以在操作以鼠标为主的界面设计不会感到有什么困扰。而在协助经历过 Clipper 时代的资深会计人员开发系统时却是截然不同的体验,当时 Clipper 还是运作在 DOS 之下、鼠标还不是主流的输入设备。输入的工作都只靠键盘来完成,控件的切换靠 Tab 或 Enter 键来触发。也因为工作型态的关系,所以他们一般都练就了盯着纸张进行数据盲打的功夫。对他们来说使用鼠标是一种工作上的阻碍。一来手要在键盘和鼠标之间移动,二来使用鼠标一定要移动目光回到屏幕上、不可能盲移(有练就这等神功的高人请让我知道,以便立碑膜拜),让原本的顺畅的工作节奏变得很没效率。

当然只要设计得当,在 Windows 下还是可以用 DOS 的操作模式来使用程序。只是遥想 Visual Basic 这种 RAD 刚出来的年代,开发人员对于只要拖拉就可以完成程序的革命性变化感到兴奋,以为控件放定位靠着鼠标就可以天下无敌了。事实上果实并不甜美,但尝到苦果的却是这群资深的会计人员,因为一般开发人员都会忽略了所有可接受 Focus 的控件属性清单里都有一个叫 TabIndex 的属性。当用户按下 Tab 期望光标依序跳到下一个控件时,光标却像打地鼠一般地乱跳,以致于用户被迫要使用 “鼠标单击” 这种没效率的输入模式,这就是很典型的没考虑操作习惯的案例。

如魔术表演般吸引用户的目光

第二项重点是吸引用户的目光,使用这个观念最极致的当属魔术表演,不同的是魔术是以误导为操作的方向,和这里说的引导是相反的效果。常见的舞台魔术为了要制造惊喜的心理感受,表演者会使用眼神、言语、肢体、声光、环境等花招来让观众聚焦在特定的角落,同时间会在不受观注的角落准备下一步要呈现的内容。当一个观众没有预期到的结果突然出现,就会产生不可置信的心理落差。

在操作介面的设计中,则是要让用户把目光专注在重要的操作元素上,例如使用动画、图像、高反差的颜色来引起用户的注意。有使用过 Android 平台上新的 Material Design 的人应该会发现,在 Gmail 画面右下角会出现一个醒目的圆形图案,点了之后就可以开始撰写新电邮。我想这样设计的概念是因为进信箱的 App 后,不是要读信就是要发信。要读信的话所有收到的信已经在画面的清单里,要看哪一封就点哪一封。然后就剩下发信这一个重点功能,所以使用明显的色调来吸引你的注意,告诉你要发信点这里就对了!

导引不见得要使用颜色、图示之类这么嗳昧的方式,直接了当的用文字来陈述也不是不行,像是按钮上搭配的文字就是最明确的导引,直接告诉你按下按钮会产生什么效果。还有另外一个以前很常看到使用文字的例子,记得在 Windows XP 的年代,只要刚安装完 Windows XP 进入桌面就会有一个很明显的特效,并且有文字告诉你要开始使用 Windows 就从左下方的开始按钮开始 (嗯,真绕舌)。

告知但不要恐吓

第三项重点是让用户了解程序发生了什么事,但却不要造成用户的恐慌。以最常见的讯息显示来说,有的开发人员会习惯在拦到异常之后就把信息写入 Log 内,就当是完成异常处理了。对用户来说,看到的是程序没有任何异样,如果今天是新增一笔数据,发生错误了,但程序并没有回馈给用户。用户当然以为数据被存起来了,哪知下次要编辑时却找不到这一笔数据,才发现被骗了,这时的心情绝对好不到哪里去!

另外还有一个很常见的情况是,用户触动了程序要求进行数据处理,程序也依指令开始动作。但这时候用户却发现程序没反应了,好像已经在处理了、又好像当掉了,不知道该等待?还是该强制中止程序?(嗳昧总是让人受尽委屈...) 会有这样的现象大多起源于在开发的阶段,开发人员在对功能进行测试时都以为瞬间工作就会完成了,殊不知这是测试用数据量不足、环境单纯的假象。等到真正上线使用,运行没有瞬间完成,反倒是画面就像是灌了液态氮一样,瞬间冻结了!

为了避免这样的情况,在二大移动平台 iOSAndroid 上都会建议开发人员不要在 Main Thread 也就是 UI Thread 上进行数据处理(1, 2),甚至会限制不得在 Main Thread 上运行远端呼叫这种不确定何时会结束的程序。如果一定要封锁画面,也许是为了要避免用户进一步的输入干扰数据处理的进行,这时也应该提供工作进度的显示资讯,让用户可以清楚地知道工作的进度和预估完成的时间,最低限度也要让用户知道程序有在动作。在可允许的情况下,最好也能提供取消的机制,让用户决定是不是要继续等待没有完成的工作。

然而,过犹不及也不是件好事,像是把底层的信息直接显示给用户。这种信息看起来是很专业,但对用户来说感受到的可能只有恐慌。因为用户大多不可能理解底层信息的意义,相对地就不可能知道要如何处理,慌张的反应是必然的。或者是过于频繁的信息显示,不管什么鸡毛蒜皮的小事都跳个信息出来,对于使用经验并不会有太大的帮助。所以信息应该要经过适当的筛选、包装,最好不要只是通知用户发生了什么事,而是建议用户可以怎么做,让用户在操作程序时的不安全感降到最低。

贴心地提供的辅助

第四项重点是方便性,程序并不是只要功能具备就足够了,是否能够方便地操作也必须要被考量在内。再以室内设计来做例子,如果在设置洗手台时没有考量到居住者的身高,虽然有洗手台、有水龙头、有镜子,而且功能都正常。但使用时因为设置的高度较低,洗手台的镜子没有办法在站立时照到全身、脸被镜子切掉一半,所以每次检视仪容时还得采半蹲的姿势;或是洗碗槽高度不够,以致洗碗时要一直弓着身体来迁就洗碗槽,长久下来让腰产生很大的负担。

回到画面的设计,以一个最常见的操作情境来看,输入日期时让你年月日一个字一个字输入比较方便?还是跳出一个日历画面让你点日期比较方便?很难说!为什么?如果是要输入最近的日期,而日历是以今天为基准点显示日历,那还满方便的。但如果是要输入出生年月日,又没有办法快速跳到指定的年份,对比较早出生的人来说那就是一场恶梦,这个时候我还宁愿一个字一个字打还比较有效率,而这就是一个直觉但使用上不方便的例子。

不方便的操作会中断用户的注意力,使得原本设定好的导引效果打了折扣,甚至是完全没有达到目的。所以这几项重点都是相辅相成的,必须要相互配合才能达到最大的导引效果,也才有可能让用户在没有外在文件的协助之下,顺利地使用程序所提供的介面。

速:让程序有效率地运行

“速” 就是程序运行的速度,也就是尽可能地缩短运行的时间,有多快跑多快。当然人的欲望是无止境的,永远不可能满足用户对速度的期望,所以重点就是要让程序的运行效率达到最大化。

一但出现程序运行速度不如预期,当然就是做效能的检测,找出运行速度的瓶颈点。如果程序有发现明确的效能瓶颈造成运行速度不理想,就针对这个部份做调校,一定会有显著的改善效果呈现出来。最担心的是检测没有办法区分出产生瓶颈的源代码,也就是慢是所有源代码的共业。这时能做的大概只有逐一地对源代码进行检视、调整,其实也差不多就是重写所有的源代码。

为了避免这种情况出现,当然最好是在编写源代码的时候就谨慎地选择要输入的源代码。以下面的二段源代码为例:

for (i = 0; i < n; i++) {
   a = b + c;
   d[i] = i * 2;
}
a = b + c;
for (i = 0; i < n; i++) {
   d[i] = i * 2;
}

运行后达到的效果都一样,但第一段的写法在效能的表现和第二段相比是有差距的。有些人可能会想,只不过是一行简单的指令,现在 CPU 都这么够力,差不了多少时间!如果把这二段源代码以时间复杂度的时间函数来表示,分别可以简单地表示成 2N 及 N+1,把这二个方程序转成图形会得到二个斜率不同的直线。随着 N 的数值变大,二条线之间的垂直距离会愈来愈远,也就是说就算 CPU 的运算能力再强大,只要循环的次数够多,二段源代码在运行时间上终究会出现不可忽视的时间差距。

这只是个简化过的示范,真实的世界中会出现的情况远比这个示范要来得复杂许多,效能差距显现的门槛就会低很多。但有经验的开发人员会犯这么明显的错吗?就像刚才说的,范例中使用的是一个很简单的循环结构,所以很轻易地就可以看出问题所在。在复杂的循环结构之下,是不是还能这么轻松地察觉程序的效能问题,可能就要打上个问号了!

再来看一个例子,在很多时候要达成需要指定的效果,可能会有很多的选项,表面上看起来挑哪一种都无所谓,但常常会失之毫厘、差之千里。像是在 .NET Framework 里有 String 和 StringBuilder 二种可用来处理字符串的选项,如果要处理的字符串会出现频繁地异动,选择 String 和 StringBuilder 在功能上都可以达成目的,但在效能上却有着明显的差异。

所以如果不希望写出来的程序永远都比别人慢上一线,但又没有什么明显的效能瓶颈,就要开始注意细节上的效能考量,毕竟聚沙能够成塔、滴水也能够穿石。再来就是充分利用 CPU 运算的技术,最常见的就属多线程或是并行计算。因应 CPU 的物理制限,新一代的 CPU 开始朝向多核心的方向发展,也就是说现在的数据处理工厂都配备了多条生产线。在处理数据的时候,二条、四条生产线同时处理数据,绝对比只用一条生产线来处理数据要快上很多。

但使用多线程来设计程序绝对不是件容易的事,最少不像喝水那么简单。如果是要同时处理多项不相关的数据,那情况会比较单纯,一种数据一条生产线、一个萝卜一个坑,大家各做各的井水不犯河水。反之,如果要处理很大量的单一数据,又想要利用所有生产线的产能来缩短运行时间,就可能会有工作如何切割、数据处理次序等问题需要烦恼。再进阶一点,因为数据都在同一个记忆体区块里,所有的生产线同步进行会产生竞争冒险可重入的情况,或是莫明其妙地进入无止尽相互等待的死锁状态,这时程序就会出现呆滞的反应。如果想要侦察这种错误,出现问题的数据组合可能稍纵及逝,不是单纯地设定断点就能拦到,没有一点想象和推理的能力,还有可能根本查不出问题的根源。

不过这细节已经脱离了这个小节的内容,牵涉到的是开发人员的素质问题,会在之后的内容说明。

这项原则为什么会排在 “引” 后面?

当一个有提供进度信息、有取消按钮但要运行二分钟的程序,和一个运行时间只要一半,但运行期间画面会冻结、没反应的程序,你选哪一个?我选前者!

还记得求学时等公交车是个很深刻的记忆,尤其是等末班车。学生时代没有钱,打的一定是最后不得已的手段。这时候等末班公交车的心理就变得很微妙,等待是一种生活体验、不知道何时结束的等待则是一种折磨。在不确定公交车过站没的情况下,这时能做的只有不住地望向公交车来的方向,怕一个闪神被公交车给跑了,但又会怀疑末班公交车是否已经过去了。如果是,应该忍痛花钱改打的早点回家,但又怕公交车其实还没过,一打的却看到公交车在远方缓缓地驶来,心里不甘,想着如果再多等一下就好了。人在候车亭里,心里会呈现一种胶着、煎熬的状态,觉得度秒如年。

假设那时的候车亭有像现在这样,提供智慧型站牌能够知道公交车到底收班了没、或是有 App 可以用 GPS 查询公交车位置,就不用遭受这种心理摧残。类似的心理效果也会出现在程序的使用上,一个不知道多久的等待在心理认知上可能会比有限度等待的时间要久,纵使实际上短很多。再加上用户可能会因为程序没有回应,心里慌乱、急于改变现状,所以干扰、中断运行,以致程序根本就没有机会完成工作,虽然他处理得比较快。

基于这样的理由,导引用户当然要比增加程序效率来得重要,在调校程序效能之前应该先解决用户感受上的问题。

谐:让程序看起来不觉得碍眼

“谐” 所想表达的概念是用户界面在配置上的和谐,让用户在看到程序时能产生优雅的感受,和 “雅” 有点异曲同工之妙,只是之前看 “雅” 的时候是比较专注在源代码的部份。

程序的操作介面优不优雅也许太过抽象、许多人没有办法在脑海中形成画面,可是只要一拿 Android 早期的画面风格和现在 Material Design 相比应该就会很有感!一个很... “工程” ,但另一个就给人有截然不同的感受,比较 “艺术” 。同样是这几个控件做的画面配置,却在套上不同的呈现手法后让使用的感受上有很大的差别、使用的愉悦度大为提升。Apple 在这方面一直都是佼佼者,也难怪 iOS 的界面设计能让这么多人拥护。

要做出让人在视觉上感到舒服的画面,是需要有美感及配色上的敏锐度,可惜这是天生的!如果你没有天份,又得不到视觉设计人员的奥援,除了多阅读一些官方提供的设计准则、模仿专业之外,还是有一些不需要天份就可以进行的细节,那就是 “对称” 和 “平衡” 。

曾经看过不少的本土自行开发的软件,在功能上比起国外同类型的产品毫不逊色,但使用起来总觉得欠了点质感。这是很可惜的一点,用廉价来形容可能太不厚道了,但的确会让人有 “这应该是不同价位带产品” 的念头,即使和国外的软件是同级的产品。会有这样的差别,就我的观察大部份是缺了对称和平衡,像是有些会看到画面中输入框的字有的大、有的小,但大小又没有一定的逻辑;或是在一样的字型大小的设定下,有些输入框比较紧贴著文字、有一些有留下不小的空间,很明显地有胖瘦不一致、排列没有对齐的情况。

而有一些则是在固定大小的视窗中,把所有可以操作的控件集中在画面的左上方,右方和下方留下一大块空白的区域。可能是我没慧根,我实在没有办法领会这样设计的意图,也许这其实是开发人员让用户在屏幕上书写注记或贴上便利贴的空间?!

这样的画面配置结果,在使用时会让人觉得碍眼、增加潜意识里使用程序的负面观感。曾经听过有一个心理测试是测试者拿着一张点了一个黑点的白纸,问受测者看到了什么,大部份的人会回答黑点而不是白纸。所以人的大脑是很容易被操弄的,同时人的大脑会被对称的东西吸引,于是保持画面上的 “对称” 和 “平衡” 就是让大脑觉得愉悦很重要的潜规则。

虽然在大家的印象中微软Windows 介面设计常常是被批评得一无是处,但其实仔细地观察会发现在 Windows 中,画面的设计是经过很严谨的规范,包含了许多 “对称” 和 “平衡” 的细节。以对话框为例,可以看到窗口的上、下、左、右边缘和最靠近边缘的控件一律保持着一定的距离,包含控件与控件之间也使用相同的距离做为间隔,当然输入框不会有胖瘦不一致的情况。而所有的控件也都以一致的对齐规则排列着,例如横向一定是对齐中心线、直向则是标题全都靠左切齐。在显示文字时使用的是相同的字型、在大小上除非有特殊的目的外也保持一致。这些配置的方式,不只是在同一个窗口中,就算是不同的窗口也维持着统一的风格。

这些细节组合出来的结果,也许不是让人惊艳的苹果经验,却是扎扎实实地表现出大公司应该展现的水准,而这也是 “谐” 希望达到的效果。虽然这是四项原则中的最后一项,但却是门槛最低的一项,并不需要血统而是每一个人都可以办到,只要订下画面配置的标准、按部就班,就可以打造出一个使用起来不碍眼的程序。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容