Public by default for classes


#1

I was surprised and disappointed to see the default modifier for classes is now public. As it said in the blog post, this seems like a controversial choice given Kotlin's aim to be a safe language.

First, I want to make sure that I’m understanding it correctly. Is a public class always visible to code in other modules? That is less safe than the new Java module system where a package must be exported for its public classes to be visible in other modules. If Kotlin supported a similar system of explicit exports from modules then public visibility would seem like a sensible default. But if public classes are automatically visible to the whole world then this change means a loss of encapsulation for anyone who doesn’t think carefully about visibility and declare their classes as internal. Which is almost everyone.

One of the points in the blog used to explain the change is that most Java codebases contain mostly public classes. I suspect this is because the default class templates in IDEs have a public modifier and no-one changes the templates or the classes they generate. The result is that Java code is full of packages that unnecessarily expose their internal implementation to the world. This shows the power of defaults and is somewhere Kotlin could be better than Java by choosing safer defaults. As it stands Kotlin will be as bad as Java 8 and worse than Java 9.

FWIW, I’ve edited my code templates in IDEA to use package scope for classes and interfaces. If I make something public it is a conscious design decision. I suspect an analysis of my code would show up a lot more non-public classes than average. That isn’t because I obsessively think about API design and make my code private, it’s because I lazily accept the defaults like most developers, I’m just using different defaults.

I’ve been re-reading some of the old threads on this subject and one of the suggestions was that classes could be internal by default and that properties, methods and constructors could be public. That seems like a better balance of safety and convenience. Within a single module an internal class is the same as a public class. Modules are normally fairly large, so for the many projects that only contain a single module the effect of default internal classes will be the same as default public classes. If a project is large enough to contain multiple modules it’s probably good for you to think about visibility and which parts of your API you want to expose to other modules.

Is there a problem with that approach I’ve missed?

Chris


#2

I think you have outlined the solution to the controversy in your post. The language can have public by default, so that we gain over Java in terms of boilerplate. But at the same time change the defaults in the IDE  so that it can automatically insert the internal modifier on top level classes in order to guard against unintentional exposure of implementation details. So we get the best of both worlds.


#3

At first, I agreed with you, but then I looked at it deeper and now I think public by default is the right choice. Here is whay :

  • In my code I’m not using the default very often. Most of the time, when I intend a class or method to be internal, I use the appropriate keyword. So, IMHO, the solution is just not to use the default.

  • I think there’s a difference between readable code and consise code. A code can be one and not the other. I believe that internal by default is counter-intuitive and I’ve always stumble on the fact that I have to remember that if something’s protection is not explicitely defined, than it’s not really protected, but not really opened either.

  • I think that we have to remember that a code is always readable by the author (at least when (s)he writes it), meaning that a code readability is not defined by the author but by a reader, and I think that saying to the reader “If I’m not specifically forbiding ou to use this, than you can use this” makes the code more readable than “You have to check if I’m authorizing you to use this”.

  • Once more, I do not use default in my code, but now there’s a place that I’m going to: data classes.
    The code

data class Person(val firstName, val lastName)

is, IMHO, way more readable than when bloated by publics

public data class Person(public val firstName, public val lastName)

The problem I see with your approach is that having a contextual default can be counter intuitive. Meaning that you have to remember that not putting a modifier can have a different effect. And what about top-level functions in this scheme ?

Here’s my counter-proposal :
1/ Forbid any top-level declaration (val, var, fun, class) to have no modifier. So, no default on top-level, you have to define the protection level.
2/ Everything in a class is by default protected the same way the class is. So if the class is public, no modifier on a property means it’s public. If a class is internal, no modifier means internal, and so on.

What do you think ?


#4

How is that the best of both worlds? The vast majority of developers don't edit the defaults in their IDE, that was my point. The default behaviour will be to expose every class to the whole world. That isn't good design and doesn't seem like a very safe default.


#5

I am with you. I think I have not expressed myself clear enough. What I am saying is that JETBRAINS should change the IDE behavior so that each time the user start writing in the editor a class declaration, say

class MyClass

the IDE automatically insert "internal"

internal class MyClass

So that the user is saved from accidental exposure of classes, but he can just delete the word "internal" if this is what he actually want. So on one hand, the language allow people who want concise code to have it (that was the official motivation behind the choice of public by default), but on the other hand protect people from mistakes. This is just a rough idea I am sure that JETBRAINS can come up with something smarter and cleaner. My point is that it does not help anyone to continue discussing what is the best default visibility, the are pros and cons to each of them. And it is possible to have the best of the options if we split responsibilities between the language itself and the IDE. I think this exactly something that JETBRAINS know to do very well. So let them do it.


#6

I think that class members inheriting the visibility of their class by default is an excellent idea. For most classes that is exactly the default behaviour you want. And if you want something to be private or protected then it's reasonable to state it explicitly.

I’d also be happy to put visibility modifiers on all top-level classes, but I suspect that wouldn’t be popular as there seems to be a lot of enthusiasm in the community for reducing boilerplate.

