打开IDEA,程序员思考的永远只有两件事!!!

微信公众号:牛奶 Yoka 的小屋
有任何问题。欢迎来撩~
最近更新:2024/07/09

[大家好,我是牛奶。]

当年面试时背了很多八股文,但在日渐重复的机械工作中(产品业务开发),计算机网络、操作系统、算法等很多晦涩难懂的基础知识已在脑海日渐模糊,每天打开 IDEA,思考的永远只有两件事:

  • 新需求的代码怎么写?
  • 新 BUG 单在代码上怎么改?

这是程序员在工作中最核心的两件事。所以就想聊聊这两件事所带来的两个问题:

  • 怎样写好代码?
  • 怎样重构代码让 BUG 更少且性能更好?

最近闲来无事读了《代码整洁之道》和《重构》,刚好这两本书,一个提供规范,一个优化代码,所以这篇文章,我分享三个看似简单,实则陷阱重重甚至很多反直觉的代码规范和重构技巧,这也是我在日常工作中最常用的三个规范(如下图),愿对诸君有所帮助。

代码命名的学问

代码命名规范

命名的学问可远比我想象的大的多。最初以为命名不好无非不方便理解,多添加注释即可,现在想来太天真,命名不仅可能影响代码阅读,还可能影响代码优化甚至代码运行。下面介绍几个比较经典的命名问题,talk is cheap,上代码:

   public class ProgrammerSalaryCalculator {public static void main(String[] args) {int age = 30;boolean isReadCleanCodeBook = true;String p = "架构师";double salary = salary(age, isReadCleanCodeBook, p);}// 计算程序员薪水。// 规则如下:如果是25-35岁的程序员,且读过《代码整洁之道》和《重构》这两本书,平均月薪2万元;//如果是35-45岁程序员,如果岗位是架构师或管理,则薪资根据公司-薪资表获取。public static double salary(int age, boolean isReadCleanCodeBook, String p) {int result = 0;if (age >= 25 && age < 35&& isReadCleanCodeBook){return 20000;} else if (age >= 35 && age <= 45) {if ("架构师".equals(p) || "管理".equals(p)) {result =  salaryDAO.getSalaryInfo(age);return result;}} else {return 0;}}
}

这段代码有 5 处问题,诸位共发现几处?我们来一一识别一下:

1、**方法名称使用动词+名词表示。**有动词有名词,才能完整表示要做的事。比如只写名词basketball,很难根据名字判断出该方法是买篮球还是打篮球,但是命名为playBasketball便一目了然。『上述计算程序员薪水的方法命名,应优化为 calculateSalary。

2、**变量名称用含义明确的名词。**不要使用意义不明的一个字母作为变量;不要使用不通用的缩写作为变量;也不要使用数字结尾的无效命名作为变量;这一类的命名很难向读者表达这些变量的意图。『上述的p变量命名,应优化为position表示岗位的含义。

3、**布尔类型的变量都不要加is。**这条出自阿里的《Java开发手册》。核心原因是,一些用于远程调用的RPC框架,在对从远程过来的对象进行反序列化解析时,会将is开头的布尔类型属性字段解析成不带is的属性字段,因为如fastJson、jackson这些工具反序列化的机制是根据字段的get方法名获取,但在JavaBean规范中,如isSuccess和success两个属性的get方法都是isSuccess(),因此解析就会出错,这就是我上文所说命名影响代码运行。『上述表示是否读过整洁代码书籍的变量命名,应优化为ReadCleanCodeBook。

4、**区分不同层的增删改查命名。**基础设施层对数据库操作的增删改查,现在基本公认使用insertXxx、deleteXxx、updateXxx、selectXxx的方法命名;为了区分基础设施层,接口层、应用层、领域层一般使用createXxx,removeXxx,modifyXxx,getXxByYy的方式进行方法命名。当然命名根据个人喜好,只要容易加以区分即可。『上述根据公司-薪资表查询薪资的DAO层方法命名可优化为selectSalaryInfoByAge。

