热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

怎么将Java代码移植到Go中

这篇文章主要讲解了“怎么将Java代码移植到Go中”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“

这篇文章主要讲解了“怎么将Java代码移植到Go中”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么将Java代码移植到Go中”吧!

测试,代码覆盖率

自动化测试和代码覆盖率追踪,可以让大型项目获益匪浅。

我使用 TravisCI 和 AppVeyor 进行测试。Codecov.io 用来检测代码覆盖率。还有许多其他的类似服务。

我同时使用 AppVeyor 和 TravisCI,是因为 Travis 在一年前不再支持 Windows,而 AppVeyor 不支持  Linux。

如果现在让我重新选择这些工具,我将只使用 AppVeyor,因为它现在支持 Linux 和 Windows 平台的测试,而 TravisCI  在被私募股权公司收购并炒掉原始开发团队后,前景并不明朗。

Codecov 几乎无法胜任代码覆盖率检测。对于 Go,它将非代码的行(比如注释)当做是未执行的代码。使用这个工具不可能得到 100%  的代码覆盖率。Coveralls 看起来也有同样的问题。

聊胜于无,但这些工具可以让情况变得更好,尤其是对 Go 程序而言。

Go 的竞态检测非常棒

一部分代码使用了并发,而并发很容易出错。

Go 提供了竞态检测器,在编译时使用 -race 字段可以开启它。

它会让程序变慢,但额外的检查可以探测是否在同时修改同一个内存位置。

我一直开启 -race 运行测试,通过它的报警,我可以很快地修复那些竞争问题。

构建用于测试的特定工具

大型项目很难通过肉眼检查验证正确性。代码太多,你的大脑很难一次记住。

当测试失败时,仅从测试失败的信息中找到原因也是一个挑战。

数据库客户端驱动与 RavenDB 数据库服务端使用 HTTP 协议连接,传输的命令和响应的结果使用 JSON 编码。

当把 Java 测试代码移植到 Go 时,如果可以获取 Java 客户端与服务端的 HTTP 流量,并与移植到 Go 的代码生成的 HTTP  流量对比,这个信息将非常有用。

我构建了一些特定的工具,帮我完成这些工作。

为了获取 Java 客户端的 HTTP 流量,我使用 Go 构建了一个 logging HTTP 代理,Java 客户端使用这个代理与服务端交互。

对于 Go 客户端,我构建了一个可以拦截 HTTP 请求的钩子。我使用它把流量记录在文件中。

然后我就可以对比 Java 客户端与 Go 移植的客户端生成的 HTTP 流量的区别了。

移植的过程

你不能随机开始迁移 5 万行代码。我确信,如果每一个小步骤之后不进行测试和验证的话,我都会被整体代码的复杂性给打败。

对于 RavenDB 和 Java 代码库,我是新手。所以我的***步是深入理解这份 Java 代码的工作原理。

客户端的核心是与服务端通过 HTTP 协议交互。我捕获并研究了流量,编写最简单的与服务器交互的 Go 代码。

当这么做有效果之后,我自信可以复制这些功能。

我的***个里程碑是移植足够的代码,可以通过移植最简单的 Java 测试代码的测试。

我使用了自底向上和自上到下结合的方法。

自底向上的部分是指,我定位并移植那些用于向服务器发送命令和解析响应的调用链底层的代码。

自上到下的部分是指,我逐步跟踪要移植的测试代码,来确定需要移植实现的功能代码部分。

在成功完成***步移植后,剩下的工作就是一次移植一个测试,同时移植可通过这个测试的所有需要的代码。

当测试移植并测试通过后,我做了一些让代码更加 Go 风格的改进。

我相信这种一步一步渐进的方法,对于完成移植工作是很重要的。

从心理学角度来看,在面对一个长年累月的项目时,设置简短的中间态里程碑是很重要的。不断的完成这些里程碑让我干劲十足。

一直让代码保持可编译、可运行和可通过测试的状态也很好。当最终要面对那些日积月累的缺陷时,你将很难下手解决。

移植 Java 到 Go 的挑战

