改善代码质量的20条编程规范

命名

命名多长最合适?

  • 命名的一个原则就是以能准确达意为目标
  • 在足够表达其含义的情况下,命名当然是越短越好。但是,大部分情况下,短的命名都没有长的命名更能达意

利用上下文简化命名

  • 在 User 类这样一个上下文中,我们没有在成员变量的命名中重复添加“user”这样一个前缀单词
  • 而是直接命名为 name、password、avatarUrl
  • 在使用这些属性时候,我们能借助对象这样一个上下文,表意也足够明确

命名要可读、可搜索

  • “可读”,指的是不要用一些特别生僻、难发音的英文单词来命名
  • “可搜索”,可以通过关键词联想的方式来自动补全和搜索,比如有统一的命名前缀,比如 “queryXXX”, “insertXXX”

如何命名接口和抽象类?

  • 对于接口的命名,一般有两种比较常见的方式
  • 一种是加前缀“I”,表示一个 Interface
    • 比如 IUserService,对应的实现类命名为 UserService
  • 另一种是不加前缀,比如 UserService,对应的实现类加后缀“Impl”
    • 比如 UserServiceImpl

注释

注释到底该写什么?

  • 注释的内容主要包含这样三个方面:做什么、为什么、怎么做
  • 注释比代码承载的信息更多
    • 命名的主要目的是解释“做什么”
    • 比如,void increaseWalletAvailableBalance(BigDecimal amount) 表明这个函数用来增加钱包的可用余额
    • boolean isValidatedPassword 表明这个变量用来标识是否是合法密码
    • 但是,对于类来说,包含的信息比较多,一个简单的命名就不够全面详尽了
    • 这个时候,在注释中写明“做什么”就合情合理了
  • 注释起到总结性作用、文档的作用
    • 代码之下无秘密
    • 阅读代码可以明确地知道代码是“怎么做”的,也就是知道代码是如何实现的,那注释中是不是就不用写“怎么做”了?实际上也可以写
    • 在注释中,关于具体的代码实现思路,我们可以写一些总结性的说明、特殊情况的说明
    • 这样能够让阅读代码的人通过注释就能大概了解代码的实现思路,阅读起来就会更加容易
  • 一些总结性注释能让代码结构更清晰
    • 对于逻辑比较复杂的代码或者比较长的函数,如果不好提炼、不好拆分成小的函数调用
    • 那我们可以借助总结性的注释来让代码结构更清晰、更有条理

注释是不是越多越好?

  • 注释太多和太少都有问题
  • 太多,有可能意味着代码写得不够可读,需要写很多注释来补充
  • 类和函数一定要写注释,而且要写得尽可能全面、详细
  • 而函数内部的注释要相对少一些,一般都是靠好的命名、提炼函数、解释性变量、总结性注释来提高代码的可读性

类、函数多大才合适?

  • 类中的代码行数不超过200行、函数或属性不超过10个
  • 还是有一个间接的判断标准
    • 当一个类的代码读起来让你感觉头大了,实现某个功能时不知道该用哪个函数了
    • 想用哪个函数翻半天都找不到了,只用到一个小功能要引入整个类(类中包含很多无关此功能实现的函数)的时候
    • 这就说明类的行数过多了

一行代码多长最合适?

  • 在Google Java Style Guide文档中,一行代码最长限制为 100 个字符
  • 总体上来讲我们要遵循的一个原则是:一行代码最长不能超过 IDE 显示的宽度
  • 需要滚动鼠标才能查看一行的全部代码,显然不利于代码的阅读

善用空行分割单元块

  • 对于比较长的函数,如果逻辑上可以分为几个独立的代码块,在不方便将这些独立的代码块抽取成小函数的情况下
  • 为了让逻辑更加清晰,除了上一节课中提到的用总结性注释的方法之外,我们还可以使用空行来分割各个代码块
  • 在类的成员变量与函数之间、静态成员变量与普通成员变量之间、各函数之间、甚至各成员变量之间,我们都可以通过添加空行的方式
  • 让这些不同模块的代码之间,界限更加明确
  • 写代码就类似写文章,善于应用空行,可以让代码的整体结构看起来更加有清晰、有条理

四格缩进还是两格缩进?

  • 只要项目内部能够统一就行了
  • 当然,还有一个选择的标准,那就是跟业内推荐的风格统一、跟著名开源项目统一

