iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Kotlin代码检查的示例分析
  • 212
分享到

Kotlin代码检查的示例分析

2023-06-04 10:06:25 212人浏览 八月长安
摘要

Kotlin代码检查的示例分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。背景Kotlin有着诸多的特性,比如空指针安全、方法扩展、支持函数式编程、丰富的语法

Kotlin代码检查的示例分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。


背景

Kotlin有着诸多的特性,比如空指针安全、方法扩展、支持函数式编程、丰富的语法糖等。这些特性使得Kotlin的代码比Java简洁优雅许多,提高了代码的可读性和可维护性,节省了开发时间,提高了开发效率。这也是我们团队转向Kotlin的原因,但是在实际的使用过程中,我们发现看似写法简单的Kotlin代码,可能隐藏着不容忽视的额外开销。下面剖析了Kotlin的隐藏开销,并就如何避免开销进行了探索和实践。

Kotlin的隐藏开销

伴生对象

伴生对象通过在类中使用compaNIOn object来创建,用来替代静态成员,类似于Java中的静态内部类。所以在伴生对象中声明常量是很常见的做法,但如果写法不对,可能就会产生额外开销。比如下面这段声明Version常量的代码:

  • 调用伴生对象的静态方法

  • 调用伴生对象的实例方法

  • 调用主类的静态方法

  • 读取主类中的静态字段

为了访问一个常量,而多花费调用4个方法的开销,这样的Kotlin代码无疑是低效的。

我们可以通过以下解决方法来减少生成的字节码:

  1. 对于基本类型和字符串,可以使用const关键字将常量声明为编译时常量。

  2. 对于公共字段,可以使用@JVMField注解。

  3. 对于其他类型的常量,最好在它们自己的主类对象而不是伴生对象中来存储公共的全局常量。

lazy()委托属性

lazy()委托属性可以用于只读属性的惰性加载,但是在使用lazy()时经常被忽视的地方就是有一个可选的model参数:

  • LazyThreadSafetyMode.SYNCHRONIZED:初始化属性时会有双重检查,保证该值只在一个线程中计算,并且所有线程会得到相同的值。

  • LazyThreadSafetyMode.PUBLICATION:多个线程会同时执行,初始化属性的函数会被多次调用,但是只有第一个返回的值被当做委托属性的值。

  • LazyThreadSafetyMode.NONE:没有双重锁检查,不应该用在多线程下。

lazy()默认情况下会指定LazyThreadSafetyMode.SYNCHRONIZED,这可能会造成不必要线程安全的开销,应该根据实际情况,指定合适的model来避免不需要的同步锁。

基本类型数组

在Kotlin中有3种数组类型:

  • IntArray,FloatArray,其他:基本类型数组,被编译成int[],float[],其他

  • Array:非空对象数组

  • Array:可空对象数组

使用这三种类型来声明数组,可以发现它们之间的区别:

Kotlin代码检查的示例分析

Kotlin声明的数组

等同的Java代码:

Kotlin代码检查的示例分析等同Java声明的数组

后面两种方法都对基本类型做了装箱处理,产生了额外的开销。  
所以当需要声明非空的基本类型数组时,应该使用xxxArray,避免自动装箱。

for循环

Kotlin提供了downTo、step、until、       reversed等函数来帮助开发者更简单的使用for循环,如果单一的使用这些函数确实是方便简洁又高效,但要是将其中两个结合呢?比如下面这样:

Kotlin代码检查的示例分析

上面的for循环中结合使用了downTo和step,那么等同的Java代码又是怎么实现的呢?

Kotlin代码检查的示例分析重点看这行代码:

IntProgression var10000 = RangesKt.step(RangesKt.downTo(10, 1), 2);

这行代码就创建了两个IntProgression临时对象,增加了额外的开销。

Kotlin检查工具的探索

Kotlin的隐藏开销不止上面列举的几个,为了避免开销,我们需要实现这样一个工具,实现Kotlin语法的检查,列出不规范的代码并给出修改意见。同时为了保证开发同学的代码都是经过工具检查的,整个检查流程应该自动化

再进一步考虑,Kotlin代码的检查规则应该具有扩展性,方便其他使用方定制自己的检查规则。

基于此,整个工具主要包含下面三个方面的内容:

  1. 解析Kotlin代码

  2. 编写可扩展的自定义代码检查规则

  3. 检查自动化

结合对工具的需求,在经过思考和查阅资料之后,确定了三种可供选择的方案:

ktlint

ktlint是一款用来检查Kotlin代码风格的工具,和我们的工具定位不同,需要经过大量的改造工作才行。

detekt

detekt是一款用来静态分析Kotlin代码的工具,符合我们的需求,但是不太适合Android工程,比如无法指定variant(变种)检查。另外,在整个检查流程中,一份kt文件只能检查一次,检查结果(当时)只支持控制台输出,不便于阅读。

