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

前言

众所周知,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

这里是它的 internal 包,理想情况下只能在??橹斜豢吹?。

简单计算可以看到大根有 46% 的公共方法和 71% 的公共类。这已经比一般的代码库好很多,这是我们应该鼓励的方向。

但是 internal 包内部的类根本不应该被公开!而这是因为 Java 的可见性限制引起的(没有??槟诳杉?/p>

如果 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,结果发现并不是??楸呓绮季直呓绮磺逦斐傻摹D?榈幕质峭耆虾趼呒?,但仍然有很多类由于到处都是 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,及其优缺点。同时在这种情况下,我们该如何解决 library 代码容易无意中被声明为 public ,导致用户使用者可以用到我们不想暴露的 API 的问题

如果本文对你有所帮助,欢迎点赞~

示例项目

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容