《构建之法-现代软件工程》读书笔记(二)

请问戴维.帕纳斯( David Lorge Parnas 软件工程专家) 先生: 您认为未来会有什么使人兴奋的软件工程技术出现吗? 前端

戴维·帕纳斯: 最有用的技术不在未来,而是已经出现好些年了,只不过咱们没好好用。面试

不少学生学了一些编程语言,读了一些技术博客,通常都豪情万丈。他们作一个项目巴不得展示本身生平所学。编程

再加上前沿技术,作一个轰动的创新。这当然值得鼓励,不过实践代表,这些每每都不能成功。后端

咱们来看下成功的例子,他们是怎么作的,例如Linux刚开发的时候:设计模式

I'm doing a (free) operating system(just a hobby, won't be big and professional like gnu) for 386(486) AT clones。网络

我正在为 386(486) AT clones 编写一个操做系统(只是一个业务爱好而已,没有GNU那么专业和庞大)。前端工程师

管理学大师彼得·德鲁克 : Those entrepreneurs who start out with the idea that they'll make it big - can be guaranteed failure.架构

那些一开始就觉得本身会作大的企业家确定会失败。运维

前言

开始本篇的时候,我想起个人初中,个人初中是在镇上念的,一周回去一次,我老是信心满满的指定了一个目标,回家把我背的书都看一遍,而后每次我都把全部的书都背了回去。可是你知道小孩子经常抵御不了诱惑,好比睡懒觉,玩游戏。当我睡醒的时候,个人小伙伴就来了,而后咱们就去打游戏。再上学的时候,我就把这些书在背到学校。这种重复性工做应该是持续了个人初中时代。想来那个时候,背回来的书一本没看的理由就是我制定的目标太过庞大,让我以为很难完成,可是那个时候的我显然没有意识到这一点,每次周末,我将书桌中的书所有装进书包的时候,我都是满心欢喜,幻想着本身在家里看书会让家里人开心。 编程语言

我又想起我上大学的项目比赛,项目组长想作一个美妆社区商城,定位上大体相等于小红书和淘宝的结合体,介绍一下咱们当时的技术背景: Servlet、JSP、JQuery、MySQL(基本SQL语句的编写),当时刚学完这些,信心爆棚。但当时有没有完成小红书加淘宝的结合体的设计目标呢! 应该是没有完成的,大体至关于咱们的目标是建一栋楼,最后建了一个风雨飘摇的茅草屋,只等"八月秋高风怒号",就“卷我屋上三重茅”。

我想假若目标太过庞大,总会让人产生莫名的畏惧心理,常规的作法就是将庞大的目标拆解为若干个看起来容易实现的小目标。

我想解决大问题当然让人感受美妙,可是把小问题真正解决好,也不容易。

浅谈软件设计原则

人们在实践中碰到的需求是常常变化的,软件设计的许多原则是从实践而来,这些原则正是为了在不断变化的需求中保证程序的可维护性和效率。咱们以两个软件设计原则为例,第一,单一职责原则(Single Responsibility Principle,SRP)指出:

一个模块(类)应该是只有一个致使它变化的缘由,一个模块应该彻底对某个功能负责。

软件设计的经典著做《敏捷软件开发:原则、模式、实践》分下下面的例子:

一个处理正方形的模块有两个功能: ① 计算面积 ② 画出这个正方形。

这个设计让一个模块负责两个不一样的职责: 进行几何运算(与显示图形无关)和图形界面绘出正方形。若是一个集合计算的程序须要使用这个模块,那么它就须要同时包括图形显示的部分(由于是在同一个模块中),这是一种浪费,同时引入了没必要要的依赖(由于图形显示和图形底层实现相关),妨碍了可移植性。另外,几何计算需求的改变和图形显示需求的改变都会致使这个模块发生的变化,增长错误发生的风险。

另外一个例子描述了一个调制解调器的API界面:

Interface Modem{
    public void dial(String pno); // pho means port number  dial 拨号
    public void hangup();  // hangup 挂断
    public void send(char c); // send 发送
    public void recv(); // recv 接收信息
}

调制解调器这个词有点生僻,可是调制解调器是Modem的义译名,它的音译名你们可能更为熟悉—猫. Modem,实际上是Modulator(调制器)与Demodulator(解调器)的合成词。调制是将数字信号转成模拟信号,解调是将模拟信号转成数字信号。

我在看到这个例子的时候,将Interface理解为接口了,我在写的时候,还在前面加上了public,若是是接口中的话,上下文就不通顺,由于上面说的是"描述了一个调制解调器的API界面",Modem是调制解调器的意思,Interface如是理解为接口的,那么这说不通。

因此"调制解调器的API界面" 应该是这么理解的,Interface是界面,Modem是调制解调器。花括号中的是API(Application Programing Interface). 接着又说道:

这个界面作了两类紧密相关的事情,链接管理(dial,hangup)和数据通讯(send,recv),他们是两类职责,仍是一类?

结论是: 根据具体状况分析,要看需求的变化是否致使这些操做同时变化。

后半部分讲的,个人理解是这个单一职责没有广泛的断定准则,要根据需求来去断定。那"这个界面作了两类紧密相关的事情"该怎么理解呢? 我理解的界面是下面这样:

可是上面不是讲的是模块吗? 这怎么又讲到界面了呢? "这个界面作了两类密切相关的事情",该怎么理解这句话?

我翻阅了《敏捷软件开发:原则、模式与实践》, 发现我可能过分理解了,这是《构建之法—现代软件工程》做者的笔误,上面的那个Modem的例子在《敏捷软件开发: 原则、模式与实践》就是Java中的接口。

那么链接管理和数据通讯应该算两种职责,仍是算一种? 换种说法,拿吃饭这件事来讲,操纵筷子和将食物放到嘴里,从吃饭的角度来讲,这两类紧密相关的事情应该是划分到一个模块中。这种理所固然的划分来源于咱们对吃饭这件事的准确理解,可是对于其余方向的需求呢?彷佛没有绝对的说法,要根据具体状况分析,要看需求的变化是否老是致使这些操做同时变化。

写到这里忽然想起微服务,起初的软件比较简单,代码量比较少,业务比较简单,用户量比较少,因此起初的软件大多先后端一体。随着硬件的快速发展,互联网的不断普及,软件逐步的再向复杂的靠近的同时,代码量也在不断的膨胀,软件开发完成又不是一锤子买卖,不像房子卖出去以后,跟开发商就关系不大了。软件开发以后还要考虑维护,这一方面软件的开发建设很像是城市建设,假如城市建设涌入了大量的人口,许多人认为这里在这里可以赚到钱,城市的执政者就会着手对城市进行扩建以适应当前城市的发展。也像是社会的发展,过去的封建社会的县令类比到如今的话是没法类比的,过去的县令身上集合了不少职位,这也许跟过去县的人口比较少有关系,随着人类社会的不断进步,原先集中在县令身上的职位被分解。这也就是对应到了微服务架构上,咱们将本来集中在一个服务的应用进行拆解,将其拆解为若干个微服务,每一个服务贯彻单一职责,对外提供的服务尽可能不存在重复,这也就印证了本文开头引用的戴维.帕纳斯那句话:

最又用的技术再也不未来,而是已经出现好些年了,只不过咱们没有好好用。

我想起刚学Spring Boot的我,我是在B站看的颜群老师的视频: SpringBoot视频教程(入门篇)),在视频的开头就讲到了微服务,说这是目前流行的软件架构,我当时还颇感到新奇,也许是当时并无开发过大一点的工程,开发过的工程代码量都比较少。当时的想法是微服务架构比较适合大型项目,便于扩容和维护,可是这几年微服务彷佛正在成为一种新常态,当初的论断仍是正确的吗?以我目前的见解是,微服务架构正在下沉,目前业界是看重微服务架构的扩容和易维护吗? 彷佛并不全是,我我的的见解是看重服务的重用能力,好比认证服务,开发一次,再次开发新项目的时候就没必要再开发了,重用以前的服务就好。