改造Lint

改造Lint来增加Lint对Kotlin代码检查的支持,一方面Lint提供的功能完全可以满足我们的需求,同时还能支持资源文件和class文件的检查,另一方面改造后的Lint和Lint很相似,学习上手的成本低。

相对于前两种方案,方案3的成本收益比最高,所以我们决定改造Lint成Kotlin Lint(KLint)插件

先来大致了解下Lint的工作流程,如下图:

Kotlin代码检查的示例分析Lint流程图

很显然,上图中的红框部分需要被改造以适配Kotlin,主要工作有以下3点:

  • 创建KotlinParser对象,用来解析Kotlin代码

  • 从aar中获取自定义KLint规则的jar

  • Detector类需要定义一套新的接口方法来适配遍历Kotlin节点回调时的调用

Kotlin代码解析

和Java一样,Kotlin也有自己的抽象语法树。可惜的是目前还没有解析Kotlin语法树的单独库,只能通过Kotlin编译器这个库中的相关类来解析。KLint用的是kotlin-compiler-embeddable:1.1.2-5库。

public KtFile parseKotlinToPsi(@NonNull File file) {        try {        org.jetbrains.kotlin.com.intellij.openapi.project.Project ktProject = KotlinCoreEnvironment.Companion.createForProduction(() -> {        }, new CompilerConfiguration(), CollectionsKt.emptyList()).getProject();        this.psiFileFactory = PsiFileFactory.getInstance(ktProject);        return (KtFile) psiFileFactory.createFileFromText(file.getName(), KotlinLanguage.INSTANCE, readFileToString(file, "UTF-8"));        } catch (IOException e) {            e.printStackTrace();        }        return null;    }     //可忽视,只是将文件转成字符流      public static String readFileToString(File file, String encoding) throws IOException {        FileInputStream stream = new FileInputStream(file);        String result = null;        try {            result = readInputStreamToString(stream, encoding);        } finally {            try {                stream.close();            } catch (IOException e) {                // ignore             }        }        return result;    }

以上这段代码可以封装成KotlinParser类,主要作用是将.Kt文件转化成KtFile对象。

在检查Kotlin文件时调用KtFile.acceptChildren(KtVisitorVoid)后,KtVisitorVoid便会多次回调遍历到的各个节点(node)的方法:

KtVisitorVoid visitorVoid = new KtVisitorVoid(){    @Override     public void visitClass(@NotNull KtClass klass) {               super.visitClass(klass);    }    @Override     public void visitPrimaryConstructor(@NotNull KtPrimaryConstructor constructor) {           super.visitPrimaryConstructor(constructor);    }    @Override     public void visitProperty(@NotNull KtProperty property) {           super.visitProperty(property);    }    ...};ktPsiFile.acceptChildren(visitorVoid);

自定义KLint规则的实现

自定义KLint规则的实现参考了Android自定义Lint实践这篇文章。

Kotlin代码检查的示例分析

上图展示了aar中允许包含的文件,aar中可以包含lint.jar,这也是Android自定义Lint实践这篇文章采用的实现方式。但是klint.jar不能直接放入aar中,当然更不应该将klint.jar重命名成lint.jar来实现目的。

最后采用的方案是:

  1. 通过创建klintrules这个空的aar,将klint.jar放入assets中;

  2. 修改KLint代码实现从assets中读取klint.jar;

  3. 项目依赖klintrulesaar时使用debuGCompile来避免把klint.jar带到release包。

Detector类中接口方法的定义

既然是对Kotlin代码的检查,自然Detector类要定义一套新的接口方法。先来看一下Java代码检查规则提供的方法:

Kotlin代码检查的示例分析

相信写过Lint规则的同学对上面的方法应该非常熟悉。为了尽量降低KLint检查规则编写的学习成本,我们参照JavaPsiScanner接口,定义了一套非常相似的接口方法:

Kotlin代码检查的示例分析

KLint的实现

通过对上述3个主要方面的改造,完成了KLint插件。

Kotlin代码检查的示例分析

由于KLint和Lint的相似,KLint插件简单易上手:

  1. 和Lint相似的编写规范(参考最后一节的代码);

  2. 支持@SuppressWarnings("")等Lint支持的注解;

  3. 具有和Lint的Options相同功能的klintOptions,如下:

mtKlint {    klintOptions {        abortOnError false         htmlReport true         htmlOutput new File(project.getBuildDir(), "mtKLint.html")    }}

检查自动化

  • 关于自动检查有两个方案:

    1. 在开发同学commit/push代码时,触发pre-commit/push-hook进行检查,检查不通过不允许commit/push;

    2. 在创建pull request时,触发CI构建进行检查,检查不通过不允许merge。

