Best practice regarding visibility

I currently am publishing a library for using swing with Processing. I declared almost everything in my library public with the motivation that if someone knows what they’re doing one can use these methods. However this has the effect that the javadoc is full of a few methods that aren’t supposed to be used when you don’t know what you’re doing.
Should I reduce the visibility of some methods so you need to access them with reflect or should I try to make clear they aren’t supposed to be used often?

1 Like

I’d just publish it unabridged and let the end user make the decision to use them or not.

2 Likes

Excellent! I hate the private keyword.

Please don’t! Reflect is such a slow boilerplate!

Actually there are 2 solutions for that!

Easiest 1 is to use comment tag @hidden just before a member you want excluded from javadoc:
docs.Oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html#hidden

This way you can still have public members w/o cluttering your generated doc.

However notice tag @hidden was introduced in JDK 9.

So if you’re using JDK 8 to compile your library you still need a later version just to run javadoc.

  • The other approach is to restrict members w/ keyword protected.
  • A protected member behaves similarly to private restriction.
  • But it opens up an access exception inside another class which extends its class.
  • We can also access protected members inside a file w/ the same package name as theirs.
  • Otherwise they behave as private members, not allowing direct access unless using reflection.
  • However javadoc includes both public & protected members by default.
  • But it’s very easy to get around it by passing parameter “-public” in order to exclude protected members from its generated doc.
  • Also you can use both strategies, declaring less restricted members w/ comment tag @hidden and more restricted members w/ keyword protected in place of private.
  • But there’s still another caveat when using the protected keyword.
  • If some class happens to instantiate another class which has protected members in it, it’s always gonna do it on the original class, not from a user’s subclass.
  • That is, even if a user extends a class in order to access/modify its protected members, the user can’t force the rest of your library to use that modified subclass.
  • In such cases you should create an extra call sig which allows a user to pass their subclass to it.

As an example, let’s say you have a class A which instantiates inside its constructor a class B:

“A.java”:

package pseudo.privated.members;

public class A {
  /**
   * @hidden
   */
  public B b;

  public A() {
    b = new B();
  }
}

However that class B contains a protected method called protectedMethod():

“B.java”:

package pseudo.privated.members;

public class B {
  protected String protectedMethod() {
    return "protected: " + getClass().getSimpleName();
  }
}

If a user desires to subclass class B in order to modify protectedMethod() it’s gonna be in vain, b/c your class A will always instantiate the original class B, never some subclass of it.

As a workaround the user could later reassign field b from class A w/ a modified class B of theirs.

However that’d feel like a poor hack, given field A::b is @hidden from javadoc.

A more streamlined solution would be to offer an alternative constructor signature which accepts a B subclass from the user:

“A.java”:

package pseudo.privated.members;

public class A {
  /**
   * @hidden
   */
  public B b;

  public A() {
    this(null);
  }

  public A(final B subclass) {
    b = subclass == null? new B() : subclass;
  }
}

A regular user would simply instantiate class A using its 1st call signature.

On the other hand, an expert user would know how to use its 2nd signature.

Regardless, either class A instantiates its own class B or use the 1 passed to it.

You could even tag the alternative signature w/ @hidden so only experts would be aware of it:

  /**
   * @hidden
   */
  public A(final B subclass) {
    b = subclass == null? new B() : subclass;
  }

Now I’m gonna leave you w/ my complete “Public Hidden Members” sketch example:

“Public_Hidden_Members.pde”:

/**
 * Public Hidden Members (v1.0.0)
 * GoToLoop (2022/May/27)
 *
 * https://Discourse.Processing.org/t/
 * best-practice-regarding-visibility/37113/4
 */

import pseudo.privated.members.A;

A a;

void setup() {
  println(a = new A()); // protected: B
  println(a.b.hiddenMethod()); // hidden: B

  println(a = new A(new BB())); // subclassed: BB
  println(a.b.hiddenMethod()); // hidden: BB

  exit();
}

import pseudo.privated.members.B;

class BB extends B {
  @Override String protectedMethod() {
    return "subclassed: " + getClass().getSimpleName();
  }
}

“A.java”:

package pseudo.privated.members;

public class A {
  /**
   * @hidden
   */
  public B b;

  public A() {
    this(null);
  }

  /**
   * @hidden
   */
  public A(final B subclass) {
    b = subclass == null? new B() : subclass;
  }

  @Override public String toString() {
    return b.protectedMethod();
  }
}

“B.java”:

package pseudo.privated.members;

public class B {
  /**
   * @hidden
   */
  public String hiddenMethod() {
    return "hidden: " + getClass().getSimpleName();
  }

  protected String protectedMethod() {
    return "protected: " + getClass().getSimpleName();
  }
}

That’s nice but the problem is that you have no fine control over what the user might update / override.

Using private, protected and final access specifiers do that and more. They help

  • users avoid mistakes using the library
  • the library creator trying to fix user errors when using parts of the library they shouldn’t
  • prevent the user bypassing any validation that you might want to apply.

Here is an example of what I mean.

Start with our library class

package forum; // would be a library package name 

public class LibraryClass {

  protected int n = 0;

  public LibraryClass() { }

  public LibraryClass(int n) {
    this.n = getValidN(n);
  }

  // private validation :- change number to positive 
  private int getValidN(int n) {
    return Math.abs(n);
  }

  public void n(int n) {
    this.n = getValidN(n);
  }

  public int n() {
    return this.n;
  }

  public String toString() {
    return this.getClass().getSimpleName()+ "  { n = " + n + " }";
  }
}

We can see that the attribute n can only be positive. What if we wanted to modify the behaviour so n can only be negative? Under no circumstances do we want to modify the library because it would adversely affect existing code that used the library. The answer is for the user to inherit from the LibraryClass and override the private validation method like this

public class UserClass extends LibraryClass {

  public UserClass(int n) {
    super();
    this.n = getValidN(n);
  }

  // private validation :- change number to negative 
  private int getValidN(int n) {
    return -Math.abs(n);
  }

  // required to enable to ensure correct validity chack is applied
  public void n(int n) {
    this.n = getValidN(n);
  }

  public String toString() {
    return this.getClass().getSimpleName()+ "  { n = " + n + " }";
  }

}

So in Processing we have

void setup() {
  LibraryClass lc = new LibraryClass(-42);
  println(lc);
  LibraryClass uc = new UserClass(42);
  println(uc);
}

Which produces the output

LibraryClass  { n = 42 }
UserClass  { n = -42 }

So we have an autonomous library class which we can inherit from to override private access methods.

In other words we have an extensible, secure library which is surely a good thing :grin:

2 Likes