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

#704: added onlyBeCalledByClassesThat conjuntion - initial part #810

Merged
merged 1 commit into from
Mar 12, 2022

Conversation

JKLedzion
Copy link
Contributor

Initial part of work for issue #704. Work is in progress but it would be great to receive some feedback if it goes in right direction.

I’ve started from adding method onlyBeCalledByClassesThat(DescribedPredicate predicate) to CodeUnitsShould.
Now I’m working on possibility to have a rule like:
should().onlyBeCalled().{byClassesThat()/methodsThat()/constructorsThat()}.

Could you let me know if my understanding of the issue is correct for below points?

  1. I’ve implemented condition “code unit should only be called by”, but few possibilities came to my mind, not sure if I’ve decided on the correct one:
    • code unit should be called only by classes that,
    • code unit should be called by at least one class that,
    • code unit should be called by all classes that (but can be called also by other)
    • code unit should be called by all classes that (and can’t be called by any other).
  2. should().onlyBeCalled().byMethodsThat() - here I’m checking if methods calling selected code unit meet provided criteria but still this code unit can be called by constructors not meeting criteria. Is it correct?

I would appreciate any feedback.
Justyna

Signed-off-by: Justyna Kubica-Ledzion justyna.kubica-ledzion@digitalnewagency.com

@codecholeric
Copy link
Collaborator

Thanks a lot for tackling this, we really appreciate it 😄 Also good idea to reach out early so we can discuss unclear points 👍