大括号是否要另起一行?

  • 要团队统一、业内统一、跟开源项目看齐就好了

类中成员的排列顺序

  • 在类中,成员变量排在函数的前面
  • 成员变量之间或函数之间,都是按照先静态(静态函数或静态成员变量)、后普通(非静态函数或非静态成员变量)的方式来排列的
  • 除此之外,成员变量之间或函数之间,还会按照作用域范围从大到小的顺序来排列
  • 先写 public 成员变量或函数,然后是 protected 的,最后是 private 的

把代码分割成更小的单元块

  • 大部分人阅读代码的习惯都是,先看整体再看细节
  • 所以,我们要有模块化和抽象思维,善于将大块的复杂逻辑提炼成类或者函数,屏蔽掉细节,让阅读代码的人不至于迷失在细节中,这样能极大地提高代码的可读性
  • 不过,只有代码逻辑比较复杂的时候,我们其实才建议提炼类或者函数。
  • 毕竟如果提炼出的函数只包含两三行代码,在阅读代码的时候,还得跳过去看一下,这样反倒增加了阅读成本

避免函数参数过多

  • 函数包含 3、4 个参数的时候还是能接受的,大于等于 5 个的时候,我们就觉得参数有点过多了,会影响到代码的可读性,使用起来也不方便
  • 对参数过多的情况,一般有 2 种处理方法
    • 考虑函数是否职责单一,是否能通过拆分成多个函数的方式来减少参数
      public User getUser(String username, String telephone, String email);
      
      // 拆分成多个函数
      public User getUserByUsername(String username);
      public User getUserByTelephone(String telephone);
      public User getUserByEmail(String email);
      
    • 将函数的参数封装成对象
      public void postBlog(String title, String summary, String keywords, String content, String category, long authorId);
      
      // 将参数封装成对象
      public class Blog {
        private String title;
        private String summary;
        private String keywords;
        private Strint content;
        private String category;
        private long authorId;
      }
      public void postBlog(Blog blog);
      

勿用函数参数来控制逻辑

布尔类型作为标识参数

  • 不要在函数中使用布尔类型的标识参数来控制内部逻辑,true 的时候走这块逻辑,false 的时候走另一块逻辑
  • 这明显违背了单一职责原则和接口隔离原则
  • 我建议将其拆成两个函数,可读性上也要更好
    public void buyCourse(long userId, long courseId, boolean isVip);
    
    // 将其拆分成两个函数
    public void buyCourse(long userId, long courseId);
    public void buyCourseForVip(long userId, long courseId);
    
    
    // 拆分成两个函数的调用方式
    boolean isVip = false;
    //...省略其他逻辑...
    if (isVip) {
      buyCourseForVip(userId, courseId);
    } else {
      buyCourse(userId, courseId);
    }
    
    // 保留标识参数的调用方式更加简洁
    boolean isVip = false;
    //...省略其他逻辑...
    buyCourse(userId, courseId, isVip);
    

根据参数是否为 null


public List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) {
  if (startDate != null && endDate != null) {
    // 查询两个时间区间的transactions
  }
  if (startDate != null && endDate == null) {
    // 查询startDate之后的所有transactions
  }
  if (startDate == null && endDate != null) {
    // 查询endDate之前的所有transactions
  }
  if (startDate == null && endDate == null) {
    // 查询所有的transactions
  }
}

// 拆分成多个public函数,更加清晰、易用
public List<Transaction> selectTransactionsBetween(Long userId, Date startDate, Date endDate) {
  return selectTransactions(userId, startDate, endDate);
}

public List<Transaction> selectTransactionsStartWith(Long userId, Date startDate) {
  return selectTransactions(userId, startDate, null);
}

public List<Transaction> selectTransactionsEndWith(Long userId, Date endDate) {
  return selectTransactions(userId, null, endDate);
}

public List<Transaction> selectAllTransactions(Long userId) {
  return selectTransactions(userId, null, null);
}

private List<Transaction> selectTransactions(Long userId, Date startDate, Date endDate) {
  // ...
}
  • 针对这种情况,我们也应该将其拆分成多个函数
  • 拆分之后的函数职责更明确,不容易用错

函数设计要职责单一

