本文正在参加「金石计划 . 瓜分6万现金大奖」

前言

众所周知,Kotlin 的默许可见性为 public,而这会带来必定的问题。比如最常见的,library 中的代码被无意中声明为 public 的了,导致用户运用者能够用到咱们不想露出的 API ,这样违背了最小知识原则,也不利于咱们后续的改动

那么已然有这些问题,为什么 Kotlin 的默许可见性还被规划成这样呢?又该怎么处理这些问题?

为什么默许为 public

其实在 Kotlin M13 版别之前,Kotlin 的默许可见性是 internal 的,在 M13 版别之后才改成了 public

那么为什么会做这个批改呢?官方是这样说的

In real Java code bases (where public/private decisions are taken explicitly), public occurs a lot more often than private (2.5 to 5 times more often in the code bases that we examined, including Kotlin compiler and IntelliJ IDEA). This means that we’d make people write public all over the place to implement their designs, that would make Kotlin a lot more ceremonial, and we’d lose some of the precious ground won from Java in terms of brevity. In our experience explicit public breaks the flow of many DSLs and very often — of primary constructors. So we decided to use it by default to keep our code clean.

总得来说,官方认为在实际的生产环境中,public 产生的频率要比 private 要高的多,比如在 Kotlin 编译器和 InterlliJ 中是 2.5 倍到 5 倍的差距

这意味着假如默许的不是 public 的话,用户需求处处手动增加 public,会增加不少模板代码,并且会失去简洁性

可是官方这个答复好像有点问题,咱们要比照的是 internal 与 public,而不是 private 与 public

因而也有不少人提出了质疑

反方观念

包含 JakeWharton 在内的许多人对这一改动了提出了质疑,下面咱们一起来看下loganj的观念

internal 是安全的默许值

假如一个类或成员最初具有过错的可见性,那么提高可见性要比降低可见性简单得多。也就是说,将 internal 类或成员更改为 public 不需求做什么额外的作业,因为没有外部调用者

在履行相反的操作的本钱则很高,假如初始时是 public 的,你要将它批改为 internal 的,就要做许多的兼容作业。

因而,将 internal 设为默许值能够跟着代码库的发展而节约许多作业。

剖析运用的数据存在缺陷

官方提到 public 产生的频率是 private 的 2.5 倍到 5 倍,但这是建立在有瑕疵的数据上的

因为 Java 供给的可见性选项缺乏,开发人员被逼两害相权取其轻。更有经历的开发人员倾向于经过命名约好和文档来处理这个问题。经历缺乏的开发人员往往会直接将可见性设置为 public。

因而,大多数 Java 代码库的 public 类和成员比其作者需求或想要的要多得多。咱们不能简单地查看 Java 可见性修饰符在普通代码库中的运用并假设它反映了作者的志愿

例如,咱们常用的 Okhttp ,由经历丰富的 Java 开发人员编写的代码库,尽管 Java 存在约束,但他们仍努力将可见性降至最低。

下面是 Okhttp 的 public 包,它们旨在构成 Okhttp 的 API

Kotlin 默认可见性为 public,是不是一个好的设计?

这里是它的 internal 包,抱负状况下只能在模块中被看到。

Kotlin 默认可见性为 public,是不是一个好的设计?

简单计算能够看到大根有 46% 的公共办法和 71% 的公共类。这现已比一般的代码库好许多,这是咱们应该鼓舞的方向。

可是 internal 包内部的类根本不该该被揭露!而这是因为 Java 的可见性约束引起的(没有模块内可见)

假如 Java 有 Kotlin 的可见性修饰符,咱们应该希望挨近 24% 的公共办法和 35% 的 public 类。此外,48% 的办法和 65% 的类将是 internal 的!

internal 的潜力被浪费了

在 Java 中,别无选择,只能经过 public 来完成模块内可见,并运用约好和文档来阻挠它们的运用。Kotlin 的 internal 可见性批改了 Java 中的这个缺陷,可是选择 public 作为默许可见性忽略了这个重要的批改。

