知识屋:更实用的电脑技术知识网站
所在位置:首页 > 科技

每日一学:如何写出让人抓狂的代码?

发表时间:2022-03-25来源:网络

点击上方“CSDN精品课”,选择“置顶公众号”

第一时间获取精品编程教程

大家好,我是苏三,又跟大家见面了。

前言

今天跟大家聊一个有趣的话题:如何写出让人抓狂的代码?

大家看到这个标题,第一印象觉得这篇文章可能是一篇水文。但我很负责的告诉你,它是一篇有很多干货的技术文。

曾几何时,你在阅读别人代码的时候,有没有抓狂,想生气,想发火的时候?

今天就跟大家一起聊聊,这20种我看了会抓狂的代码,看看你中招了没?

1.不注重代码格式

代码格式说起来很虚,下面我用几个案例演示一下,不注重代码格式的效果。作为这篇文章的开胃小菜吧。

1.1 空格

有时候必要的空格没有加,比如:

@Service @Slf4j public class TestService1{ public void test1(){ addLog("test1");  if (condition1){  if (condition2){  if (condition3){  log.info("info:{}",info);   }   }   } } }

你看了这段代码有何感想,有没有血压飙升的感觉?

代码好像揉到一起去了。

那么,如何把血压降下来呢?

答:加上空格即可。

正解:

@Service @Slf4j public class TestService1 {     public void test1() {        addLog("test1");        if (condition1) {          if (condition2) {            if (condition3) {                log.info("info:{}", info);             }           }         }     } }

只加了一些空格,稍微调整了一下,这段代码的层次结构一下子变得非常清晰了。

好吧,我又冷静下来了。

1.2 换行

写代码时,如果有些必要的换行没有加,可能会出现这样的代码:

public void update(User user) {     if (null != user.getId()) {         User oldUser = userMapper.findUserById(user.getId());         if(null == oldUser)throw new RuntimeException("用户id不存在");         oldUser.setName(user.getName());oldUser.setAge(user.getAge());oldUser.setAddress(user.getAddress());         userMapper.updateUser(oldUser);     } else { userMapper.insertUser(user);     } }

看了这段代码,是不是有点生无可恋的感觉?

简单的加点空格优化一下:

public void update(User user) {     if (null != user.getId()) {         User oldUser = userMapper.findUserById(user.getId());         if(null == oldUser) {             throw new RuntimeException("用户id不存在");         }         oldUser.setName(user.getName());         oldUser.setAge(user.getAge());         oldUser.setAddress(user.getAddress());         userMapper.updateUser(oldUser);     } else {         userMapper.insertUser(user);     } }

代码逻辑一下子变得清晰了许多。

2.随意的命名

java中没有强制规定参数、方法、类或者包名该怎么起名。但如果我们没有养成良好的起名习惯,随意起名的话,可能会出现很多奇怪的代码。

2.1 有意义的参数名

有时候,我们写代码时为了省事(可以少敲几个字母),参数名起得越简单越好。假如同事A写的代码如下:

int a = 1; int b = 2; String c = "abc"; boolean b = false;

一段时间之后,同事A离职了,同事B接手了这段代码。

他此时一脸懵逼,a是什么意思,b又是什么意思,还有c...然后心里一万个草泥马。

给参数起一个有意义的名字,是非常重要的事情,避免给自己或者别人埋坑。

正解:

int supplierCount = 1; int purchaserCount = 2; String userName = "abc"; boolean hasSuccess = false;

2.2 见名知意

光起有意义的参数名还不够,我们不能就这点追求。我们起的参数名称最好能够见名知意,不然就会出现这样的情况:

String yongHuMing = "苏三"; String 用户Name = "苏三"; String su3 = "苏三"; String suThree = "苏三";

这几种参数名看起来是不是有点怪怪的?

为啥不定义成国际上通用的(地球人都能看懂)英文单词呢?

String userName = "苏三"; String susan = "苏三";

上面的这两个参数名,基本上大家都能看懂,减少了好多沟通成本。

所以建议在定义不管是参数名、方法名、类名时,优先使用国际上通用的英文单词,更简单直观,减少沟通成本。少用汉子、拼音,或者数字定义名称。

2.3 参数名风格一致

参数名其实有多种风格,列如:

//字母全小写 int suppliercount = 1; //字母全大写 int SUPPLIERCOUNT = 1; //小写字母 + 下划线 int supplier_count = 1; //大写字母 + 下划线 int SUPPLIER_COUNT = 1; //驼峰标识 int supplierCount = 1;

如果某个类中定义了多种风格的参数名称,看起来是不是有点杂乱无章?

所以建议类的成员变量、局部变量和方法参数使用supplierCount,这种驼峰风格,即:第一个字母小写,后面的每个单词首字母大写。例如:

int supplierCount = 1;

此外,为了好做区分,静态常量建议使用SUPPLIER_COUNT,即:大写字母 + 下划线分隔的参数名。例如:

private static final int SUPPLIER_COUNT = 1;