public boolean checkUserIfExisting(String telephone, String username, String email)  { 
  if (!StringUtils.isBlank(telephone)) {
    User user = userRepo.selectUserByTelephone(telephone);
    return user != null;
  }
  
  if (!StringUtils.isBlank(username)) {
    User user = userRepo.selectUserByUsername(username);
    return user != null;
  }
  
  if (!StringUtils.isBlank(email)) {
    User user = userRepo.selectUserByEmail(email);
    return user != null;
  }
  
  return false;
}

// 拆分成三个函数
public boolean checkUserIfExistingByTelephone(String telephone);
public boolean checkUserIfExistingByUsername(String username);
public boolean checkUserIfExistingByEmail(String email);
  • 对于函数的设计来说,更要满足单一职责原则
  • 相对于类和模块,函数的粒度比较小,代码行数少
  • 所以在应用单一职责原则的时候,没有像应用到类或者模块那样模棱两可,能多单一就多单一

移除过深的嵌套层次

定义

  • 代码嵌套层次过深往往是因为 if-else、switch-case、for 循环过度嵌套导致的
  • 个人建议,嵌套最好不超过两层,超过两层之后就要思考一下是否可以减少嵌套
  • 过深的嵌套本身理解起来就比较费劲
  • 嵌套过深很容易因为代码多次缩进,导致嵌套内部的语句超过一行的长度而折成两行,影响代码的整洁

去掉多余的 if 或 else 语句


// 示例一
public double caculateTotalAmount(List<Order> orders) {
  if (orders == null || orders.isEmpty()) {
    return 0.0;
  } else { // 此处的else可以去掉
    double amount = 0.0;
    for (Order order : orders) {
      if (order != null) {
        amount += (order.getCount() * order.getPrice());
      }
    }
    return amount;
  }
}

// 示例二
public List<String> matchStrings(List<String> strList,String substr) {
  List<String> matchedStrings = new ArrayList<>();
  if (strList != null && substr != null) {
    for (String str : strList) {
      if (str != null) { // 跟下面的if语句可以合并在一起
        if (str.contains(substr)) {
          matchedStrings.add(str);
        }
      }
    }
  }
  return matchedStrings;
}

使用编程语言提供的 continue、break、return 关键字,提前退出嵌套


// 重构前的代码
public List<String> matchStrings(List<String> strList,String substr) {
  List<String> matchedStrings = new ArrayList<>();
  if (strList != null && substr != null){ 
    for (String str : strList) {
      if (str != null && str.contains(substr)) {
        matchedStrings.add(str);
        // 此处还有10行代码...
      }
    }
  }
  return matchedStrings;
}

// 重构后的代码:使用continue提前退出
public List<String> matchStrings(List<String> strList,String substr) {
  List<String> matchedStrings = new ArrayList<>();
  if (strList != null && substr != null){ 
    for (String str : strList) {
      if (str == null || !str.contains(substr)) {
        continue; 
      }
      matchedStrings.add(str);
      // 此处还有10行代码...
    }
  }
  return matchedStrings;
}

调整执行顺序来减少嵌套


// 重构前的代码
public List<String> matchStrings(List<String> strList,String substr) {
  List<String> matchedStrings = new ArrayList<>();
  if (strList != null && substr != null) {
    for (String str : strList) {
      if (str != null) {
        if (str.contains(substr)) {
          matchedStrings.add(str);
        }
      }
    }
  }
  return matchedStrings;
}

// 重构后的代码:先执行判空逻辑,再执行正常逻辑
public List<String> matchStrings(List<String> strList,String substr) {
  if (strList == null || substr == null) { //先判空
    return Collections.emptyList();
  }

  List<String> matchedStrings = new ArrayList<>();
  for (String str : strList) {
    if (str != null) {
      if (str.contains(substr)) {
        matchedStrings.add(str);
      }
    }
  }
  return matchedStrings;
}

将部分嵌套逻辑封装成函数调用,以此来减少嵌套


// 重构前的代码
public List<String> appendSalts(List<String> passwords) {
  if (passwords == null || passwords.isEmpty()) {
    return Collections.emptyList();
  }
  
  List<String> passwordsWithSalt = new ArrayList<>();
  for (String password : passwords) {
    if (password == null) {
      continue;
    }
    if (password.length() < 8) {
      // ...
    } else {
      // ...
    }
  }
  return passwordsWithSalt;
}

// 重构后的代码:将部分逻辑抽成函数
public List<String> appendSalts(List<String> passwords) {
  if (passwords == null || passwords.isEmpty()) {
    return Collections.emptyList();
  }

  List<String> passwordsWithSalt = new ArrayList<>();
  for (String password : passwords) {
    if (password == null) {
      continue;
    }
    passwordsWithSalt.add(appendSalt(password));
  }
  return passwordsWithSalt;
}

