IntelliJ IDEA Java Tips & Tricks

Java 最佳做法

Read this post in other languages:

好的代码都会遵循一定规则,了解这些规则将增大您成功的几率。 我们将在本文中分享一些 Java 最佳做法,为您提供帮助。 我们将介绍必知提示和技巧,涵盖软件开发的总体建议以及 Java 和项目特定的专业知识。 我们开始吧!

常规

首先,牢记以下有关现代编码的一般规则。 

干净好过聪明

代码的主要目的是被理解和维护,而不是炫耀技术能力。 干净的代码会让软件更易于调试、维护和扩展,使参与项目的所有人受益。 复杂并不是荣誉奖章,简单和可读才是。

考虑以下示例。

  • 聪明的: 

这是交换变量 `a` 和 `b` 值的非常规方式。 虽然聪明,但乍一看可能会令人困惑。

  • 干净的: 

这是更常见的方式。 虽然额外需要一行代码,但其直观性使大多数程序员都能更轻松地理解。 

保持简短

确保方法和类不会太长。 虽然对于类的确切行数或单词数没有严格规定,但建议的做法是保持重点突出且连贯的结构。 对于方法,通常每个方法的推荐代码量为 10 到 20 行左右。 如果方法变得更长,最好将其分成更小、更易管理的部分。

如果您想练习识别方法是否过长,可以观看技术指导 Emily Bache 制作的精彩视频。 

IntelliJ IDEA 还可以提供多种重构选项来帮助您摆脱长方法和类。 例如,它允许您提取方法以将长方法分解为更短的方法。

命名很难,所以要多加留意

方法和变量的正确名称可以作为理解代码目的和功能的直观指南,对于有效沟通至关重要。 以下是您需要了解的最重要的命名约定。 

我们建议避免单字母变量,确保方法名称反映其操作,并将对象和字段名称与业务域对齐,增强清晰度和沟通。 例如,名为 calculateTotalPrice() 的方法可以立即传达其目的,而像 calculate() 这样的模糊名称则使功能不够明确。 同样,名为 customerEmailAddress 的变量清楚直观,而 cea 这样的缩写可能指代任何内容,导致困惑。

另一个例子是,指定相关单位而不是命名变量超时。 使用 timeoutInMs 或 timeoutInMilliseconds 可以避免单位混淆。

测试、测试、测试 

要确保应用按预期工作并在发生变化时继续运行,必须测试代码。 测试有助于及早发现问题,使修正更便宜、更容易。 它们还会指导您代码应该如何工作,并最大限度地减小后续更新时破坏代码的几率。 

好的测试名称很重要,因为它们显示了每个测试的作用和查找的内容。 例如,名为 AlertWhenEmailIsMissing()checks 的测试是用于在缺失电子邮件时发出警报,您无需深入了解细节。 

您可以阅读 Marit van Dijk 的这篇博文,了解有关测试的更多信息。 

特定于语言

以下提示和技巧将帮助您在使用 Java 编写时避免一些常见错误,提高您的代码质量。 

使用 switch 表达式代替过多的 If 语句

使用 switch 表达式可以将多个条件合并到一个结构中,让代码更易读、更清晰。 这种方式简化了代码,使其更易理解和维护。 

下面以不同类型的冰淇淋及其主要成分举例。

  • 过多 else-if

在本例中,代码使用了一系列 else–if 语句将冰淇淋口味与其主要成分相匹配。 随着口味数量的增加,if 语句的数量可能会变得难以管理,使代码更难阅读。

  • 切换

在这个不同版本的示例中,我们使用 switch 表达式而不是多个 if-else 条件获得了相同的结果。 在将单个变量与多个常量值进行比较时,switch 表达式更紧凑、更简洁且更容易理解。

IntelliJ IDEA 提供了一种特殊的检查,可以在几秒钟内将 if 语句转换为 switch 表达式。 

您可以在 Java 技术布道师 Mala Gupta 最近发布的博文中找到更多精彩的 switch 用法示例。 

避免空 catch 块

