设计-简约而不简单

本文来自hxfirefox,他是笔者在某国内大型企业提供敏捷XP咨询项目的内部教练。本文也是由他交给笔者帮助review,同时也授权发布在笔者的博客中。

原文地址为:直接不等于简单

码农的博弈

了解XP(极限编程)的人都知道,XP有一项实践叫做简单设计(simple design),站在这项实践对立面的是过度设计。当我们从客户价值的中心视角去审视那些我们遇到过的过度设计,自然而然就会得出一个结论:

“又TM被那些美其名曰项目经理和程序员的孙子们给忽悠了,这些功能我其实都用不到,但我还花了这么多冤枉钱去购买,下次议价时一定要砍掉80%的预算。”

一旦得出这个结论,那么很快客户和开发团队将陷入无止境的撕逼状态,群体攻击增强300%,单体理智降低80%,所以为了避免程序猿的世界被破坏,并从根本上保障码农群体可怜的经济来源,就应当想办法给客户这样一种错觉:

“你要的功能必须值这个价,如果想要新增一个功能就应该要额外收费。”

对于开发人员而言,想在这场博弈中获胜的最佳方法就是砍掉那些完全只为满足自我虚荣心(以此证明自己技艺是如何炉火纯青)的多余设计和实现,只完美地产出客户真正需要和关心的功能,这就是简单设计。

似乎简单的直接设计

理论总是非常easy,但是,请注意这里的但是,由于汉字的博大精深和内涵丰富,再遇上程序员这种伴随二进制进化的只有0和1二个极端的特殊生物,“简单”一词的含义被引申到了更广的范围,演化成了简单粗暴,出现了一种在编码中随处可见的风景——我称之为直接设计(directly design)

直接设计看上去像是一种“按图索骥”的编程方法,开发人员将流程图上的处理及分支用直白的代码表达出来,比如最近在工作中遇到的一个例子:

产生的代码如下: 例1

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onMsgRecvdFromPort(RecvMsg msg) {
    checkNotNull(msg);
    if (msg.getIn().getPortType() == InPortType.A) {
        doRecord();
    } else if (msg.getIn().getPortType() == InPortType.B) {
        doRecord();
    } else {
        handleMsg(msg);
    }
}

也许团队中有那么一两个了解过clean code和重构的人,那么这段代码可能演变成如下: 例2

1
2
3
4
5
6
7
8
9
10
@Override
public void onMsgRecvdFromPort(RecvMsg msg) {
    checkNotNull(msg);
    if (msg.getIn().getPortType() == InPortType.A || msg.getIn().getPortType() == InPortType.B) {
        doRecord();
    } else {
        handleMsg(msg);
    }
}

但这还不够,再改造一下: 例3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onMsgRecvdFromPort(RecvMsg msg) {
    checkNotNull(msg);
    if (!isPortTypeAOrB(msg)) {
        handleMsg(msg);
    }
    doRecord();
}
private boolean isPortTypeAOrB(RecvMsg msg) {
    return msg.getIn().getPortType() == InPortType.A || msg.getIn().getPortType() == InPortType.B;
}

现在看上去似乎舒服多了,代码也好理解了,进行到这一步代码可以算是大的提升,但是这就结束了吗?其实这只是转嫁了问题,问题并没有结束,因为现在isPortTypeAOrB方法开始变得复杂难懂起来。不论编码资历深浅,大多数开发人员都写过类似例1的代码,这些直接设计总是自觉或不自觉地跑出来,像个幽灵一样。那么这些直接设计从何而来?

审视自己的经历,直接设计代码产生的原因有很多,归结起来有以下几种可能性:

  • 习惯于面向过程编程的开发人员转向面向对象,惯性使然
  • 新手们被要求严格地按规划的流程编码,这是最快地让新手熟练起来的方法
  • 开发人员误解了简单的含义,认为简单就是直接,忽视了设计,也即简单而不设计