private String appendSalt(String password) {
  String passwordWithSalt = password;
  if (password.length() < 8) {
    // ...
  } else {
    // ...
  }
  return passwordWithSalt;
}

学会使用解释性变量

常量取代魔法数字

public double CalculateCircularArea(double radius) {
  return (3.1415) * radius * radius;
}

// 常量替代魔法数字
public static final Double PI = 3.1415;
public double CalculateCircularArea(double radius) {
  return PI * radius * radius;
}

使用解释性变量来解释复杂表达式


if (date.after(SUMMER_START) && date.before(SUMMER_END)) {
  // ...
} else {
  // ...
}

// 引入解释性变量后逻辑更加清晰
boolean isSummer = date.after(SUMMER_START)&&date.before(SUMMER_END);
if (isSummer) {
  // ...
} else {
  // ...
} 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于CRNN模型的多位数字序列识别的应用【代码+数据集+python环境+GUI系统】

基于CRNN模型的多位数字序列识别的应用【代码数据集python环境GUI系统】 基于CRNN模型的多位数字序列识别的应用【代码数据集python环境GUI系统】 背景意义 多位手写数字识别&#xff0c;即计算机从纸张文档、照片、触摸屏等来源接收并解释可理解的手写数字输入的能力。 随着…

2024软件测试面试秘籍(含答案+文档)

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Part1 1、你的测试职业发展是什么&#xff1f; 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师…

低代码可视化-uniapp海报可视化设计-代码生成

在uni-app中&#xff0c;海报生成器通常是通过集成特定的插件或组件来实现的&#xff0c;这些插件或组件提供了生成海报所需的功能和灵活性。我们采用了lime-painter海报组件。lime-painter是一款canvas海报组件&#xff0c;可以更轻松地生成海报。它支持通过JSON及Template的方…

【Linux】如何升级宝塔面板

执行命令&#xff0c;即可升级 curl https://io.bt.sy/install/update_panel.sh|bash

【Unity 实用工具篇】 | UGUI 循环列表 SuperScrollView,快速上手使用

前言 【Unity 实用工具篇】 | UGUI 循环列表 SuperScrollView&#xff0c;快速上手使用一、UGUI ScrollRect拓展插件&#xff1a;SuperScrollView1.1 介绍1.2 效果展示1.3 使用说明及下载 二、SuperScrollView 快速上手使用2.1 LoopListView22.2 LoopGridView2.3 LoopStaggered…

【Python爬虫】获取汽车之家车型配置附代码(2024.10)

参考大哥&#xff0c;感谢大哥&#xff1a;https://blog.csdn.net/weixin_43498642/article/details/136896338 【任务目标】 工作需要想更方便地下载汽车之家某车系配置清单&#xff1b;&#xff08;垃圾汽车之家不给下载导出表格&#xff0c;配置页叉掉了车系要出来还要重新…

提问: 监督学习, 无监督学习, 机器学习, 深度学习的关系? (通义千问2.5的回答)

前言: 以下内容由AI大模型通义千问大模型2.5生成 监督学习, 无监督学习, 机器学习, 深度学习的关系? 监督学习、无监督学习、机器学习和深度学习是人工智能领域的几个重要概念&#xff0c;它们之间存在一定的关系和区别。下面我将详细解释这些概念及其相互之间的关系&#xf…

Unity中使用UnityEvent遇到Bug

UnityEvent绑定过程中&#xff0c;放在Start&#xff08;&#xff09;中绑定会报错&#xff08;通过脚本添加UnityEvent事件脚本&#xff0c;绑定&#xff09; 绑定事件放在OnEnable&#xff08;&#xff09;中不会报错&#xff0c;但是依然不可以立刻添加UnityEvent事件脚本紧…

GeoWebCache1.26调用ArcGIS切片

GeoServer GeoWebCache (osgeo.org) 一、版本需要适配&#xff1a;Geoserver与GeoWebCache、jdk等的版本适配对照 ​ 查看来源 二、准备工作 1、数据&#xff1a;Arcgis标准的切片&#xff0c;通过ArcGIS Server发布的切片文件&#xff0c;注意切片的存储格式为exploded&…

rust入门基础总结

