如何开发好项目

  • 为什么坏软件会发生在好人身上

这个世界上无法用金钱解决的事情不多,糟糕的软件就是其中一个。大型航空公司做出来的航班搜索app经常还比不上那帮学生做出来的。尽管既有的出租公司面临共享乘车服务的威胁,但全世界的出租车公司的打车应用都很糟糕。而那些用起来很痛苦的企业IT系统通常都是拥有大量预算的项目,而且这些项目都是用了多年的时间才开发出来的。所以不管软件糟糕的原因是什么,似乎都跟没钱无关。

令人吃惊的是,糟糕软件的根本原因跟特定的工程选择关系不大,更多是与如何管理开发项目有关。最糟糕的软件项目通常会以非常特殊的方式展开:

项目业主一开始想要开发特定的解决方案,但又从来都没有明确过自己想要解决的问题是什么。然后,他们收集了大量利益相关者的一连串要求。接着把这个清单交给外部相应的大型开发团队,由他们从头开始去开发这一高度定制化的软件。一旦所有要求得到满足,当系统发布,项目宣告完成时,每个人都会弹冠相庆。

糟糕软件的根本原因跟特定的工程选择关系不大,更多是与如何管理开发项目有关。

但是,尽管这套系统技术上符合规范,但把它交到实际用户手中时却发现了严重问题。系统运行缓慢,令人困惑,并充斥着难以捉摸的错误,导致使用起来完全是一种充满挫败感的体验。不幸的是,此时外部开发团队已被解散,已经没有剩余资源来进行必要的修复了。等到数年后再发起新项目时,一切跟这些问题原因有关的知识已经离开了组织,然后又来过一轮。

用什么样的编码语言,系统架构或界面设计合适会因项目而异。但是,软件特有的一些特征始终会导致传统管理的做法失败,但同时却让小型的初创企业用微薄的预算就能取得成功:

  1. 重用好软件很容易;这是让你可以快速开发出好东西的奥秘;
  2. 制约软件的不在于投入到开发的资源量,而在于软件在崩溃之前会变得多复杂;
  3. 软件的主要价值不在于生成的代码,而在于编写代码的人所积累的知识。

了解这些特征也许未必能保证好的结果,但这确实有助于说明为什么这么多项目会产生不好的结果。此外,这还引出了一些核心的运营原则,利用好这些原则可以大大地提高成功的机会:

  1. 开始要尽可能的简单;
  2. 寻找问题然后进行迭代;
  3. 尽可能雇用最好的工程师。

虽然有很多更加微妙的因素需要考虑,但这些原则构成了可以开始开发好软件的基础。

  • 重用软件可以让你快速开发出好东西

软件很容易复制。就机械级这个层面来说,代码行可以一字不差地拷贝粘贴到另一台计算机上。通常互联网上有很多有关如何用线上提供的现成代码模块来开发不同类型系统的教程。现代软件几乎从来都不是从头开发的。即便是最具创新性的应用,也是通过组合和修改现有软件开发出来的。

可重用代码模块的最大来源是开源社区。开源软件的代码可以自由发布,供任何人查看和使用。开源社区的许多最大的贡献者都是科技巨头。用开源代码不仅可以让应用程序开发更快,而且可以让你接触到比自己开发的任何技术都复杂得多的技术。而最流行的开源代码甚至还更安全,因为有更多人关注并及时修复漏洞。这就是数字技术能够取得如此迅速的进步的原因:哪怕是初出茅庐的工程师也可以利用上本行业最先进的工具。

如果你把所有时间都花在重新开发已有技术上面的话,你是无法取得技术进步的。软件工程就是要开发自动化系统,而第一个被自动化掉的东西就是常规的软件工程工作。关键是要了解要重用的合适系统是什么,如何根据你的独特需求去定制好,以及修复在此过程中发现的新问题。

软件工程就是去开发自动化系统,而常规的软件工程工作就是第一批会被自动化掉的东西之一。

  • 软件受制于复杂性

软件的有用性通常受其复杂性的限制,而不是为了开发它所投入的资源量。

IT系统往往功能丰富,但用户还是很讨厌,因为它们变得实在是太令人困惑了。相比之下,排名靠前的应用往往因其简洁直观而受到称赞。学习使用软件很难。到了一定程度之后,新功能其实还会让用户感觉更糟糕,因为累积下来复杂性开始变得不堪重负。比方说,iTunes作为苹果媒体生态体系的枢纽已经将近20年,到了今年终于拆分成了三个不同的应用(分别用于音乐、播客和电视节目),因为它的功能对于一个应用来说已经变得太过复杂了。从可用性的角度来看,限制不在于实现了多少功能,而在于如何融入到简单直观的界面。

