Why the default should be internal
Visibility should be minimized
I can’t say it better than Josh Bloch, and I doubt this is controversial here.
- Effective Java, Item 13: Minimize the accessibility of classes and members
- How to Design a Good API and Why it Matters (PDF), slides 14 and 16 (note that public classes and members constitute an API).
Internal is a safe default
If a class or member initially has the wrong visibility, it’s much easier to increase visibility than reduce it. That is, changing an internal class or member to public requires no effort since there are no external callers – doing the reverse after external callers have been written could have large ripple effects. Therefore, making internal the default can save substantial work as code bases evolve.
Why the default is public
The default visibility was changed from internal to public in M13, on pragmatic grounds. The blog post announcing M13 included this reasoning for the change:
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.
Why the reasoning for public-by-default is flawed
Kotlin is a deeply pragmatic language, designed with great care and feet planted firmly on the ground. That spirit clearly motivates the above argument. While the motivation is definitely to be applauded, the reasoning misses the mark; it conflates brevity with cleanliness and ends up sweeping a major problem under the rug.
Kotlin tries to encourage good practice
Broadly, Kotlin does an excellent job of correcting flaws in Java that make it easier to write bad code than good.
A similar analysis of Java code would likely have revealed that most classes are not final. However, we know that most classes should be closed (Effective Java Item 17: Design and document for inheritance or else prohibit it). Consequently, Kotlin (correctly) makes classes closed by default despite the preponderance of bad practice in Java code bases.
Closed-by-default is only one example of Kotlin encouraging good practice with language constructs; nullable types and vals are others.
The analysis uses flawed data
Because Java provides inadequate options for visibility, developers are forced to choose the lesser of two evils. More experienced developers tend to address this problem with naming conventions and documentation; less experienced developers tend to throw up their hands and just make things public without even that.
Consequently, most Java code bases have many more public classes and members than their authors need or want. We cannot simply look at the use of Java visibility modifiers in an average code base and assume that it reflects its author’s wishes, let alone best practice!
For instance, let’s look at okhttp, a code base written by experienced Java developers who strive to minimize visibility despite the limitations of Java.
Here are its public packages, the ones intended to comprise its API:
Methods Classes ------------------------------ Public : 618 46 Package : 283 13 Protected : 2 0 Private : 237 0 ------------------------------ 1140 59
And here are its “internal” packages, which would ideally only be visible within the module.
Methods Classes ----------------------------- Public : 559 49 Package : 398 24 Protected : 13 0 Private : 423 0 ----------------------------- 1393 73
A simple count of public entities would show 46% public methods and 71% public classes. This is already much better than the average code base, which is the direction we should encourage.
But the internal packages should not be public at all! About half of the methods and classes which are public in okhttp are public only because Java requires them to be. If Java had Kotlin’s visibility modifiers, we should expect closer to 24% public methods and 35% public classes. Further, 48% of methods and 65% of classes would be internal!
The potential of internal visibility is squandered
In Java, there’s no choice but to make module-internal entities public and use convention and documentation to discourage their use. Kotlin’s internal visibility fixes this flaw in Java, but the choice of public for default visibility ignores that important correction.
Public-by-default squanders the potential of Kotlin’s internal visibility. It’s a rare (lone?) example of an unsafe default in Kotlin. It uncharacteristically encourages a bad practice that Java actually discourages, and in so doing is big step backward from Java when Kotlin has the means to take a big step forward.
It’s not too late!
Changing the default from public to internal will result in compile-time errors which are easily found and fixed in IntelliJ; it should even be possible to for the plugin to offer a quick-fix, finding entities with default visibility which are referenced outside the module and adding the public modifier, while leaving unreferenced entities as (correctly) internal by default.
Kotlin is fast approaching 1.0, but we’re not there quite yet. The adoption so far has been by an enthusiastic community of well-wishers who have followed Kotlin through a series of breaking changes in the milestones and betas. This is a passing but still present opportunity to make an important change to default visibility.