优雅的Java代码养成之道

优雅的Java代码养成之道

作为一个Java程序员,我们应该遵循什么样的潜规则,让我们的代码看起来优雅又易懂(虽然优雅和易懂有的时候就是矛盾的)。最基本的是,不能让接手我们代码的人,破口大骂:什么TMD辣鸡代码。

配置一个完善的编译器

我认为Java有着这个世界上最好的编辑器:Intellij idea。Eclipse党不要拍砖头。笔者认为配置好工具可以事半功倍。以前我不认为Java和其他语言相比有什么开发优势,无论是性能还是团队合作。后来我深入了解了Java,自己使用Java独立开发项目。发现只要按照idea的提示写代码,起码能写出60分的代码,如果你使用了你并不熟悉的高级语法,比如stream等lamda表达式,idea会自动提示出使用更合适的语法。配置自动格式化,起码不会让你的代码看起来一团乱麻。个人推荐使用save action来完成自动格式化。虽然最好是自己手写出的代码就可以满足规范。自动引入包,并对包配置相应的格式。让你的import是有条理的排列组合。写完代码一定要看编辑器有没有给你提示。比如无用的临时变量、某个Boolean变量总是为true或false、需要添加注释、Optional的使用姿势不对、出现了重复代码… 一系列问题,当写完代码,编辑器是清爽的时候,说明代码完成了75%。剩下的25%是自测和QA。

正文

老生常谈的变量命名

变量命名真的是让人头疼,到现在为止,不规范的变量命名还是会出现在工作的项目中,这块个人觉得没什么好说的。完全看自己。尽量望文知意,变量命名要让人知道这个变量是干什么的,不要害怕变量很长,没关系的,没有人会因为你的变量很长而觉得头疼,但是在一个作用域内,没必要添加无意义的前缀。比如resourceId在Resource这个POJO里,完全可以用id代替。不过有前缀还是比都省略了好。笔者宁愿所有变量都冗余字段,也不希望出现莫名其妙的无意义的变量。

代码的边界控制

这个其实挺玄学的,所谓边界也就是封装,遵循单一职责的原则去封装代码,不要将内部实现泄漏出去。控制好代码的访问权限。一个简单的例子,如果在Controller层和Service层都对这个变量的不同属性有赋值的情况,这就是边界控制有问题。不要将同一件事放到多个地方去完成。这个需要经验来提高代码封装能力。代码封装能力是一个程序员的核心竞争力之一。

使用通用的工具函数

建议使用成熟的工具函数,看起来会优雅一些,对于某些场景会省了很多判断。

Java中最常出现的就是NPE。所以代码中会有无数的空值判断。

反例,并不是说这样不对,而是说下面的看起来更好一些

if (data != null) {
  System.out.println("Graceful java code");
}

正例

if (Objects.nonNull(data)) {
  System.out.println("Graceful java code");
}

对于数组和字符串经常会需要这样判断

原始的写法

if(Objects.nonNull(list) && !list.isEmpty()) {
    System.out.println("Graceful list java code");
}

if(Objects.nonNull(str) && !str.isEmpty()) {
    System.out.println("Graceful str java code");
}

更省事的写法

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

if(CollectionUtils.isNotEmpty(list)) {
      System.out.println("Graceful list java code");
}
if(StringUtils.isNotEmpty(str)) {
      System.out.println("Graceful str java code");
}

如果不想引入这些依赖,那就自己将这里面常用的代码做封装,放入自己的代码中。反正这些工具库中的代码也没多复杂,就算自己写一下也没什么的。

慎用NULL

经常代码中在异常分支中直接返回null。笔者认为有的情况不适合这样。曾经在工作中调用别人的接口,对于接口的返回值,曾有一些争执。我认为既然是返回一个数组,那么异常情况那就是返回空数组,而不是直接返回Null。当时的同事觉得这样做是可以的,理由是一个Java程序员一定要考虑Null。直到今日我还是认为不能只图自己一时爽,而忽略调用者的使用体验。没有理由让调用方多做很多事情,毕竟服务提供方只有一个,使用方可能有很多,每个人都做这样的判断,成本无形就提高了。**保证返回的数组和列表不为null, 避免调用函数的空指针判断。**但是接入第三方SDK或者中间件时,一定要阅读好文档(阅读好文档都很难绝对准确,有可能存在着文档与实际情况不符,总之对接第三方时,带着质疑去对接),做好空值判断。提高自己代码的健壮性。

使用Optional注意事项