即使忽略了可用性,一旦项目变得过于复杂,工程进度也会停滞不前。给应用增加的每一行新代码都有机会跟其他的每一行发生交互。应用的代码库越庞大,开发新功能时引入的bug就越多。最终,制造出来的新bug的功率盖过了开发新功能的功率。这又被称为“技术债务”,是专业软件开发的主要挑战。为什么许多大型IT系统会存在多年未能解决的问题?这就是原因所在。为项目增加更多的工程师只会增加混乱:代码库瘦身之后,应用反而跑得更快了。

开发好软件需要扩大和减少复杂性的交替循环。

在这种情况下,前进的唯一方法是后退一步,把代码库合理化并进行简化。可以重新设计系统架构以限制意料之外的交互。了非关键的功能哪怕开发出来了也可以删掉。可以部署自动化工具来检查错误和编写错误的代码。比尔·盖茨曾经说过“靠代码行数来衡量编程的进度就好比靠重量来衡量飞机的建造进度一样”。人的头脑只能处理有限的复杂性,因此软件系统能变得多复杂要取决于这种复杂性预算的使用效率。

开发好软件需要扩大和减少复杂性的交替循环。随着新功能被开发出来,失调就会在自然地在系统中积累。当这种混乱开始引起问题时,开发工作就得放一放以腾出时间去清理。这两步是必要的,因为柏拉图式的好设计根本就是子虚乌有:这要取决于你的需求以及你遇到的实际问题。即便简单如如Google搜索栏这样的用户界面之下也隐藏着庞大的复杂性,而这些复杂性是无法在一次迭代中得到完善的。挑战在于要管理好这个循环,让它变得足够混乱以取得有意义的进展,但又不要让它变得太过复杂以至于不堪重负。

柏拉图式的好设计根本就是子虚乌有:这要取决于你的需求以及你遇到的实际问题。

  • 软件关乎的是知识的形成而不是写代码

在软件开发中,大多数想法都是很糟糕的;这不是谁的错。那只是因为可能的想法数量太多了,多到哪怕非常小心谨慎以及明智地做出选择,任何特定的想法可能都未必见效。要想取得进展,你需要从一堆糟糕的想法开始,抛弃最坏的想法,然后逐步形成最有希望的想法。苹果就是富有远见设计的典范,苹果在推出最终产品之前会经历几十个原型。最终产品看起来可能好像很简单;但其背后却是非常错综复杂的认知的结果,他们是在对比了替代方案之后才选择出特定解决方案的。

即使在产品被开发出来之后,这种认知仍然很重要。如果新团队接管了自己不熟悉的软件代码,这部分软件很快就会出现退化。操作系统会升级,业务需求会变化,会发现需要修复的安全问题。处理这些微妙的错误往往比一开始开发软件还要难,因为这需要对系统的体系结构和设计原则有深入的了解。

在短期内,不熟悉的开发团队可以靠临时修复来解决这些问题。但随着时间的推移,由于额外代码的权宜性,新错误会累积。由于设计范式不匹配,用户界面变得混乱,并且整个系统的复杂性增加。软件不应该看作是静态的产品,而应视为开发团队共同理解的生动体现。

软件不应该看作是静态的产品,而应视为开发团队共同理解的生动体现。

这就是为什么依靠外部供应商来进行核心软件开发很难的原因所在。你也许会得到一个能跑的系统及其代码,但是有关它是怎么开发的,以及为什么做出这样的设计选择的宝贵知识却被带走了。这也是为什么把系统交给新供应商进行“维护”往往会导致问题的原因。即使系统进行了很好的存档,每次新团队接管时都会丢失了一些知识。慢慢地,这套系统变成了许多不同作者的代码拼凑而成。继续运行变得越来越难;到最后,再也没人能真正理解系统是如何运作的。

为了让软件长期保持运转良好,在外部帮助下让你的员工跟着一起学,这样可以让关键的工程知识留在你的组织之内。

  • 开发好软件的3个原则

1. 开始要尽可能的简单

对于特定领域而言,想成为“一站式解决方案”的项目往往注定是要失败的。做这种项目的理由似乎足够合理:要想确保应用能解决大家的问题,还有什么比让它尽可能多地解决问题更好的方法呢?毕竟,像超市这样的实体店就很管用。但应用的不同之处在于,实体店开张后增加销售的新商品相对容易,而应用的功能要想增加一倍其开发难度可不仅仅是增加一倍,而且也更难用了。

开发优秀的软件需要聚焦:要从可以问题最简单的解决方案开始。一个精心设计但过于简单的应用添加必要功能从来都不是问题。但是,一个什么都可以做但什么都做不好的大型IT系统往往没法简化和修复。即使是像微信、Grab、Facebook这样“什么都做”也做得很成功的应用,也是从非常具体的功能开始的,只是在站稳了脚跟之后才慢慢开始扩展的。软件项目很少会因为太小而失败; 失败是因为太大了。

