Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to check top level classes which are unnecessary public? #583

Closed
prondzyn opened this issue Apr 23, 2021 · 9 comments
Closed

How to check top level classes which are unnecessary public? #583

prondzyn opened this issue Apr 23, 2021 · 9 comments

Comments

@prondzyn
Copy link

I have a question. I'm trying to compose rule which find classes that are public but are not accessible anywhere outside of its package. Unfortunately I'm stuck. Do you have any clues for that?

@hankem
Copy link
Member

hankem commented Apr 24, 2021

find classes that are public but are not accessible anywhere outside of its package

As public classes are by definition accessible outside of their packages, I assume that you meant accessed... 😉

In this case, the following rule (using getDirectDependenciesToSelf(), checking more general dependencies than getAccessesToSelf(), which would only consider field accesses and method calls) might be a good starting point:

    @ArchTest
    ArchRule not_unnecessarily_public = classes()
            .that().areTopLevelClasses().and(are(onlyUsedFromSamePackage()))
            .should().notBePublic();
    
    static DescribedPredicate<JavaClass> onlyUsedFromSamePackage() {
        return DescribedPredicate.describe("only used from same package", javaClass ->
                javaClass.getDirectDependenciesToSelf().stream().allMatch(dependency ->
                        dependency.getOriginClass().getPackage().equals(javaClass.getPackage())
                )
        );
    }

@prondzyn
Copy link
Author

Yes, accessed is the proper word :)

Thanks @hankem. I was testing something similar before, but the case is not so straightforward. I still have several "false positive" findings like:

  1. static nested class accessed, so top-level class also need to be public
//this class have to be public because StaticNestedClass is public and accessed outside but not_unnecessarily_public test is caching it
public class TopLevelClass {  
  
  ...

  public static class StaticNestedClass {

    ....
  }
}
  1. public constant accessed from outside
//this class have to be public because CONSTANT is public and accessed outside but not_unnecessarily_public test is caching it
public class TopLevelClass {  
  
 public static final String CONSTANT = "Thanks!";
}
  1. enum usage
//this class have to be public because FIRST, SECOND ara public and accessed outside but not_unnecessarily_public test is caching them
public enum SomeEnum {
  FIRST, SECOND
}
  1. static method usage
//this class have to be public because doSomethingCommon() is public and accessed outside but not_unnecessarily_public test is caching it
public final class Utilities {

  public static String doSomethingCommon() {
    ...
  }
}

How should I improve the not_unnecessarily_public test to avoid above cases?

@timtebeek
Copy link
Contributor

Sounds like you're running up against some of the same limitations that I encountered when trying to detect unused classes and methods. I suspect you can adapt the rules I used there for your purpose as well.

Some of the limitation I ran up against are:

In short you won't be able to completely detect all cases correctly (yet), so it might be best to combine your rule with FreezingArchRule.freeze() to handle any false positives you encounter. Hope this comment helps!

@prondzyn
Copy link
Author

Thanks @timtebeek for the tips. I will be following changes in ArchUnit regarding this.

@hankem
Copy link
Member

hankem commented Apr 27, 2021

@prondzyn, I agree that my suggested starting point has false positives – some of them, as @timtebeek pointed out (🙏), require new features in ArchUnit, others like §2 in #583 (comment) ("[primitive] constant") unfortunately can't be resolved due to bytecode limitations. For §1 in #583 (comment) ("static nested class"), I believe that one could work out a more sophisticated solution.

Regarding §3 ("enum") and §4 ("static method"): I'd expect that the rule suggested in #583 (comment) would handle those correctly. Can you provide a (counter) example?

@prondzyn
Copy link
Author

In my case the "enum" §3 is because one particular enum type is used as generic type like: Set<MyEnumType> , so I understand ArchUnit is not able to handle it because of bytecode limitations.

For "static method" §4 I have for example a util class:

public final class XssStripper {
  public static String stripXss(String param) {
    return param;
  }
}

and call to the method:

  public <T> Optional<String> test(Selector<T> selector) {
    return selector.getFilters()
        .map(XssStripper::stripXss);
  }

@hankem
Copy link
Member

hankem commented Apr 28, 2021

Ah, I see. 👍 Thanks! Then I was just mislead by the categorization, and these issues are those noted in #583 (comment):

@timtebeek
Copy link
Contributor

With the recent additions in 0.23 I think it should now be possible to create a rule as described in this issue, without false positives. Would that be enough to close this issue?

@codecholeric
Copy link
Collaborator

I would also guess that the recent additions should have solved this, so I'm gonna close the issue for now. But feel free to reopen if there is still a problem!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants