我想以一个奇怪但令人惊讶地没有争议的论断来开始这篇文章,那就是:程序员是人。
我希望以此为前提,探讨如何改善程序员的境地。所以,请,无论您对此主题有何看法,为了论证,请允许我这个假设。
人们可以从这个前提出发得出许多结论,但这里我关注的是以下结果:如果程序员是人,那么对人机工程学的研究应该可以阐明如何设计程序员使用的最基本工具:API、编程语言以及类似工具。
为了明确起见,当我提到人机工程学时,我指的是所有人使用的所有事物的设计实践,而不仅仅是人机交互(HCI)。设计门把手、汽车仪表板、图形布局和马提尼酒调酒器的人们在HCI方面已经领先很久了,优秀的HCI从业者会借鉴这些经验来指导自己的工作。现在轮到我们了。
人机工程学领域的人们多年来一直在进行研究,建立方法和经验法则,但我们几乎从未直接将它们应用于我们的API设计。这是一个主要的失败。当我们讨论设计原则时,我们应该利用所有可以使用的工具。那些可以帮助我们理解如何使某物更可用、减少错误和更舒适的工具——人机工程学研究的基本重点——应该被拥抱和采纳。我坚信,将数十年的研究成果引入我们设计API和其他软件的方式中,可以快速而显著地提高生产力并降低错误率。
第一个例子:渐进式展开
作为API设计者,我们可能会谈论简单性和强大功能之间的权衡,但我们可以求助于人机工程学来寻找解决这个问题的工具。其中一个工具是渐进式展开:与其将专家级功能与基本功能放在同一级别,不如将它们放在标有“专家”的门后。以汽车为例,大多数人可以使用驾驶室内的控件,但除了少数例外(例如,玻璃水),他们永远不需要打开引擎盖。您知道发动机在那里,但大多数人只是不理会那部分。少数专家会打开它,并使用这些专家级控件调整汽车,如果您想这样做,您就知道在哪里可以找到。但是,如果操作汽车向您展示整个引擎作为控制结构,那么大多数人会发现驾驶非常复杂,即使他们只需要学会不要看“所有那些电线、管道、阀门和东西”。
您在GUI设计中相当频繁地看到渐进式展开,通常以“高级”或“专家”按钮的形式出现。这可能会公开浏览器中的Web代理设置或打印机上很少需要的颜色平衡调整配置。
我们可以在API中做同样的事情。例如,Java Swing JButton类——基本GUI按钮——有超过100种方法。但是,如果您考虑GUI按钮,通常在大多数时候您只关心大约六件事。当面对这种巨大的复杂性时,您如何知道从哪里开始?您应该调整首选大小还是最小大小?如果您更改文本会发生什么?您是否需要触发那些称为“更改侦听器”的东西,如果需要,是哪种?
为了开始控制这种胡说八道,您可以首先将JButton方法分解为三个主要组
现在我们可以使用渐进式展开来帮助降低JButton类的复杂性:将专家级内容放在getExpertKnobs()方法返回的对象中,并将图形子系统挂钩在getIntegrationHooks()方法返回的对象中,您将得到一个只有少数方法的按钮API——我们都需要的基本方法。
如果您只是计算方法和类型,那么整个系统会稍微复杂一些,但您不应该陷入认为所有方法都相同的陷阱。如果您查看呈现给程序员的内容,那么在使用按钮时,系统将从需要考虑的100多种方法减少到少于10种。所有功能都将存在且可用,但仅在您需要时才可用。在大多数情况下,您可以完全忽略它,因为它们将位于标有“您可能不需要在这里查看”的“门”后。如果有人足够残酷地将功能流放到门后,那么对于非专家用户来说,API几乎是不言自明的。(公平地说,Swing的复杂性并非独一无二。这种弊病似乎感染了大多数著名的GUI框架,包括Microsoft Foundation Classes、X11工具包等等。)
渐进式展开在这里所做的是减少表面积,我用这个术语来谈论用户在确信自己可以使用某物之前必须了解多少。API中的方法越多,用户就必须阅读和理解更多内容,然后才能知道,例如,更改标签是否像调用setText一样简单,或者其他方法是否会影响这一点。表面积越大,即使对于简单的应用程序,也很难学会很好地使用该工具。
第二个例子:受众
另一种可以应用于程序员工具的人机工程学方法是考虑受众(有时称为用户模型)。一个好的UI设计有一个受众的概念:我们为哪种人设计?他们知道什么,不知道什么,期望什么?如果UI有一个清晰且一致的受众,用户就更容易预测系统将做什么。这减少了不愉快的意外,意味着用户无需阅读文档就可以猜测如何做事(他们几乎从不这样做)。听起来像是编程语言或API中的一个非常好的功能,不是吗?
即使用户不是目标受众的一部分,这也可以工作。如果您呈现某种已知类型的用户的一致模型,其他类型的用户也可以潜在地适应,有意识地或以其他方式通过目标用户的角色进行思考。
例如,如果您将这个问题应用于C++,您会发现主要问题。有时C++认为程序员喜欢让编译器代表他们做显而易见的事情,并在编译器出错时纠正编译器。例如,C++将生成一个默认的复制构造函数,该构造函数可以创建您的对象类型的副本。是的,默认的复制构造函数可能是错误的,但它通常是正确的,因此该语言会为您提供帮助。如果它是错误的,任何C++程序员都应该知道如何提供一个可以正常工作的替代品。
在其他时候,C++对用户有不同的模型:它认为它不应该做任何有可能出错的事情,无论显而易见的假设在多少次情况下是正确的。一个例子是,如果您定义了如何检查两个对象是否等效(重写==运算符),会发生什么。似乎很明显,知道两个对象是否等效可以让您知道它们是否不等效。也就是说,知道如何测试x == y,那么x != y可以被认为是!(x == y)。
但是C++没有为您定义!=,因为在某些奇怪的情况下,这可能不成立,尽管这种情况的数量肯定微乎其微。它肯定比默认复制构造函数出错的情况要少得多。
那么,C++是一种帮助您处理通常正确的显而易见的事情的语言,还是一种更关心形式正确性的语言?例如,猜猜看:如果您定义了一个构造函数,可以从Bar对象创建一个新的Foo对象,那么C++是否使用它来自动定义foo = (Foo) bar将如何工作?换句话说,它是否覆盖了将Bar赋值给Foo的=运算符?您对这两个问题的直觉是否不同?
这种不一致性使得系统更难学习和更难正确使用。您没有用户可以掌握的一致的、可理解的受众模型,而是拥有大量特殊情况,您既无法预测也无法使用某些经验法则作为提示。
接下来该怎么做?
当您使用一个GUI,它在您拥有的专业知识和控制水平方面存在严重不一致,或者当您只想更改显示字体时,它会向您展示一个包含100个选项的对话框,那么说设计师犯了一个错误是否公平?那么为什么不以同样的方式评判API或语言设计者呢?
问题在于,这些基本经验法则和人机工程学经验并非我们为自己构建的主要工具(编程语言和API)的设计讨论的一部分。我们应该改变这一点。如果我们能从人机工程学中学习以做得更好,我们将能够编写更少错误的代碼。并且因为它会更容易,我们可以花更少的时间学习如何做事,而花更多的时间去做事。人机工程学的研究是为人类服务的,而不是为GUI服务的。并且(如上所述)程序员是人类。
因此,让我们从以下定理出发:API或编程语言是呈现给用户(程序员)的编程模型的用户界面。
让我们看看人机工程学的几个典型经验法则,看看它们如何应用。
相似的事物应该看起来相似。 如果两件事意味着相同或非常相似的事物,它们应该以表达这种相似性的方式呈现。在GUI中,这意味着无论有多少种方式和理由打开文件,打开文件的基本交互都应该是相同的。对于API,这可能意味着如果有多个可以启动和停止的事物,那么应该对每种事物使用相同的术语。例如,选择“启动”和“停止”并将其用于所有启动和停止操作非常重要,而不是在某些地方使用“结束”或“终止”或“关闭”或“销毁”来结束执行。
作为一个反例,C有两种相当不同的变量声明机制:一种用于声明函数参数(用逗号分隔),另一种用于所有其他变量(用分号终止)。尽管我们都学过这一点,但这是您可能会绊倒的小事之一。除了函数参数之外的任何地方,您都可以通过“double x, y;”声明x和y具有相同的类型;但在方法参数中,它必须是“double x, double y”,这失去了变量类型之间的联系。在第一种形式中,您可以自然地将坐标更改为保存在float变量中,而在第二种形式中,您必须独立更改x和y的类型,就好像以某种方式只更改x是合理的一样。
使用强制功能来防止错误。 您可以将此视为“这个按钮打开浴室灯,那个按钮启动全球热核战争。不要混淆它们!”规则。
强制功能使使用者做一些事情来防止(或降低可能性)某种类型的错误。例如,许多汽车不允许您在汽车未处于正确档位时取出点火钥匙。这使得您不太可能在离开汽车时将汽车置于空档,然后眼睁睁地看着它滑下山坡并撞到您岳母的新雷克萨斯。
在编程中,您可以将此应用于许多领域。在许多语言中,您可以使编写某些类型的错误代码成为不可能。在C++中,const的存在正是为了这个目的。如果编译器根本不允许您对对象进行修改调用,并且您只发出对对象的const引用,那么用户就不会犯修改他们不应该修改的东西的错误。(好吧,至少不是随意地——您可以强制转换掉const,但这显然是可疑的行为,所以您至少让他们做了一些引起警报的事情。这仍然是一个强制功能。)
另一个例子很常见:如果您没有正确的令牌,您就无法执行特定操作。任何时候您看到一个函数需要某种类型的句柄,而该句柄只能在特定位置获得,您都被迫首先做某事。例如,在Java中,您可以使用File对象打开,但您只能关闭生成的FileInputStream对象,而不能关闭File对象。这使得编写在未打开文件的情况下关闭文件的代码成为不可能。
从用户内部思考。 有趣的是,强制功能原则也证明了实际应用人机工程学原则的难度。读取文件与销毁现有文件是截然不同的操作类别,后者风险更大。正常的强制功能方法是使销毁文件比读取文件更麻烦。也许我们可以使销毁文件成为一个多步骤过程,或者使用更长的函数名称,这样就不会被意外键入。
然而,程序员不仅仅是人类,而且是出了名的懒惰和解决问题的人类。面对这样的设计,程序员会编写将所有步骤组合在一起的单个方法,或者为长方法名称创建快捷方式,或者以其他方式消除“麻烦的访问”问题。我知道我会这样做。因此,在应用此原则时,我们受到“材料”(编程语言)和受众的限制。
换句话说,您的受众具有特定的特征:习惯、假设、知识(正确和错误的)以及习俗。程序员是人。他们也是特定类型的人。而某些特定API的用户则更具体。
然而,他们几乎肯定不是您。您正在思考如何解决问题,各种方法的优点,一种算法和另一种算法之间的详细权衡,有关完成所涉及工作的文献等等。当您完成弄清楚该做什么时,您很可能成为世界上帮助人们完成这项任务的最专家之一。
而您的用户呢?他们只是希望它发生。他们将具有不同程度的专业知识,但即使是最专业的专家也只有一个使用您系统的理由:这样他们就可以尽可能少地考虑它。如果您做得很好,他们对您的代码的使用将仅与您让他们的使用量一样大。请记住,如果可能,您的用户希望您提供一个命令:dwim(执行我的意思)。
因此,您必须像您的用户一样思考:从他们的愿望和观点出发,而不是从您对解决方案和机制的复杂理解出发来思考问题。您的设计应该问:“用户想做什么?”而不是“我如何呈现Whilfolze的第三方程来优化Guilemorting原理的应用?”如果Whilfolze和Guilemorting对解决用户的实际问题有有用的见解,您应该应用他们的见解,而不是让用户告诉您如何应用它们。用户应该说“解决这个问题”,如果这是一个好主意,您的代码应该使用Whilfolze和/或 Guilemorting。
考虑一下汽车设计师问“用户如何控制汽车?”与“用户如何调整燃油输入、喷油器、气缸、火花塞、风扇、差速器等?”之间的区别。第一个问题更有可能产生像我这样的汽车白痴可以使用的设计,因为它提出的问题是我会问的问题。将设计视为调整复杂的汽车参数的一种方式几乎肯定会产生更复杂的设计,其中向我展示了更多替代方案和功能。我不想要那样。如果我想成为一名汽车专家,我会打开引擎盖。我只是想让这东西工作。
解决问题的一种方法是:编写您的用户想要编写的伪代码,然后尽可能少地添加使其工作。
主要问题是:用户试图解决什么问题?当用户想要解决问题时,他们手中有什么?用户如何看待问题?用户必须告诉我什么,我可以为自己推断出什么?这首先要对您的用户是谁有一个很好的定义,当然,大多数设计似乎都忽略了这项任务。
请记住,用户有一个臭名昭著的历史,他们认为自己知道某事应该如何完成,但却是错误的。一个经典的例子是C的register关键字。这个想法是用户(在本例中是程序员)会告诉编译器哪些局部变量应该存储在快速访问的处理器寄存器中,因为程序员知道什么是关键的。事实证明,编译器在寄存器分配方面几乎总是比用户聪明。register关键字很快变成了建议性的,而且现在我怀疑所有C/C++编译器都只是忽略它,暗自窃笑。
一头扎进人机工程学
自计算机在巴贝奇的眼中闪烁着洛夫莱斯分析的光芒以来,良好的计算机设计一直是一个话题。经验提高了我们对良好设计的理解。我们甚至深入到一些受人机工程学影响的领域,以寻找描述我们理解的方法,例如使用建筑学的模式语言概念来描述我们自己的设计模式(和反模式)。
然而,我们很少做的是深入人机工程学领域以获取其见解,并将它们应用于我们设计的内容。我们可能是最后一批意识到我们的用户实际上是人类的人,因此直接学习关于如何为人类设计事物的知识。API和语言设计者应该一头扎进人机工程学领域,并将其经验教训带回他们的工作中。而我们程序员——我们用户——应该要求这样做。
进一步阅读
Norman, D. A. 2002. 《设计心理学》。Basic Books。可能是关于为人设计的最棒的书。想想不起眼的门——设计师有多少种方法可以把它搞砸?
Tufte, E. R. 2001. 《定量信息的视觉显示》,第2版。Graphics Press。关于信息显示人机工程学的杰作。通过类比,Tufte在他的书中展示的大部分内容都可以应用于API和编程语言设计。作为一种人机交互工具,API传达有关我可以做什么以及如何做的信息。
Raskin, J. 2000. 《人性化界面》。Addison-Wesley Professional。从人类和人性的角度思考人机界面。许多优秀、独特的想法。(独特意味着您可能会发现您绝对讨厌的东西,但在您决定为什么当您讨厌它时他是错的时,您将以新的和谨慎的方式思考以人为中心的设计。)
KEN ARNOLD,一位自由顾问,是JavaSpaces的最初首席架构师。他是面向对象设计和实现的领先专家,并且是关于Jini、Java和设计原则的几本书籍和文章的作者。在Sun工作之前,Arnold是最初的Hewlett-Packard架构团队的成员,该团队设计了CORBA,阿波罗计算机的多个用户界面和Unix项目,以及加利福尼亚大学旧金山分校的分子图形。在很久以前,他是加州大学伯克利分校4BSD团队的成员,他在那里创建了用于终端独立屏幕导向程序的curses库包,并与Mike Toy和Glen Wichman共同创作了电脑游戏Rogue。他于1985年获得了加州大学伯克利分校计算机科学学士学位。
最初发表于Queue vol. 3, no. 5—
在数字图书馆中评论这篇文章
Vinnie Donati - 推动组织的可访问性
在本文中,我们将探讨微软如何在整个组织中推动可访问性,并将仔细研究促进包容性文化的基本框架和实践。通过检查诸如意识建设、战略发展、可访问性成熟度建模等方面,我们旨在为开始可访问性之旅的组织提供指南。我们的想法是分享我们学到的东西,希望您可以借鉴它,对其进行调整以适应您公司的目标,并以一种不仅仅是打勾活动的方式来培养可访问性,而是真正融入您的文化。
Shahtab Wahid - 设计系统是可访问性交付工具
设计系统是为消费者(设计师和开发人员)构建的基础设施,他们致力于应用程序。一个成功的设计系统允许组织中的消费者快速扩展跨应用程序的设计和开发,提高生产力并建立一致性。然而,许多消费者没有准备好为可访问性而构建。组织能否使应用程序的可访问性支持构建具有可扩展性、生产力和一致性?本文探讨了设计系统如何成为支持可访问性的重要工具。
Juanami Spencer - 移动应用程序的可访问性考虑
在创建移动应用程序时,考虑可访问性至关重要,以确保尽可能广泛的受众可以使用和享受它们。与桌面体验相比,移动可访问性具有独特的考虑因素,但它为那些日常活动中依赖移动设备的用户提供了巨大的价值。通过牢记这些考虑因素,移动产品开发团队可以更好地支持和改善所有用户的日常生活。本文探讨了移动应用程序的一些关键可访问性考虑因素,并重点介绍了Bloomberg Connects应用程序在产品和流程中支持可访问性的几种方式。
Chris Fleizach, Jeffrey P. Bigham - 系统级可访问性
本文通过我们使iPhone能够使用VoiceOver屏幕阅读器进行非视觉使用的工作来说明系统级可访问性。我们为非视觉使用重新构想了触摸屏输入,引入了适用于屏幕阅读器控制的新手势,并且对于输出,我们添加了对合成语音和可刷新盲文显示器(输出触觉盲文字符的硬件设备)的支持。我们添加了应用程序可以采用的新可访问性API,并使我们的用户界面框架默认包含它们。最后,我们添加了一个可访问性服务,以桥接这些新的输入和输出与应用程序之间的联系。