Don't make me think思想在代码设计中的应用

遵循用户习惯

Don’t make me think这一思想来自交互设计人员必读书《Don’t make me think》。这本书主要讲的是针对大众用户,网页交互设计应该符合用户使用习惯,不需要过多思考就能找到自己想要的东西。

例如在网页中,鼠标移动到超链接上就会变成手的形状,表明这个地方可以点击。这一特点已经成为了用户习惯,虽然网页中可以利用CSS设置鼠标形状,但是一个合理的网页设计,应该尽量遵循用户的习惯,避免让用户感到困惑。当用户感到使用起来很困惑很不方便时,就更可能放弃访问这个网站。

习惯是可以改变的

Don’t make me think思想,设计思路不是一成不变的,而是要适应使用者的习惯,而使用者的习惯可能会随着时间发生变化;另一方面,必要时也可以主动培养用户的使用习惯。下面举一个图标设计的例子。

图形界面的计算机刚开始出现时,图标设计流行拟物形态,例如按钮在视觉上是凸起的,按下后变成凹陷效果,并且有金属、塑料、木头等材质的质感。这样即使没有接触过虚拟界面的大众用户,借助现实世界中的生活经验,也能很快明白怎么用,不需要过多思考。

后来随着计算机、智能手机大面积普及,用户已经完全习惯了点击图标、按钮、菜单,扁平化风格逐渐普及起来。扁平化风格具有设计容易、美观、简洁的特点,已经成了现在交互设计的主流。

习惯是难以改变的

万物有惯性,人有习惯,人们不喜欢改变,习惯的力量很强大,是难以改变的。下面举一个从ListView到RecyclerView的例子。

Android开发做滚动列表时,经典的ListView往往是最为人所熟悉的,也是我最开始学Android时就开始使用的列表组件。听说RecyclerView功能更强大,就去做了一些初步了解。但是由于习惯问题,看了介绍文章,觉得RecyclerView太难用了,没有Header、Footer支持,没有OnItemClickListener,甚至连分割线,都还要自己写代码实现。刚开始实际使用,代码写起来也总是很不顺手。

但是因为一些原因,不得不用RecyclerView,包括NestedScrolling接口的支持等。一段时间的使用后,慢慢发现RecyclerView的设计还是合理的,尤其是其灵活性很强,能实现不少以前ListView难以实现的功能。至于不能支持ListView原生就有的一些功能,通过实现一套封装就可以解决了。

墨守成规还是颠覆传统

代码设计就像是在制造一个产品,设计时需要考虑用户的心理,好的设计才能被更多人所接受并使用。在代码设计过程中,什么时候应该使用保守方案,什么时候应该克服阻力去创新呢?一个简单的评价依据,就是对比用户的学习成本和带来的收益。

一个例子就是很多项目中都会有大量的Utils工具类,这些工具类常常都是静态方法,在能解决一些常见问题的情况下,再做一些合理注释,其学习成本不高,效果也很好。

但是对于一个足够复杂的组件,往往不容易做出Don’t make me think的效果。这时就需要深入思考其学习成本与收益了,并且常常需要在两者之间进行一定的取舍。

学习成本与收益

如何评估收益

设计一个组件,一定是为了解决某个或者某些问题。如何评估其带来的收益呢?

  1. 组件提供的功能,或者说所能解决的问题。问题越复杂,使用这个组件而不是自己开发,收益越大;能解决的问题越多,使用这个组件的机会也越多。
  2. 使用频度:使用频度越高的组件,带来的收益越大。即使是一个很简单的工具类,当其需要被大量使用时,其价值也是很大的。
  3. 使用必要性:必须要用的组件,带来的收益很大。代码常常都是有权限的,并不是人人都能随便修改任何代码;另一方面底层的硬件、操作系统、基础库也会对上层代码产生很多限制。因此有时必须使用某些组件才能解决特定问题,这个组件的价值也就越大。
  4. 解决问题的效果,主要是代码执行性能。例如一些需要算法解决的问题,好的算法可以大大降低运行成本,而用户自行设计同样高性能的算法并不容易。

如何提高收益

  1. 模块职责清晰和单一,借助模块组合可实现更多灵活的功能。
  2. 保留一定的可扩展性。对于特殊需要,能通过扩展进行实现。例如RecyclerView可以通过自定义LayoutManager,实现特殊的滚动、定位逻辑。
  3. 对关键代码逻辑和算法进行优化。
  4. ……

如何降低学习成本

在确保收益的情况下,能降低学习成本,一个组件就会设计的越成功。一些降低组件学习成本的方法如下。

  1. 【重要】减少新概念的引入,因为理解新概念的成本比较高。一些不好的代码设计会提出过多新的概念,最后解决的却只是一个本来并不复杂的问题,导致用户学习成本很高而没有得到应有的收益。

    例如刚开始学习ListView时,Adapter的概念就比较难理解。

  2. 对于常规使用,屏蔽内部复杂设计,对外保留简单接口。

    例如RecyclerView代码量较大,设计有一定复杂度。但对于常规使用,需要的知识也不是很多,尚能接受。

  3. 避免过度优化。例如一些没必要的代码复用,会增加代码嵌套层级,增加理解难度。

  4. 合理的API命名。合理的API命名本身就能对代码进行解释,用户一看就能理解。

  5. 完善的代码注释(JavaDoc)。注释包含方法内部的注释和对外暴漏接口的注释,前者方便用户理解源码,后者方便不关心设计细节的用户使用。

  6. 完善的设计和使用文档(Wiki)。对于复杂组件,除了代码中的注释,还需要提供单独的设计和使用文档。如果能用简单直观的图形来解释设计思路,能大大降低学习成本。

  7. 完善的示例(Demo)。