默许 public 会浪费 Kotlin 内部可见性的潜力。它一反常态地鼓舞了 Java 实际上不鼓舞的不良做法,当 Kotlin 有办法向前迈出一大步时,这样做是从 Java 倒退了一大步。

正方观念

关于一些质疑的观念,官方也做了一些回应

咱们曾经将 internal 设置为默许可见性,仅仅它没有被编译器查看,所以它被像 public 相同被运用。然后咱们尝试翻开查看,并意识到咱们需求在代码中增加许多 public。在使用(Application)代码,而不是库(library)代码中,常常包含许多 public。咱们剖析了许多 case,结果发现并不是模块鸿沟布局鸿沟不明晰造成的。模块的区分是完全合乎逻辑的,但仍然有许多类因为处处都是 public 关键字而变得非常丑恶。

在主结构函数和依据委托特点的 DSL 中这个状况特别严峻:每个特点都承受着 public 一遍又一遍地重复的视觉担负

因而,咱们意识到类的成员在默许状况下必须与类自身相同可见。请注意,假如一个类是内部的,那么它的公共成员实际上也是内部的。所以,咱们有两个选择:

默许可见性是揭露的 或者类具有与其成员不同的默许可见性。

在后一种状况下,函数的默许值会依据它是在顶层仍是在类中声明而改动。咱们决定保持一致,因而将默许可见性设置为了 public.

关于库作者,能够经过 lint 规则和 IDE 查看,以保证一切 public 的声明在代码中都是显式的。这会给库代码开发者带来必定的本钱,但比起不一致的默许可见性,或者在使用代码中增加许多 public,这好像并不是一个问题,总得来说长处大于缺陷。

怎么处理默许可见性的问题

总得来说,两边的观念各有各的道理,不过从 M13 到现在现已许多年了,Kotlin 的可见性一直默许是 public,看样子 Kotlin 官方现已下了定论

那么咱们该怎么处理库代码默许可见性为 public,导致用户运用者能够用到咱们不想露出的 API 的问题呢?

Kotlin 官方也供给了一个插件供咱们运用:binary-compatibility-validator

这个插件能够 dump 出一切的 public API,将代码与 dump 出来的 api 进行比照,能够避免露出不必要的 api

使用插件

plugins {
    id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.12.1"
}

使用插件很简单,只要在 build.gradle 中增加以上代码就好了

插件使命

该插件包含两个使命

  • apiDump: 构建项目并将其公共 API 转储到项目 api 子文件夹中。API 以人类可读的格式转储。假如 API 转储文件现已存在,它将会被覆盖。
  • apiCheck: 构建项目并查看项目的公共 API 是否与项目 api 子文件夹中的声明相同。假如不同则抛出反常

作业流

咱们能够经过以下作业流,保证 library 模块不会无意中露出 public api

准备阶段(一次性作业):

  • 使用插件,配置它并履行 apiDump ,导出项目 public api
  • 手动验证您的公共 API (即履行 apiCheck 使命)。
  • 提交项目的 api (即 .api 文件) 到您的 VCS。

惯例作业流程

  • 后续提交代码时,都会构建项目,并将项目的 API 与 .api 文件声明的 api 进行比照,假如两者不同,则 check 使命会失败
  • 假如是代码问题,则将可见性批改为 internal 或者 private,再从头提交代码
  • 假如确实应该增加新的 public api,则经过 apiDump 更新 .api 文件,并从头提交

与 CI 集成

惯例作业流程中,每次提交代码都应该查看 api 是否产生变化,这主要是经过 CI 完成的

以 Github Action 为例,每次提交代码时都会触发查看,假如查看不经过会抛出以下反常

Kotlin 默认可见性为 public,是不是一个好的设计?

总结

本文主要介绍了为什么 Kotlin 的默许可见性是 public,及其优缺陷。一起在这种状况下,咱们该怎么处理 library 代码简单无意中被声明为 public ,导致用户运用者能够用到咱们不想露出的 API 的问题

假如本文对你有所协助,欢迎点赞~

示例项目

github.com/RicardoJian…