移植的目标是要尽可能与 Java 代码库一致,因为移植的代码需要与 Java 未来的变化保持同步。

有时我吃惊于自己以一行一行的方式移植的代码量。而移植过程中,最耗费时间的部分是颠倒变量的声明顺序,Java 的声明顺序是 type name ,而 Go  的声明顺序是 name type 。我真心希望有工具可以帮我完成这部分工作。

String vs. string

在 Java 中, String 是一个本质上是引用(指针)的对象。因此,字符串可以为 null 。

在 Go 中 string 是一个值类型。它不可能是 nil ,仅仅为空。

这并不是什么大问题,大多情况下我可以无脑地将 null 替换为 "" 。

Errors vs. exceptions

Java 使用异常来传递错误。

Go 返回 error 接口的值。

移植不难,但需要修改大量的函数签名,来支持返回错误值并在调用栈上传播。

泛型

Go (目前)并不支持泛型。

移植泛型的接口是***的挑战。

下面是 Java 中一个泛型方法的例子:

public  T load(Class clazz, String id) {

调用者:

Foo foo = load(Foo.class, "id")

在 Go 中,我使用两种策略。

其中之一是使用 interface{} ,它由值和类型组成,与 Java 中的 object  类似。不推荐使用这种方法。虽然有效,但对于这个库的用户而言,操作 interface{} 并不恰当。

在一些情况下我可以使用反射,上面的代码可以移植为:

func Load(result interface{}, id string) error

我可以使用反射来获取 result 的类型,再从 JSON 文档中创建这个类型的值。

调用方的代码:

var result *Foo err := Load(&result, "id")

函数重载

Go 不支持(很大可能永远不会支持)函数重载。

我不确定我是否找到了正确的方式来移植这种代码。

在一些情况下,重载用于创建更简短的帮助函数:

void foo(int a, String b) {} void foo(int a) { foo(a, null); }

有时我会直接丢掉更简短的帮助函数。

有时我会写两个函数:

func foo(a int) {} func fooWithB(a int, b string) {}

当潜在的参数数量很大时,有时我会这么做:

type FooArgs struct {     A int     B string } func foo(args *FooArgs) { }

继承

Go 并不是面向对象语言,没有继承。

简单情况下的继承可以使用嵌套的方法移植。

class B : A { }

有时可以移植为:

type A struct { } type B struct {     A }

我们把 A 嵌入到 B 中,因此 B 继承了 A 所有的方法和字段。

这种方法对于虚函数无效。

并没有好方法移植那些使用虚函数的代码。

模拟虚函数的一个方式是将结构体和函数指针嵌套。这本质上来说,是重新实现了 Java 免费提供的,作为 object 实现一部分的虚表。

另一种方式是写一个独立的函数,通过类型判断来调度给定类型的正确函数。

接口

Java 和 Go 都有接口,但它们是不一样的内容,就像苹果和意大利香肠的区别一样。

在很少的情况下,我确实会创建 Go 的接口类型来复制 Java 接口。

大多数情况下,我放弃使用接口,而是在 API 中暴露具体的结构体。

依赖包的循环引入

Java 允许包的循环引入。

Go 不允许。

结果就是,我无法在移植中复制 Java 代码的包结构。

为了简化,我使用一个包。这种方法不太理想,因为这个包***会变得很臃肿。实际上,这个包臃肿到在 Windows 下 Go 1.10  无法处理单个包内的那么多源文件。幸运的是,Go 1.11 修复了这个问题。

私有(private)、公开(public)、保护(protected)

Go 的设计师们被低估了。他们简化概念的能力是***的,权限控制就是其中的一个例子。

其他语言倾向于细粒度的权限控制:(每个类的字段和方法)指定最小可能粒度的公开、私有和保护。

结果就是当外部代码使用这个库时,这个库实现的一些功能和这个库中其他的类有一样的访问权限。

Go 简化了这个概念,只拥有公开和私有,访问的范围限制在包的级别。

这更合理一些。

当我想要写一个库,比如说,解析 markdown,我不想把内部实现暴漏给这个库的使用者。但对于我自己隐藏这些内部实现,效果恰恰相反。

Java  开发者注意到这个问题,有时会使用接口作为修复过度暴漏的类的技巧。通过返回一个接口,而不是具体的类,这个类的使用者就无法看到一些可用的公开接口。

并发

简单来说,Go 的并发是***的,内建的竞态检测器非常有助于解决并发的问题。

我刚才说过,我进行的***个移植是模拟 Java 接口。比如,我实现了 Java CompletableFuture 类的复制。

只有在代码可以运行后,我才会重新组织代码,让代码更加符合 Go 的风格。

流畅的函数链式调用

RavenDB 拥有复杂的查询能力。Java 客户端使用链式方法构建查询:

List results = session.query(User.class)                         .groupBy("name")                         .selectKey()                         .selectCount()                         .orderByDescending("count")                         .ofType(ReduceResult.class)                         .toList();

链式调用仅在通过异常进行错误交互的语言中有效。当一个函数额外返回一个错误,就没法向上面那样进行链式调用。

为了在 Go 中复制链式调用,我使用了一个“状态错误(stateful error)”的方法:

type Query struct {     err error }  func (q *Query) WhereEquals(field string, val interface{}) *Query {     if q.err != nil {         return q     }     // logic that might set q.err     return q }  func (q *Query) GroupBy(field string) *Query {     if q.err != nil {         return q     }     // logic that might set q.err     return q }  func (q *Query) Execute(result inteface{}) error {     if q.err != nil {         return q.err     }     // do logic }

链式调用可以这么写:

var result *Foo err := NewQuery().WhereEquals("Name", "Frank").GroupBy("Age").Execute(&result)

JSON 解析

Java 没有内建的 JSON 解析函数,客户端使用 Jackson JSON 库。

Go 在标准库中有 JSON 的支持,但它没有提供足够多的钩子函数来展现 JSON 解析的过程。

我并没有尝试匹配所有的 Java 功能,因为 Go 内置的 JSON 支持看起来已经足够灵活。

Go 代码更短

简短不是 Java 的属性,而是写出符合语言习惯代码的文化的属性。

在 Java 中,setter 和 getter 方法很常见。比如,Java 代码:

class Foo {     private int bar;      public void setBar(int bar) {         this.bar = bar;     }      public int getBar() {         return this.bar;     } }

Go 语言版本如下:

type Foo struct {     Bar int }

3 行 vs 11 行。当你有大量的类,类内有很多成员时,这么做可以不断累加这些类。

大部分其他的代码***长度基本差不多。

使用 Notion 来组织工作

我是 Notion.so 的重度用户。用最简单的话来说,Notion 是一个多级笔记记录应用。可以把它看做是 Evernote 和 wiki  的结合,是由***软件设计师精心设计和实现的。

下面是我使用 Notion 组织 Go 移植工作的方式:

怎么将Java代码移植到Go中

下面是具体的内容:

我有一个没有在上面展示的带日历视图的页面,用来记录在特定时间的工作内容和花费时间的简短笔记。因为这次合约是按小时收费,所以工作时长的统计是很重要的信息。感谢这些笔记,我知道我在  11 个月里在这次开发上花费了 601 个小时。

客户喜欢了解进展。我有一个页面,记录了每月的工作总结,如下所示:

怎么将Java代码移植到Go中

这些页面与客户共享。

  • 当开始每天的工作时,短期的 todo list 很有用。

怎么将Java代码移植到Go中

  • 我甚至用 Notion 页面管理发票,使用“导出为 PDF”功能来生成发票的 PDF 版本。

待招聘的 Go 程序员

你的公司还需要 Go 开发者吗?你可以雇用我

额外的资源

针对问题,我提供了一些额外的说明:

Hacker News discussion  /r/golang discussion

感谢各位的阅读,以上就是“怎么将Java代码移植到Go中”的内容了,经过本文的学习后,相信大家对怎么将Java代码移植到Go中这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程笔记,小编将为大家推送更多相关知识点的文章,欢迎关注!


推荐阅读
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
author-avatar
手机用户2602905767
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有