Java的Optional充分体现了函数式编程的思想。笔者第一次了解option是在Scala中,Java的Optional与之类似,都是一个容器,里面有可能有值或者是Null,具体的使用方式网上太多例子了,在这里我只想说,如果使用Optional作为返回值,可以增强stream处理。但是建议使用Optional作为返回值,内部handle住异常。否则Optional里面是Null的时候,容易使人误解。我曾对接同事写的sdk,调用一个方法。返回值是optional,我想当然的就认为是异常内部处理了,否则为什么使用Optional,直接去掉Optional不好么。但是事实往往是超乎我的认知的,仍然抛了异常出来。没有及时handle,给我造成了一些麻烦。《Effective Java》也提到使用Optional代替抛异常和返回Null。

避免超长和重复的代码段

每一行代码字符数不超过 80。这是上个世纪 60 年代的大型机的古板缺陷,总之不要过长。如果一个函数体的代码实现超过了一定行数,那么说明这个函数,有需要封装的地方。至于如何封装,那就看具体情境。甚至每一个循环体、每一个逻辑分支都可以做封装。如果你愿意写注释,你每个写注释的地方都是可以做函数封装的地方。有人说尽量让代码方便阅读。这个行数很显然会根据分辨率以及字体的大小有关系。原则上最好一屏可以显示,阅读和维护起来都方便。行数反而是禁锢了开发人员的发挥。个人认为只要你觉得影响阅读了,该封装了。或者说Idea提示你Duplicate code fragment。那么Just Do it。

减少if else的使用

逻辑稍微复杂的时候,不可避免的出现大量if else。为了代码的可维护性,减少if else的使用,及时return。否则if else嵌套层级过深将会是灾难。尽量使用卫语句。

拒绝无用日志

一般需要抛出异常之前,一定有日志,日志一定要清晰的指出,当时发生了什么。最起码入参要打出来。不能之后出现问题,对着日志一头雾水。毕竟日志就是为了解决问题用的,如果不能给出足够的线索,最后排查问题的难度就是自己给自己添加的,怪不得别人。

反例,出现问题,根本不知道为什么抛的异常

try {
  URL urlPrefix = new URL(URL_PREFIX);
  return new URL(urlPrefix, urlPath).toString();
} catch (MalformedURLException e) {
  log.error("转换Url失败", e);
}

正例 当问题发生时,把产生问题的原因打出来了

try {
  URL urlPrefix = new URL(URL_PREFIX);
  return new URL(urlPrefix, urlPath).toString();
} catch (MalformedURLException e) {
  log.error("转换Url:{}失败", urlPath, e);
}
DO、 VO、 DTO 的使用