人人都爱直接设计,不只是开发人员,因为那样不费脑力,有章可循,且按图索骥后责任就变成了流程的设计人员,既可以轻轻松松,又能趋利避害,不这么做似乎于情于理都很难说过去。其实直接设计并不代表代码质量有问题,相反只要意图足够清晰和简单,那么还是要推荐直接设计,毕竟开发人员都是这样被教育出来的。但是直接设计有一个很突出的缺陷——丑陋,因为总是会把过多的细节暴露出来,尤其是在分支处理上,就像上面的例1那样。

也许有人觉得这样直接挺清晰,挺容易理解,其实问题也就在这里,现在这样的分支只有两个,当用户觉得这样的需求还不能满足需要时,就会要求更多,也许会有5个,10个甚至近百个分支,那时对于开发人员而言就要不断地增加新的分支代码,就像下面的代码这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public void onMsgRecvdFromPort(RecvMsg msg) {
    checkNotNull(msg);
    if (msg.getIn().getPortType() == InPortType.A) {
        doRecord();
    } else if (msg.getIn().getPortType() == InPortType.B) {
        doRecord();
    } else if (msg.getIn().getPortType() == InPortType.C) {
        doRecord();
    } else if (msg.getIn().getPortType() == InPortType.D) {
        doRecord();
    } else if (msg.getIn().getPortType() == InPortType.E) {
        doRecord();
    }
    ...
    ...
    else {
        handleMsg(msg);
    }
}

并且在新增分支时还要小心翼翼地考虑与原有分支的逻辑关系,嵌套分支看来是在所难免了,用不了几个迭代,这些代码就会变得一堆意大利面条。

也许,万幸的是,功能都实现,你幸福地点上一根烟,满足地看着自己的杰作,突然,有个新手菜鸟心怀崇敬地问你:“大牛,这段代码是什么意思?”,你盯着代码半天心里嘀咕着,这TM是什么鬼,我怎么也看不懂了,然后只好敷衍地回答一句“这个不明白吗?回去看看设计文档!”,好不容易打发走了这个新手,项目经理找到了你,告诉了你一个晴天霹雳,客户又改需求了,可能又要新增十几个分支,你眼前一黑,感叹一声又要加班了,但又不得不重新重头解读一遍自己创作的一切,看看哪里能够插入一个新需求,于是加班又开始了。

简单设计需要设计

直截了当地设计过多地暴露细节造成扩展性和维护性也直截了当地下降,这种结局是所有开发人员都努力想避免的,如此看来简单设计并不简单,关键是设计,因为简单设计更需要设计,套用一句经典的广告语:简约而不简单,这才是简单设计想到达到的目的。现在试着重新解读简单设计,个人认为简单设计原则可以分成三个层次:

  • 实现具有用户价值的需求,简单的说就是用户要什么你就给他什么
  • 代码设计应当职责简单,简单地说就是做好一件事
  • 设计应尽可能针对一到两个问题展开,做到即设计要简单,足够针对性的解决问题即可

让我们看看从上面角度怎么来设计,仍然以上面的例子为例。根据这个原则,将上述需求实例化,可以得到:

  • when port type == A, it should not handle message
  • when port type == B, it should not handle message
  • when port type != A && != B, it should handle message

将端口类型进行归纳,可以发现其实端口是否处理消息由端口类型决定,一种端口类型是不需要处理消息类型,而另一种则是需要处理类型,因此端口消息处理只需要关心哪些端口是属于需要处理的类型即可。从这点出发可以看出例1做了太多可以委托他人去做的事情,因此设计上需要考虑将功能分离,特别是判断逻辑与功能主体剥离,使得单个主体的功能尽量简单来满足简单设计的第二条原则,按照上述思路,转化为如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void onMsgRecvdFromPort(RecvMsg msg) {
    checkNotNull(msg);
    parseMsg(msg);
}
private void parseMsg(RecvMsg msg) {
    if (!filter(msg)) { // only ports not in disabled list could be parsed
        handleMsg(msg);
return;
    }
    doRecord();
}
private boolean filter(RecvMsg msg) {
    return DisabledPortFilter.getInstance().contains(msg.getIn());
}