软件项目很少会因为太小而失败; 失败是因为太大了。

遗憾的是,在实践的时候保持项目聚焦非常困难:光是收集所有利益相关者的需求就已经折腾出了大量功能。

管理这种膨胀的方法之一是采用优先列表。需求还是要收集,但每项需求都要进行标记,看看是分别究竟是绝对关键的功能,还是具有高附加值的功能,还是非常有用的功能。这样计划流程的排期就可以没那么紧张了,因为没有哪项功能被明确排除掉。然后,利益相关者就可以更加安心地讨论哪些功能是最重要的,而不必担心项目会遗漏什么。这种方法也让增加更多功能所需要进行的取舍明确了。利益相关者如果想要提高某项功能的优先级,就必须考虑愿意降低哪些功能的优先级。团队可以从最关键的目标开始,在时间和资源允许的情况下按照这个有限列表做下去。

我们所有最成功的应用均采用了类似的流程。Form.gov.sg一开始只是一个人工操作的Outlook宏,第一个用户的设置工作就花了我们六个小时,但今天它已经处理了约一百万次公开提交。Data.gov.sg最初就是把一个开源项目直接拷贝过去,现在已经发展到每月超过300000次访问。Parking.sg大概还有200项可能的待开发功能,但时至今日它仍有超过110万用户。这些系统虽然简单正是因为如此才受到好评。

2.找出问题然后迭代

事实上,现代软件太复杂了,变更太快了,以至于再多的计划也消除不掉所有的缺陷。这就像写一篇好文章一样,早期草稿的尴尬对于了解最终论文应该写成什么样是必要的。要想开发出好软件,你先得开发坏软件,然后再积极寻找问题并改进解决方案。

这可以先从简单的事情开始,比方说先跟你想要帮助的人交流一下。其目的是了解你想要解决的问题之根源,并避免仅根据先入为主的偏见直接跳到解决方案。我们刚开始做Parking.sg的时候,我们的假设是执法人员会认为计算优惠券的烧脑令人沮丧。但是,在跟经验丰富的执法人员度过了一个下午之后,我们发现,对于专业人士来说,做这些计算其实非常简单。这一次谈话为我们省下来数个月的潜在时间浪费,让我们把项目重点放在帮助司机上面。

要提防伪装成问题陈述的官僚主义目标。“驾驶员在处理停车券的时候感到沮丧”是个问题。“作为我们部门的家庭数字化计划的一部分,我们需要为司机开发app”不是。“用户对在政府网站上查找信息很困难感到恼火”是一个问题。“作为数字政府蓝图的一部分,我们需要重建我们的网站以符合新的设计服务标准”不是。如果我们的最终目标是让公民的生活更美好,我们需要明确承认那些会导致他们的生活更加糟糕的事情。

清楚的问题陈述让你可以对理论上难以确定的不同解决方案的可行性进行实验性测试。跟聊天机器人交流未必比浏览网站更容易,用户可能并不想在自己的手机上安装又一个app,不管它可以如何确保国家的安全。软件看似显而易见的解决方案往往存在致命缺陷,而这种缺陷又是直到投入使用才会出现。其目标还不是开发最终产品,而是先得尽快尽可能低成本地识别出这些问题。用非功能性的原型去测试界面设计。用半功能性的原型去尝试不同的功能。用匆忙写成的原型代码来帮助更快地收集反馈。在此阶段创建的任何东西都应视为一次性的。这个过程的需要输出不是编写的代码,而是更清楚地了解该开发的东西是什么。

提防伪装成问题陈述的官僚主义目标。如果我们的最终目标是让大家的生活更美好的话,我们就得明确承认会让他们的生活变得更加糟糕的事情。

在对合适的解决方案有了充分理解之后,你就可以开始开发实际的产品了。此时你不再去探索新想法并且把范围收窄,以便用你的办法识别出问题。可以先从少数测试人员开始,这是能迅速识别需要修复的bug的人。随着问题得到解决,你可以向更大的人群开放,让他们找出更加难以捕捉到的问题。

大多数人只会提供一次反馈。如果你一开始就面向大量受众开发的话,每个人反馈给你的东西都会是一样的,这样你就会进展不下去。哪怕是最好的工程师开发出的最好的产品创意也会出现重大问题。这里的目的是反复改进输出,细心打磨,直到好产品出现。

即使在完成所有这些迭代之后,发布之后出现的产品问题也是最重要的。出现概率只有0.1%的问题在测试期间可能不会被注意到。但是,一旦你拥有了一百万用户,只要问题一天得不到解决,你每天就得应付一千位愤怒的人。你需要在对用户构成实质性伤害之前先解决新的移动设备、网络中断或安全攻击造成的问题。我们给Parking.sg开发了一系列辅助系统,这些系统可以不断地检查主系统是否存在算错钱,出现重复的停车会话以及app崩溃问题。随着时间的推移,建立一个“免疫系统”可以让你避免不堪重负,因为新问题的出现是不可避免的。