阿里巴巴的分层领域模型规约:

  • DO( Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
  • DTO( Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
  • BO( Business Object):业务对象。 由Service层输出的封装业务逻辑的对象。
  • AO( Application Object):应用对象。 在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
  • VO( View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
  • POJO( Plain Ordinary Java Object):POJO专指只有setter/getter/toString的简单类,包括DO/DTO/BO/VO等。
  • Query:数据查询对象,各层接收上层的查询请求。 注意超过2个参数的查询封装,禁止使用Map类来传输。

领域模型命名规约:

  • 数据对象:xxxDO,xxx即为数据表名。
  • 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
  • 展示对象:xxxVO,xxx一般为网页名称。
  • POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。

常用的一般是 DO 、VO、 DTO。不要为了省事,而省略哪一层级,否则扩展时候就是火葬场。甚至有的时候会出现 DO VO内容几乎一致,那么就使用工具做不同领域模型之间的转换。

import org.springframework.beans.BeanUtils;

BeanUtils.copyProperties(voObject, doObject);
Lombok 的使用

lombok的出现很好的解放了Java程序员,对lombok的善用,能让java程序员如虎添翼。最原始的代码就不贴了。

import lombok.Data;

@Data
public class Student {
    private String name;
    private int age;
}
Student student = new Student();
student.setName("小明");
student.setAge(18);

相信合理使用这样的链式代码,会更多的程序带来很好的可读性。对阅读者和书写者都是不错的体验。

import lombok.experimental.Accessors;

@Accessors(chain = true)
@Data
public class Student {
    private String name;
    private int age;
}

Student student = new Student()
        .setAge(24)
        .setName("zs");

当然还有更好的Builder模式,看起来优雅写起来也很方便。

import lombok.Data;
import lombok.Builder;
import lombok.experimental.Accessors;

@Builder
@Data
public class Student {
    private String name;
    private int age;
}

Student student = Student.builder()
                .name("小明")
                .age(18)
                .build();
Mybatis使用注意返回类型

如果使用Mybatis写方法返回值,如果是基本类型,请使用包装类型,因为有可能返回Null,Java自带原始类型无法表示Null值。包装类型可以。

toString方法的使用

如果需要类型转换为String类型,不建议使用toString()方法,建议使用String.valueOf(),避免NPE以及由于toString方法被覆盖导致的其他问题。因为toString()方法是Object里面定义的方法,Java中所有类都是继承Object,只是sun公司当年为了方便输出而定义的方法。

Java关键字使用

Java官方建议文档 如果两个或多个字段修饰符出现在字段声明中,则按惯例,它们的出现顺序与文档中说明的顺序一致。

对于字段,所述生产按以下顺序列出修饰符:

@Annotation public protected private static final transient volatile

对于方法:

@Annotation public protected private abstract static final synchronized native strictfp

三元表达式的使用

Java官方建议 使用三元表达式的时候,保证三元操作符中的两个操作数类型一致。避免出现不同的元中既有基础类型又有包装类型,导致空指针异常。

关于三方库的使用

如果底层依赖的库有C/C++编写的并依赖了glibc库,需要额外注意,SRE目前构建的Docker镜像的Linux发行版是Alpine,与其他Linux发行版相比更加精简。很多依赖都没有添加到镜像中。例如glibc在Alpine中被Musl libc 代替。如果项目中有依赖到glibc的地方,容易抛出NoClassDefFoundError异常。在本地开发时可能没有类似问题。在QA环境会触发。如果是依赖的三方.so文件,建议在提测之前检测一下.so文件所依赖的系统库。

objdump -x file.so | grep NEEDED .检测运行环境是否存在对应依赖。

关于JDBC的使用

allowMultiQueries 批量执行sql时,需要开启此参数为true。官方文档,此参数设置为true后,在sql语句中携带分号,可以实现多语句执行。Mysql 命令行Shell此参数默认开启。使用Jdbc,此参数默认为false。

关于动态配置的使用

如果代码中的常量可能出现变动,最好使用动态配置,而不是写死在代码中。尽量避免由于常量改变出现发版的情况。因为每次上线都有一定的流程,且会有不可预知的风险,原则上避免因微小改动导致的上线操作。

关于前后端交互的Long型字段

禁止服务端在超大整数下使用Long类型作为返回。在Long类型能表示的最大值是2的63次方-1,在取值范围之内,超过2的53次方(9007199254740992)的数值转化为JS的Number时,有些数值会有精度损失。返回的字段不可避免出现大型整数时,需要转为String类型,由于技术栈使用Jackson三方ku 可以对相应字段添加注解 @JsonSerialize(using = ToStringSerializer.class)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,养成类小游戏常见的有宠物、花园、农场等,这里以宠物为例,给出一个简单的Java代码实现。 首先,我们需要定义一个宠物类Pet,包含宠物的基本属性和方法: ``` public class Pet { private String name; // 宠物名字 private int age; // 宠物年龄 private int health; // 宠物健康值 private int love; // 宠物亲密度 // 构造方法 public Pet(String name, int age, int health, int love) { this.name = name; this.age = age; this.health = health; this.love = love; } // getter和setter方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getHealth() { return health; } public void setHealth(int health) { this.health = health; } public int getLove() { return love; } public void setLove(int love) { this.love = love; } // 吃饭方法,增加健康值 public void eat() { health += 10; } // 玩耍方法,增加亲密度 public void play() { love += 10; } // 显示宠物信息 public void showInfo() { System.out.println("宠物名字:" + name); System.out.println("宠物年龄:" + age); System.out.println("宠物健康值:" + health); System.out.println("宠物亲密度:" + love); } } ``` 接下来,我们可以编写一个PetGame类,作为游戏的主要逻辑: ``` import java.util.Scanner; public class PetGame { private Pet pet; public PetGame() { // 初始化宠物 pet = new Pet("小狗", 1, 100, 100); } // 游戏主循环 public void run() { Scanner scanner = new Scanner(System.in); while (true) { System.out.println("请选择操作:"); System.out.println("1. 喂食"); System.out.println("2. 玩耍"); System.out.println("3. 显示宠物信息"); System.out.println("4. 退出游戏"); int choice = scanner.nextInt(); switch (choice) { case 1: pet.eat(); System.out.println("宠物吃饱了!"); break; case 2: pet.play(); System.out.println("宠物玩得很开心!"); break; case 3: pet.showInfo(); break; case 4: System.out.println("游戏结束!"); return; default: System.out.println("无效操作,请重新选择!"); break; } } } public static void main(String[] args) { PetGame game = new PetGame(); game.run(); } } ``` 在PetGame类中,我们定义了一个Pet类型的成员变量pet,表示当前的宠物。在游戏主循环中,我们不断提示用户选择操作,根据用户的选择来调用相应的宠物方法,实现喂食、玩耍、显示信息等功能。 这个小游戏比较简单,可以根据自己的需求进行扩展和优化。例如,可以加入宠物升级、打扫卫生等功能,让游戏更加有趣和具有挑战性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值