您好,欢迎来到12图资源库!分享精神,快乐你我!我们只是素材的搬运工!!
  • 首 页
  • 当前位置:首页 > 开发 > WEB开发 >
    将5万行Java代码移植到Go学到的阅历
    时间:2019-04-19 21:07 来源:网络整理 作者:网络 浏览:收藏 挑错 推荐 打印

    将5万行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> T load(Class<T> 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<ReduceResult> results = session.query(User.class) 

    (责任编辑:admin)