总的来说,我们的办法是利用不同的反馈回环来有效地识别问题。小的反馈回环可以轻松快速地进行修正,但会错过了更广泛的问题。大型的反馈回环可以捕捉到更广泛的问题,但速度会比较慢且费用较高昂。你当然希望同时使用这两者,用紧凑的回环尽可能多地解决问题,同仍保留较大的反馈回环来捕获意外错误。开发软件不是为了避免失败;而是要有策略地尽快遇到失败,从而获取开发出好东西所需的信息。

3.尽可能雇最好的工程师

软件做得好的关键是要有好的工程师。Google、Facebook、Amazon、Netflix以及微软都运营着数量庞大的全球最大技术系统,但是,他们的面试流程是出了名的精挑细选,可以说是最严苛的,各自都在为了招募到最能干的面试者而展开激烈竞争。随着这些公司的壮大,哪怕是给应届生的薪水也水涨船高是有原因的,而且这并非因为他们喜欢撒钱。

史蒂夫·乔布斯和马克·扎克伯格均表示,最好的工程师的工作效率至少是普通工程师的10倍。这并不是因为好的工程师写代码的速度要快10倍。而是因为他们做出了更好的决定,从而节省了10倍的工作量。

好的工程师对自己可重用的现有软件掌握得更好,从而可以最大限度地减少必须从头开始开发的东西。他们对工程工具也掌握得更好,会借助来自动完成大部分的日常工作。自动化还意味着把人给解放出来,让工程师可以去解决一些意外错误,最好的工程师在这方面会做得更好。好的工程师设计的系统也更健壮,更容易被他人理解。这就产生了乘数效应,让他们的同事可以在他们的基础上更快更可靠地开展工作。总的来说,好的工程师之所以如此高效,不是因为他们制造出的代码更多,而是因为他们做出的决定可以将你从自己不知道可以避免的工作中拯救出来。

这也意味着最优秀的工程师组成的小团队的开发速度往往比一般工程师组成的大型团队还要快。他们会充分利用可用的开源代码和复杂的云服务,并将普通的任务的繁琐丢给自动化测试和其他工具来处理,这样以来他们就可以专注于工作的创造性问题解决方面。通过确定关键功能的优先级并减少不重要的工作,他们靠用户来快速测试不同想法。这是经典著作《人月神话》的核心论点。(总的来说,增加更多的软件工程师并不能加快项目进度,而只会让它变得更加庞大。)

开发软件不是为了避免失败;而是要有策略地尽快遇到失败,从而获取开发出好东西所需的信息。

跟一般工程师组成的更大团队相比,好工程师组成的规模较小的团队制造出来的错误和安全问题也更少。跟写论文类似,作者越多,最终的组合产品需要协调的编码风格,假设和怪癖也越多,可能会暴露问题的地方也越多。相比之下,由优秀工程师组成的小规模团队开发的系统会更加简洁,一致,也能更好地为创建者所理解。没有简单性就无法得到安全性,简单性很少是大规模协作的结果。

技术工作的协作性越强,就越需要更好的工程师。工程师代码里面出现的问题不仅会影响他自己的工作,也会影响同事的工作。在大型项目中,糟糕的工程师最终会给彼此制造更多的工作,因为错误和糟糕的设计选择造成大量问题。大型项目需要建立在可靠的代码模块上,还需要采用有效的设计,并且要有非常明确的假设。你的工程师越好,你的系统在因为自身重量而坍塌之前就能变得越大。这就是为什么最成功的科技公司,尽管规模庞大,但仍然坚持要求最好的人才的原因。系统复杂性的硬限制不在于技术工作的数量,而在于质量。

  • 结论

好的软件开发要从清楚地了解你想要解决的问题开始。这让你可以去测试很多可能的解决方案,并把精力集中在好的上面。通过重用适当的开源代码和云服务,允许马上访问已做好的软件系统和复杂的新技术,可以加速开发的进程。开发周期会在探索和整合之间交替进行,先是快速而混乱地探索新想法,然后再聚焦和简化以保持复杂性的可管理性。随着项目的推进,它要逐渐接受后续更大范围人群的测试,以便解决日益不常见的问题。发布对于好的开发团队来说才是真正工作的开始:应该开发一层层的自动化系统来快速处理好问题,并防止对真正用户造成伤害。最后,尽管软件开发会有无限的复杂性,但理解好这一过程可以为处理好如何开发好软件的复杂性奠定基础。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值