To answer your questions:

  1. To be consistent with the rest "should only be called by classes that" should follow your first point. I.e. every class that calls the respective code unit should match the predicate. Otherwise we would e.g. use "any class that" or sth. like that. But we should also add Javadoc in the end to explain this precisely 🙂
  2. If you say "only be called by methods that" I would forbid constructor calls. You are right that both is valid (again, that's why we should definitely add Javadoc), you could as well ignore constructor calls and only assert method calls. But we will also have "only be called by code units that", and that one will match methods and constructors, so given that I think it makes sense to forbid constructors when we talk about "only...by methods". Also, if we ponder how you would write the condition if you really want to forbid constructors. Then you would have to write something like "only be called by methods that ... are not constructors" and that just sounds weird to me 😉

@@ -171,6 +171,12 @@ public JavaClass getRawReturnType() {
return fieldAccesses;
}

@PublicAPI(usage = ACCESS)
public abstract Set<? extends JavaAccess<?>> getCallsOfSelf();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't the bound here be JavaCall instead of JavaAccess?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

* @see #getCallsOfSelf()
*/
@PublicAPI(usage = ACCESS)
public static final ChainableFunction<JavaCodeUnit, Set<? extends JavaAccess<?>>> GET_CALLS_TO_SELF =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By convention the constant name should be the exact method name in upper case. I.e. if the getter is getCallsOfSelf then the function should be GET_CALLS_OF_SELF

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected.

@PublicAPI(usage = ACCESS)
public Set<JavaConstructorCall> getCallsOfSelf() {
return getReverseDependencies().getCallsTo(this);
}

@PublicAPI(usage = ACCESS)
public Set<JavaConstructorCall> getMethodCallsOfSelf() {
Copy link
Collaborator

@codecholeric codecholeric Feb 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is confusing to me because "get method calls..." sounds to me as if it would return JavaMethodCall. But instead it's returning JavaConstructorCall which is no "method call". I know where you're coming from, but I think we need to find a different naming here 🤔 Maybe it would be something like getCallsOfSelfByMethods or ...FromMethods? 🤔 Or if we only need it in one place we don't add a new API method and simply filter in the one place 🤷‍♂️ We should ponder carefully if this is a common use case that should go in the API or not...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method was removed as all calls should be analyzed not only calls done by methods.

@Override
@PublicAPI(usage = ACCESS)
public boolean isMethod() {
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to override this because the default is false anyway, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've missed that.

@Override
@PublicAPI(usage = ACCESS)
public boolean isConstructor() {
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, the default is false anyway. As long as we go with a default (which you could question 🤷‍♂️) we should use it then I guess...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

@JKLedzion
Copy link
Contributor Author

JKLedzion commented Feb 28, 2022

Thanks @codecholeric for quick review and feedback :).

I’ve made corrections and added methods onlyBeCalled().byConstructorsThat(DescribedPredicate predicate) / onlyBeCalled().byCodeUnitsThat(DescribedPredicate predicate).

Ad. 2. I’ve implemented logic as follows:
.should().onlyBeCalled().byMethodsThat - calls done by methods are assessed against provided predicate, calls done by constructors are treated as violation of the rule. Method getMethodCallsOfSelf() was removed as all calls should be analyzed.

Currently below listed posibilities are implemented but I’m not sure if all of them are needed.
Maybe only following pattern should be left:
{constructors()/methods()/codeUnits()}.should().onlyBeCalled().by…
WDYT?

constructors()/methods()/codeUnits().should().onlyBeCalledByClassesThat(DescribedPredicate predicate)
constructors()/methods()/codeUnits().should().onlyBeCalledByClassesThat()
constructors()/methods()/codeUnits().should(onlyBeCalledByClassesThat(DescribedPredicate predicate)
constructors()/methods()/codeUnits().should().onlyBeCalled().byClassesThat(DescribedPredicate predicate)
constructors()/methods()/codeUnits().should().onlyBeCalled().byClassesThat()
constructors()/methods()/codeUnits().should().onlyBeCalled().byMethodsThat(DescribedPredicate predicate)
constructors()/methods()/codeUnits().should().onlyBeCalled().byConstructorsThat(DescribedPredicate predicate)
constructors()/methods()/codeUnits().should().onlyBeCalled().byCodeUnitsThat(DescribedPredicate predicate)

@codecholeric
Copy link
Collaborator

Thanks a lot for all your hard work 🙏 😄 Yes, I think there are already enough separate ways to express the same thing, we should probably not introduce both versions onlyBeCalledByClassesThat and onlyBeCalled().byClassesThat 👍 I think we should only go with the second version as it's more flexible and encapsulates this part of the syntax in a separate interface.

@JKLedzion
Copy link
Contributor Author

Thanks for confirmation.
I’ve removed onlyBeCalledByClassesThat from CodeUnitsShould leaving only onlyBeCalled().byClassesThat.
You mentioned earlier adding precise JavaDoc for this new methods. I put them in OnlyBeCalledSpecification interface but I’m not sure if I was precisely enough 😄 WDYT?

* <br><br>
* E.g.
* <pre><code>
* {@link ArchRuleDefinition#codeUnits() codeUnits()}.{@link GivenCodeUnits#should() should()}.{@link CodeUnitsShould#onlyBeCalled()} onlyBeCalled().{@link OnlyBeCalledSpecification#byClassesThat(DescribedPredicate)} byClassesThat(equivalentTo(SomeClass.class))}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the version with predicate byClassesThat(equivalentTo(SomeClass.class)) maybe we can give an example with the fluent API, e.g. byClassesThat().areAnnotatedWith(SomeAnnotation.class) or something like that? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used example from tests.

Copy link
Collaborator

@codecholeric codecholeric left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for all this hard word, very nice 😍 To me your Javadoc looks good, I added some minor suggestions to adjust it, but in general it's exactly how I imagined it 😃 Doesn't need to be a novel, but the important point (e.g. that byConstructors excludes methods) should be prominent 👍
I added some comments about minor things I noticed that could maybe be improved if you're still motivated?
In a future PR we can also fix the generics on MembersThat/CodeUnitsThat/... so that it can be offered as a fluent API completion of byCodeUnitsThat() 🙂 (But I consider that out of scope for this PR)

@JKLedzion
Copy link
Contributor Author

Yes, still motivated😃 but I will be able to work on changes from Wednesday next week.

@JKLedzion
Copy link
Contributor Author

JKLedzion commented Mar 10, 2022

Hi, I've added modification for almost all comments, hope my understanding was correct 😄

There is one which I'm not sure about:
But if we unify CodeUnitCallsCondition it would likely just take description and predicate into the constructor separately anyway 🤷‍♂️ (at least that's how the respective Classes... does it)

There are 4 methods in ArchConditions using the same class CodeUnitOnlyCallsCondition, each with different description (i.e. "only be called by classes that “, “only be called by methods that ")
that is why I'm passing description as a parameter to CodeUnitOnlyCallsCondition constructor.
Also I’m passing extended predicate (i.e. origin.is(method().and(predicate)) so extracting predicate description inside CodeUnitOnlyCallsCondition constructor would end up with description: “… should only be called by methods that is method and custom predicate”.

In a future PR we can also fix the generics on MembersThat/CodeUnitsThat/... so that it can be offered as a fluent API completion of byCodeUnitsThat() 🙂 (But I consider that out of scope for this PR)

I think I can work on that PR also.

... which allows restricting callers of code units via

* `...onlyBeCalled().byClassesThat(..)`
* `...onlyBeCalled().byMethodsThat(..)`
* `...onlyBeCalled().byConstructorsThat(..)`
* `...onlyBeCalled().byCodeUnitsThat(..)`

Signed-off-by: Justyna Kubica-Ledzion <justyna.kubica-ledzion@digitalnewagency.com>
Copy link
Collaborator

@codecholeric codecholeric left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing all the comments and all your hard work!! I've squashed it now and adjusted the message a little bit to make it ready to be merged 🙂
Gonna merge as soon as CI is green 🚀

@codecholeric
Copy link
Collaborator

BTW: I've already fixed up the generics on MembersThat and others to make it reusable -> #832. I hope by that change it would now be easy to add this later on, i.e. that it's possible to add onlyBeCalled().byMethodsThat() in an easy way.

@codecholeric codecholeric merged commit 348819d into TNG:main Mar 12, 2022
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

Successfully merging this pull request may close these issues.

2 participants