小程序中的大道理--综述

2023-11-25 11:01
文章标签 程序 综述 大道理

本文主要是介绍小程序中的大道理--综述,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

以下将用一个小程序来探讨一些大道理, 这些大道理包括可扩展性, 抽象与封装, 可维护性, 健壮性, 团队合作, 工具的利用, 可测试性, 自顶向下, 分而治之, 分层, 可读性, 模块化, 松耦合, MVC, 领域模型, 甚至对称性, 香农的信息论等等.

为什么不用大程序来说大道理呢?

因为大程序太大了, 代码一端上来, 读者就晕菜了, 看不过来甚至压根不想去看, 这样说理就很抽象了, 效果反而不好.

小程序中也能说出大道理来吗?

我们有句话, 叫"以小见大", 我们又常常有种说法, 叫:

麻雀虽小, 五毒俱全. (咦? 好像应该是五脏俱全…总之你明白我的意思就好了. )

所以呢, 小程序也是可以来说大道理的, 而且小程序又有短小的特点, 大家看得也没那么累, 也很快能看懂. 毕竟那种代码, 叫什么来着, “意大利面条式的代码”, 大家在实际的开发中, 已经见得太多了.

意大利面(spaghetti), 翻译过来好像叫"通心粉", 非常长的一条条, 彼此缠缠绕绕的, 所以"意大利面条式的代码"就是又长又绕, 让人非常头痛的那种代码.

按我们的习惯, 也许叫它"裹脚布式的代码"大家觉得更熟悉, 更形象一点, 也正好符合"又长又臭"的特点.

啊, 说"又长又臭"可能有点刻薄了, 毕竟大家都可能写过这样的代码(本人就写过好多), 即便现在不会再写这样的代码, 想当年应该也是写过的, 除非你从一开始觉悟通天, 那我就无话可说了.

这种代码我们在工作中见得太多了, 所以这里就不再弄出来考验大家的毅力了, 闲话少提, 让我们看个简单的例子.

我们的例子

就是要打印出如下的一个三角形图案:

  ****
*****

当然了, 这只是以三行为一个示例, 我们的程序应该接受任意的正整数, 比如, 给一个 5, 就要能打出 5 行的类似的三角形来. 让我们来看看如何写出这样一个程序, 并在这个过程中借此兜售我们的大道理.

玩具式的代码

我知道很多"数学帝"可能一眼就被图案中的规律吸引过去了, 他们很快就指出星号是等差数列, 然后很快就弄出了计算每行前面要缩进多少个空格的公式, 然后呢, 一层循环, 二层循环, blablablah…然后最里面几条优雅而性感的 print 语句, 搞掂!一种智商上的优越感油然而生, 接着他们可能就要问:

这么简单的东西, 你也好意思拿出来讲?

下面是这样的一个代码, 能够完全实现以上要求(只演示了 3 行的情况):

public static void main(String[] args) {int i = 3;for (int j = 0; j < i; j++) {for (int k = 0; k < i - j; k++) {System.out.print(" ");}for (int z = 0; z < 2*j+1; z++) {System.out.print("*");}System.out.println();}
}

这里用的是 java 语言来演示, 包括以下的. 我相信像 java 这样烂大街的语言, 即使你没这个背景看懂也不是难事, 在代码中也不会用到什么高深的特性. (这一点皆因我的能力有限所导致, 而不是想装逼的意愿所能决定的~)

怎么说呢, 我们不要以上那种"玩具式"的代码(toy program), 我们要的是生产级(production, 生产环境)的代码.

生产级的代码

让我们来看看如何写出这样的代码.

可扩展性(Extensibility)

首先呢, print 语句是绝对要避免的. 你要明白, print 语句写得太死, 而需求是不断在变化的, 有句话是怎么说的?

唯一不变的就是变化本身.

客户今天跟你说的是要 print 这个图案, 你要是按着客户怎么说, 你就怎么做, 你可就惨了.

客户哪一天突然又会说, 再加点特性, 要能输出到文件;哪一天又说, 再加点 web service, 能供其它程序调用.

让我们多留点心眼, 代码如下:

public void printPattern(int lineCount) {String pattern = getPattern(lineCount);System.out.print(pattern);
}