3.出现大量重复代码

ctrl + c 和 ctrl + v可能是程序员使用最多的快捷键了。

没错,我们是大自然的搬运工。哈哈哈。

在项目初期,我们使用这种工作模式,确实可以提高一些工作效率,可以少写(实际上是少敲)很多代码。

但它带来的问题是:会出现大量的代码重复。例如:

@Service @Slf4j public class TestService1 {     public void test1()  {         addLog("test1");     }     private void addLog(String info) {         if (log.isInfoEnabled()) {             log.info("info:{}", info);         }     } } @Service @Slf4j public class TestService2 {     public void test2()  {         addLog("test2");     }     private void addLog(String info) {         if (log.isInfoEnabled()) {             log.info("info:{}", info);         }     } } @Service @Slf4j public class TestService3 {     public void test3()  {         addLog("test3");     }     private void addLog(String info) {         if (log.isInfoEnabled()) {             log.info("info:{}", info);         }     } }

在TestService1、TestService2、TestService3类中,都有一个addLog方法用于添加日志。

本来该功能用得好好的,直到有一天,线上出现了一个事故:服务器磁盘满了。

原因是打印的日志太多,记了很多没必要的日志,比如:查询接口的所有返回值,大对象的具体打印等。

没办法,只能将addLog方法改成只记录debug日志。

于是乎,你需要全文搜索,addLog方法去修改,改成如下代码:

private void addLog(String info) {     if (log.isDebugEnabled()) {         log.debug("debug:{}", info);     } }

这里是有三个类中需要修改这段代码,但如果实际工作中有三十个、三百个类需要修改,会让你非常痛苦。改错了,或者改漏了,都会埋下隐患,把自己坑了。

为何不把这种功能的代码提取出来,放到某个工具类中呢?

@Slf4j public class LogUtil {     private LogUtil() {         throw new RuntimeException("初始化失败");     }     public static void addLog(String info) {         if (log.isDebugEnabled()) {             log.debug("debug:{}", info);         }     } }

然后,在其他的地方,只需要调用。

@Service @Slf4j public class TestService1 {     public void test1()  {         LogUtil.addLog("test1");     } }

如果哪天addLog的逻辑又要改了,只需要修改LogUtil类的addLog方法即可。你可以自信满满的修改,不需要再小心翼翼了。

我们写的代码,绝大多数是可维护性的代码,而非一次性的。所以,建议在写代码的过程中,如果出现重复的代码,尽量提取成公共方法。千万别因为项目初期一时的爽快,而给项目埋下隐患,后面的维护成本可能会非常高。

4.从不写注释

有时候,在项目时间比较紧张时,很多人为了快速开发完功能,在写代码时,经常不喜欢写注释。

此外,还有些技术书中说过:好的代码,不用写注释,因为代码即注释。这也给那些不喜欢写代码注释的人,找了一个合理的理由。

但我个人觉得,在国内每个程序员的英文水平都不一样,思维方式和编码习惯也有很大区别。你要把前人某些复杂的代码逻辑真正搞懂,可能需要花费大量的时间。

我们看到spring的核心方法refresh,也是加了很多注释的:

public void refresh() throws BeansException, IllegalStateException {   synchronized (this.startupShutdownMonitor) {    // Prepare this context for refreshing.    prepareRefresh();    // Tell the subclass to refresh the internal bean factory.    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();    // Prepare the bean factory for use in this context.    prepareBeanFactory(beanFactory);    try {     // Allows post-processing of the bean factory in context subclasses.     postProcessBeanFactory(beanFactory);     // Invoke factory processors registered as beans in the context.     invokeBeanFactoryPostProcessors(beanFactory);     // Register bean processors that intercept bean creation.     registerBeanPostProcessors(beanFactory);     // Initialize message source for this context.     initMessageSource();     // Initialize event multicaster for this context.     initApplicationEventMulticaster();     // Initialize other special beans in specific context subclasses.     onRefresh();     // Check for listener beans and register them.     registerListeners();     // Instantiate all remaining (non-lazy-init) singletons.     finishBeanFactoryInitialization(beanFactory);     // Last step: publish corresponding event.     finishRefresh();    }    catch (BeansException ex) {     if (logger.isWarnEnabled()) {      logger.warn("Exception encountered during context initialization - " +        "cancelling refresh attempt: " + ex);     }     // Destroy already created singletons to avoid dangling resources.     destroyBeans();     // Reset 'active' flag.     cancelRefresh(ex);     // Propagate exception to caller.     throw ex;    }    finally {     // Reset common introspection caches in Spring's core, since we     // might not ever need metadata for singleton beans anymore...     resetCommonCaches();    }   }  }

如果你写的代码完全不写注释,可能最近一个月、三个月、半年还记得其中的逻辑。但一年、两年,甚至更久的时间之后,你确定还能想起当初的逻辑,而不需要花费大量的时间去重新看自己的代码梳理逻辑?

说实话,不写注释,到了项目后期,不光是把自己坑了,还会坑队友。

为什么把这一条单独拿出来?

因为我遇到过,接过锅,被坑惨了。

5.方法过长

我们平时在写代码时,有时候思路来了,一气呵成,很快就把功能开发完了。但也可能会带来一个小问题,就是方法过长。

伪代码如下:

public void run() {     List userList = userMapper.getAll();     //经过一系列的数据过滤     //此处省略了50行代码     List updateList = //最终获取到user集合         if(CollectionUtils.isEmpty(updateList)) {       return;     }     for(User user: updateList) {        //经过一些复杂的过期时间计算        //此处省略30行代码     }          //分页更新用户的过期时间     //此处省略20行代码          //发mq消息通知用户     //此处省略30行代码 }

上面的run方法中包含了多种业务逻辑,虽说确实能够实现完整的业务功能,但却不能称之为好。

为什么呢?

答:该方法总长度超过150行,里面的代码逻辑很杂乱,包含了很多关联性不大的代码块。该方法的职责太不单一了,非常不利于代码复用和后期的维护。

那么,如何优化呢?

答:做方法拆分,即把一个大方法拆分成多个小方法。

例如:

public void run() {     List userList = userMapper.getAll();     List updateList = filterUser(userList);          if(CollectionUtils.isEmpty(updateList)) {       return;     }         for(User user: updateList) {         clacExpireDay(user);     }         updateUser(updateList);    sendMq(updateList);  } private List filterUser(List userList) {     //经过一系列的数据过滤     //此处省略了50行代码     List updateList = //最终获取到user集合     return updateList; } private void clacExpireDay(User user) {     //经过一些复杂的过期时间计算     //此处省略30行代码 } private void updateUser(List updateList) {     //分页更新用户的过期时间     //此处省略20行代码 } private void sendMq(List updateList) {     //发mq消息通知用户     //此处省略30行代码 }

这样简单的优化之后,run方法的代码逻辑一下子变得清晰了许多,光看它调用的子方法的名字,都能猜到这些字方法是干什么的。

每个子方法只专注于自己的事情,别的事情交给其他方法处理,职责更单一了。

此外,如果此时业务上有一个新功能,也需要给用户发消息,那么上面定义的sendMq方法就能被直接调用了。岂不是爽歪歪?

换句话说,把大方法按功能模块拆分成N个小方法,更有利于代码的复用。

顺便说一句,Hotspot对字节码超过8000字节的大方法有JIT编译限制,超过了限制不会被编译。

6.参数过多

我们平常在定义某个方法时,可能并没注意参数个数的问题(其实是我猜的)。我的建议是方法的参数不要超过5个。

先一起看看下面的例子:

public void fun(String a,               String b,               String c,               String d,               String e,               String f) {    ... } public void client() {    fun("a","b","c","d",null,"f"); }

上面的fun方法中定义了6个参数,这样在调用该方面的所有地方都需要思考一下,这些参数该怎么传值,哪些参数可以为空,哪些参数不能为空。

方法的入参太多,也会导致该方法的职责不单一,方法存在风险的概率更大。

那么,如何优化参数过多问题呢?

答:可以将一部分参数迁移到新方法中。

这个例子中,可以把参数d,e,f迁移到otherFun方法。例如:

public Result fun(String a,               String b,               String c) {    ...    return result; } public void otherFun(Result result,               String d,               String e,               String f) {          ...      } public void client() {    Result result = fun("a","b","c");    otherFun(result, "d", null, "f"); }

这样优化之后,每个方法的逻辑更单一一些,更有利于方法的复用。

如果fun中还需要返回参数a、b、c,给下个方法继续使用,那么代码可以改为:

public Result fun(String a,               String b,               String c) {    ...    Result result = new Result();    result.setA(a);    result.setB(b);    result.setC(c);    return result; }

在给Result对象赋值时,这里有个小技巧,可以使用lombok的@Builder注解,做成链式调用。例如:

@NoArgsConstructor @AllArgsConstructor @Builder @Data public class Result {     private String a;     private String b;     private String c; }

这样在调用的地方,可以这样赋值:

Result result = Result.builder() .a("a").b("b").c("c") .build();

非常直观明了。

此时,有人可能会说,ThreadPoolExecutor不也提供了7个参数的方法?

public ThreadPoolExecutor(int corePoolSize,                           int maximumPoolSize,                           long keepAliveTime,                           TimeUnit unit,                           BlockingQueue workQueue,                           ThreadFactory threadFactory,                           RejectedExecutionHandler handler) {      ...                      }

没错,不过它是构造方法,我们这里主要讨论的是普通方法。

7.代码层级太深

不知道你有没有见过类似这样的代码:

if (a == 1) {    if(b == 2) {       if(c == 3) {          if(d == 4) {             if(e == 5) {               ...             }             ...          }          ...       }       ...    }    ... }

这段代码中有很多层if判断,是不是看得人有点眼花缭乱?

有同感的同学,请举个手。

如果你没啥感觉,那么接着往下看:

for(int i=0; i
收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