Java杂谈之如何优化写出漂亮高效的代码

目录
  • 命名中的不一致
  • 方案中的不一致
  • 代码中的不一致
  • 总结

大部分程序员对于一致性本身的重要性是有认知的。但通常来说,大家理解的一致性都表现在比较大的方面,比如,数据库访问是叫 DAO还是叫 Mapper,Repository?在一个团队内,这是有统一标准的,但编码的层面上,要求往往就不是那么细致了。所以,我们才会看到在代码细节上呈现出了各种不一致。我们还是从一段具体的代码来分析问题。

命名中的不一致

有一次,我在代码评审中看到了这样一段代码:

enum DistributionChannel {
  ​WEBSITE
  ​KINDLE_ONLY
  ​AL
}

使用标记作品的分发渠道,从这段代码的内容上,我们可以看到,目前的分发渠道包括:

  • 网站(WEBSITE)
  • 只在Kindle(KINDLE_ONLY)
  • 全渠道(ALL)

面对这段代码,我有些疑惑,于是我提了一个问题:

  • WEBSITE 和 KINDLE_ONLY 分别表示的是什么?

WEBSITE 表示作品只会在我们自己的网站发布,KINDLE_ONLY 表示这部作品只会在 Kindle 的电子书商店里上架。

  • 二者是不是都表示只在单独一个渠道发布?

是啊!

  • 既然二者都有只在一个平台上架发布的含义,为什么不都叫 XXX 或者 XXX_ONLY?

呃,你说得有道理。

问题原因就是这里 WEBSITE 和 KINDLE_ONLY 两个名字不一致。

表示类似含义的代码应该有一致的名字,比如,很多团队里都会把业务写到服务层,各种服务的命名也通常都是 XXXService。
一旦出现不一致名字,通常都表示不同含义。比如,对于那些非业务入口的业务组件,它们的名字就会不一样,会更符合其具体业务行为,像BookSender ,它表示将作品发送到翻译引擎。

一般枚举值表示的含义应该都有一致的业务含义,一旦出现不同,我就需要确定不同的点到底在哪里,这就是我提问的缘由。

显然,这段代码的作者给这两个枚举值命名时,只是分别考虑了它应该起什么名字,却忽略了这个枚举值在整体中扮演的角色。

理解这一点,改动是很容易,后来,代码被统一成了一个形式:

enum DistributionChannel {
  ​WEBSITE
  ​KINDLE
  ​AL
}

方案中的不一致

// 生成时间戳
public String nowTimestamp() {
  DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  Date now = new Date();
  return format.format(now);
}

当一个系统向另外一个系统发送请求时,需要带一个时间戳过去,这里就是把这个时间戳按照一定格式转成了字符串类型,主要就是传输用,便于另外的系统进行识别,也方便在开发过程中进行调试。

这段代码本身的实现是没有问题的。它甚至考虑到了 SimpleDateFormat 这个类本身存在的多线程问题,所以,它每次去创建了一个新的 SimpleDateFormat 对象。

那我为什么还说它是有问题的呢?因为这种写法是 Java 8 之前的写法,而我们用的 Java 版本是 Java 8 之后的。

在很长的一段时间里,Java 的日期时间解决方案一直是一个备受争议的设计,它的问题很多,有的是概念容易让人混淆(比如:Date 和 Calendar 什么情况下该用哪个),有的是接口设计的不直观(比如:Date 的 setMonth 参数是从 0 到 11),有的是实现容易造成问题(比如:前面提到的 SimpleDateFormat 需要考虑多线程并发的问题,需要每次构建一个新的对象出来)。

这种乱象存在了很长时间,有很多人都在尝试解决这个问题(比如 Joda Time)。从 Java 8开始,Java 官方的 SDK 借鉴了各种程序库,引入了全新的日期时间解决方案。这套解决方案与原有的解决方案是完全独立的,也就是说,使用这套全新的解决方案完全可以应对我们的所有工作。

我们现在的这个项目是一个全新的项目,我们使用的版本是 Java 11,这就意味着我们完全可以使用这套从 Java 8 引入的日期时间解决方案。所以,我们在项目里的约定就是所有的日期时间类型就是使用这套新的解决方案。

现在你可能已经知道我说的问题在哪里了,在这个项目里,我们的要求是使用新的日期时间解决方案,而这里的 SimpleDateFormat 和 Date 是旧解决方案的一部分。所以,虽然这段代码本身的实现是没有问题的,然而,放在项目整体中,这却是一个坏味道,因为它没有和其它的部分保持一致。

后来使用了新的解决方案:

public String nowTimestamp() {
  ​LocalDateTime now = LocalDateTime.now()
  return now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}