这种单一职责也能够从Web软件工程师的分工能够一见端倪,咱们知道许多计算机的硬件能力大体以每两年提升一倍的速度发展。可是软件开发的流程却没有这样的提速过程,开发成本也没有降低,本来前端、后端、运维、DBA集一体的Web工程师被拆成四个职业:

  • 前端工程师
  • 后端工程师(也有称之为后端工程师)
  • 运维

    当前的Web开发领域,DevOps十分流行,DevOps是一个合成词,是Developers(开发者)和Operators的结合,即开发和运维团队一体化。本来从后端仔身上划走的运维,又从新回到了后端仔身上。
  • DBA

这是一种拿着锤子看全世界都是钉子的想法吗?彷佛是,又彷佛不是。拆分与分工再人类世界能够随处见到,当你开始创业,起初只是小买卖,你大能够没必要请会计注册公司,就像农村的小饭店同样,你能够是老板也能够是厨师,同时还能够是服务员和会计、保洁。可是你没想到你的厨艺很不错,好吃不贵,价钱实惠,很快你的饭店就吸引了不少人来,慢慢的你发现,一人身兼多职让你支撑不住,人实在太多了,为了避免让你父母受累,你开始招人,将你身上的保洁和服务员的职责分配出去,你仍然不想请会计,你以为的你的生意尚未那么大,渐渐的你发现一个厨子彷佛有点支撑不了当前的用户量,你天天仍然被累的半死,因而你开始琢磨着请厨子、招学徒,让他们负责炒菜,你负责进菜,可是彷佛仍是要招洗菜的,让厨子洗菜又作菜,厨子不看堪重负,厨子提出了抗议。

因而你开始招洗菜的,很快你就想开分店,你想扩大你生意的规模,可是天天的账让你有点头疼,你不想将本身的精力太过集中在算帐上。因此你请了个帐房,这个故事还会发展下去,你会请更多的人,来承接你身上的工做,可是你在分配工做的时候,两我的之间的工做会尽可能不出现重合,你不会想让一我的既作厨子又作帐房,你但愿这我的尽量的专业。

另外一个重要的软件设计原则时开放—封闭原则(Open-Closed Principle,OCP)

软件实体应该是能够扩展的,同时是不可修改的。

具体的说:

  • 容许扩展(Open for extension)。当应用的需求发生改变时,咱们能够对模块进行扩展,从而改变模块的功能
  • 不容许修改(Closed for modification)。对模块行为进行扩展时,没必要改变的自己

那何时该使用这些原则呢? 《敏捷软件开发: 原则、模式与实践》一书也同时指出:

变化的轴线仅当变化实际发生时才具备真正的意义。若是没有征兆,那么去应用SRP,或者其余原则都是不明智的。

遵循OCP的代价也是昂贵的....., 显然,咱们但愿把OCP的应用限定在可能会发生的变化上。......最终,咱们会一直等到变化发生时才采起行动。

个人理解是这些原则应对的是变化,开发者根据本身的经验进行了预测认为这里会发生变化,这须要设计人员具有一些从经验中得到的预测能力,有经验的设计人员但愿本身对用户和应用很理解,可以以此来判断各类变化的可能性。而后,他能够设计对于最有可能发生的变化遵循OCP原则。

要预测准确这一点很不容易作到,由于它意味着要根据经验猜想那些应用程序在生长历程中有可能遭受的变化。若是开发人员猜想正确,他们就得到成功。若是他们猜想错误,他们会遭受失败。而且在大多数状况下,他们都会猜想错误。