这里更偏向于方案2,因为pre-commit/push-hook可以通过--no-verify命令绕过,我们希望所有的Kotlin代码都是通过检查的。

KLint插件本身支持通过./gradlew mtKLint命令运行,但是考虑到几乎所有的项目在CI构建上都会执行Lint检查,把KLint和Lint绑定在一起可以省去CI构建脚本接入KLint插件的成本。

通过以下代码,将lint task依赖klint task,实现在执行Lint之前先执行KLint检查:

//创建KLint task,并设置被Lint task依赖 KLint klintTask = project.getTasks().create(String.fORMat(TASK_NAME, ""), KLint.class, new KLint.GlobalConfigAction(globalScope, null, KLintOptions.create(project))) Set<Task> lintTasks = project.tasks.findAll {    it.name.toLowerCase().equals("lint")}lintTasks.each { lint ->    klintTask.dependsOn lint.taskDependencies.getDependencies(lint)    lint.dependsOn klintTask} //创建Klint变种task,并设置被Lint变种task依赖 for (Variant variant : androidProject.variants) {     klintTask = project.getTasks().create(String.format(TASK_NAME, variant.name.capitalize()), KLint.class, new KLint.GlobalConfigAction(globalScope, variant, KLintOptions.create(project)))     lintTasks = project.tasks.findAll {         it.name.startsWith("lint") && it.name.toLowerCase().endsWith(variant.name.toLowerCase())     }     lintTasks.each { lint ->         klintTask.dependsOn lint.taskDependencies.getDependencies(lint)              lint.dependsOn klintTask     }}

检查实时化

虽然实现了检查的自动化,但是可以发现执行自动检查的时机相对滞后,往往是开发同学准备合代码的时候,这时再去修改代码成本高并且存在风险。CI上的自动检查应该是作为是否有“漏网之鱼”的最后一道关卡,而问题应该暴露在代码编写的过程中。基于此,我们开发了Kotlin代码实时检查的IDE插件。

Kotlin代码检查的示例分析

KLint IDE插件

通过这款工具,实现在Android Studio的窗口实时报错,帮助开发同学第一时间发现问题及时解决。

Kotlin代码检查实践

KLint插件分为Gradle插件和IDE插件两部分,前者在build.gradle中引入,后者通过Android  Studio安装使用。

KLint规则的编写

针对上面列举的lazy()中未指定mode的case,KLint实现了对应的检查规则:

public class LazyDetector extends Detector implements Detector.KtPsiScanner {    public static final Issue ISSUE = Issue.create(            "Lazy Warning",             "Missing specify `lazy` mode ",            "see detail: https://wiki.sankuai.com/pages/viewpage.action?pageId=1322215247",            CateGory.CORRECTNESS,            6,            Severity.ERROR,            new Implementation(                    LazyDetector.class,                    EnumSet.of(Scope.KOTLIN_FILE)));    @Override     public List<Class<? extends PsiElement>> getApplicableKtPsiTypes() {        return Arrays.asList(KtPropertyDelegate.class);    }    @Override     public KtVisitorVoid createKtPsiVisitor(KotlinContext context) {        return new KtVisitorVoid() {            @Override             public void visitPropertyDelegate(@NotNull KtPropertyDelegate delegate) {                boolean isLazy = false;                boolean isSpeifyMode = false;                KtExpression expression = delegate.getExpression();                if (expression != null) {                    PsiElement[] psiElements = expression.getChildren();                    for (PsiElement psiElement : psiElements) {                        if (psiElement instanceof KtNameReferenceExpression) {                            if ("lazy".equals(((KtNameReferenceExpression) psiElement).getReferencedName())) {                                isLazy = true;                            }                        } else if (psiElement instanceof KtValueArgumentList) {                            List<KtValueArgument> valueArguments = ((KtValueArgumentList) psiElement).getArguments();                            for (KtValueArgument valueArgument : valueArguments) {                                KtExpression argumentValue = valueArgument.getArgumentExpression();                                if (argumentValue != null) {                                    if (argumentValue.getText().contains("SYNCHRONIZED") ||                                            argumentValue.getText().contains("PUBLICATION") ||                                            argumentValue.getText().contains("NONE")) {                                        isSpeifyMode = true;                                    }                                }                            }                        }                    }                    if (isLazy && !isSpeifyMode) {                        context.report(ISSUE, expression,context.getLocation(expression.getContext()), "Specify the appropriate thread safety mode to avoid locking when it’s not needed.");                    }                }            }        };    }}

检查结果

Gradle插件和IDE插件共用一套规则,所以上面的规则编写一次,就可以同时在两个插件中使用:

  • CI上自动检查对应的检测结果的html页面:  
    Kotlin代码检查的示例分析