5、最后一点非命名问题,却和命名有关。Bob大叔在书里说:

“每次写注释,你都该做个鬼脸,感受自己在表达能力上的失败。”——Robert C. Martin

Bob大叔强调,**能用代码表示意图的地方就不要用注释。**注释越多,反而表明你代码的表达能力越弱。他倒是不排斥长命名,书里的很多案例的命名都挺长,看他的代码,你能明显感觉到为了让读者能看懂代码,各种命名真是煞费苦心,哈哈。注释还有一个弊端在于,我们更新大量代码逻辑后,往往不会同步更新注释,当较多的注释和代码逻辑不一致时,就会给后续代码理解造成误解。当然,有些注释不能删,比如版权注释,比如一些实在晦涩难懂的逻辑等。

除以上几个关键的命名问题,还有包名小写,类名首字母大写;在接口类前面不要加一个I前缀,因为它是C# 的习惯(这个有些反直觉);集合类变命名展示出key和value的集合含义等。这些相对容易理解,这里就不展开分析了。

代码命名优化

修改变量名需要全局修改,修改前先使用快捷键 crtl+shift+F10 跑一下测试用例,确保功能正常。shift+F6 全局修改命名,修改完成后再次跑一下测试用例,确保优化无误。优化后正确代码如下:

public class ProgrammerSalaryCalculator {public static void main(String[] args) {int age = 30;boolean readCleanCodeBook = true;String position = "架构师";double salary = calculateSalary(age, readCleanCodeBook, position);}public static double calculateSalary(int age, boolean isReadCleanCodeBook, String position) {int result;if (age >= 25 && age < 35&&isReadCleanCodeBook){return 20000;} else if (age >= 35 && age <= 45) {if ("架构师".equals(position) || "管理".equals(position)) {  return salaryDAO.SelectSalaryInfo(age);}} else {return 0;}}
}

代码重复

“在简单代码规则中,我最在意代码重复。如果同一段代码反复出现,就表示某种想法未在代码中得到良好的体现。”——Robert C. Martin

代码重复是大佬最重视的一点。像我这种菜鸟,最开始以为重复代码可以一眼识别,IDEA也有辅助工具提示代码重复,没有多重要,too young too simple。诸君且看,重复代码共分为三类:

  • 同一类中的代码重复
  • 互为兄弟的子类间代码重复
  • 不同类间的代码重复

我们常见第一类,但第二三类重复也同样重要,且较为隐蔽。由于我们很多系统开发都是多人协作。很难避免不会出现不同类间存在重复代码。所以,这里针对三类重复代码提供一些优化方式:

同一类中的代码重复

这类处理最简单。直接将两个方法合并为一个方法。IDEA提供了快捷键的方式,**选中重复代码块,按下 Crtl+Alt+M,IDEA就会自动识别重复代码块并抽取公共方法,之后给这个方法命名即可。**当然,在修改代码前后,别忘了 crtl+shift+F10 跑一下测试用例,确保重构前后代码逻辑没有发生实质变化。具体操作过程如下:

具体视频地址

互为兄弟的子类间代码重复

两个兄弟类中代码重复,我们第一时间就是想办法把重复代码提到父类中。怎么提呢?

  1. 先利用Crtl+Alt+M快捷键把重复代码抽取为方法。

  2. 使用快捷键Crtl+Alt+Shift+T->Pull Members up(有些版本IDEA需要去Refactor中去找Pull Members up)将重复方法上移父类。

  3. 将另一个类中重复代码块替换为父类对应方法。

同理,上述每一步对代码的改动都需要crtl+shift+F10 运行相应测试用例,保证代码逻辑无改动。

具体演示如下,该案例直接将两个方法合并至父类:

具体视频地址

不同类间的代码重复

这种最复杂,不过和上一类重复大差不差。只不过将重复方法提取到新类中:

  1. 先利用Crtl+Alt+M快捷键把重复代码抽取为方法。

  2. 再通过Crtl+Alt+Shift+T->Extract Delegate抽取为新类+方法

  3. 将另一个类中重复代码块替换为新类对应方法。

