写点什么

多数程序员难以用简单的方式开发应用?

  • 2019-08-22
  • 本文字数:2825 字

    阅读完需:约 9 分钟

多数程序员难以用简单的方式开发应用?

心理学中有一篇相当古老、但又非常重要的论文,题为《魔法数字七(上下浮动二):人类信息处理能力中的一些限制》。这篇文章衡量了大脑处理信息的极限,并给出了一个具体的数字:人脑可以同时容纳五到九个概念。我们当然能够把这个有趣的结论延伸到诸多领域当中,但对软件开发人员而言,下面两项含义最为重要:


  • 构造(模型、实现、设计、模式等……)越简单越好,因为其概念更易于描述。

  • 构造良好且组合良好的抽象,应该具有较少的特殊规则与意外情况,同样是因为其概念更易于描述。



总而言之,这是一场以降低概念描述难度为目标的心理战,夺取的是珍贵的心理容纳空间。

简单构造

正如英国计算机专家 Hoare 所言:软件设计构建有两种方法,一是使其尽可能简单,从而一目了然确定其中不存在缺陷;另一种方法则是使其极为复杂,以至于看不出什么明显的缺陷。


遗憾的是,他还补充道:第一种方法其实要困难得多。


在软件开发当中,添加新的模块、新的类以及越来越多的代码是件很简单的事。这能够解决软件需要面对的更多用例,但更大的代码量所产出的结果却越来越少。正如 Jamie Zawinski 所说:每一个程序都会不断扩展,一直扩展到像人一样会收邮件、看邮件。而那些无法扩展的程序,最终会被能够扩展的程序所取代。


从本质上讲,我们一直在不断扩展程序。我们向其中添加更多模块、更多类以及更多代码,而且对于每一段代码,我们都需要确保其能够与原有代码顺畅协作,从而在这个复杂性呈指数增长的过程中继续保持进一步扩展的空间。之所以会这样作,是因为增量总是更简单的——至少在起步阶段更简单。


在另一方面,越是简单的东西开发起来就越困难。这要求开发人员考虑潜在的模式与行为,并消除一切不必要的元素。但最终,这样的思维方式能够成就更出色的成果,因为我们只需要使用极少的概念与精力,就可以描述并理解程序,确保大家专注于更多宏观目标、而非细枝末节。

检测过于复杂的构造

实际上,发现这类复杂而臃肿的构造并不困难。我们看看程序手册就能知道个大概,如果其中有专门的章节来描述结构背景之下的众多词汇,基本可以断定程序复杂度已经失控。


下面来看一个例子,即 Abstract Factory(抽象工厂)模式。为了描述其工作原理,我们需要定义很多名称:


  • AbstractFactory 接口

  • <<creates>>活动

  • Factory1(或者 ConcreteFactory)

  • ProductA 以及 ProductBinterfaces

  • ProductA1 以及 ProductB1classes

  • new 关键字

  • Abstract Factory 类图

  • :Client 以及 factory:Factory1 序列图


之所以存在这么多名称与图类,是因为复杂性比简单性更易于实现。这也迫使程序员采用针对性过强的对象创建方式,而无法选择复用存储在某处的对象等其它可能的解决方案。通过以下方式,我们尝试降低其整体复杂性:


  • 从整体模式当中删除“Factory”名称。

  • 删除 new 关键字以及 <<creates>>活动。


现在,该模式只关注能够返回 ProductA 或者 ProductB 实例的对象。它可以对该类进行实例化、从数据结构中获取预先构建的对象,或者从某处提取该对象——具体方法无穷无尽,但这些都不重要;真正重要的是我们的目标:开始时没有对象,结束时有了对象。现在再来看名称列表,两项操作:


  • 第一项,利用接口 ProductA 获取一个对象。

  • 第二项,利用接口 ProductB 获取一个对象。


这样,我们可以自由地将该操作放在某个类、某个结构或者是某个指针当中。在使用时,我们只需要配合必要的参数对该操作进行调用,它就会为我们提供相应的对象。


在设置中,我们可以将第一项操作设置为任何能够生成 ProductA 的形式,并将第二项操作设置为任何能够生成 ProductB 的形式。没有类图、没有序列图、没有额外的类、没有不必要的字,也没有歧义——因为我们能够对某项操作做的只有调用,将其作为参数进行传递,获取返回结果并加以存储。毕竟函数与操作在本质上就是带有参数的值。我们要如何利用接口 ProductA 获取某个对象?如何才能返回一个 ProductA?答案很简单:使用第一项操作。

构造良好的抽象

我们已经在 Abstract Factory 的修正版中描述了拥有良好构造的抽象结构。


构造良好的抽象


AbstractFactory 模式显然不是什么好抽象,因为它包含一切开发者不关心、也没必要关心的繁琐细节——例如属于抽象、必须创建对象,以及使用 new 关键字等。作为用户,我并不关心这么多细节,但这些细节就在眼前,想躲都躲不开。


相反,我只希望拥有“一项能够利用 ProductA 接口为获取对象的操作。”我只需要进行调用,它就能返回 ProductA 的结果。至于具体获取方式,我不知道、也不在乎。

组合良好的抽象

AbstractFactory 模式不是什么好抽象,因为它没有得到良好组合。看看它用到的两种方法:其一是创建 ProductA;其二是创建 ProductB。为什么要把它们放在同一个 Factory 当中?为什么不能把这两项操作分开,并让希望将二者一同使用的用户将其放进同一个元组中?A 和 B 在同一个类中,绝对不是什么良好的构造方式。我该如何组合?如果其它地方也有需要使用 ProductA 的操作怎么办?那项操作是否需要了解 AbstractFactory 中的错误链?


