2019独角兽企业重金招聘Python工程师标准>>>

继续扒


接着上一篇的叙述,健壮性也有了,现在是时候处理点实际的东西了,但我们依然不会一步到底,让我们来看看。

一而再地抽象(Abstraction Again)

让我们继续无视那些空格以及星号等细节,我们看到什么呢?

我们只看到一整行的内容,当传入3时就有3行,传入4时就有4行。我们用一个方法getLineContent来表示这样一个抽象。代码如下:

    public String getPattern(int lineCount) {if (lineCount < 1) {throw new IllegalArgumentException("行数不能小于1!");}if (lineCount > 20) {throw new IllegalArgumentException("行数不能大于20!");}StringBuilder pattern = new StringBuilder();for (int lineNumber = 0; lineNumber < lineCount; lineNumber++) {pattern.append(getLineContent(lineNumber));}return pattern.toString();}

黑盒子,输入以及输出(Black Box, Input & Output)

先不急着让IDE生成代码,现在集中精力思考一下,我们仅仅在这一层面上去思考,把getLineContent看作类似电路那样有一些输入端和输出端的黑盒子:

  1. 返回的值是我们想要的吗?

  2. 传入的参数是否足够让“getLineContent”里面完成它的工作呢?

第一点是可以肯定的,但传入的参数是否足够了呢?

如果按上述代码,不管是3行的情况,还是5行的情况,获取第一行的内容时,调用的都是getLineContent(0),按照输入决定输出的原则,结果将一样。

但我们很清楚,5行情况下的第一行前面的空格肯定要多于3行的情况,如下图:

所以很显然,只传入一个lineNumber是不够的,还要把总的行数lineCount也传进去。

自顶向下(Top-down)