具体操作如下:
具体视频地址

最后,并非所有的重复都需要消除。多微服务间的数据模型、模型转换、client连接、以及完全不相干业务中极少量的逻辑业务重复等,不需要消除重复。过度的消除重复可能会导致代码太过耦合,是否需要消除重复还是需要视具体的业务场景来定。

视频中所使用的快捷键如下:

快捷键含义
crtl+shift+F10跑该类对应的测试用例
Crtl+Alt+M重复代码抽取公共方法
Crtl+Alt+Shift+T->Pull Members up将方法上移到父类中
Crtl+Alt+Shift+T->Extract Delegate将方法提取到一个新/旧类中

视频中对应的代码:

public class Main {
public static void main(String[] args) {
calculateChenGuangMarketPrice("apple", 1);
calculateDaShanMarketPrice("apple", 1);System.out.println("Hello world!");}public static Double calculateChenGuangMarketPrice(String type, Integer number) {Double prices;
switch(type) {
case "Orange":prices = 3.0;
break;
case "apple":prices = 5.0;
break;
default:
throw new IllegalArgumentException("error type:" + type);}
return prices * number;}public static Double calculateDaShanMarketPrice(String type, Integer number) {Double prices;
switch(type) {
case "Orange":prices = 3.0;
break;
case "apple":prices = 5.0;
break;
default:
throw new IllegalArgumentException("error type:" + type);}
return prices * number;}
} 

测试代码:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;@RunWith(MockitoJUnitRunner.class)
public class MarketTest {
@InjectMocks
private Market market;@Test
public void calculateChenGuangMarketPriceTest() {String type = "apple";Integer number = 2;Double expected = 10.0;Assert.assertEquals(expected, market.calculateChenGuangMarketPrice(type, number));}@Test
public void calculateDaShanMarketPriceTest() {String type = "apple";Integer number = 2;Double expected = 10.0;Assert.assertEquals(expected, market.calculateDaShanMarketPrice(type, number));}
}

“消除重复和提高表达力让我在整洁代码方面获益良多,只要铭记这两点,改进脏代码时就会大有不同。”——Robert C. Martin

合格的方法

类中的方法编码时,针对入参、方法实现、出参,有三个在实际开发中常用的规范需要注意:

  • 方法入参不要超过5个
  • 方法行数不超过50行
  • 方法出参不要返回null

针对每一点,咱们都展开来说说。

方法入参优化

方法入参不要超过5个。超过5个我们就要想办法简化。我们常见的优化手法一般是参数合并法,比如针对多个入参是从同一数据结构中抽取,直接传递该数据结构作为入参;如果几项关联参数总是同时出现,将这几个关联参数整合为对象参数传递;在《重构》一书中提出的另外三种优化建议,我觉更应该引起重视:

  • 移除标记参数
  • 以查询取代参数
  • 函数组合成类
移除标记参数

对于标记参数,我真是深受其害,我曾在实际开发过程中看到两行代码,调用了一个方法四次进行条件判断,这个方法的后两个参数是布尔类型,调四次就是对这两个布尔类型入参的四种组合。每次看到这里,我都要去思考下这四种入参分别是执行方法中的哪一个逻辑,阅读非常吃力。在《重构》一书第11章第3节强调:

“我不喜欢标记参数,因为它们让人难以理解到底有哪些函数可以调用、应该怎么调用。拿到一份 API 以后,我首先看到的是一系列可供调用的函数,但标记参数却隐藏了函数调用中存在的差异性。使用这样的函数,我还得弄清标记参数有哪些可用的值。布尔型的标记尤其糟糕,因为它们不能清晰地传达其含义——在调用一个函数时,我很难弄清 true 到底是什么意思。如果明确用一个函数来完成一项单独的任务,其含义会清晰得多。——Martin Fowler