而DisabledPortFilter负责管理禁用端口,提供注册及过滤功能,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DisabledPortFilter {
    // FilterRule in HashMap means rule for filting with port
    // Sometimes you need to composite multi-conditions to filting, not only type of port
    // FilterRule is an interface, so any one wants to use filter should offer an implementation
    private HashMap<InPort, FilterRule> disableHandleList = Maps.newHashMap();
    private static DisabledPortFilter portFilter = new DisabledPortFilter();
    private DisabledPortFilter() {
    }
    public static DisabledPortFilter getInstance() {
        return portFilter;
    }
    public void registDisabledPort(InPort inPort, FilterRule rule) {
        disableHandleList.put(inPort, rule);
    }
    public void unregistDisabeldPort(InPort inPort) {
        disableHandleList.remove(inPort);
    }
    public boolean contains(InPort in) {
        return !disableHandleList.get(in).matchFilter(in);
    }
}

FilterRule定义如下:

1
2
3
public interface FilterRule {
    public boolean matchFilter(InPort inPort);
}

将例1中在一个方法中执行的过程分解到多个类中,每个类的职责更为单一,将复杂的过滤逻辑通过转化放在各个实现类中,也可以帮助开发者及维护者能够在某一时间点只关注其中某一中过滤规则。完成上述转化后,原来可能冗余繁复的分支处理消失了,取而代之的是短短的几行简单易懂的代码。并且转化后还带来了维护上的便利与代码扩展性的提升,当客户新增需求时,只需要增加对应的FilterRule实现,并注册到DisabledPortFilter中就可以,而不用去修改原有代码,不知不觉中又契合了OCP原则。 对照前后例子,发生变化原因是针对逻辑判断与功能主体分离这一点问题进行了设计,后面的设计都是在此基础上展开,一次只设计一个切入点使得开发人员更容易控制开发思路,而不至于过多复杂的设计带来的思维混乱,因此简单设计原则中的第三条显得尤为重要,很多时候是我们自己想的太多而导致停滞不前,举步维艰。

简单设计之路

简单设计是一条光明大道,但通向简单设计的路却并不简单,布满荆棘,很多时候并非我们不知道简单设计,而是在一次次与时间、进度博弈的过程中自觉或不自觉地放弃了简单设计,不少简单设计只需要我们再多想那么一点点,捅破这层窗户纸并不难,要做的只是多想一点,多看一眼,往往这片刻的思考就会对我们的编码产生巨大的影响,这也正是通向简单设计道路上唯一可以依靠的工具,你要做的只是多想一点,多看一眼。

作者:破  狼 
出处:http://www.cnblogs.com/whitewolf/ 
本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。该文章也同时发布在我的独立博客中-个人独立博客博客园--破狼51CTO--破狼。http://www.cnblogs.com/whitewolf/p/4712867.html

时间: 2024-12-21 11:06:41

设计-简约而不简单的相关文章

手机界面设计:让APP简约而不简单

文章描述:让APP简约而不简单. 想要APP好用,就要往里不停塞东西,这似乎已经成了一种"常识".结果往往是,一坨,惨不忍睹,你懂的.应该抱怨么?不应该.因为这其实也是设计师最该做的事儿,在一堆限制条件的重压下找到创新的解决方案.这里总结了一些比较可行的方法,和大家分享. 一.以用户需求为出发点 这似乎已经是别说烂的一句话.之所以觉得还有提的必要,是因为这才是创新的源泉和交互设计师的安身立命之本,同时也在实际中面临着很大的困难.PM老大们都忙着塞满feature list,一封邮件扔过

简约而不简单,极速迅雷只为下载而生

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 相信很多雷友吐槽过迅雷7客户端的广告影响体验的问题.用户想免费又想产品无广告体验好是可以理解的,但正如鱼与熊掌不能兼得,广告是软件收入的来源,事实上如QQ等号称完美体验的互联网产品也不能免俗.但最近迅雷推出的"极速迅雷"下载客户端,却实现了雷友们想鱼与熊掌可以兼得的美好愿望,这不得不说是迅雷强化以用户为中心的产品及运营思路

简约而不简单:网站着陆页的设计