My main worry with this change is that most people just go with the defaults, and Kotlin’s new defaults will lead to very leaky APIs. Does anyone know what the planned behaviour will be WRT Java 9 modules? Will all public classes be automatically exported?


#7

Everything in a class is by default protected the same way the class is: well probably Mark is right. Contextual definition of visibility is not good for readability of code


#8

I think that class members inheriting the visibility of their class by default is an excellent idea. For most classes that is exactly the default behaviour you want.
I don't think this is the case actually. Take for instance a private class, having all its members private by default renders it useless most of the time. Furthermore I don't think it adds to readability if you have to look at the class to determine what the visibility of a function is.


#9

My understanding of the suggestion is that the default visibility of members is the same as the visibility of the class - i.e. if you can see the class you can see the members. So members of a private class with no modifier would be visibile in the source file where the class is defined. Just like the class.


#10

So far the Kotlin team have been very receptive of feedback from the community which is why I'm raising my concerns over what I see as an unfortunate design choice.

My concern is that we’re trading safety for brevity. The advantage is that we don’t have to write “public” in so many places in our code. But the disadvantage is that our APIs are now exposed to the world by default which is widely accepted as poor design. It seems like a small gain for a large loss. That’s the problem I have with it.


#11

chriskent schrieb:

I suspect this is because the default class templates in IDEs have a public modifier and no-one changes the templates or the classes they generate. The result is that Java code is full of packages that unnecessarily expose their internal implementation to the world. This shows the power of defaults and is somewhere Kotlin could be better than Java by choosing safer defaults. As it stands Kotlin will be as bad as Java 8 and worse than Java 9.


My working theory for this is quite similar. I believe there are two main reasons why there are so many public classes out there:

  1. public is the default in templates for new classes
  2. Eclipse doesn’t show visibility in its workspace view as IDEA does in the project view; thus, people don’t realize how much they really expose by making everything public.


I strongly believe that making things public (i.e. exporting them through module boundaries) should be a concious design decision and not a default. This is something I value a lot higher than having to write public at a couple more places to be compatible with Java. Thus, I’d prefer that default visibility should definitely be internal. I have seen so much well designed functionality gone bad because access to internal functionality of other modules was to easy and convenient. Kotlin should not look back at what has been the default with Java but look forward at what will be possible with the module system of Java 9.


#12

> I think that class members inheriting the visibility of their class by default is an excellent idea.

I disagree, I think this will lead to confusing code because understanding a default visibility is now context dependent. Every fun foo() that I read will have a different visibility and I’ll have to go check the top class definition to find out what it is.


#13

Is it ever enough to look at the method definition in isolation to know whether you can invoke it? A public method on an internal class can't be called from another module because the class isn't visible. Unless the method is defined by an interface. In which case the interface must be visible in the calling module. It's a similar story for public or internal methods on private classes.


#14

> but look forward at what will be possible with the module system of Java 9.

I totally agree with this - Java 9 changes things a lot in terms of visibility for the better.  The thing is I see it the opposite way to your comment and that is … with java 9 modules the most important thing will be to define / control what your module is going to make public. That is, all the classes internally in the module can all have the local public ‘modifier’ but that won’t really matter in that what is most important it is what the module defines as public to other modules.

As I see it, in the java 9 modules world having classes default to public makes sense (and we use the java 9 module visibility features to hide the ‘internal’ code/classes from all the module users/consumers … and not the modifier on each individual class).


#15

Is there a firm plan for how Kotlin will interact with Java 9 modules? Will it work the same as in Java? i.e. will there be a mechanism to export a subset of the public classes in a module? If that's the case then I would have less problem with classes being public by default. Or will all public classes be automatically exported?

If Kotlin does support explicit exports then the internal modifier might be redundant as it would be the same as public but not exported.

Although Java 9 modules won’t help Android developers who I imagine are one of the most important target audiences for Kotlin.


#16

There's not a very firm plan for Java 9 modules will be like. But we intend to at least support whatever Java has there


#17

> First, I want to make sure that I'm understanding it correctly. Is a public class always visible to code in other modules?

As soon as Java has run-time modules and strong encapsulation, Kotlin will have it too. Only on 9, of course.

> FWIW, I’ve edited my code templates in IDEA to use package scope for classes and interfaces. If I make something public it is a conscious design decision

You can do the same in Kotlin: have them prefixed by internal

> I’ve been re-reading some of the old threads on this subject and one of the suggestions was that classes could be internal by default and that properties, methods and constructors could be public.

This is problematic, because classes may be nested in onther classes, and it makes defaults irregular. But we’ll discuss it again


#18

> You can do the same in Kotlin: have them prefixed by `internal`

That only fixes my code. Every other developer who uses Kotlin’s defaults without thinking about visibility will still be exposing their internal implementation details to the world.


#19

As they've been happily doing for ages in Java without complaining, because it's not a problem for them :)

I totally understand the move to change the world though tweaking defaults. We had numerous discussions along these lines.
But we find the ease of use and regularity of the language to be far more important in this particular case. And those who disagree have a pretty easy workaround at their disposal.