这里的标记参数具体是指调用者用它来指示被调函数应该执行哪一部分逻辑的参数。并非只有布尔类型是标记参数,举个非布尔类型标记参数案例:

    public Integer setDimension (String name, String value) {int res = 0;if (Objects.equals(name, "height")) {this.height = value;res = 1;return res;}if (Objects.equals(name, "width")) {this.width = value;res = 2;return res;}return res;}

优化后为:

public Integer setHeight (String value) {this.height = value;return 1;}public Integer setwidth (String value) {this.width = value;return 2;}

一个方法中,存在多个处理逻辑,本身就违背单一职责原则(SRP),所以针对标记参数,强烈建议使用正确的命名拆分该方法。

以查询取代参数

我们写的接口,除了给前端提供,很多时候需要对外部系统提供,这个时候,如果一个参数我们可以自己获取,就不要再让外部来传参了。不然联调接口的时候会出现很多麻烦事。调用方可能参数传错,可能参数漏传,可能要求非必填等等。重构的作者也强调:

“如果调用函数时传入了一个值,而这个值由函数自己来获得也是同样容易,这就是重复。这个本不必要的参数会增加调用者的难度,因为它不得不找出正确的参数值,其实原本调用者是不需要费这个力气的。
——Martin Fowler

函数组合成类

这一点不是很好理解。看下图,两个方法,转变为类中的两个方法。同时,这两个方法的参数没了。我觉得这个重构手段更名为**“参数消失术”**还更好理解点。


这样做的原因在于,两个方法参数相同,且这两个方法总是和其参数同时出现,我们便将关联性较强的功能和数据放到一个类中。而类本身的定义为:**具有相同特性(数据元素)和行为(功能)的对象的抽象就是类。**因此,我们不过就是从参数的角度,对数据和行为做了一次抽象。《重构》的作者认为:

如果发现一组函数形影不离地操作同一块数据(通常是将这块数据作为参数传递给函数),我就认为,是时候组建一个类了。类能明确地给这些函数提供一个共用的环境,在对象内部调用这些函数可以少传许多参数,从而简化函数调用,并且这样一个对象也可以更方便地传递给系统的其他部分。——Martin Fowler

方法内容优化

我第一次优化代码,做的第一件事,便是把我写的好几处代码提取为各自的方法。在这之前,我本能的认为,一个方法里面调用好多方法,是一种不规范的行为。没想到不论是Robert C. Martin还是Martin Fowler大佬,都非常支持小函数。他们指出,**活的最长、最好的程序,其中的函数都比较短。**看到这句话,彻底打破我之前的编码风格。不过,针对方法/函数的抽取也不能太过随意,应尽可能的将业务逻辑从上往下依次抽象出方法。用比如打印发票举个例子,我绘制了一个打印发票的思维导图(如下图),在我看来,从左往右,每个分支便可抽象出一个方法,所有的业务逻辑都可以在思维导图中逐层拆解最后转换为包含一层层方法的代码

另外,Martin Fowler也给出提取方法的时机:

每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。——Martin Fowler

当我们习惯不断递归的抽象出方法进行开发,那一般方法行数基本不会超过50行。

方法出参优化

我写代码时,最烦的一件事,便是对接口返回的数据进行判空处理。几乎任何一个接口返回,都需要进行判空。虽然很烦,又不得不做。不然你不知道什么情况下就会出现空指针异常。Bob大叔建议,如果你想返回null值,不如抛出异常,或者返回特例对象。举个例子:

  List<Employee> employees = getEmployees();
if (employees != null) {for(Employee e : employees) {totalPay += e.getPay();}
}

像上述这种判空,如果getEmployees方法直接返回空集合Collections.emptyList(),则完全可以避免判空操作:

List<Employee> employees = getEmployees();
for(Employee e : employees) {
totalPay += e.getPay();
}

因此,出参为集合的接口返回,如果为空,建议一律返回 java.util.Collections.emptyList/emptyMap/emptyXXX等自带的空集合。

后记