文章目录 前言1、输出格式规范一、占位符相关&#xff08;一&#xff09;{}与{:?} 二、参数替换方式&#xff08;一&#xff09;位置参数&#xff08;二&#xff09;具名参数 三、格式化参数&#xff08;一&#xff09;宽度&#xff08;二&#xff09;对齐&#xff08;三&…

电脑异常情况总结

文章目录 笔记本无症状息屏黑屏 笔记本无症状息屏黑屏 &#x1f34e; 问题描述&#xff1a; 息屏导致黑屏&#xff1b;依次操作计算机--》右键--》管理--》事件查看器--》Windows日志--》系统&#xff1b;从息屏到异常黑屏之间出现了很多错误&#xff0c;如下&#xff1a;事件…

如何区别医疗器械唯一标识(UDI)、医用耗材统一标识码(HCBS)和医保医用耗材编码

医疗器械唯一标识&#xff08;UDI&#xff09;、医用耗材统一标识码&#xff08;HCBS&#xff09;和医保医用耗材编码三种重要标识&#xff0c;在医疗领域发挥着举足轻重的作用。 医疗器械唯一标识UDI码是被比喻成医疗器械产品的“身份证”&#xff08;每个人都有&#xff09;…

「AIGC」n8n AI Agent开源的工作流自动化工具

n8n AI Agent 是一个利用大型语言模型(LLMs)来设计和构建智能体(agents)的工具,这些智能体能够执行一系列复杂的任务,如理解指令、模仿类人推理,以及从用户命令中理解隐含意图。n8n AI Agent 的核心在于构建一系列提示(prompts),使 LLM 能够模拟自主行为。 传送门→ …

鸿蒙软件开发中常见的如何快速自动生成二维码?QRCode组件

QRCode 用于显示单个二维码的组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 二维码组件的像素点数量与内容有关&#xff0c;当组件尺寸过小时&#xff0c;可能出现无法展示内容的情况&…

电脑输入账号密码后,屏幕黑屏只有鼠标解决办法

最近办公电脑出现了两次输入密码后,屏幕黑屏之后鼠标能动的问题,只能有手机查一些资料尝试自己解决,具体什么原因导致的暂时还不清楚。解决这个问题大概有两个方案吧&#xff0c;第一次黑屏用的第一个方案&#xff0c;第二次发现第一个方案不好用了就用的第二个方案。 方案一 …

【Python-AI篇】人工智能机器学习

1. 机器学习简介 1.1 机器学习流程 获取数据 SKLEARN数据集查看数据分布数据集分类 数据基本处理 缺省值处理缩小数据范围异常数据处理数据分割类别不平衡数据处理方式 特征工程机器学习(算法) K-近邻算法线性回归线性回归的改进-岭回归逻辑回归(分类)决策树朴素贝叶斯SVMEM算…

创建型模式-----(单例模式)

目录 基本概念 饿汉式&#xff1a; 懒汉式&#xff1a; 上锁双判空版本 std::call_once版本&#xff1a; C11标准后局部静态变量版本&#xff1a; 项目中单例模板的应用 基本概念 单例模式&#xff1a;在程序运行期间只有一份&#xff0c;与程序生存周期一样&#xff0c;…

记一行代码顺序引起的事故

01 前情回顾 在这里跟同学们分享一个前几天在线上遇见的 bug… bug描述&#xff1a;客户端轮询服务端接口获取数据做打字机效果展示&#xff0c;会偶现输出到一半就停止不动了&#xff0c;但是数据还没输出完&#xff08;如下图&#xff0c;到红色部分就卡住了&#xff09;。…

【Axure高保真原型】移动案例

今天和大家分享多个常用的移动案例的原型模板&#xff0c;包括轮盘滑动控制元件移动、页面按钮控制元件移动、鼠标单击控制元件移动、元件跟随鼠标移动、鼠标拖动控制元件移动、键盘方向键控制元件移动&#xff0c;具体效果可以点击下方视频观看或打开下方预览地址查看哦 【原…

虚拟装配解决方案:在虚拟现实中实现移动零件与三维模型碰撞检测

装配过程占产品开发时间和成本的很大一部分。在投入生产前对产品装配进行碰撞检测能够有效的降低因设计疏忽所导致的重复试错所导致的成本增加&#xff0c;并进一步降低设计审核整体流程所需时间。 选择、移动和操作3D模型的各个部分 TechViz多通道软件具有通用零件识别引擎&am…