着陆页是一个在线营销的概念,是指当访客点击一个搜索引擎优化的搜索结果进入的第一个页面或"着陆"页面.这是一个重要的页面,它和提供的产品或服务的广告有点类似,提供了与产品相关的精确的信息,告诉客户可以购买的产品或服务. 着陆页对于帮助网站把访问者转换成销售非常重要.因此,产品的消息必须明确,设计必须能吸引人且简单易懂.登陆页面的设计应有助于吸引访客购买由网站提供的产品或服务.如果它具有从潜在游客到销售较高的转换率,着陆页的设计被认为是做得优秀的.一个伟大的网站着陆页面的设计是能够让访问者

网站着陆页设计:设计简约着陆页的参考实例

文章描述:着陆页对于帮助网站把游客变成顾客非常重要.因此,产品的消息必须明确,设计必须能吸引人且简单易懂.登陆页面的设计应有助于吸引访客购买由网站提供的产品或服务.如果它从游客到顾客的转换率很高,那么着陆页的设计被认为是做得优秀的.一个伟大的网站着陆页面的设计 山边小溪:Landing Page──引导页,常被直译为"着陆页".在互联网营销中,引导页(Landing Page,有时被称为首要捕获用户页)就是当潜在用户点击广告或者利用搜索引擎搜索后显示给用户的网页.一般这个页面会显示和所

需要时显示 设计不是简单就行而是不能复杂

近几年移动平台风生水起,APP多得数不胜数,交互方式也是遍地开花,相信大家都玩过那么几个让人惊艳的APP.大家看到的亮点或是转场够炫.或是拟物得恰到好处.又或是突破性的操作方式,但我认为"需要时显示"也是许多设计中的精妙之笔,是设计师应遵循的原则之一. 需要时显示 首先谈谈"需要时显示"这个概念,记忆中这句话有2个出处: Extras on Demand. -<Designing Interfaces> 摘抄:让80%的使用情形更容易,而剩下的20%至少

素材公社,让设计变得简单

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 简单是设计的最高境界,创作一个漂亮的设计往往用最简单的表现形式就可以胜任,但简单并不代表容易,事实上,很多设计人员都走过一条从复杂到简单的风格路线,这说明设计简单的作品是需要经验积累的.素材公社结合100万设计师灵感,创作出让人过目不忘的效果! 素材公社网,创立于2008年,经历了近四年的褪变与发展,第九部落业已成为湖南省最新锐的网络信息公司

Illustrator设计简约风格GOOGLE手机壁纸制作教程

给各位Illustrator软件的使用者们来详细的解析分享一下设计简约风格GOOGLE手机壁纸的制作教程. 教程分享:   第一步:图案形状 1.新建rgb颜色模式文档.600*800px,72ppi.我们先创建图案背景.使用矩形工具(m)建一个25*25px的正方形. 2.然后使用"直线段"工具(),在正方形的中心点一下,然后按住alt+shift键,拖拽到角以绘制出一个完美的45度角,从角到角划分正方形. 3.另一边也是同样的操作.选择所有图形元素,打开"路径查找器&qu

设计公式:简单有效的竞品分析

前两天,应某位老大的邀请,为产品策划们做了一次讲座.因为事先准备并不十分充分,讲得也有点急促,所以可能在很多同学听来,会有一些枯燥和不着边际.所以干脆大致的把讲义复述下来,供所有感兴趣的同学们批评指教. 前言 一个设计去给策划们讲课,多少也算是跨职业了,所以最终选定了一个对双方都比较简单而有效的方式--"竞品分析"--来做主题. 说起来,设计和策划还是有一些共同点的.我们所从事的设计,其实可以称为狭义上的设计,无非是把一些交互和视觉元素堆积起来,把一具虚无的需求,变成一个具体的产品.而

引导用户的视觉流设计:视觉信息最简单的解读办法

文章描述:给不完整的视觉信息寻找最简单直接的解读办法. 如果你对某个设计的第一印象非常深刻,又或者你本能的觉得某个设计十分给力,那么其实极有可能你被"格式塔"了! 目前许多设计偏好运用一个或多个格式塔心理学的原则.这一举动不但能让设计有更多灵动的感觉,还比一般的设计更容易留住欣赏者得目光. 格式塔是德文Gestalt的译音,英文往往译成form(形式)或shape(形状),因此格式塔心理学所研究的出发点就是"形".但这里的形又不止于一般所指的形式或形状,还包括由知觉