Java 中的空 catch 块是内部没有任何用于处理异常的代码的 catch 子句。 当这种块捕获异常时,什么也不会发生,程序会继续运行,就好像没有出现错误一样。 这可能会使问题难以发现和调试。

  • catch

在此示例中,我们捕获异常但不采取任何措施。 

IntelliJ IDEA 通过检查高亮显示此类情况并提供解决方案: 

  • 记录异常

一种异常处理方式是借助 e.printStackTrace() 进行记录,它将堆栈跟踪打印到控制台,帮助我们识别和调试潜在问题。

  • 记录异常并重新抛出

理想情况下,catch 块将识别 IOException,然后将错误消息打印到控制台并重新抛出异常以供后续处理。 

这样,您就可以全面了解问题所在。

  • 记录异常并返回替代值

解决空 catch 块问题的另一种方式是记录异常但返回有意义的值。 

选择集合而不是数组以获得更大的灵活性

虽然 Java 中的数组高效易用,但其大小固定,并且提供的运算有限,不太适合多种数据操作。 

Java 中的集合提供了更多的灵活性和实用性,例如 ArrayList 或 HashSet。 ArrayList 提供了动态大小调整和许多实用方法,并且更易用,特别是与泛型搭配。 我们来看一些代码示例: 

  • 数组

我们创建了一个 String 数组。 由于 Java 中的数组大小固定,如果我们想添加第十一个元素,我们必须创建一个新数组并复制所有元素。

  • 集合

作为先前代码的替代方案,我们可以使用名为 ArrayList 的集合类。 像 ArrayList 这样的集合可以在运行时增长和收缩,从而提供更大的灵活性。 它们还提供了强大的数据操作方法,例如 .add()、.remove()、.contains()、.size() 等。

拥抱不可变性

不可变对象是指创建后状态无法更改的对象。 它们通过移除与跟踪可变状态更改相关的复杂性来帮助编写更安全、更简洁的代码。 这可以最大限度地减少错误和意外副作用的风险,确保行为一致,并简化调试和维护应用程序的过程。 在 Java 中,我们使用 final 实现不可变性。 

  • final

在此示例中,我们将创建一个 Car 类并打印其品牌和型号。 然后,我们将更改汽车型号并再次打印。 控制台输出将显示 Car 类的状态已经更改,表明可变行为。

  • final

在下方的改进代码中,您可以看到我们做了同样的事,但是无法更改汽车型号或品牌,因为我们没有 setter 方法,并且 Car 类为 final。 控制台输出将显示 Car 类的状态保持不变,表明不可变行为。

复合先于继承

在 Java 中,通常最好使用复合(对另一个类的对象有引用或依赖项)而不是继承(从超类创建子类)。 复合使代码更加灵活并且更易测试。

  • 继承

在我们的示例中,GamingComputer 继承自 BasicComputer。 这可能会导致问题,因为如果 BasicComputer 发生变化,可能会破坏 GamingComputer。 此外,GamingComputer 被锁定为一种 BasicComputer,限制了其灵活性。

  • 复合 

在第二个示例中,Computer 类由 Memory 和 Processor 组成,它们是充当字段的独立类实例。 每个类都独立定义其方法和行为。 这种方式更加灵活,因为您可以交换不同的 Memory 或 Processor 类型,或者在运行时更改其行为,而无需更改 Computer 类。

如需更多示例,请参阅《简单攻略:在 Java 中创建继承》

使用 lambda 简化函数式接口

Java 中的函数式接口是一种只有一个抽象方法的接口。 lambda 提供了一种简洁、富有表现力的实现方式,无需匿名类的样板代码。 

  • 无 lambda

下面我们将使用一个匿名内部类实现 Comparator 接口进行排序。 它体积庞大,并且对于更复杂的接口可能不可读。

  • 有 lambda

这段代码的作用与上面相同,但我们使用 lambda 函数而不是匿名类。 这使代码更加紧凑直观。

使用增强的 for 循环或流

与传统 for 循环相比,Java 中的增强 for 循环(for-each 循环)和流提供了更可读和更紧凑的方式来迭代集合或数组。

  • 经典 for 循环