编码的问题其实非常多,除这三个代码规范,还包括为提高代码健壮性对线程池、序列化/反序列化的规范;为防止OOM对接口返回大对象的规范,对循环调用RPC的规范;为保证数据库事务问题对事务注解的规范;为防止随意打印日志对日志框架和日志级别的规范等。
我这里指出的代码规范和重构技巧仅仅九牛一毛,想要写出好的代码,还需要多写多练,提高基本功,总结出自己的优秀编码习惯。

愿我们每位开发者不断提高编码能力,此生再无BUG!!!

----------------end----------------
我是牛奶,目前是一名互联网开发菜鸟,主要聚焦于互联网技术开发和个人成长的高营养价值内容分享,感兴趣的小伙伴可以在下方加个关注,大家一起共同学习和进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/869193.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

混合贪心算法求解地铁线路调度

一、问题描述 城市轨道交通的繁荣发展&#xff0c;带来了车辆资源需求的日益增加。如何兼顾运营服务水平和运营成本&#xff0c;以最少的车底优质地完成运输任务成为一大严峻问题。本题在后续的描述中将由多辆动车和拖车组合而成的车组称为车底。在日常的运营组织中&#xff0…

【文档智能】LACE:帮你自动生成文档布局的方法浅尝

前言 往期很多文章都介绍了【文档智能】上布局识别&#xff08;版式分析&#xff09;的技术思路&#xff0c;版式分析是通过对文档版式进行布局识别&#xff0c;识别文档中的元素类型的过程。这次来看看一个有趣的思路&#xff0c;通过已有的元素类型&#xff0c;来生成可控的…

赠你一只金色的眼 - 富集分析和表达数据可视化

GOplot包介绍 GOplot包用于生物数据的可视化。更确切地说&#xff0c;该包将表达数据与功能分析的结果整合并进行可视化。但是要注意该包不能用于执行这些分析&#xff0c;只能把分析结果进行可视化。在所有科学领域&#xff0c;由于空间限制和结果所需的简洁性&#xff0c;切…

Agent如何帮助大模型“增强记忆”?

Agent如何帮助大模型“增强记忆”&#xff1f; 原创 格林 神州问学 2024年07月08日 17:50 日本 记忆反馈 >规划&#xff1f; 来源|神州问学 引言 去年6月份&#xff0c;Lilian发布了关于LLM驱动的Agent的结构和组件&#xff0c;其中包括规划、行动、工具还有记忆&#xff…

带有子节点的树状表的父节点拖动排序#Vue3#Sortable插件

带有子节点的树状表的父节点拖动排序#Vue3#Sortable插件 使用Sortable插件这里要保证获取到的是父节点的下标&#xff0c;属性newDraggableIndex获取到的就是只有父节点的下标。设置子节点不能被拖动&#xff0c;最后在逐个调用接口进行数据库中顺序的更新。 <template>…

【Python】已解决:SyntaxError: invalid character in identifier

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;SyntaxError: invalid character in identifier 一、分析问题背景 在Python编程中&#xff0c;SyntaxError: invalid character in identifier是一个常见的编译…

面经-计算机网络-数据结构-堆

1.什么是堆 堆是一种满足以下条件的树&#xff1a; 堆中的每一个节点值都大于等于&#xff08;或小于等于&#xff09;子树中所有节点的值。或者说&#xff0c;任意一个节点的值都大于等于&#xff08;或小于等于&#xff09;所有子节点的值。 2.堆的用途 当我们只关心所有数…

tk Text文本框赋值,清空

import tkinter as tk# 创建主窗口 root tk.Tk() root.title("文本框内容赋值示例")# 创建一个Text小部件 text_area tk.Text(root, height10, width50) text_area.pack()# 将内容赋值给Text小部件 text_area.insert(tk.END, "这是文本框中的内容。\n")#…

android CameraX构建相机拍照

Android CameraX 是一个 Jetpack 支持库&#xff0c;旨在简化相机应用的开发工作。它提供了一致且易用的API接口&#xff0c;适用于大多数Android设备&#xff0c;并可向后兼容至Android 5.0&#xff08;API级别21&#xff09;。 CameraX解决了在多种设备上实现相机功能时所遇…