现在把代码改下,多传入一个参数,并让IDE为我们生成getLineContent的代码,作些简单修改,最终如下:

    public String getPattern(int lineCount) {if (lineCount < 1) {throw new IllegalArgumentException("行数不能小于1!");}if (lineCount > 20) {throw new IllegalArgumentException("行数不能大于20!");}StringBuilder pattern = new StringBuilder();for (int lineNumber = 0; lineNumber < lineCount; lineNumber++) {pattern.append(getLineContent(lineCount, lineNumber));}return pattern.toString();}private String getLineContent(int lineCount, int lineNumber) {// TODO Auto-generated method stubreturn null;}

那么,这样一种先从高层考虑起的做法,就是所谓的自顶向下了,接下来我们还会不断地以这种方式来完成这个小程序。

自顶向下是一种很重要的思考及处理问题的方式,如果你还不习惯这样去考虑问题(包括写代码),现在是时候尝试一下了。

项目进度(Project Progress)

另外,getPattern方法里面的TODO标识可以去掉了,这个方法已经算是完成了,如果现在太阳就快下山了,那么你也可以提交它了,你的项目经理也很乐意看到“代码量天天在增长”,这给了他信心,让他觉得“项目正在稳步推进”,当他给项目总监或者客户汇报时,他就可以展示一些“进度”给他们看了。

当管理者看不到进度时,他们就会感觉到压力,这种压力会转移到你身上,你甚至会“被志愿加班”。这种压力除了损害我们的健康外没有任何好处,所以你要学聪明一点,当管理者问起你的时候,你就大声对他们说:“我今天又提交了XXX行代码。”,然后你就拍拍屁股准时下班了。

再而三的抽象(Abstraction, again and again)

现在把目光投向getLineContent方法。经过观察,可以看出一行内容由三个部分组成,我们再一次忽略具体的细节。

如上,三种颜色表示了三个部分,我们再一次运用抽象,先不考虑传什么参数,有点像是写伪代码(pseudo code)那样快速把程序的骨架(Skeleton)写出来:

    private String getLineContent(int lineCount, int lineNumber) {// TODO Auto-generated method stubStringBuilder content = new StringBuilder();// 1. 空格部分content.append(getFirstPart());// 2. 星号部分content.append(getSecondPart());// 3. 换行部分content.append(getThirdPart());return content.toString();}

现在再来仔细考虑往里面传入参数的问题:

  • 第一个方法getFirstPart,其实是有关于输出前置空格的,前面已经分析过“5行情况下的第一行前面的空格肯定要多于3行的情况”,所以它需要两个参数。

  • 第二个方法getSecondPart,是关于输出星号的,可以看出,无论是3行还是5行,第一行都是1个星,第二行都是3个星,所以这个跟总行数lineCount无关,只与行号lineNumber有关,所以只要传入一个参数即可。

  • 第三个方法getThirdPart,其实就是一个换行,所以不需要传任何参数。

有人可能有些疑问:这样是不是分得太细了?抽象与封装究竟要到什么样的程度呢?

过度工程(Overengineer)

特别地,让我们看看第三个方法:getThirdPart。我们知道,这最后其实就是一个换行,一条语句即可搞掂,所以再封装就没有必要了。

过度的抽象与封装有时反而使得程序臃肿难读,半天也找不到具体“干活”的语句在哪,性能方面也会受到损害。

Java语言中已经可以直接表达换行的语义,最终结果如下:

    private String getLineContent(int lineCount, int lineNumber) {StringBuilder content = new StringBuilder();// 1. 空格部分content.append(getFirstPart(lineCount, lineNumber));// 2. 星号部分content.append(getSecondPart(lineNumber));// 3. 换行部分content.append(System.lineSeparator());return content.toString();}private String getFirstPart(int lineCount, int lineNumber) {// TODO Auto-generated method stubreturn null;}private String getSecondPart(int lineNumber) {// TODO Auto-generated method stubreturn null;}

抽象不足(Lack of Abstraction)

另一方面,我们也要警惕缺少必要的封装层次的情况。不幸的是,很多情况,我们都是缺少必要的抽象与封装。

做过维护的同学可能都见过那种超长超恐怖的方法,里面的语句有的甚至高达几千行,哪怕是在方法内找一个变量的定义,也能让你想起周杰伦与费正清合唱的那首歌——《千里之外》,去维护这样的方法自然不是什么愉快的经历。

这里之所以不厌其烦地对这个小程序不断的抽象下去,是想告诉大家,即使是如此之小的一个程序,抽象到这一地步,语义层面依然还没有过度的倾向。

通常,如果程序语言已经可以直接表达出我们想要的语义,封装就可以结束了。我们来审视一下前两个方法,显然,还不能直接表达,所以封装还可以继续。

一般地,如果一条语句就能表达的时候,抽象与封装也就基本到头了。

同时,不必过于刻板地去遵循这些,有时三两条语句可以表达时,不封装也是很正常的;

而有时为了提供更清晰的语义,哪怕只有一条语句,你再封装一下也是可取的。

当然了,对于目前这个小程序,大家可能觉得已经有些过度封装了,但在后面我们将看出,其实还没到最抽象的阶段。现在先不争论这一点,说到后面我们就明白了。

分而治之(Divide and Conquer)

其实抽象与封装还能带来什么好处呢?那就是这里要讨论的分而治之了。

我们可以回顾一下程序写到现在,我们可曾遇到什么“阻碍”没有?

答案是没有。你可以看看前面的代码,都是简简单单的for循环,append之类的。

有人可能不服气地说:

“困难的地方都被你这种一层又一层的抽象与封装延后了,代码写了半天啥事也没干到。”

这种评价对不对呢?的确,前面通过抽象不断地压制那些细节的表达,不断地推迟对其的处理。

想像有一个房间,衣服,物品堆放得乱七八糟,这时有人拿来一个大箱子,把这些东西通通塞了进去。把这些东西”封装“起来后,房间自然整洁了,但我们也很清楚,箱子里依旧是一团糟。

但这个比喻并不适合这里的情况,我们的抽象并不是简单地把问题转移了,通过一层层抽象的手段,一个大问题在不断被分解成一个个小问题。

1. 有些足够清晰的小问题,我们已经在这一过程中把它解决掉了。

比如,输出一个换行的问题。

2. 而那些还不够清晰的小问题,也已经通过抽象被我们所孤立(isolate,或者叫隔离)出来了,有的已经看到了解决的曙光。

比如,在上一步,我们还是有两个参数传了进来,但通过在里面进一步划分成新的子问题,可以看到,有些子问题只要一个参数即可解决了。

所以,抽象并不是什么事也没干,相反,它干了很重要的事情。

通过抽象,问题正在被分解与简化;通过抽象,我们构建出了程序的骨架。

在这一过程中,大问题分解成小问题并被安排到了适当的位置,与其它的小问题隔离开来,有个词怎么说的,”众神归位“,大概就是这样一个意思。

群魔乱舞,你怎么去应付呢?如果他们都呆在自己的位置上,我们就可挨个收拾他们了。

抽象不存在“事不过三”(No Limits for Abstraction)

让我们继续,我们还可以继续抽象吗?答案是肯定的。无论是参数更多的getFirstPart,还是参数更少的getSecondPart,它们都还可以分成两部分:

  1. 拿到一个数量N(你甭管怎么算出来)

  2. 输出N个空格或星号

代码如下:

    private String getFirstPart(int lineCount, int lineNumber) {int count = getElementCountOfFirstPart(lineCount, lineNumber);StringBuilder part = new StringBuilder();for (int i = 0; i < count; i++) {part.append(" ");}return part.toString();}private String getSecondPart(int lineNumber) {int count = getElementCountOfSecondPart(lineNumber);StringBuilder part = new StringBuilder();for (int i = 0; i < count; i++) {part.append("*");}return part.toString();}private int getElementCountOfFirstPart(int lineCount, int lineNumber) {// TODO Auto-generated method stubreturn 0;}private int getElementCountOfSecondPart(int lineNumber) {// TODO Auto-generated method stubreturn 0;}

现在再来看看如何实现最后的两个方法,以一个四行的图案为例:

图中规律已经很明显,最终结果如下:

    /*** 获取每行第一部分的元素个数* @param lineCount 总行数* @param lineNumber 行号,从0开始* @return*/public int getElementCountOfFirstPart(int lineCount, int lineNumber) {return lineCount - lineNumber - 1;}/*** 获取每行第二部分的元素个数* @param lineNumber 行号,从0开始* @return*/public int getElementCountOfSecondPart(int lineNumber) {return lineNumber * 2 + 1;}

这里把最后的两个方法加了注释,并把它们改成了public,为什么呢?下面将作些解释。

抽象到数字

有人可能不太理解,为什么要抽象到如此之深,这里最后两个方法都只有一条语句,直接在上一层就写了不就完了?

可以看到,最后两个方法,返回的都是int类型,也即一个数字。我们都知道,数字是非常纯粹,非常抽象的一种概念,抽象到了这一层,已经不能再抽象了。比如,单独拿一个“1”出来,它是非常抽象的:

1可以是一粒土豆,1也可以是一颗红薯;

1可以是一匹元代马,1也可以是一头程序猿。

抽象(abstract)作为一个动词而言,它的原始意义,有“把…抽取出来”的意思,即有把东西抽离,剥离的意思。

我们说数字很纯粹,为什么要追求这种纯粹呢?这一过程中我们又把什么剥离了?

耦合,解耦合,得意而忘形(Coupling, Decoupling,$%#&…)

我们都听过一种说法,叫“言不达意”或者又叫“词不达意”,表明我们用“言”来表达“意”,当然“达不达”就是另一回事了;另一方面:

“言者所以在意,得意而忘言。”——《庄子 外物》

而《晋书&middot;阮籍传》中有一段对阮籍的描述:

“嗜酒能啸,善弹琴。当其得意,忽忘形骸。”

这就是所谓的“得意忘形”的最初意义:

指得其意,即其思想精髓,而不必计较形,即表现形式。

而“形意交融”则表明形跟意常常是混在一起的,“意”需要通过“形”传递给我们。

要表达的意思与它的载体之间的这种紧密关系,用我们软件领域的说法,就叫“耦合”。

这里可以算是耦合的一种,耦合还可以有很多其它方面的理解。

这种形与意的交融有时并不是件什么好事,陶渊明在他的《归去来兮辞》里说:

既自以心为形役,奚惆怅而独悲。

回到我们的问题,前面一直在处理这么一个图案,那么,这个图案它的“形”是什么呢?而它的“意”又是什么呢?

显然,那些一个个的星号(以及前面的空格)就是所谓的“形”了,而“意”呢?

其实就是前面说的“抽象到了极致的数字”了,这就是图案的“意”。

通过把“形”从图案中剥离,或者说把“意”从图案中抽取出来,我们就能“得意而忘形”,从而达到解耦合的目的。

把握住了“意”,我们就不必拘泥于空格或者星号,我们可以使用各种各样的“形”,最终出来的图案依然可以看到“三角形”的影子。

如果你已经对所谓的MVC(Model-View-Control,模型-视图-控制)有些了解,那你是否在这里看到了Model跟View的影子呢?

再一次的,由于篇幅过长,这次还是不能“扒到底”,美腿有点长,再扒一半,就此膝斩。Hold住,余下主题我们下回再见。下一篇见

小程序中的大道理三

转载于:https://my.oschina.net/goldenshaw/blog/316773

小程序中的大道理之二相关推荐

  1. 在H5、微信小程序中使用canvas绘制二维码、分享海报

    在H5.微信小程序中使用canvas绘制二维码.分享海报 文章目录 在H5.微信小程序中使用canvas绘制二维码.分享海报 前言 一.canvas绘制二维码 1.H5中使用canvas 2.微信小程 ...

  2. 如何在微信小程序中生成二维码:一个最简单的案例就让你明白

    使用weapp.qrcode.js 在 微信小程序 中,快速生成二维码 一.效果 二.具体步骤.代码 下载weapp-qrcode代码 然后 将 dist 目录下的weapp.qrcode.esm.j ...

  3. 在微信小程序中识别付款二维码

    一.前言 由于微信小程序的规则限制,在小程序内部是无法直接识别二维码的,这样对于想通过微信小程序给微信公众号引流的想法大抵都被扼杀了,偶然间发现微信官方小程序"微保"竟然做到了,而 ...

  4. 转载:在微信小程序中 生成二维码

    目录 转载: weapp-qrcode-canvas-2d 仓库地址 测试环境 使用 安装方法1:直接引入 js 文件 安装方法2:npm安装 安装完成后调用 例子1:没有使用叠加图片 例子2:使用叠 ...

  5. 六一:如何在Datawhale开源学习小程序中管

    我们的组队学习马上就要开营了,本次组队学习与以往不同的是小程序中增加了队伍管理的功能. 为了方便大家组队,Datawhale的 六一同学 为大家准备了在Datawhale开源学习小程序中队伍管理的教程 ...

  6. 如何在微信小程序中使用iconfont

    开篇废话 开发过小程序的童鞋肯定都会遇到这样的问题,当我们在小程序中使用iconfont官方推荐的方法插入字体时,我们总会得到一个打印机(滑稽).那么如何在小程序中正确的使用iconfont呢? 一. ...

  7. 微信小程序中换行,空格(多个空格)写法

    在HTML5中我们都知道编辑文档换行的时候直接用<br>就可以了,但在wxml中却识别不了<br>标签. 空格,换行在小程序中的写法整理如下: 必须在<text>标 ...

  8. Canvas绘图在微信小程序中的应用:生成个性化海报

    Canvas绘图在微信小程序中的应用:生成个性化海报 如极客时间的一些实现案例: 基础语法 Canvas本质是一个可以使用脚本(通常为JavaScript)来绘制图形的 HTML 元素,默认大小为30 ...

  9. 微信小程序中嵌套html_微信小程序:web-view嵌套H5实现微信支付功能解决方案及填坑...

    ab7117c7d4947210c39e126a01d23ede.jpg 最近一个多月加班比较严重,偶尔休息一天也是在补睡眠+陪家人,比较长时间没有来进行总结记录了.今天不加班,开始为这段时间做的东西 ...

  10. java程序的最小程序单位_微信小程序中rpx与rem单位使用

    原作者: 小小小 来自: 授权地址 本文讲解rpx和rem应用于微信小程序,如果你还没有入门,建议先从下面看起: 如果看完上面几篇文章,我们开始进入正题吧~~ 一.rem的使用 1) js中导入下面这 ...

最新文章

  1. Python 字典(Dictionary) get()方法
  2. linux系统重装后挂载数据盘,Linux重装系统后如何重新挂载数据盘?
  3. C语言经典例100-将学生成绩写入文件
  4. WebLogic Server的单点登陆功能--转载
  5. php curl模拟post提交,php curl模拟post提交数据示例
  6. IDEA開發 java web 初步
  7. 每天一小时python官方文档学习(六)————循环和条件控制的进阶用法
  8. InnoDB 事务/锁/多版本分析?你了解多少?
  9. android 480p分辨率,[RK3399][Android7.1] HDMI显示屏(副屏)调试记录小结
  10. 学而思“变身”乐读后宣布下架,已收家长费用“多退少不补”
  11. 来自lombok的注解(解决idea中的找不到get,set方法,找不到log的问题)
  12. MySQL视图和事务
  13. 省级国土空间基础信息平台建设方案分析
  14. mysql排序同值排名一致
  15. The Robustness of Deep Networks A geometrical perspective论文解读
  16. mysql2018漏洞_MySQL多个远程安全漏洞CVE-2018-2562/91 大批版本受影响
  17. IDEA插件系列(100):CPU Usage Indicator插件——显示CPU使用情况
  18. php 利用gd库及tcpdf 自动多图片生成pdf
  19. 什么叫单模光纤_单模光纤和多模光纤的区别是什么?英文标识分别是什么?
  20. 设备驱动中的kobject(kernel-4.7)

热门文章

  1. PAIP.AHK调试以及同于脚本的调试法
  2. paip.提升用户体验---网站导航栏的设计
  3. SSH 有关密钥和私钥 的那些事儿
  4. VSCode : vscode-remote下无法写入文件及linux文件读写权限
  5. linux: tee日志记入文件
  6. 云计算时代,观测产品Sunfire的成长史
  7. 英特尔新CEO上任后公布首份财报,数据中心业务同比下降20%
  8. 倒计时按钮_倒计时牌都不会做,妹子何必嫁这货
  9. 【单目标优化求解】基于matlab多子群改进的海洋捕食者算法(MSMPA)求解单目标优化问题【含Matlab源码 1783期】
  10. 【单目标优化求解】基于matlab混沌算法求解单目标问题【含Matlab源码 1410期】