一旦您开始使用多种语言,您可能会开始质疑现状。您可能会以不同的方式看待事物,并为您钟爱的编程语言带回一些良好的习惯。我认为“代码简单”是软件开发的一个被低估的特性,我们需要更加关注代码的可读性和简单性。
Java 是并且一直是我在整个职业生涯中使用的主要编程语言。如果我想为原型快速构建一些东西,或者如果我需要在紧迫的期限内选择技术堆栈,那么 Java 是我的第一选择。特别是在 Java 8 之后,它的功能变得越来越丰富和强大。
当来自非 Java 社区的人抱怨 Java 冗长而复杂时,我实际上不明白这是什么意思?
近年来,我有机会在我的官方项目中使用Go和DotNet Core。除此之外,我还使用了NodeJS和Python,供我个人使用。
我理解为什么很多人喜欢NodeJS和Python,因为它们是一种包含电池的东西。对于 Java 中的相同任务,您可能需要包含外部库,而在 Python/NodeJS 中,标准库本身以非详细的方式提供大多数常用功能。如果您熟悉Kotlin,那么在使用C时您会感到宾至如归。
我对 Go 有过这样的经历。我第一次使用它时不太喜欢它,但关于 Go 的简单让我印象深刻!
我们来看下面的Java/SpringBoot代码。
@Service @Transactional @RequiredArgsConstructor public class CustomerService { private final CustomerRepository customerRepository; private final AddressRepository addressRepository; |
public void createCustomer(Customer customer) {
customerRepository.save(customer);
addressRepository.saveCustomerAddress(customer.getAddress());
}
}
对于至少使用过一次 SpringBoot 的任何 Java 开发人员,这段代码应该不需要任何解释。但是这里发生了很多事情。
- 在为CustomerService被标记为一个Spring bean可以注入到其它Spring Bean
- 由于 Spring 4.x 不需要在构造函数上添加@Autowired注释。如果只有一个构造函数,它将自动用于创建 bean。
- 该类使用@Transactional进行注释,这意味着当有人调用createCustomer()方法时,将启动并在成功完成后提交数据库事务。
- 如果customerRepository.save()或addressRepository.saveCustomerAddress()方法抛出任何RuntimeException,则事务将自动回滚。
- 如果CustomerService.createCustomer()是从不同类的另一个事务方法调用的,那么它将在父事务中运行而不是启动新事务。
虽然这看起来很熟悉且易于阅读,但在幕后发生了很多事情。
让我们看看下面的 Go 代码,它的作用与上面相同:
type CustomerService struct { customerRepository CustomerRepository addressRepository AddressRepository } |
func (c CustomerService) createCustomer(customer Customer) error {
// Get a Tx for making transaction requests.
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
// Defer a rollback in case anything fails.
defer tx.Rollback()
err := customerRepository.save(tx, customer)
if err != nil {
return err
}
err = addressRepository.saveCustomerAddress(tx, customer.Address)
if err != nil {
return err
}
// Commit the transaction.
if err = tx.Commit(); err != nil {
return err
}
return nil
}
嗯,这是我们上面实现的相同功能的典型 Go 实现。假设它做了它打算做的事情,乍一看,这就是我的感觉:
- 每次第三条语句的错误检查都非常冗长
- DB 事务处理无处不在,我们可能需要对所有 DB 交互方法重复相同的操作。
- 业务逻辑和底层技术细节(错误/事务处理)纠缠不清
这里没有魔法:没有隐藏的魔法。一个有 20 多年经验的开发人员可以阅读和理解这段代码,就像一个有 6 个月经验的毕业生可以阅读和理解它一样。
但是,没有魔法意味着:……只有当你不明白它是如何运作的时候,它才是魔法。
这一点将我引向我想讨论的下一点。但首先让我们不要急于得出哪种语言更好的结论,因为这不是本文的意图。
开发人员在软件开发过程中面临的最大挑战是什么?
我们都知道,与编写新代码相比,我们花在阅读现有代码上的时间更多。在典型的长期企业应用程序开发中,现有开发人员离开,新开发人员加入项目,最大的挑战是理解现有代码。如果代码很简单,那么它很容易理解。
但是量化“代码简单性”并不容易。对你来说看起来更简单的东西,对我来说可能看起来很复杂,反之亦然。所以我们(开发人员和社区)尽最大努力寻找尽可能简单地编写代码的方法。不同的社区根据上下文、时间、需求等采取不同的方法。
下面是我对 Java 和 Go 如何采用不同方法来实现代码简单性的看法。
Java实现简单代码的方式:抽象
我觉得 Java 的标准库太低级和冗长,无法执行许多常见任务,例如读取文件、进行 HTTP 调用等。为了避免样板和冗长,创建了commons-lang、Guava等库,提供更高级别的抽象屏蔽开发人员的所有低级细节。
最重要的是,许多企业应用程序需要一组通用的功能,如日志记录、配置、监控等。因此创建框架来解决这些通用需求,抽象出通用应用程序级样板代码。
随着时间的推移,抽象之上有抽象之上的抽象……
但是,这些抽象通过处理应用程序级样板代码并让您专注于业务逻辑来帮助您更快地构建业务应用。
这样做的缺点是:程序员如果很好奇,需要“了解这些抽象在幕后如何工作,并在框架/库中发挥作用”。
在我看来,这就是事情严重出错的地方
现在,每家公司都希望全栈开发人员能够编写前端、后端和一些基础设施即代码。没有时间正确学习所有这些抽象在幕后是如何工作的。为了满足交付时间表,开发人员希望尽快完成工作。在那里添加这 2 个注释,在此处添加这 4 行配置,如果它正在工作,那么它就完成了。如果遇到任何错误,请搜索 stackoverflow 中的第一个建议,如果它有效,则完成。工作已经完成,但没有任何关于它实际工作方式的线索。
随着时间的推移,这种工作模式让人害怕说“Java 很复杂”。
Go实现简单的方式:清晰/详细胜过聪明
Go 社区更喜欢通过“no-magic”来保持代码简单,这意味着没有注释、没有反射、没有 ORM 等。有很多用于 ORM、Http 路由器等的库,但 Go 社区更喜欢“如果可能的话坚持标准库”方法。
Go 社区似乎更喜欢编写更清晰和冗长的代码,而不是创建巧妙的更高抽象。
最好的部分是使用 Go 的标准库,您可以在不需要外部依赖的情况下实现大部分应用程序需求,当然还有更多的代码行。
冗长的好处是“任何对 Go 有点熟悉的人都可以轻松阅读任何惯用的 Go 代码并理解它”!
如果你现在是 Go 的崇拜者,你可能会因为一遍又一遍地阅读“冗长”而生气,并问“冗长”是什么意思?
上面代码中几乎每三行实现一次错误检查,我们检查Map是否包含键等的方式在我看来都很冗长。
基于我对 Go 的有限经验,我非常喜欢它,因为它的“无聊”性质。为了学习 Go,我在 GitHub 上阅读了许多文章和开源代码库,没有太多新东西要学习。
在看到几个 Go 代码存储库后,认为:从企业长期应用程序开发的角度来看,这是一件了不起的事情。学习的魔力更少,新开发人员更容易入职。
我们如何让 Java 变得“更简单”?
Java 是大型企业的首选是有原因的。开发速度快,上市速度快。我可以自信地说,您可以在几个小时内使用 SpringBoot/Quarkus/Micronaut 创建生产级 Java 应用程序,而不是几周或几个月。由于这些框架已经提供了包含电池的应用程序框架,您需要做的就是编写业务逻辑。
如果我能从 Go 中汲取一些想法并将它们带到 Java,特别是 SpringBoot,那么这些将是:
- 除非是简单的 CRUD 应用程序,否则不要用ORM。我更愿意花费编写更多代码的一次性成本而不是重复调试成本。我更喜欢JOOQ 而不是JPA。
- 我知道向后兼容性是 Java/SpringBoot 成功的关键原因之一。但我喜欢从 SpringBoot 中剥离一些东西
- 支持 XML 配置,我没有看到任何使用 XML 配置的绿地项目。XML 支持可以作为单独的模块提取。
- 无需从 14 个不同来源加载配置属性
- 使用和调试 AOP 的一些更简单的方法(如果您曾经尝试过调试 AOP 代码,那么您就知道我在说什么)
- Quarkus喜欢实时重新加载和连续测试
- 我们真的需要“com.mycompany.myproject.mymodule”的私有业务应用程序封装结构吗?对于开源库是的,但是对于商业应用程序??
总结
如果您是从未尝试过 Go 的 Java 开发人员,我强烈建议您看看 Go。你可能完全喜欢也可能不喜欢 Go,但你会爱上的东西却很少。
说了这么多,“Java 是并将永远是我最喜欢的语言”,从其他语言中学习东西的感觉很好。