26.5 Django模板层

1. 模版介绍 在Django中, 模板(Templates)主要用于动态地生成HTML页面. 当需要基于某些数据(如用户信息, 数据库查询结果等)来动态地渲染HTML页面时, 就会使用到模板.以下是模板在Django中使用的几个关键场景: * 1. 动态内容生成: 当需要根据数据库中的数据或其他动态数据来生…

linux使用chattr与lsattr设置文件/目录防串改

背景 linux服务器下,防止某个文件/目录被串改(增删改),可以使用chattr与lsattr设置,这是一种保护机制,用于防止意外地修改或删除重要的文件内容。 chattr与lsattr使用 1.设置目录 图中/tmp/zhk,设置目录属性文件可能被设置为不可更改(immutable)或者只追加(append …

1.Frida框架-Frida环境搭建

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;微尘网校 前置条件&#xff1a;需要科学上网 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F…

python—读写csv文件

目录 csv库方法参数 读取数据 csv.reader方法 文件指定行或列数据读取操作 txt文件的readlines、read方法 csv.DictReader方法 写入数据 txt文件的write&#xff0c;writelines csv.writer方法 csv.DictWriter方法 读写联合(修改及插入数据) 读写csv 文件时&#xf…

【Notepad】Notepad_6.3.1 的中文版安装详情

目录 &#x1f33c;1. Notepad的认识 &#x1f33c;2. Notepad中文版安装详情 &#x1f33c;1. Notepad的认识 Notepad 是 Windows 操作系统中的一个文本编辑器程序&#xff0c;通常用于创建和编辑简单的文本文件&#xff0c;如文本文档 (.txt)。它非常轻量且功能简单&#…

【本地docker启动私有大模型】

一、最终效果 中英文对话 生成代码 二、资源配置 本文选择的模型运行内存需要 4G&#xff0c;因此宿主机建议内存大于8G&#xff0c;CPU建议 6 核以上&#xff1b; 参考博主该mac配置可以相对流畅运行。只需要 CPU资源&#xff0c;不需要 GPU。 三、搭建步骤 启动docker容…

Msfvenom制作自己的专属Shell

Msfvenom制作自己的专属Shell 如何通过Msfvenom来生成用户自己的专属Shell?有时候我们上传Shell到目标主机后&#xff0c;不仅我们自己可以连接&#xff0c;其他用户也可以连接&#xff0c;有时候会导致我们丢失该Shell&#xff0c;甚至该shell被用户发现并查杀。 实验环境 …

如何在 SwiftUI 中开发定制 MapKit 功能

文章目录 介绍地图样式imagery-map 地图交互地图控件总结 介绍 在上一篇文章中&#xff0c;我们探讨了 SwiftUI 中新的 MapKit API 的基础知识。现在&#xff0c;让我们深入 MapKit API 的定制点&#xff0c;以便根据我们的需求定制地图呈现。 地图样式 新的 MapKit API 引入…

LabVIEW开发阀门自动校准装置

1. 装置概述与目标 在工业和实验室环境中&#xff0c;阀门的准确性和稳定性对于流体控制和实验数据的可靠性非常重要。LabVIEW可以作为开发阀门自动校准装置的理想工具&#xff0c;提供高度可定制化的解决方案。 2. 硬件与设备选择 型号选择&#xff1a;为了实现阀门自动校准…

这8款宝藏软件,才是安卓手机必装App!

​AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 1.我的日记——My Diary My Diary 是一款带锁的免费安卓日记工具。 它可用于记录每日日记、秘密想法、旅程、心情追踪或任何私人时刻。 你可…

☺初识c++(语法篇)☺

目录 一命名空间&#xff08;namespace&#xff09;&#xff1a; 二cout与cin简述&#xff1a; 三缺省参数&#xff1a; 四函数重载&#xff1a; 五引用&#xff1a; 六内联函数: 七c中的nullptr简述&#xff1a; 一命名空间&#xff08;namespace&#xff09;&#xff1…