AbstractFactory myFactory = new Factory1();ProductA myProduct = myFactory.createProductA();AbstractFactory somethingDifferent = new SomethingDifferentFactory();SomethingDifferent result = somethingDifferent.fabricate(myProduct);return result
复制代码


相反,如果拥有“一项能够利用接口 ProductA 为我获取对象的操作”,而另有一项操作要求使用 ProductA 以构造别的结果,那就可以调用第一项操作来获取 ProductA,然后再用它来执行第二项操作。在类型方面:


  • operation : () -> ProductA

  • otherOperation: ProductA -> SomethingDifferent


因此: otherOperation(operation())。完事,就这么简单。

检测组合性是否良好

遗憾的是,很难通过语义检测组合性是否良好,因为开发人员已经习惯了那些没有组合性可言的做法,因此很多人压根不知道所谓组合良好究竟是什么概念。好的组合其实能够轻松感受到,这有点像是乐高积木,每一块都能轻松与其它块匹配起来,共同搭建出规模可观的作品。


这块有个三角形尖头,我们看看哪些积木块有三角形开孔。


当具有良好的组合性时,我们不需要使用适配器(或者不会设置适配器这样的概念,因为连接机制本身非常简单)。当具有良好的组合性时,我们只需要将各部分连接起来即可构建成一个整体,而不必担心这些部分会以意想不到的方式相互作用。


其整体,就是各部分相加得出的精确总和,仅此而已。

总结

我们已经看到了 AbstractFactory 如何破坏“获取 ProductA”这样一个简单的概念:一个 Factory 即为一个 Abstract,其通过 createProductA 方法利用 new 以 <<construct>>一个拥有 ProductA <<interface>>的 ProductA1。


这里单是关键字与概念就多达 10 个,超出了最聪明的大脑所能轻松处理的极限。更糟糕的是,由于太过刻板,我们只能使用这项操作创建对象、而无法复用对象。这也是编程为何如此困难的原因之一:我们毫无意义地抬高了它的难度。


原文链接:


http://www.javiercasas.com/articles/rule-of-seven


2019-08-22 13:5819521

评论 1 条评论

发布
暂无评论
发现更多内容

互联网安全架构设计原则

阿泽🧸

互联网安全 11月月更

dns-client占用cpu过高的问题解决方案

我是一个茶壶

win10 DNS 11月月更

web--文件上传

我是一个茶壶

WEB安全 文件上传 11月月更

uniapp简单入门

格斗家不爱在外太空沉思

vue.js uniapp 11月月更

Paddle模型性能分析工具Profiler:定位瓶颈点、优化程序、提升性能

汀丶人工智能

自然语言处理 nlp 性能分析 11月月更

Apache Beam基本架构

穿过生命散发芬芳

11月月更 Apache Beam

2022-11-02:以下go语言代码输出什么?A:编译错误;B:apple;C:ant;D:panic。 package main import “fmt“ func main() {

福大大架构师每日一题

golang 福大大 选择题

跟着卷卷龙一起学Camera--MIPI 02

卷卷龙

ISP camera 11月月更

1024共码未来(一览中华风华,API First)

叶秋学长

程序员 API 1024 11月月更

写给关系数据库开发者的 TDengine 入门指南

TDengine

数据库 tdengine 时序数据库

GoFrame的gmap相比Go原生的map,天然支持排序和有序遍历!?

王中阳Go

Go golang 高效工作 学习方法 11月月更

聊聊香港优才(56/100)

hackstoic

【Linux】调试器-gdb使用

过眼云烟

学习 记录 11月月更

快速创建软件安装包-ClickOnce

沙漠尽头的狼

从柯里化讲起,一网打尽 JavaScript 重要的高阶函数

掘金安东尼

前端 11月月更

微信小程序—制作一个简单的跑步小程序

格斗家不爱在外太空沉思

JavaScript 微信小程序 11月月更

不借助 Fiori client,直接在手机浏览器里调用 SAP UI5 BarcodeScanner 实现条形码扫描的可能性?

汪子熙

JavaScript 前端开发 Fiori SAP UI5 11月月更

河北首家城商行传统核心业务国产化,TDSQL突破三“最”为秦皇岛银行保驾护航

腾讯云数据库

数据库 分布式 tdsql 客户案例 腾讯云数据库

python小知识-python序列化

AIWeker

Python 人工智能 python小知识 11月月更

铸剑记:2022国产手机自研技术演义

脑极体

从1024开始,我们漫谈编程的本质

盐咔咔

编程 编译原理 11月月更

第一篇文章 | 记录我的Java学习之路 | 一切从零开始

祖国滴花骨朵儿

学习笔记 Java学习 零基础 11月月更

计算机网络:组帧

timerring

计算机网络 11月月更

uniapp多端分享(app,小程序,公众号)

格斗家不爱在外太空沉思

vue.js uniapp 11月月更

前端面经

肥晨

前端面试题 11月月更 前端面筋 超全面试题

RocksDB 7 终于解决了 Compaction 时性能下降问题

Kvrocks

redis RocksDB kvrocks

一文搞懂Go读写Excel文件

盐咔咔

Go 后端 11月月更

HTTP和HTTPS是什么 二者区别是什么

肥晨

11月月更 http和https http工作原理

装了我这 10 个 IDEA 神级插件后,同事也开始情不自禁的嘚瑟了

沉默王二

IntelliJ IDEA

🚀使用“release-it”一气呵成:version、tag、changelog 等

小鑫同学

前端 nodejs 11月月更

JavaScript的垃圾回收机制

肥晨

js 垃圾回收机制 11月月更

多数程序员难以用简单的方式开发应用?_语言 & 开发_Javier Casas_InfoQ精选文章