软创互联

3条你必须知道的软件开发原则

原则
软件

#1

原文标题:3 Key Software Principles You Must Understand
原作者信息:2012年9月7日 Chris Peters

原则1:不要重复(Don’t Repeat Yourself,DRY原则)

原则2:保持简洁(Keep it Simple Stupid,KISS原则)

原则3:适可而止(You Ain’t Gonna Need It,YAGNI原则)

如果你从业于软件开发行业,那么新技术,新语言,新概念将一直伴随着你。我们都会不时的感到疑虑:我可以跟的上这些改变并且保持着足够的竞争力吗?花些时间从我特别喜欢的电影《Casablanca》中总结出一句话来:任时光流逝,基础永存。

What’s true for love, is true for code.

基本原理将永存不破。如果你了解软件开发的基本思想,你将快速的适应新技术。在这篇教程中,我们会结合其它原则来讨论这三个基本原则。他们提供了非常强大的方式去管理复杂的软件。我将分享一些我个人的见解和想法,希望在编程或者解决实际问题时能够有用。


原则1.不做重复的事( Don’t Repeat Yourself

降低可管理单元复杂度的一个基本策略就是将他们拆解成更小的单元。

理解这个原则简直太重要了,我不会再讲第二遍。通常将其首字母缩写为DRY,出现在Andy Hunt和Dave Thomas所作的《The Pragmatic Programmer》一书中,但究其概念本身则由来已久。它指的是软件最小的部分。

当你在构建一个大的软件项目时,你将经常被无处不在的复杂事物所困扰。人们并不擅长管理复杂事物;他们更擅长于在特定范围内挖掘极具创造性的解决方案。降低可管理单元复杂度的一条最基本原则就是将系统切分成更为容易的单元。起初,你可能希望将你的系统切分成许多组件,每个组件都能体现它本身的子系统,而这个子系统包含可以实现特定功能的所有东西。

举例来讲,如果你在构建一个内容管理系统(CMS),负责人员管理的模块将会成为一个组件。这个组件可以继续被切分为诸如角色管理单元等等更多的子组件,并且它有可能会与其他的组件进行信息交互,比如安全组件。

就这样不断的将系统切分成组件,再进一步将组件切分为子组件,你终将会切分到一个层次,在这个层次上原本那些复杂单元被精简为一个个单一职责的单元。这些职责可以在一个类里面实现(假设我们在构建面向对象的应用)。类包含方法和属性,方法实现算法,算法及其子部分计算或者囊括了构建业务逻辑的最小模块。

The DRY principle states that these small pieces of knowledge may only occur exactly once in your entire system.
DRY原则指出这些小片段的知识只能在你的整个系统仅仅出现一次

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
每一个小段知识在一个系统中必须拥有一个单一,清晰,权威的呈现。

注意知识与其呈现的区别。如果我们正在CMS中实现数据库连接,那将会有个初始化数据库驱动的代码段,传递认证信息并将连接的引用保存到一个变量里。这个代码段就是知识的一部分,它是讲述某件事是如何完成的。而保存有连接引用的变量则是知识的呈现,并且这是可以被别的模块使用的。如果数据库认证信息改变了,我们将不得不修改代码段,而不是它的呈现。

一个完美的应用,每一个小的业务逻辑将他的知识封装在呈现里,并且声明为一个变量或者一个类属性。

这个变量本身是被封装在一个类里,这个类可以被描述为一个职责的呈现。类又被封装在一个组件里,组件可以被描述为功能的呈现。

除非我们达到软件项目的最顶层,即一堆日益复杂的呈现,否则我们无以继续这样的方案。这种看待软件复杂度的方式被称作模块化架构,而DRY便是它非常重要的一部分。

DRY

实现DRY

有很多种方式可以实现DRY,Hunt and Thomas建议代码生成器和数据转换。但从本质上来讲,DRY是一种将逻辑包装到呈现中的哲学。

正如一个应用中的每个部分都可以被看作是呈现,每个部分都公开了基本逻辑的特定片段:用户管理公开了CMS注册用户的访问,用户类代表单一用户并且公开了它的属性(如用户名)。它通过数据库的呈现获取属性。

DRY和模块化架构需要很好的计划。为了取得一个自上而下的表现层次,将你的应用按照逻辑分离的层次结构切分成更小部分,同时让他们可以互相通信。如果你必须管理大型项目,那以组件的方式组织他们并在组件之间使用DRY将是一个不错的选择。请尽量遵守以下规则:

  • 做一个应用程序的可视化层次结构,并将主要组件映射进去。复杂项目的每个组件间可能需要一个专用的映射。
  • 如果正处于职责关联的层次,你可能需要转换成UML图表(或者相似的)。
  • 书写大量代码之前,在你的软件项目中命名它的层次结构。定义它的呈现,并且确保知道它在周遭组件中所担当的角色。
  • 确保不同复杂层之间的呈现不会相互依赖(比如一个组件依赖于另一个组件里的一个类)。

上面所列的数据库驱动是一个简单的例子,它不像现实世界涉及到很多层(如一个特定的数据库抽象层),并且在封装逻辑方面——特别是牵涉到设计模式,你要做的事还很多。但即便你只是刚刚开始编程,有一件事也必须牢记于心:

  • 当你发现自己正在编写的代码与之前写过的相似甚至是一模一样时,花些时间想想你在做什么,千万不要让自己做重复的事情。

在现实生活中,要想一个应用程序是100%的DRY是极其困难的,但并不是不可能。不管怎样,对于一个DRY程度低到无法接受的应用程序,高难度的维护也很平常。因此,如果你看下那些代码,超过50%的软件项目最终走向失败也不足为奇。

劣质的代码很少是由劣质的程序员造成的。

很多人趋向于认为劣质的代码是由不够优秀的程序员造成的。但在我的经验里,却恰恰相反。通常劣质的代码是由劣质的客户经理以及公司内配置失衡的管理流程导致的。

一个例子

作为一个例子,假设你作为一名技术顾问受雇于一个有很多代码质量问题和维护问题的公司。你翻阅源代码并且发现了好多漏洞和重复代码——代码并不是DRY的!这个是劣质代码的典型特征,这也并不是理由。如果你看过他们的版本控制系统——又称作历史代码,你偶然发现了一些在临近截止日或者里程碑的时刻才产生的漏洞。花些时间查看都发生了哪些变化,这时你很可能会面临一个需求变更。

正如前面提到的,DRY的实现需要很好的计划。

截止时间点上的强制变更会强迫开发人员去实现一些不够精炼的方案。一旦代码因妥协而被修改,DRY原则将被彻底的牺牲在进一步的变化之中。在IT行业有很多成功企业都是由那些对技术有很好理解的人所创建,甚至是程序员自己:Bill Gates, Mark Zuckerberg, Steve Wozniak,Steve Jobs, Larry Page, Sergey Brin 以及 Larry Ellison,这是为什么呢?原因就是他们知道需要在那些方面下功夫才能完成一些事情。相反,很多公司趋向于将工程需求交到客户经理的手中,并且将概念性的部分交给业务顾问——这些人是从来没有实现过任何模块的!

因此,很多技术概念只是在Powerpoint, Photoshop中奏效,而且只在27″宽屏显示器上。如果在静态网站的时代,这样的方法或多或少也可能算作是一个不错的选择,但并不适用于当下——多设备间交互应用盛行的时代。因为程序员是软件生产线的最后一环,从概念上讲,他们是那群必须对错误做出快速修复的人。如果是客户经理,就是那些Hold不住喜欢在最后时刻做出变更的客户的人来做这事,计划被抛在九霄云外,竞实现一些快速的欠考虑的方案。这样一来,代码变得不再DRY。

这个例子有点极端(即便如此,这样的情节我可是亲眼所见的),但它论证了DRY只是理论上的概念,而这个概念正受到现实世界各方的挑战。如果你正在一个要求你以这种方式工作的公司里,或许你该建议在流程上做出一些改变(比如在技术性项目的初期引进一些技术专家)。


原则2:保持简单直接(Keep it Simple Stupid)

19世纪后叶,物理学家们纠结于解释当物体达到很远的距离,就像整个太阳系的距离时,他们间的引力,磁力,和光磁力是如何相互影响的。正因如此,一种称作以太的中间物质被假设出来了。据说,光就是通过这种中间物质传播的,并且一些其他无法解释的现象也被归结到它的身上。多年以后,一种假设扩充了这个理论,这种假设将以太的假定条件调整为实验结果。一些假设是武断的,一些还会导致其他的问题,总之整个理论异常复杂。

瑞士专利局的一名雇员,Albert Einstein,提出了解决整个以太理论的革命性想法:如果我们接受“时间并不是连续的而是相对的”这个观点后,那在计算超长距离时产生的奇怪问题都将烟消云散。他在相互竞争的情况下选用最少的假设给出最简洁的解释,这种难以置信的创新思维被引用作Ockhams’s Razor。

很多行业都有相似的思想。软件开发(还有其他行业)中,我们将其称作KISS原则。对于这个缩写还有很多不同版本,但主旨只有一个,那就是你应该以最简洁的方式做好某件事。

人类历史的实质性进展都是那些极具横向思维的人们推动的

HTTP

超文本传输协议是一个作为精简方案而广受传播的完美实例:它是为了基于文件的超文本传输而设计的,也是当前高交互性应用和桌面应用的基石。也许我们不得不找寻出解决这种协议内诸多限制的方案,甚至,或许在将来的某一天它会被其他事物取而代之。但不管怎样,现状是:基于多种请求方式(GET或者POST)、状态码或者纯文本参数,HTTP已被证明是兼具伸缩性和健壮性的。这就是为什么HTTP被WEB开发者不断推向至高的极限位置并且岿然不倒的原因。

我们认为这种方法是理所当然的,但软件开发及其标准化的历史上到处充斥着过度复杂与半途而废的解决方案。甚至于还有一个专有的短语来形容这种失败:膨胀软件(bloatware)。这样的软件通常也被描述为DOD,胎死腹中(dead onarrival)。讲到膨胀软件,我有一个与非DRY理论非常相似的理论。不管怎样,互联网的成功可以被描述为简单而高效的的成功案例。

那么要想取得最简洁的方案都需要些什么呢?在软件开发中,这都归结为可维护性与可读性。因此,KISS原则应该在需求阶段就被引入。当你正在考虑如何将客户需求转变为可实现的组件时,请试着确认以下几个部分:

功能在付出与收益之间往往达不到很合适的比例
功能间会高度依赖
功能往往会变的更复杂
我曾经参与过一个项目,这个项目的客户希望将Excel表格导入到他的员工管理软件中。Excel是一个拥有复杂文件格式的第三方软件。格式相当复杂,因为它富文本的:你可以向其添加图表或者其他一些内容,这些特性却并不是客户所需要的,他仅仅关注数据。所以,实现Excel导入功能需要实现一些并不是必须的功能。另外,Excel已经有好多个版本,同时微软每年都在发布新版本。这将会变得异常难于维护,并且将会带来不菲的开销。

我们最终实现了以逗号分隔的文本导入功能,这只需要几行代码就可以完成。数据头真的很小(等同于EXCEL工作表的CSV格式),并且这个方案是可维护的且具有前瞻性。反正EXCEL可以导出CSV(将来客户希望使用的其他程序也可以)。由于这个方案是低成本的,它是KISS规则的好实践。

总结:如果你觉得一个任务看起来很复杂,请尝试一些跳出思维定势的想法。如果某人正在向你阐述他的需求,而你觉得实现起来会很困难,这种情况下往往你是正确的。当一些功能只是很难实现时,超复杂的解决方案便会很平常的接踵而来。这就是事实,因为在整个流程中有很多人参与,而这些人根本不具备给出一个可信赖的得失分析的专业技术。因此,他们看不到问题所在。反复考量需求,看看将他们剥离到本质后还是不是客户真正想要的。花时间去讨论关键点,再解释下为什么其他的解决方案可能会更合适。


原则3:你不需要它(You Ain’t Gonna Need It)

Google+发布时,Facebook的创始人Mark Zuckerberg是第一批在这个社交网络中注册账号的人之一,而这个社交网络的目标就是将他打倒。他在“关于我”的章节只写了一句话“我在构建一些事物”。我真诚的认为这是一个聪明的总结,因为他用很少简单的词语描述出Coding的真谛。你为什么要成为程序员?热衷技术方案?追寻效率之美?不管答案是什么,但肯定不是“用标准函数创建第1000001个企业网站”。然而大多数人都是用它来挣钱的。不管在哪里工作,你都会不时的面对烦人且重复的任务。

原则“你不需要它(YAGNI)”就是用来处理这些任务的。基本的翻译就是:如果概念上没有提到,那代码中也不能出现。举个例子来讲,将数据库访问抽象在一层是惯例,他们处理不同驱动间的差异,像MySQL, PostgreSQL and Oracle。如果你正工作于一个发布在共享主机的企业网站上,那他们改变数据库的几率有多大呢?请记住概念是用预算记下来的。

如果数据库抽象没有预算,也就不会有数据库抽象。如果像改变数据库这种不太可能的事情真的发生了,那理所当然得为此变化买单。

你可能已经注意到了YAGNI与DRY驱动的模块化架构之间的不同:后者将项目切分成可控的组件来降低复杂度,而前者是通过减少组件个数来降低复杂度。YAGNI很像KISS原则,因为它也是致力于构建简单的方案。然而,KISS是通过尽可能容易的完成某件事情来实现精简方案;但YAGNI是通过根本就不实现它来达到精简。

Theodore Sturgeon,一名美国的科幻作家,阐明了一条法则:90%的一切都是废话。这是一个非常激进的想法,在现实世界的项目中没有多大用处。但请记住“废话”是很耗时的。一个很好的经验法则:软件项目中,大概80%的时间在做20%的功能。想想你自己的项目吧!每次我都会惊讶于这个机其精确的80:20规则。

如果你在一个因紧迫的期限和不精确的概念而臭名昭著的公司里,这将是一个强大的策略。你并不会因为实现了数据库抽象层而受到奖励。可能你的老大甚至都不知道什么是数据库抽象层。

正如这个概念听起来太简单,很难区分必须与非必须。例如,如果你对一个使用数据库抽象的类库或者框架很满意,你将会为其倾注大量的时间。主要的概念是以另一种方式看待软件:我们是被培训成书写可维护的且经的起时间考验的软件的。这意味着我们得培养自己的超前思想。未来将发生什么?这是大型项目的关键。但是面对小规模项目的开销问题时,不要想的太远(不知道这样翻译对不对)。如果一个小规模的企业网站确实发生了根本改变,他们可能不得不从头开始。这与全部的预算比起来不算什么重大问题。

项目计划

当你在为一个项目准备必做清单时,请考虑以下想法:

减少抽象层级以期较低水平的复杂度
将功能与特色区分开
适度假设非功能性的需求
找出耗时任务并且处理好
让我们稍微深入了解一些细节。对于清单中的第一个条目我已准备好了例子:在数据库抽象层不要隐藏数据库驱动。对任何可能增加你软件复杂度的事物都要持怀疑态度。请注意,抽象往往是由第三方类库提供的。比如,这取决于你的编程语言,一个持久层,像Java的Hiernate,PHP的Doctrine或者Ruby的Active Record等等都带有数据库抽象和对象关系映射。每个类库都增加了复杂度。我们必须要维护它。更新、补丁和安全修复是必须要做的。

我们每天都在实现特性,因为我们期望他们是有用的。因此,我们想的比较超前,同时做的也很多。例如,很多客户想要一个手机站点。手机是一个被广泛接受的词汇,并不是设计决策。它只是一个用例!当然了,那些访问手机站点的人们手机。这就意味着,相对于那些悠闲自在的在桌面上访问站点的人们,他们可能想要获得其他信息或者功能。想一下影院网站:公共汽车上的人们可能更希望获取即将发布电影的上映日期,而不是50MB的预告片。

有了一笔适当的预算,你就可以为手机的需求完成一个专门的分析。如果没有这个分析,你会很简单的只提供一些和桌面站点一样的信息。很多情况下,这将是很不错的!因为手机浏览器足以聪明到可以自己调整桌面站点到他们的显示屏上,一个激进的YAGNI方法可能根本就不会写个手机站点。

非功能性的需求并不会描述软件行为,他们只会描述一些可以用来判别软件质量的附加属性。因为描述软件品质意味着软件知识,所以劣质的概念经常会被标记为缺少功能性需求。可维护性、文档化程度以及易集成度都是非功能性需求。非功能性需求是应该是可度量的。因此,如果说“这个页面应该加载的快些”,那就显得太不具体了,但要是这样说的话“在测试平均性能时,这个页面最多要在2秒内加载完毕”就显得非常具体且具度量性。如果你想应用YAGNI原则,请适度的假设些非功能性需求如果没有在概念中提到(或者他们被提到了,但没有被具体化)。如果是你自己在写非功能性需求,请从实际出发:一个日访问只有20-50个页面的小型公司不需要做出三天的性能调整——页面应该足够快因为服务器并不繁忙。如果公司可以增加日访问量,一台好的服务器或者托管方案不应该太贵。

最后,但并非不重要,请记住80:20的经验法则!我们必须认清耗时的部分。如果一个部分是完全必要的,那你必须实现它。问题是:你如何实现它?它必须是一个小范围内的最新框架吗?如果文档没有按时更新时你需要转换为(使用)仅仅是可以发布的类库吗?当并不是所有的CMS扩展都可用时,你应该使用新的CMS吗?这样做的话必须要投入多少调研呢?“这就是我们一直以来的做事方式”不是一个令人兴奋的做事方式,但这可以不带惊喜的完成工作。

但这并不是说你就可以开始编写漏洞百出的不洁代码了,认识到这一点很重要。你在编写一个轻量级的,而不是乱七八糟的应用程序。不管怎样,YAGNI是一个实用的方法。如果你为了减少一些代码重复而要改动很多代码,那我个人认为你或许可以在预算上下点功夫,有一些非DRY的代码也是OK的。因为它是一个小的应用,所以增加的维护复杂度是可以接受的。毕竟我们始终是生活在现实中的。

让我们回到最初的论题:我们喜欢构建事物。当Beethoven谱写Diabelli Variations时,这是合同工。我不认为他妥协于预算。他付出了更多的努力,因为他不想写出一首普普通通的乐曲;他想创作出一首完美的曲子。

我当然不是暗指我们全都是天才,每一行代码都熠熠闪耀着我们的聪明才智,但我喜欢像谱曲一样来构思软件架构。我是一名充满激情的开发者,因为我想构建完美的曲子,同时也想能对我自己构思的事物引以自傲。

如果你想成为一名经验丰富且久经业务考验的开发者,那你必须熟练掌握YAGNI原则。如果你想保持你的激情,你必须一直战斗着。

总结

软件原则是一种看待软件的方式。对于我而言,一个好的原则应该基于简单的概念,但当它在面对其他技术或者哲学时应该逐步发展成为一个复杂的思想体系。你最喜欢的软件原则是什么?