咱们该如何遵循OCP呢? 咱们来看下面一个例子,以这个例子来讲明OCP:

这个例子一样来源于《敏捷软件开发:原则、模式与实践》,书上用的是C++来描述OCP,这里我改装成了Java。咱们能够很容易的看出,若是咱们再增长一种形状,Shape类中的drawAllShapes方法不用改动,就能画出新的形状。图中打印能够理解为绘制对应图形的行为。

这种设计是符合OCP的,无需改动本来的代码,咱们就完成了扩展,还不会引起连锁改动。这让我想起了工厂模式,不懂什么是工厂模式的,能够参看个人文章: 欢迎光临Spring时代-绪论](https://juejin.cn/post/692755...

假设再增长了一种Excel类型,这个WorkBookFactory还须要改动,我想并不能这么说,我想是POI的开发者认为Excel的变化不会那么强吧,不会每一年都增长一种文件格式,这来源于POI的设计者对Excel的了解,这是一种预测,同时也是在减小开发者使用POI的心智负担,过分的设计致使软件趋于复杂,增长维护和使用成本。

上面的Shape是完美的符合开闭原则的吗? 彷佛并不全是,咱们彻底能够提出一个需求,让drawAllShapes方法不得不修改,《敏捷软件开发:原则、模式与实践》 给出的变化是要求全部的圆必须在正方形以前绘制,在这种状况下,DrawAllShapes彷佛没法不对这种变化作到封闭,若是你说你能够在传入List参数的时候,将圆放入正方形以前,不就能够了吗?那我这里就再提出一个需求,要求将正方形和圆形绘制在一块儿。除非你是穿越过来的,那么总有你没有预测到的状况,不管模块你作的是多么的"封闭",都会存在一些没法对之封闭的变化,没有一种模型能够应对全部的变化。

既然没法作到绝对封闭,那么就必须作到有策略地对待这个问题。也就是说,设计人员必须对于他设计的模块应该对那种变化作出选择。

开发者必须先猜想出哪一种变化是最有可能的,而后构造抽象来隔离那些变化。

这让我想起了设计模式,你们认为这个颇有用,国内的面试也很看重,这在某种程度上致使了设计模式的滥用,开发者在未对具体的业务比较熟悉以前,就开始展开了预测,开始应用设计模式,这很容易形成项目种的设计模式满天飞,我想起我前公司的资深架构师对我说的话,那天他看见我在看设计模式,便对我说,对于我这种程度的人来讲,设计模式无助于我代码功力的提高,他这么多年的开发,也没用过几回。

总结一下

对业务要熟悉,这可让你洞悉变化,预测变化,构建抽象应对变化,能够避免改动产生连锁反应,你总不但愿,以前开发完成的东西,再改一遍Bug吧。对于开闭原则我以前的理解是,假如这个部分的代码已经上线平稳运行了,那么就尽量的不要去改动他,那么这里又补充了 一下开闭原则,即根据经验预测变化,而后构建抽象,应对变化。可是应当谨慎的引入,这回增长阅读成本和维护成本,可是我经常收到的反驳意见是,我引入又不会出什么事,又怎么啦。

单一职责我以前的理解是方便重用,假如一个方法完成了A和B这两件事,我假设由于某种须要须要用到A,不须要用到B,那么这个方法我就永不了,同时也让阅读变得简单,其实我当初的想法是能够涵盖在变化中,我预测未来我会用到A,我作出的应对策略是将方法作的尽可能效一点。

计划与估计

当你可以度量你所说的,而且可以用数字表达它时,就表示你了解它,若你不能度量它,不能用数字去表达它,那说明你的知识时匮乏的、不能使人满意的。—开尔文勋爵(英国物理学家)

作项目是会有工期的,经常会让开发者去估计工时,这是让我颇为头疼的一个问题,我项目经理让我估计的时候,个人脑壳是一片空白,我当时的想法是这要估计少了要加班,估计多了项目经理又不满意,因而我就请教当时公司中的前辈,我把个人担忧如实说了,前辈表示没事,时间不够再要。可是我仍是想估计一下,我想对本身的开发能有一个衡量。

"估计"这一技术看似容易,其实大有学问,当约翰·巴克斯启动FORTRAN项目后,他的上司会按期询问完成日期,他老是给出一样的答复:"六个月"。但实际上,项目一共花了近三年的时间。

再开始估计以前,咱们须要弄清楚几个概念:目标、估计和决心。单独拎出来这三个词,咱们是不会混淆的,可是在实际应用中,咱们却很容易混淆这三个词。

  • 目标: 代表一个但愿达到的状态。好比你但愿追上一个女孩子,但你应该清楚这是一个目标,不必定能实现。
  • 估计: 以当前了解的状况和掌握的资源,要花费多少人力物力实践才能实现某事。

    注意前半句以当前了解的状况和掌握的资源,我想起我上初中的时候,比较喜欢看网络小说,印象很深的一部小说是《斗破苍穹》,吞噬异火的把握,这也是一种估计。我当时很想学会这种估计,好比咱们上初中的时候,有一次是初中周四放假了,

    下周我就满怀指望的也但愿周四也放假,我当时的估计是当时的天气,就像模像样的估计了一个八成几率。可是最后仍是没有放假。

  • 决心: 保证某个时间以前完成预先规定的功能和质量。若是下的决心没有作好估计,那么大几率是实现不了的。

这种状况在软件项目中也能够看到,软件项目中的延迟更是比比皆是—为何咱们估计得不许呢?由于难么?为何软件估计这么难呢?其实全部的估计都难,若是你只是瞎估计,没有找出估计后面的假设的话。不信的话,咱们作一些估计的练习,不用搜索引擎,你估计一下下面的数目(数量级正确就行)

  • 中国的陆地边界长度
  • 非洲人口密度
  • 长江一年的流量
  • 2013年亚洲货币流通的总量

怎么样?你的估计和实际状况差几个数量级? 可是若是你把你作出估计的依据,也就是假设列出来,那这是否是会让你对本身的判断更加自信呢。软件工程专家Paul Rook说: 咱们其实并非不会估计,咱们真正不会的,是把估计后面藏着的假设所有列举出来。咱们平时的估计还有一个维度就是参考前人的经验。软件工程师在长期的实践中,也摸索出一套经验公式:实际时间花费主要取决于两个因素—对某件事的估计时间X,以及他作过相似开发的次数N。

Y = X ± X ➗N // Y是实际时间花费、中间的±表示加上或减去。

例如刚毕业的你被分派到了一个用户管理模块的任务,你估计须要三天,可是你历来没有作过用户管理模块,因此N就是0,高中数学告诉咱们,0不能当除数,可是大学数学告诉咱们极限状况下就能够,因此你花费的时间是3+无穷大?也就是说在项目给定的时间内根本没法完成,或者是3-无穷大? 不但没有完成任务,还写了不少bug来,让团队花更多的时间来处理,把项目拖垮。

注意这是个经验公式,因此跟实际花费时间存在偏差是很是正常的事情,这也就须要你在完成需求以后,分析缘由,以便减小偏差,假如一直作相同的项目,估计值会愈来愈准确,由于愈来愈熟练。可是也不会有人一直愿意作重复的是事情,这会让人厌倦。

写在最后

写到最后的时候忽然以为本文不彻底像是《构建之法—现代软件工程》的读书笔记了,在写软件设计原则的时候,参考了一下《敏捷软件开发: 原则、模式与实践》,这在《构建之法—现代软件工程》种只花了两页来介绍,可是主要内容仍是来自于它,也糅合了本身的若干感悟,但愿会对诸君有所帮助。封面是在海南度假的时候,别人拍的,感受很漂亮。

参考资料

相关文章
相关标签/搜索