在这段代码中,我们使用 for 循环遍历列表。 它需要一个计数器,处理元素索引并定义停止条件 – 这都增加了复杂性。

  • 增强的 for 循环

此循环 (forEach) 消除了对计数器的需要,并直接提供了列表中的每个条目,简化了代码并减小了出错的几率。 您可以借助 IntelliJ IDEA 中的检查来应用。 

流为我们提供了每个条目,类似于增强的 for 循环,但也允许我们执行复杂的操作,例如筛选和映射。

使用 try-with-resources 语句保护资源

try-with-resources 语句可以帮助您确保每个资源在使用后正确关闭。 不关闭 try 块中的资源可能会导致内存问题和应用程序错误,影响性能和可靠性。

  • 手动关闭资源

在下面的示例中,我们手动处理系统资源 FileInputStream,如果关闭时抛出异常,可能会出现资源泄漏等问题。 

  • 使用 try-with-resources 语句

在改进版本中,在 try 块内声明 FileInputStream 意味着 Java 会将其自动关闭,无论我们是正常离开还是出现异常。

理清深度嵌套的代码

深度嵌套的代码突出了最初很难注意到的逻辑问题。 这通常是因为使用了大量条件句,但这些条件句是编码必需的,不能直接除去。 不过,我们仍然需要找到简化代码的方式。

  • 大量条件句

您可以看到冗余嵌套条件句看起来有多奇怪。 

  • 重构的代码

我们使用“保护子句”技术来消除嵌套条件句, 在满足某些条件时快速退出函数,以看起来更清晰的代码保持相同的逻辑。

项目特定 

对于处理包含多个依赖项的项目,我们还提供了一些实用注意事项。 

确保依赖项处于最新状态 

请确保项目的依赖项处于最新状态,以增强安全性、引入新功能并修正 bug。 定期更新可以确保项目顺利运行并与其他工具保持兼容。 

IntelliJ IDEA 可以帮助您使依赖项保持最新状态。 首先,通过 Preferences/Settings | Plugins(偏好设置/设置 | 插件)从 JetBrains Marketplace 安装 Package Search 插件。 然后,导航到 Dependencies(依赖项)工具窗口查看项目中的所有现有依赖项,点击它们旁边的 Upgrade(升级)链接。 

检查存在漏洞的依赖项和 API

定期扫描项目查找依赖项和 API 中的薄弱环节,有助于您最大限度地降低安全风险、遵守预定义的规则并保持一切顺利运行。 快速解决这些漏洞有助于保护项目及其用户免受潜在威胁。

要在 IntelliJ IDEA 中查找存在漏洞的依赖项,首先导航到 Code | Analyze Code(代码 | 分析代码),然后选择 Show Vulnerable Dependencies(显示存在漏洞的依赖项)。 结果将在 Problems(问题)工具窗口的 Vulnerable Dependencies(存在漏洞的依赖项)标签页中显示。

您还可以在 Project(项目)工具窗口中右键点击文件夹或文件,例如 pom.xml 或 build.gradle,然后从上下文菜单中选择 Analyze Code | Show Vulnerable Dependencies(分析代码 | 显示存在漏洞的依赖项)。 

即使没有明确检查漏洞,IntelliJ IDEA 也会高亮显示 pom.xml 或 build.gradle 中的漏洞。 

避免循环依赖关系 

当项目的各个部分在循环中相互依赖时,就会出现循环依赖关系。 例如,A 部分需要 B 部分的某些内容,但 B 部分也需要 A 部分的某些内容。这可能会使项目变得混乱和难以开展,因为很难弄清楚一个部分从哪里开始,另一个部分在哪里结束。 最好避免这些循环,保持内容清晰且易于管理。

要避免循环依赖项,您可以使用依赖关系矩阵。 这有助于直观呈现项目中组件之间的依赖关系。

结论 

希望我们的建议能够简化您的日常任务,我们期待您编写出更简单、清晰和专业的代码,成为更高效的开发者。 

IntelliJ IDEA 将帮助您找到这篇博文中讨论的条目并提供自动修正。 试用一下,然后告诉我们您的想法!

本博文英文原作者:

Sue

Irina Mariasova

image description

Discover more