检测结果的html页面

  • Android Studio上对应的实时报错信息:

    Kotlin代码检查的示例分析

实时报错信息

借助KLint插件,编写检查规则来约束不规范的Kotlin代码,一方面避免了隐藏开销,提高了Kotlin代码的性能,另一方面也帮助开发同学更好的理解Kotlin。

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注编程网精选频道,感谢您对编程网的支持。

--结束END--

本文标题: Kotlin代码检查的示例分析

本文链接: https://www.lsjlt.com/news/237299.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Kotlin代码检查的示例分析
    Kotlin代码检查的示例分析,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。背景Kotlin有着诸多的特性,比如空指针安全、方法扩展、支持函数式编程、丰富的语法...
    99+
    2023-06-04
  • shell语法检查模式的示例分析
    这篇文章主要介绍shell语法检查模式的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!启用 verbose 调试模式在进入本指导的重点之前,让我们简要地探索下 verbose 模式。它可以用 -v ...
    99+
    2023-06-12
  • Python代码示例分析
    这篇文章主要介绍了Python代码示例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Python代码示例分析文章都会有所收获,下面我们一起来看看吧。题目加粗,注意事项红色(...
    99+
    2024-04-02
  • Oracle中SCN与检查点的示例分析
    这篇文章主要介绍Oracle中SCN与检查点的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1.SCN 的定义SCN(System Change Number ),也就是通常...
    99+
    2024-04-02
  • webpack代码拆分的示例分析
    这篇文章主要介绍webpack代码拆分的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!如果利用 webpack 将项目中的所有代码打包在一起,很多时候是不适用的,因为代码中有...
    99+
    2024-04-02
  • Css中代码的示例分析
    小编给大家分享一下Css中代码的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!text-rendering: optim...
    99+
    2024-04-02
  • AngularJS脏检查机制及$timeout的示例分析
    这篇文章主要介绍AngularJS脏检查机制及$timeout的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!||浏览器事件循环和Angular的MVW“脏检查”是Angul...
    99+
    2024-04-02
  • CSS代码重构的示例分析
    小编给大家分享一下CSS代码重构的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. 重构和架构重构是指在不改变代码行为...
    99+
    2024-04-02
  • Java数组代码的示例分析
    本篇文章给大家分享的是有关Java数组代码的示例分析,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。数组分类 一维数组1 一维数组的定义和初始化2 对一维数组的操作, 遍历,添加...
    99+
    2023-06-02
  • Ruby基础代码的示例分析
    Ruby基础代码的示例分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Ruby语言的学习和其他编程语言一样,首先要从基础开始。在这里我们为大家介绍了一下Ruby基础代码中...
    99+
    2023-06-17
  • JavaScript手写代码的示例分析
    小编给大家分享一下JavaScript手写代码的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. 实现一个new操作符...
    99+
    2024-04-02
  • JavaScript代码覆盖的示例分析
    这篇文章给大家分享的是有关JavaScript代码覆盖的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。它为什么是有用的 作为一名JavaScript开发者,你可能经常发现...
    99+
    2024-04-02
  • CSS代码风格的示例分析
    这篇文章主要介绍了CSS代码风格的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。 CSS 代码风格 基本设置 2 空格缩进 ...
    99+
    2024-04-02
  • ajax通用代码的示例分析
    这篇文章主要介绍ajax通用代码的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!具体如下:<html> <head> <script ...
    99+
    2024-04-02
  • JavaScript代码简化的示例分析
    这篇文章主要为大家展示了“JavaScript代码简化的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“JavaScript代码简化的示例分析”这篇文章吧...
    99+
    2024-04-02
  • PHP代码优化的示例分析
    这篇文章主要介绍了PHP代码优化的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一小段“劣质”的PHP代码优化的过程,请仔细体会优化...
    99+
    2024-04-02
  • Linux下c++程序内存泄漏检测代码的示例分析
    这期内容当中小编将会给大家带来有关如何解决Linux下c++程序内存泄漏检测代码,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Linux下对于程序内存泄漏检测的方法很多,最常用的的莫过于使用valgrin...
    99+
    2023-06-17
  • 测试VS2010代码的示例分析
    这篇文章主要介绍测试VS2010代码的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!VS 2008中的代码智能提示为帮助示范这个VS 2010代码中的智能提示的改进,让我们先来在VS 2008中做一个简单的例...
    99+
    2023-06-17
  • Visual Studio 2010F#代码的示例分析
    今天就跟大家聊聊有关Visual Studio 2010F#代码的示例分析,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Visual Studio 2010F#作为一个高效的.NET...
    99+
    2023-06-17
  • jQuery实用的示例代码分析
    本篇内容介绍了“jQuery实用的示例代码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!★ 使用 jQuery 来切换样式表$...
    99+
    2023-06-27
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作