我们先借助 getPattern 方法拿到要打印的内容, 这样, 如果哪天要输出到文件, 哪天要供 web service 调用, 我们都可以把这个 getPattern 方法提供出去.

我们只要多抽象出那么一层来, 就会给我们带来很多方便.

抽象与封装(Abstraction & Encapsulation)

抽象与封装同时也是很多其它特性的基础, 在后面我们还会不断说到这一主题.

getPattern 就是一个抽象, 是对一系列动作的一个封装.

可能有人会比较教条地认为抽象与封装只能在类层次中进行, 这常常导致在类的内部缺少必要的抽象层次, 常常是一大件事情在一个方法里完成, 方法巨大巨长无比, 这样的所谓面向对象编程不过是虚有其表, 其模块性甚至还比不上那些用面向过程语言写就的代码.

printPattern 层面, 我们不需要知道 getPattern 的细节, 我们只需要传入所需参数及定义好需要的返回值即可.

大道理: 定义好输入与输出, 描述清楚想要做的事, 先不用去管细节.

然后呢, 我们是不是需要手动去把这个方法写出来呢?

利用好你的工具(Tools)

你不用手动去做这些, 以 eclipse 为例, 只要把光标定位到错误的地方(可以按Ctrl+“.”(点)快速定位), 然后按下"Ctrl+1", 然后选择"Create Method"即可:

eclipse create method

工具将根据传入参数及返回值自动为我们生成方法, 结果如下:

eclipse create method

只要输入与输出定义清楚了, 工具就能自动帮我们生成方法定义, 这里默认它是 private 的, 我们可以把它改成 public.

这里说的是 Eclipse 这个 IDE, 其它的我相信也会有类似的功能. 如果你偏好轻量级的文本编辑器, 那我就不敢说也一定有这些功能了.

利用好任务标识(Task Tags)

我们可以看到, 生成的代码里有个 TODO, 显示出了特别的颜色, 这是个任务标识.

类似的标识还有 FIXME, XXX, 甚至你还可以自定义标识.

打开 eclipse 的菜单-- windows–preferences, 在过滤框中输入"task tag":

eclipse task tag

这些有什么用呢? 我们可以看下, 在编辑器的左右侧, 都有显著的标志提示有个任务标识存在;在 Markers 视图里, 有列举出这些标识:

eclipse todo task tag

在代码质量分析工具 sonar 中, 它也会追踪这些标识. 下图是我在 sonar.oschina.net 上的一个项目的截图:

sonar todo tag

这些有什么用呢? 我们在写代码中, 写到一半, 很可能被某些难题卡住了, 为了不中断正常的流程, 我们先用个 TODO 来标识, 然后就可以继续地把一些简单的问题先处理完, 再回过头来对付这些.

又或者像现在这样, 我们生成了出来了这个方法, 工具为我们自动加了入"TODO"的标识, 毕竟方法的主体还没有, 可不巧的是, 现在到了下班时间了, 然后呢, 我们就可以存盘并提交到 svn 或者 git 上去了. 有人可能要说: "啊? 不是吧, 你的代码都没写完你怎么就提交了? "

没关系, 我们已经标识好了 TODO, 所以它会提醒我们还有工作是没做完的. 另外我们为何如此着急提交呢? 因为我们并不是在单打独斗:

团队合作(Teamwork)

我们前面说了, 我们可能还要做输出图案到文件的需求, 很可能你有个同事哥们, 他就正做着这个模块, 而他现在呢, 就在等着你这个 getPattern 方法. 你提交了, 他就可以继续写他的代码了:

package org.jcc.core.demo;public class PatternFile {private Pattern pattern;public PatternFile(Pattern pattern) {this.pattern = pattern;}public void generatePatternFile(int lineCount) {String content = pattern.getPattern(lineCount);saveInFile(content);}private void saveInFile(String content) {// TODO Auto-generated method stub//System.out.println(content);}
}

可以看到, 他的类依赖你的类, 在他的方法 generatePatternFile 里还调用了 getPattern 方法, 你没实现, 那又怎样呢? 接口好了就行了!

面向接口编程(Interface)

有人可能比较死板, 比较教条主义, 以为呢, 说到接口就一定要弄个 interface, 其实呢, 我们这个方法 getPattern 就是一个承诺, 一个约定, 一个协议, 也是一个广义上的接口.

有人可能要问, 你方法细节还没有实现, 他怎么测试? 别担心, 办法会有的:

利用 Mockito 来测试

代码如下:

@Test
public void testGeneratePatternFile() {// 用mockito来模拟接口的行为, 为此我们手动构建一个三行的图案Pattern pattern = Mockito.mock(Pattern.class);String mockContent = "  *" + System.lineSeparator() + " ***" + System.lineSeparator() + "*****" + System.lineSeparator();// 当调用getPattern方法时, 就返回这里定义好的内容. Mockito.when(pattern.getPattern(3)).thenReturn(mockContent);// 测试generatePatternFile方法, 在它的里面将会调用getPattern方法PatternFile pf = new PatternFile(pattern);pf.generatePatternFile(3);// TODO 断言文件存在并且文件中的内容与mockContent一致// assert that file is exists and content in file is equals the mock content
}

以上我们用一个 mock 对象以及 when, thenReturn 来主动模拟一个尚未实现的方法.

你也许对 Mock 之类的技术还不太了解, 但这些词表达的意思我想大家都不难明白. Mock 的更详细介绍请自行百度之.

借助 Mockito, 这个哥们就可以这样写好他的代码, 并完成他的测试了, 然后可以提交他的代码, 宣布工作完成, 接着他就可以飞到马尔代夫去度假去了.

可以看到, 尽管我们的功能八字还没一撇, 可只要我们坚持面向接口编程, 时时想着团队合作, 经常提交已经写好的代码, 特别是公共接口方面的代码, 我们的同事就能及时推进他们的工作, 甚至比我们还早完成, 这都是有可能的, 都是正常的, 也是我们应该追求的.

而利用好抽象及封装, 我们还能得到好几个好处:

可测试性(Testability)

通过以上举例, 可以看到, 我们可以手动构建一个图案, 并交给程序去判断(注: 为了简短起见, 代码中省略了具体的 assert 细节). 而如果是开头那样直接就打印了呢? 你根本没法让程序去判断, 只能通过人眼去观察输出, 这样就给 自动化的测试(Automatic Test) 带来了困难.

可重用性(Reusability)

getPattern 被抽象出来之后, 可以看到, 不但可以在 printPattern 方法里使用, 也可以在 generatePatternFile 方法里使用. 而如果按开头那样呢? 你没法复用, 你还是不得不重构;又或者你可能只是简单地把代码复制一遍了, 再作些改动.

当然, 现在这个程序很小, 全部拷贝一遍好像也很快, 但如果是很大的程序呢? 又或者我们又要拓展到可供 web service 调用, 难道就这样拷贝下去? 哪一天程序要做些小调整, 难道又要一一去修改吗?

不要重复(DRY: Don’t Repeat Yourself)

管理重复性一直都是程序开发中的重大关切, 在目前这个小程序里, 这一问题还不是那么迫切, 这个在此就不作详述, 以后会另写一些文章来做些介绍.

好了, 说了一大通, 绕了一大圈, 测试也测了, 同事也度假去了, 我们也要赶紧我们的工作. 那么接下来是不是赶紧写那些实现呢? 不!

我们已经介绍了不少的"ility"结尾的单词, 接下来还要说到!我有点担心大家说我"zhuangbility", 有句话说: “Don’t zhuangbility, zhuangbility leads to leipility”(莫装逼, 装逼遭雷劈), 没办法, 为了阐述这些大道理, 我也只好冒着被 leipility 的危险.

可维护性(Maintainability)

你首先把注释写好:

怎么说呢? 现在 IT 工作强度很大, 过劳死是不稀奇的事, 写着写着说不定哪天人就挂了. 一个人挂了不要紧, 工作可不能挂!(不是在说笑话, 貌似有些公司或老板表现出来的态度就是这样的~)

别人要能顺利接手你的活, 这是关键.

其实没必要说"挂了"这些不吉利的话, 也可能是有人要生了, 比如你老婆要生了, 你也休产假去了, 你写到一半, 老板把你的工作转交给你的同事.

试想, 要是一点注释都没有, 你的同事接手起来就很困难, 他要加班加点才能早点弄清你的代码的意图. 所以呢, 不要害了你的同事!把代码的可维护性做好, 大家的健康也才有更好的可维护性!

代码如下:

/*** 获取指定行数的图案, 比如3行时: *   **  **** ******  * @param lineCount 指定的行数* @return 图案的字符串表示, 包括换行符在内*/
public String getPattern(int lineCount) {// TODO Auto-generated method stubreturn null;
}

其实, 良好的命名同样也是可维护性的关键, 比如上面的 getPattern, lineCount, 而不是像最前面那个示例中的 i, j, k, z 等乱七八糟的名字.

另外, 丰富的抽象层次也是如此, 这点我们后续还会不断提及.

好了, 注释也写完了, 然后呢, 现在该轮到写那个该死的等差数列了吧? 不!

健壮性(Robustness)

Robustness 又常常音译成鲁棒性.

作者在大学时读的是自动化专业, 在那些自动控制理论里, 老出现什么鲁棒性, 看了让人犯晕, 不如直接叫健壮性.

我倒是想起了小时候老爸常买给我喝的 Robust(即乐百氏, 与娃哈哈类似的饮品), 味道是不错, 不过喝完身体挺没见得健壮到哪去, 也许喝得还不够多~

我们先要把判断做好, 输入负数或者输入的数字太大了, 你要拒绝它们, 同时在注释中也作出说明:

/*** 获取指定行数的图案, 比如3行时: *   **  **** ******  * @param lineCount 指定的行数, 1-20之间* @return 图案的字符串表示, 包括换行符在内*/
public String getPattern(int lineCount) {if (lineCount < 1) {throw new IllegalArgumentException("行数不能小于1!");}if (lineCount > 20) {throw new IllegalArgumentException("行数不能大于20!");}// TODO return null;
}

我知道我在这里说这些, 有些人可能已经不耐烦了, 他们想着的是写那些有技巧的代码, 那些有挑战性的部分, 那些 tricky 的部分, 那些能体现出他们智商上的优越感的部分.

有个词是怎么说的, “rocket science”(火箭科学, 喻指那些高精尖的技术), 特别的有些刚毕业的心气很高的学生, 满脑子想的可能就是这些. 可是呢, 类似情况不是没有, 但通常是很少的:

骚年, 不是我在打击你, 你也许真的想多了. 工作上, 我们多数时候处理的都是一些细节的问题, 一些琐碎的事情, 一些按部就班的样板式的代码, 需要的不是多高的智商, 多么 tricky 的技巧, 要是是耐心, 细致, 严谨, 一丝不苟.

为何一开始就要把这些做好呢? 因为到了后面, 你就没时间去做了. 这一点你一定要相信我, 以下引自 wiki 的"90-90法则":

前 90% 的代码要花费你 90% 的开发时间, 剩余的 10% 的代码要花费你另一个 90% 的开发时间.

The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.

–Tom Cargill, 贝尔实验室

而最后如果因为时间紧急, 就这样没保护就上了生产环境, 一旦出了问题, 你会花更多的时间去收拾这些烂摊子, 而最终你还是不得不将这些补上.

有一个"墨菲定律"(Murphy’s Law)大意是这么说的:

有可能出错的的东西一定会出错.

现在不擦屁股, 后面还有得擦. 你省掉了纸尿裤, 你的程序就裸奔了, 你就等着洗更多的外套.

我们也常说: "该来的一定会来. "如果用电影<<无间道>>里的话来说呢, 那就是:

“出来混, 迟早要还的”. (哇塞, 说得太精彩了. 这些编导或者剧作家不去写教科书太可惜了. )

所以呢, 不要有侥幸的心理, 把程序从一开始就写健壮才是正道.

小结

说了半天, 我们甚至连一行核心代码都没写, 不过, 文章至此倒是要先做一个阶段了结了. 我们说写代码有个原则, 那就是方法不能太长, 最好一个屏幕就能显示完, 否则看起来就很累了;自然的, 文章也不能写得太长, 否则写起来, 读起来都很累人.

所以呢, 虽然一开始那里提了好多的道理, 本来也是想一扒到底的, 但扒到一半发现已经很长了, 所以上半身扒完, 就此腰斩, 下半身留待后面继续扒, 下半身更精彩, 我们下回再见.

下一篇, 见 小程序中的大道理之二 .

这篇关于小程序中的大道理--综述的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/423674

相关文章

python编写朋克风格的天气查询程序

《python编写朋克风格的天气查询程序》这篇文章主要为大家详细介绍了一个基于Python的桌面应用程序,使用了tkinter库来创建图形用户界面并通过requests库调用Open-MeteoAPI... 目录工具介绍工具使用说明python脚本内容如何运行脚本工具介绍这个天气查询工具是一个基于 Pyt

Ubuntu设置程序开机自启动的操作步骤

《Ubuntu设置程序开机自启动的操作步骤》在部署程序到边缘端时,我们总希望可以通电即启动我们写好的程序,本篇博客用以记录如何在ubuntu开机执行某条命令或者某个可执行程序,需要的朋友可以参考下... 目录1、概述2、图形界面设置3、设置为Systemd服务1、概述测试环境:Ubuntu22.04 带图

Python程序打包exe,单文件和多文件方式

《Python程序打包exe,单文件和多文件方式》:本文主要介绍Python程序打包exe,单文件和多文件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python 脚本打成exe文件安装Pyinstaller准备一个ico图标打包方式一(适用于文件较少的程

Python程序的文件头部声明小结

《Python程序的文件头部声明小结》在Python文件的顶部声明编码通常是必须的,尤其是在处理非ASCII字符时,下面就来介绍一下两种头部文件声明,具有一定的参考价值,感兴趣的可以了解一下... 目录一、# coding=utf-8二、#!/usr/bin/env python三、运行Python程序四、

无法启动此程序因为计算机丢失api-ms-win-core-path-l1-1-0.dll修复方案

《无法启动此程序因为计算机丢失api-ms-win-core-path-l1-1-0.dll修复方案》:本文主要介绍了无法启动此程序,详细内容请阅读本文,希望能对你有所帮助... 在计算机使用过程中,我们经常会遇到一些错误提示,其中之一就是"api-ms-win-core-path-l1-1-0.dll丢失

SpringBoot后端实现小程序微信登录功能实现

《SpringBoot后端实现小程序微信登录功能实现》微信小程序登录是开发者通过微信提供的身份验证机制,获取用户唯一标识(openid)和会话密钥(session_key)的过程,这篇文章给大家介绍S... 目录SpringBoot实现微信小程序登录简介SpringBoot后端实现微信登录SpringBoo

uniapp小程序中实现无缝衔接滚动效果代码示例

《uniapp小程序中实现无缝衔接滚动效果代码示例》:本文主要介绍uniapp小程序中实现无缝衔接滚动效果的相关资料,该方法可以实现滚动内容中字的不同的颜色更改,并且可以根据需要进行艺术化更改和自... 组件滚动通知只能实现简单的滚动效果,不能实现滚动内容中的字进行不同颜色的更改,下面实现一个无缝衔接的滚动

Java使用WebView实现桌面程序的技术指南

《Java使用WebView实现桌面程序的技术指南》在现代软件开发中,许多应用需要在桌面程序中嵌入Web页面,例如,你可能需要在Java桌面应用中嵌入一部分Web前端,或者加载一个HTML5界面以增强... 目录1、简述2、WebView 特点3、搭建 WebView 示例3.1 添加 JavaFX 依赖3

防止SpringBoot程序崩溃的几种方式汇总

《防止SpringBoot程序崩溃的几种方式汇总》本文总结了8种防止SpringBoot程序崩溃的方法,包括全局异常处理、try-catch、断路器、资源限制、监控、优雅停机、健康检查和数据库连接池配... 目录1. 全局异常处理2. 使用 try-catch 捕获异常3. 使用断路器4. 设置最大内存和线

使用Python创建一个功能完整的Windows风格计算器程序

《使用Python创建一个功能完整的Windows风格计算器程序》:本文主要介绍如何使用Python和Tkinter创建一个功能完整的Windows风格计算器程序,包括基本运算、高级科学计算(如三... 目录python实现Windows系统计算器程序(含高级功能)1. 使用Tkinter实现基础计算器2.