之所以会这样,因为一个项目中,应对同一个问题出现了多个解决方案,如果没有统一约定,项目成员会根据自己写代码时的感觉随机选择方案,导致方案不一致。

为什么一个项目中会出现多个解决方案?

  • 时间

时间消逝,技术发展,人们会主动意识到原方案的问题,就会提出新方案,像这里 Java 日期时间的解决方案,就是 JDK 本身随时间演化造成的。有的项目时间比较长,也会出现类似问题。

  • 因为自己的原因引入

比如,在代码中引入做同一件事情类似的程序库。比如判断字符串是否为空或空串,就有 Guava 和 Apache Commons Lang,都能做同样事情,所以,程序员也会根据自己的熟悉程度选择其中之一来用,造成代码不一致。

这两个程序库是很多程序库的基础,经常因为引入了其它程序库,相应的依赖就出现在我们的代码中。所以,我们必须约定,哪种做法是我们在项目中的标准做法,以防出现各自为战的现象。比如,在我的团队中,我们就选择 Guava 作为基础库,因为相对来说,它的风格更现代,所以,团队就约定类似的操作都以 Guava 为准。

代码中的不一致

public void createBook(final List<BookId> bookIds) throws IOException {
  ​List<Book> books = bookService.getApprovedBook(bookIds)
  ​CreateBookParameter parameter = toCreateBookParameter(books)
  ​HttpPost post = createBookHttpRequest(parameter)
  ​httpClient.execute(post)
}

在翻译引擎中创建作品的代码:

  • 首先,根据要处理的作品 ID,获取其中已审核通过的作品
  • 然后,发送一个 HTTP 请求在翻译引擎中创建出这个作品

有什么问题?
这段代码的不一致,这些代码不是一个层次的代码!

首先是获取审核通过的作品,这是一个业务动作,接下来的三行其实是在做一件事,也就是发送创建作品的请求,这三行代码:

  • 创建请求的参数
  • 根据参数创建请求
  • 最后把请求发送出去

三行代码合起来完成了一个发送创建作品请求这么一件事,而这件事才是一个完整的业务动作。

所以,这个函数里的代码并不在一个层次上,有的是业务动作,有的是业务动作的细节。理解到这,把这些业务细节的代码提取到一个函数:

public void createBook(final List<BookId> bookIds) throws IOException {
  ​List<Book> books = bookService.getApprovedBook(bookIds)
  ​createRemoteBook(books)
}

private void createRemoteBook(List<Book> books) throws IOException {
  ​CreateBookParameter parameter = toCreateBookParameter(books)
  ​HttpPost post = createBookHttpRequest(parameter)
  ​httpClient.execute(post)
}

结果上看,原来的函数(createBook)里都是业务动作,而提取出来的函数(createRemoteBook)则都是业务动作的细节,各自语句都在一个层次。

分清代码处于不同层次,基本功还是分离关注点!

一旦分解出不同关注点,还可进一步调整代码的结构。
像前面拆分出来的这个方法,我们已经知道它的作用是发出一个请求去创建作品,本质上并不属于这个业务类的一部分。
所以,还可通过引入一个新模型,将这个部分调整出去:

public void createBook(final List<BookId> bookIds) throws IOException {
  List<Book> books = this.bookService.getApprovedBook(bookIds);
  this.translationEngine.createBook(books);
}

class TranslationEngine {
  public void createBook(List<Book> books) throws IOException {
    ​CreateBookParameter parameter = toCreateBookParameter(books)
    ​HttpPost post = createBookHttpRequest(parameter)
    ​httpClient.execute(post)
  ​
  ​..
}

一说到分层,大多数人想到的只是模型的分层,很少有人会想到在函数的语句中也要分层。各种层次的代码混在一起,许多问题也就随之而来了,最典型莫过长函数。

我们在做的依然是模型分层,只不过,这次的出发点是函数的语句。“分离关注点,越小越好”的意义所在。观察代码的粒度足够小,很多问题自然就会暴露出来。

程序员开始写测试时,有一个典型的问题:如何测试一个私有方法。有人建议用一些特殊能力(比如反射)去测试。我给这个问题的答案是,不要测私有方法。
之所以想测试私有方法,就是分离关注点没有做好,把不同层次的代码混在一起。前面这段代码,如果要测试前面那个 createRemoteBook 方法还是有一定难度的,但调整之后,引入了 TranslationEngine 这个类,这个方法就变成了一个公开方法,就可以按照一个公开方法去测试了,所有问题迎刃而解。

很多程序员纠结的技术问题,其实是一个软件设计问题,不要通过奇技淫巧去解决一个本来不应该被解决的问题。

总结

对于一个团队来说,一致是非常重要的,是降低集体认知成本的重要方式。我们分别见识了:

  • 命名中的不一致
  • 方案中的不一致
  • 代码中的不一致。

类似含义的代码应该有类似的命名,不一致的命名表示不同含义,需要给出一个有效解释。

方案中的不一致:

  • 由于代码长期演化造成的
  • 项目中存在完成同样功能的程序库

无论是哪种原因,都需要团队先统一约定,保证所有人按照同一种方式编写代码。

代码中的不一致常常是把不同层次的代码写在了一起,最典型的就是把业务层面的代码和实现细节的代码混在一起。解决这种问题的方式,就是通过提取方法,把不同层次的代码放到不同的函数里,而这一切的前提还是是分离关注点,这个代码问题的背后还是设计问题。

保持代码在各个层面上的一致性。

到此这篇关于Java杂谈之如何优化写出漂亮高效的代码的文章就介绍到这了,更多相关Java 代码优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java编程实现快速排序及优化代码详解

    普通快速排序 找一个基准值base,然后一趟排序后让base左边的数都小于base,base右边的数都大于等于base.再分为两个子数组的排序.如此递归下去. public class QuickSort { public static <T extends Comparable<? super T>> void sort(T[] arr) { sort(arr, 0, arr.length - 1); } public static <T extends Comparabl

  • 44条Java代码优化建议

    前言 2016年3月修改,结合自己的工作和平时学习的体验重新谈一下为什么要进行代码优化.在修改之前,我的说法是这样的: 就像鲸鱼吃虾米一样,也许吃一个两个虾米对于鲸鱼来说作用不大,但是吃的虾米多了,鲸鱼自然饱了.代码优化一样,也许一个两个的优化,对于提升代码的运行效率意义不大,但是只要处处都能注意代码优化,总体来说对于提升代码的运行效率就很有用了. 这个观点,在现在看来,是要进行代码优化的一个原因,但不全对.在机械工艺发展的今天,服务器动辄8核.16核,64位CPU,代码执行效率非常高,Stri

  • Java性能优化之数据结构实例代码

    -举例(学生排课)- 正常思路的处理方法和优化过后的处理方法: 比如说给学生排课.学生和课程是一个多对多的关系. 按照正常的逻辑 应该有一个关联表来维护 两者之间的关系. 现在,添加一个约束条件用于校验.如:张三上学期学过的课程,在排课的时候不应该再排这种课程. 所以需要出现一个约束表(即:历史成绩表). 即:学生选课表,需要学生成绩表作为约束. -方案一:正常处理方式- 当一个学生进行再次选课的时候.需要查询学生选课表看是否已经存在. 即有如下校验: //查询 学生code和课程code分别为

  • Java利用策略模式优化过多if else代码

    前言 不出意外,这应该是年前最后一次分享,本次来一点实际开发中会用到的小技巧. 比如平时大家是否都会写类似这样的代码: if(a){ //dosomething }else if(b){ //doshomething }else if(c){ //doshomething } else{ ////doshomething } 条件少还好,一旦 else if 过多这里的逻辑将会比较混乱,并很容易出错. 比如这样: 摘自cim中的一个客户端命令的判断条件. 刚开始条件较少,也就没管那么多直接写的:

  • 详解Java代码常见优化方案

    首先,良好的编码规范非常重要.在 java 程序中,访问速度.资源紧张等问题的大部分原因,都是代码不规范造成的. 单例的使用场景 单例模式对于减少资源占用.提高访问速度等方面有很多好处,但并不是所有场景都适用于单例. 简单来说,单例主要适用于以下三个方面: 多线程场景,通过线程同步来控制资源的并发访问. 多线程场景,控制数据共享,让多个不相关的进程或线程之间实现通信(通过访问同一资源来控制). 控制实例的产生,单例只实例化一次,以达到节约资源的目的: 不可随意使用静态变量 当某个对象被定义为 s

  • Java杂谈之如何优化写出漂亮高效的代码

    目录 命名中的不一致 方案中的不一致 代码中的不一致 总结 大部分程序员对于一致性本身的重要性是有认知的.但通常来说,大家理解的一致性都表现在比较大的方面,比如,数据库访问是叫 DAO还是叫 Mapper,Repository?在一个团队内,这是有统一标准的,但编码的层面上,要求往往就不是那么细致了.所以,我们才会看到在代码细节上呈现出了各种不一致.我们还是从一段具体的代码来分析问题. 命名中的不一致 有一次,我在代码评审中看到了这样一段代码: enum DistributionChannel

  • 浅谈JS如何写出漂亮的条件表达式

    多条件语句 多条件语句使用Array.includes 举个例子 function printAnimals(animal) { if (animal === "dog" || animal === "cat") { console.log(`I have a ${animal}`); } } console.log(printAnimals("dog")); // I have a dog 这种写法在条件比较少的情况下看起来没有问题,此时我们只

  • 如何写出优雅的JS 代码

    变量 使用有意义和可发音的变量名 // 不好的写法 const yyyymmdstr = moment().format("YYYY/MM/DD"); // 好的写法 const currentDate = moment().format("YYYY/MM/DD"); 对同一类型的变量使用相同的词汇 // 不好的写法 getUserInfo(); getClientData(); getCustomerRecord(); // 好的写法 getUser(); 使用可

  • 一篇文章教你写出干净的JavaScript代码

    目录 1. 变量 使用有意义的名称 避免添加不必要的上下文 避免硬编码值 2. 函数 使用有意义的名称 使用默认参数 限制参数的数量 避免在一个函数中做太多事情 避免使用布尔标志作为参数 避免写重复的代码 避免副作用 3. 条件语句 使用非负条件 尽可能使用简写 避免过多分支 优先使用 map 而不是 switch 语句 4.并发 避免回调 5. 错误处理 6.注释 只注释业务逻辑 使用版本控制 总结 一段干净的代码,你在阅读.重用和重构的时候都能非常轻松.编写干净的代码非常重要,因为在我们日常

  • 通过数据库和ajax方法写出地图的实例代码

    ajax教程 AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 不是新的编程语言,而是一种使用现有标准的新方法. AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下. 客户端部分:html.js.css代码部分: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www

  • LyScript实现计算片段Hash并写出Excel的示例代码

    本案例将学习运用LyScript计算特定程序中特定某些片段的Hash特征值,并通过xlsxwriter这个第三方模块将计算到的hash值存储成一个excel表格,本例中的知识点可以说已经具备了简单的表格输出能力,如果时间充裕完全可以实现自动化报告生成. 第一步实现计算特定片段的特征值,此类代码实现原理用户传入一个rva相对地址以及读入指令长度,并通过内置的hashlib库实现计算内存段内指令的特征,如下代码先来实现计算两段指令特征. import hashlib import zlib,bina

  • 12条写出高质量JS代码的方法

    书写出高质量的JS代码不仅让程序员看着舒服,更加能够提高程序的运行速度,以下就是我们的小编整理方法: 一.如何书写可维护性的代码 当出现bug的时候如果你能立马修复它是最好的,此时解决问题的四路在你脑中还是很清晰的.否则,你转移到其他任务或者bug是经过一定的时间才出现的,你忘了那个特定的代码,一段时间后再去查看这些代码就 需要: 1.花时间学习和理解这个问题 2.化时间是了解应该解决的问题代码 还有个问题,特别对于大的项目或是公司,修复bug的这位伙计不是写代码的那个人(且发现bug和修复bu

  • java两个线程同时写一个文件

    本文实例为大家分享了java两个线程同时写一个文件的具体代码,供大家参考,具体内容如下 1.多线程    线程是程序执行流的最小单元.是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源.一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行.由于线程之间的相互制约,致使线程在运行中呈现出间断性.线程也有就绪.阻塞和运行三种基本状态.就绪状态是指线程具备运行的所有条

  • Java写入写出Excel操作源码分享

    这两天帮老师做一个数据库,将所有实验交易的数据导入到数据库中,但是不想天天在实验室里面待着,气氛太压抑,就想着先把数据读进EXCEL中,哪天带到实验室导进去 数据原来是这样的,不同的实验有一个专门的文件夹,实验名的文件夹下有不同班级的文件夹,班级文件夹下有该班级日期文件夹,存储的是不同时间下该班做实验的数据EXCEL,原来的EXCEL中没有班级和时间,现在需要通过读取EXCEL名以及班级名来将该信息作为一列,加入到EXCEL中. 下面是源代码,嘿嘿,顺便还做了一个可视化窗口. 类ExcelRea

  • MySQL优化之如何写出高质量sql语句

    前言 关于数据库优化,网上有不少资料和方法,但是不少质量参差不齐,有些总结的不够到位,内容冗杂.这篇文章就来给大家详细介绍了26条优化建议,下面来一起看看吧 1. 查询SQL尽量不要使用全查 select *,而是 select + 具体字段. 反例: select * from student; 正例: select id,name, age from student; 理由: 只取需要的字段,可以节省资源.减少CPU和IO以及网络开销. select * 进行查询时,无法使用到覆盖索引,就会

随机推荐