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

Issue #14726: new check: ConstructorsDeclarationGroupingCheck #14749

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions config/checkstyle-checks.xml
Expand Up @@ -539,6 +539,7 @@
<module name="NoFinalizer"/>
<module name="OneStatementPerLine"/>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="ConstructorsDeclarationGrouping"/>
<module name="PackageDeclaration"/>
<module name="ParameterAssignment"/>
<module name="RequireThis"/>
Expand Down
2 changes: 2 additions & 0 deletions config/checkstyle-non-main-files-suppressions.xml
Expand Up @@ -234,6 +234,8 @@
files="src[\\/]xdocs[\\/]checks[\\/]coding[\\/]nofinalizer.xml.template"/>
<suppress id="propertiesMacroMustExist"
files="src[\\/]xdocs[\\/]checks[\\/]coding[\\/]overloadmethodsdeclarationorder.xml.template"/>
<suppress id="propertiesMacroMustExist"
files="src[\\/]xdocs[\\/]checks[\\/]coding[\\/]constructorsdeclarationgrouping.xml.template"/>
<suppress id="propertiesMacroMustExist"
files="src[\\/]xdocs[\\/]checks[\\/]coding[\\/]parameterassignment.xml.template"/>
<suppress id="propertiesMacroMustExist"
Expand Down
1 change: 1 addition & 0 deletions config/jsoref-spellchecker/whitelist.words
Expand Up @@ -239,6 +239,7 @@ config
configurationloader
Connell's
constantname
constructorsdeclarationgrouping
Contextualizable
contextualization
contextualized
Expand Down
2 changes: 2 additions & 0 deletions config/linkcheck-suppressions.txt
Expand Up @@ -223,6 +223,7 @@
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/coding/AvoidDoubleBraceInitializationCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/coding/AvoidInlineConditionalsCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/coding/AvoidNoArgumentSuperConstructorCallCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/coding/ConstructorsDeclarationGroupingCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/coding/CovariantEqualsCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/coding/DeclarationOrderCheck.ScopeState.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/coding/DeclarationOrderCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
Expand Down Expand Up @@ -949,6 +950,7 @@
<a href="com/puppycrawl/tools/checkstyle/checks/coding/AvoidDoubleBraceInitializationCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/coding/AvoidDoubleBraceInitializationCheck.html#%3Cinit%3E()</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/checks/coding/AvoidInlineConditionalsCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/coding/AvoidInlineConditionalsCheck.html#%3Cinit%3E()</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/checks/coding/AvoidNoArgumentSuperConstructorCallCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/coding/AvoidNoArgumentSuperConstructorCallCheck.html#%3Cinit%3E()</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/checks/coding/ConstructorsDeclarationGroupingCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/coding/ConstructorsDeclarationGroupingCheck.html#%3Cinit%3E()</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/checks/coding/CovariantEqualsCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/coding/CovariantEqualsCheck.html#%3Cinit%3E()</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/checks/coding/DeclarationOrderCheck.ScopeState.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/coding/DeclarationOrderCheck.ScopeState.html#%3Cinit%3E()</a>: doesn't exist.
<a href="com/puppycrawl/tools/checkstyle/checks/coding/DeclarationOrderCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/coding/DeclarationOrderCheck.html#%3Cinit%3E()</a>: doesn't exist.
Expand Down
2 changes: 2 additions & 0 deletions pom.xml
Expand Up @@ -3264,6 +3264,7 @@
<param>com.puppycrawl.tools.checkstyle.checks.coding.AvoidDoubleBraceInitializationCheck*</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.AvoidInlineConditionalsCheck*</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.AvoidNoArgumentSuperConstructorCallCheck*</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.ConstructorsDeclarationGroupingCheck*</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.CovariantEqualsCheck*</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.DeclarationOrderCheck*</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.DefaultComesLastCheck*</param>
Expand All @@ -3288,6 +3289,7 @@
<param>com.puppycrawl.tools.checkstyle.checks.coding.AvoidDoubleBraceInitializationCheckTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.AvoidInlineConditionalsCheckTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.AvoidNoArgumentSuperConstructorCallCheckTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.ConstructorsDeclarationGroupingCheckTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.CovariantEqualsCheckTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.DeclarationOrderCheckTest</param>
<param>com.puppycrawl.tools.checkstyle.checks.coding.DefaultComesLastCheckTest</param>
Expand Down
@@ -0,0 +1,138 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2024 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
///////////////////////////////////////////////////////////////////////////////////////////////

package org.checkstyle.suppressionxpathfilter;

import java.io.File;
import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.Test;

import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
import com.puppycrawl.tools.checkstyle.checks.coding.ConstructorsDeclarationGroupingCheck;

public class XpathRegressionConstructorsDeclarationGroupingTest extends AbstractXpathTestSupport {

private final Class<ConstructorsDeclarationGroupingCheck> clazz =
ConstructorsDeclarationGroupingCheck.class;

@Override
protected String getCheckName() {
return clazz.getSimpleName();
}

@Test
public void testClass() throws Exception {
final File fileToProcess = new File(
getPath("InputXpathConstructorsDeclarationGroupingClass.java"));

final DefaultConfiguration moduleConfig = createModuleConfig(clazz);

final String[] expectedViolation = {
"10:5: " + getCheckMessage(clazz,
ConstructorsDeclarationGroupingCheck.MSG_KEY, 6),
};

final List<String> expectedXpathQueries = Arrays.asList(
"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingClass']]"
+ "/OBJBLOCK/CTOR_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingClass']]",
"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingClass']]"
+ "/OBJBLOCK/CTOR_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingClass']]"
+ "/MODIFIERS",
"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingClass']]"
+ "/OBJBLOCK/CTOR_DEF/IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingClass']"

);

runVerifications(moduleConfig, fileToProcess, expectedViolation, expectedXpathQueries);
}

@Test
public void testEnum() throws Exception {
final File fileToProcess = new File(
getPath("InputXpathConstructorsDeclarationGroupingEnum.java"));

final DefaultConfiguration moduleConfig = createModuleConfig(clazz);

final String[] expectedViolation = {
"12:5: " + getCheckMessage(clazz,
ConstructorsDeclarationGroupingCheck.MSG_KEY, 8),
};

final List<String> expectedXpathQueries = Arrays.asList(
"/COMPILATION_UNIT/ENUM_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingEnum']]"
+ "/OBJBLOCK/CTOR_DEF"
+ "[./IDENT[@text='InputXpathConstructorsDeclarationGroupingEnum']]",

"/COMPILATION_UNIT/ENUM_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingEnum']]"
+ "/OBJBLOCK/CTOR_DEF"
+ "[./IDENT[@text='InputXpathConstructorsDeclarationGroupingEnum']]"
+ "/MODIFIERS",

"/COMPILATION_UNIT/ENUM_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingEnum']]"
+ "/OBJBLOCK/CTOR_DEF/IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingEnum']"
);

runVerifications(moduleConfig, fileToProcess, expectedViolation, expectedXpathQueries);
}

@Test
public void testRecords() throws Exception {
final File fileToProcess = new File(
getNonCompilablePath("InputXpathConstructorsDeclarationGroupingRecords.java"));

final DefaultConfiguration moduleConfig = createModuleConfig(clazz);

final String[] expectedViolation = {
"14:5: " + getCheckMessage(clazz,
ConstructorsDeclarationGroupingCheck.MSG_KEY, 8),
};

final List<String> expectedXpathQueries = Arrays.asList(
"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingRecords']]"
+ "/OBJBLOCK/RECORD_DEF[./IDENT[@text='MyRecord']]"
+ "/OBJBLOCK/COMPACT_CTOR_DEF[./IDENT[@text='MyRecord']]",

"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingRecords']]"
+ "/OBJBLOCK/RECORD_DEF[./IDENT[@text='MyRecord']]"
+ "/OBJBLOCK/COMPACT_CTOR_DEF[./IDENT[@text='MyRecord']]/MODIFIERS",

"/COMPILATION_UNIT/CLASS_DEF[./IDENT"
+ "[@text='InputXpathConstructorsDeclarationGroupingRecords']]"
+ "/OBJBLOCK/RECORD_DEF[./IDENT[@text='MyRecord']]"
+ "/OBJBLOCK/COMPACT_CTOR_DEF[./IDENT[@text='MyRecord']]"
+ "/MODIFIERS/LITERAL_PUBLIC"
);

runVerifications(moduleConfig, fileToProcess, expectedViolation, expectedXpathQueries);
}
}
@@ -0,0 +1,20 @@
//non-compiled with javac: Compilable with Java14

package org.checkstyle.suppressionxpathfilter.constructorsdeclarationgrouping;

public class InputXpathConstructorsDeclarationGroupingRecords {
public record MyRecord(int x, int y) {

public MyRecord(int a) {
this(a,a);
}

void foo() {}

public MyRecord {} // warn

public MyRecord(int x, int y, int z) {
this(x+y, z);
}
Comment on lines +16 to +18
Copy link
Member

@nrmancuso nrmancuso Apr 29, 2024

Choose a reason for hiding this comment

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

@Zopsss @romani it looks like we are emulating the behavior of OverloadMethodsDeclarationOrder where a user may need to run the check multiple times in order to make checkstyle happy. Is there some technical limitation that prevents us from using the following logic:

  • First constructor is where constructors should live
  • If there is something that is not a constructor after the first one, all other constructors are flagged

?

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 we can do this, I agree with this. Waiting for @romani's opinion. If he agrees then I'll start working on the new implementation

Copy link
Member

Choose a reason for hiding this comment

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

@nrmancuso @romani If this is the way to want to move, we should probably create an issue for the other check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nrmancuso I've changed the implementation according to your new idea. Should I push the new changes or wait for other maintainers approval?

Copy link
Member

Choose a reason for hiding this comment

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

@nrmancuso I've changed the implementation according to your new idea. Should I push the new changes or wait for other maintainers approval?

If you’ve already done the work, you can checkout a new branch from what you’ve done, and open a draft PR. Then you can reset this branch from the origin. This way you have both versions preserved and we can see what the other looks like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Here is the draft PR: #14844

Copy link
Member

Choose a reason for hiding this comment

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

Done. Here is the draft PR: #14844

This new PR makes it even more obvious that we should do this, IMO.

This is my vision:

  1. Create a list of all children of the class/enum/whatever def
  2. Create sublist from first constructor , taking while the AST is a constructor
  3. Take the difference of the original list and the sublist
  4. Filter out non-constructors
  5. log all remaining items

Copy link
Contributor Author

Choose a reason for hiding this comment

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

made the implementation stateless on that PR, can you please check that out? and sorry for the late reply I wasn't active for few days

}
}
@@ -0,0 +1,11 @@
package org.checkstyle.suppressionxpathfilter.constructorsdeclarationgrouping;

public class InputXpathConstructorsDeclarationGroupingClass {
InputXpathConstructorsDeclarationGroupingClass() {}

InputXpathConstructorsDeclarationGroupingClass(int a) {}

int x;

InputXpathConstructorsDeclarationGroupingClass(String str) {} // warn
}
@@ -0,0 +1,13 @@
package org.checkstyle.suppressionxpathfilter.constructorsdeclarationgrouping;

public enum InputXpathConstructorsDeclarationGroupingEnum {
ONE;

InputXpathConstructorsDeclarationGroupingEnum() {}

InputXpathConstructorsDeclarationGroupingEnum(String str) {}

int f;

InputXpathConstructorsDeclarationGroupingEnum(int x) {} // warn
}
Expand Up @@ -557,6 +557,8 @@ private static void fillChecksFromCodingPackage() {
BASE_PACKAGE + ".checks.coding.OneStatementPerLineCheck");
NAME_TO_FULL_MODULE_NAME.put("OverloadMethodsDeclarationOrderCheck",
BASE_PACKAGE + ".checks.coding.OverloadMethodsDeclarationOrderCheck");
NAME_TO_FULL_MODULE_NAME.put("ConstructorsDeclarationGroupingCheck",
BASE_PACKAGE + ".checks.coding.ConstructorsDeclarationGroupingCheck");
NAME_TO_FULL_MODULE_NAME.put("PackageDeclarationCheck",
BASE_PACKAGE + ".checks.coding.PackageDeclarationCheck");
NAME_TO_FULL_MODULE_NAME.put("ParameterAssignmentCheck",
Expand Down
@@ -0,0 +1,110 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2024 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
///////////////////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.checks.coding;

import java.util.HashMap;
import java.util.Map;

import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

/**
* <p>
* Checks that all constructors are grouped together.
* If there is any code between the constructors
* then this check will give an error.
* Comments between constructors are ignored.
* </p>
* <p>
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
* </p>
* <p>
* Violation Message Keys:
* </p>
* <ul>
* <li>
* {@code constructors.declaration.grouping}
* </li>
* </ul>
*
* @since 10.16.0
*/

@FileStatefulCheck
public class ConstructorsDeclarationGroupingCheck extends AbstractCheck {
Copy link
Member

Choose a reason for hiding this comment

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

Can you make it stateless?
It will work well when we make checkstyle multithreading.

Copy link
Contributor Author

@Zopsss Zopsss Apr 9, 2024

Choose a reason for hiding this comment

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

Done. Firstly I tried making it stateless using other ways but the mvn clean verify was giving errors so I have added the suppressions to make it work.

"com.puppycrawl.tools.checkstyle.checks.coding.ConstructorsDeclarationGroupingCheck"
+ ".parents"

"com.puppycrawl.tools.checkstyle.checks.coding.ConstructorsDeclarationGroupingCheck"

After adding the suppressions, mvn clean verify was running perfectly


Update: pitest coding-1 has still many surviving mutations. Should I suppress them? Because it is showing surviving mutators for those lines which are required to make the check work properly and mutating them would make test cases fail.

Copy link
Member

@mahfouz72 mahfouz72 Apr 9, 2024

Choose a reason for hiding this comment

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

mutating them would make test cases fail.

@Zopsss how is that? if the tests fail when hardcoding the mutation then how did they survive in the first place maybe you hardcode the mutation in the wrong way

Update: Add the Test class to the TargetTests of the pitest-coding-1 profile in pom.xml

Copy link
Contributor Author

@Zopsss Zopsss Apr 9, 2024

Choose a reason for hiding this comment

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

how is that?

NVM it was showing that many surviving mutations because I didn't added the Test class in the TargetTests of pitest-coding-1. Thanks for helping me out. Now it is showing only 3 surviving mutations :)

@romani Looking into the surviving mutations now 🫡


Update: all the surviving mutations has been killed. Now generating the diff report.

Copy link
Member

Choose a reason for hiding this comment

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

I am "unresolving" this item to continue discussion. We should be able to make this check stateless by using all class definitions as required tokens (CLASS_DEF_, ENUM_DEF, etc...), then checking class members. This should allow us to make this check stateless, perhaps at the cost of a slightly larger memory footprint. @Zopsss @romani thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should be able to make this check stateless by using all class definitions as required tokens (CLASS_DEF_, ENUM_DEF, etc...), then checking class members.

do you mean we should use an approach like this?

public void visitToken(DetailAST ast) {
final int parentType = ast.getParent().getType();
final int[] tokenTypes = {
TokenTypes.CLASS_DEF,
TokenTypes.ENUM_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.LITERAL_NEW,
TokenTypes.RECORD_DEF,
};
if (TokenUtil.isOfType(parentType, tokenTypes)) {
checkOverloadMethodsGrouping(ast);
}
}

The OverloadMethodsDeclarationOrderCheck uses this approach and it is stateless however the visitToken() gets called every time for class, enum, interface, etc... Then we're travelling the whole block to check that all the methods are grouped together. By this approach we can make the check stateless but then we will need to travel the whole class, enum, records, etc, we will be checking each and every tokens of the block, this is a time consuming method as we're travelling through the whole block and checking every tokens.

We're currently calling the Check for only CTOR and COMPACT_CTOR tokens only, by using this implementation we're saving a lot of time instead of travelling the whole block. This is an optimized method to check that all constructors are grouped together or not.

We're also clearing the HashMap every time before calling the visitToken(), so the HashMap does not contain any unnecessary data:

@Override
public void beginTree(DetailAST rootAst) {
allObjBlocks.clear();
}

If we make the current implementation stateless then we cannot use this optimized approach. The check will take more time to execute. So I guess if we're able to make the Check optimized then we should be using the current approach. The Check implementation will also become complex if we use the OverloadMethodsDeclarationOrder's approach. Correct me if I'm wrong.

Copy link
Member

Choose a reason for hiding this comment

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

optimized

Consider: https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil

by using this implementation we're saving a lot of time instead of travelling the whole block

Can you prove this matters?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consider: https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil

by using this implementation we're saving a lot of time instead of travelling the whole block

Can you prove this matters?

I gone through the page tried to understand how premature optimization should be avoided in some places if the optimization is not necessarily required. However I would like to give some light to the following paragraph:

What this means is that, in the absence of measured performance issues you shouldn't optimize because you think you will get a performance gain. There are obvious optimizations (like not doing string concatenation inside a tight loop) but anything that isn't a trivially clear optimization should be avoided until it can be measured.

The biggest problems with "premature optimization" are that it can introduce unexpected bugs and can be a huge time waster.

In the last line, it says that premature optimization can introduce unexpected bugs and it can be a time waster. In our case the current implementation is bug free, easy to understand and does not contain complex code. The other approach you're suggesting might be difficult to implement and understand, it can also introduce new bugs which might take quite a time to solve.


#14749 (comment)

Here, Mr. Roman suggested that if we didn't find any way to make the Check stateless then it is okay to keep them stateful. I indeed agree that the Stateless approach you're suggesting will work but again it will be difficult to understand, might take some time to implement, might contain bugs and will perform lots of travelling in the Tree.

But I'm not sure how much operating resources does a stateful check and a stateless check requires. I'm not sure how much they can affect on the performance of the Checkstyle.

If you think I'm wrong somewhere, please let me know. I'm not experienced as you're, this is my first time having a conversation on optimizing the system performance.

Copy link
Member

@rnveach rnveach Apr 29, 2024

Choose a reason for hiding this comment

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

Sorry, I didn't read all this but just a few comments. We aren't multithreaded any time soon, so its not a big deal if we can't change the type of check here. Since we don't have an implementation of multithreading there is no telling how big a performance there may be doing either type. I don't see stateless being a big performance improvement. It just means we don't have to instantiate the check for each new file it processes. While new instances are cheap, it really depends on how we will do that instantiation and populate any property values which will decide which type of check is better for performance as well as the impact from the check not having any memory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After reading @rnveach's comment, do we still need to transition to the stateless check or is it okay to keep this check stateful? @nrmancuso @romani please tell me what do you think about this?


/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY = "constructors.declaration.grouping";

/**
* Specifies different Object Blocks scope.
*/
private final Map<DetailAST, Integer> allObjBlocks = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

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

This is a state.
If we can not avoid state ( fields of class) , we should clean/reset fields at the begging of each file validation. Same instance of Check is reused over all files to validate.

If you see no way to make it stateless, it is ok. You can just explain a reason on why stateless is not possible, and stay with stateful.

Copy link
Contributor Author

@Zopsss Zopsss Apr 14, 2024

Choose a reason for hiding this comment

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

This is a state. If we can not avoid state ( fields of class) , we should clean/reset fields at the begging of each file validation. Same instance of Check is reused over all files to validate.

Done. Used the beginTree() method to achieve this:

@Override
public void beginTree(DetailAST rootAst) {
allObjBlocks.clear();
}

Edit: looking into the surviving mutation.


If you see no way to make it stateless, it is ok. You can just explain a reason on why stateless is not possible, and stay with stateful.

I guess we can't make this check stateless because the check requires tracking the scope of the overloaded constructors, so we need a global variable to keep track of this. A local variable will not work in this situation. Here is the example of what I'm trying to say:

private enum Testing {
one;
int x;
Testing() {}
Testing(String f) {}
String str;
Testing(int x) {} // violation
private abstract class Inner {
Inner() {}
int x;
String neko;
Inner(String g) {} // violation
}
Testing(double d) {} // violation
}

@romani

Copy link
Member

Choose a reason for hiding this comment

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

Ok


@Override
public int[] getDefaultTokens() {
return getRequiredTokens();
}

@Override
public int[] getAcceptableTokens() {
return getRequiredTokens();
}

@Override
public int[] getRequiredTokens() {
return new int[] {
TokenTypes.CTOR_DEF,
TokenTypes.COMPACT_CTOR_DEF,
};
}

@Override
public void beginTree(DetailAST rootAst) {
allObjBlocks.clear();
}

@Override
public void visitToken(DetailAST ast) {
final DetailAST currObjBlock = ast.getParent();
final Integer previousCtorLineNo = allObjBlocks.get(currObjBlock);
Copy link
Member

Choose a reason for hiding this comment

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

Why we can not simply check previous sibling on each ctor?
We might need some collection to keep track if ctor is first or not. This collection will just track that ctor is detected, so all other ctors should be in siblings.

Copy link
Contributor Author

@Zopsss Zopsss Apr 14, 2024

Choose a reason for hiding this comment

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

This will work in most of the situations. We can keep track if the current CTOR is first or not and check for its previous sibling, but if there is another block between the constructors then this approach will not work. For example:

private enum Testing {
one;
int x;
Testing() {}
Testing(String f) {}
String str;
Testing(int x) {} // violation
private abstract class Inner {
Inner() {}
int x;
String neko;
Inner(String g) {} // violation
}
Testing(double d) {} // violation
}

Here, the Testing(double d) {} occurs after an inner class and based on our check's implementation we will loose track of the previous CTOR and its position if we use a normal variable like a boolean or something to keep track of the previous constructor as it will get reset. That's why I used HashMap to keep track of all blocks and see if there is a constructor occurred in current block or not. We're getting the previous CTOR's line number of the current block and checking if it is null or not, null means there is no previous CTOR in the current block. If there is no CTOR occurred, then put the current block in the HashMap and store the constructor's line number. But if there is a constructor already occurred in the current block, then we check for the previous sibling, if the previous sibling is not a constructor then we will log the violation and if the previous sibling is a constructor then we will update the previous constructor's line number to current constructor's line number.

And we have provided the last occurred constructor's line number in the violation so user can know where the previous constructor is located.

This is the very early implementation of this check: https://github.com/checkstyle/checkstyle/blob/feba1977f846e7cb4fbed8ee76239ca9ee7a5140/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/ConstructorsDeclarationGroupingCheck.java

In this, I used a simple boolean variable to keep track of the previous CTOR, but this type of approach will fail for the example I mentioned above.

Here is an example in the diff report: https://checkstyle-diff-reports.s3.us-east-2.amazonaws.com/7452e65_2024163113/reports/diff/spotbugs/index.html#A15

Our check would not able to detect this if we simply check the previous CTOR without tracking the block of the constructor.

@romani

Copy link
Member

Choose a reason for hiding this comment

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

Ok

Copy link
Member

@nrmancuso nrmancuso Apr 29, 2024

Choose a reason for hiding this comment

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

Looks like this approach might also mitigate the issue here. In this case we would always operate, statelessly, in the context of a single class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah by using that approach we will be operating the check statelessly but that approach will be more time consuming, am I right? I've given my opinion here


if (previousCtorLineNo != null) {
final DetailAST previousSibling = ast.getPreviousSibling();
final int siblingType = previousSibling.getType();
final boolean isCtor = siblingType == TokenTypes.CTOR_DEF;
final boolean isCompactCtor = siblingType == TokenTypes.COMPACT_CTOR_DEF;

if (!isCtor && !isCompactCtor) {
log(ast, MSG_KEY, previousCtorLineNo);
}

allObjBlocks.put(currObjBlock, ast.getLineNo());
}
else {
allObjBlocks.put(currObjBlock, ast.getLineNo());
}
}
}
Expand Up @@ -3,6 +3,7 @@ assignment.inner.avoid=Inner assignments should be avoided.
avoid.clone.method=Avoid using clone method.
avoid.double.brace.init=Avoid double brace initialization.
avoid.finalizer.method=Avoid using finalizer method.
constructors.declaration.grouping=All constructors should be grouped together. Previous constructor was at line number ''{0}''.
covariant.equals=covariant equals without overriding equals(java.lang.Object).
declaration.order.access=Variable access definition in wrong order.
declaration.order.constructor=Constructor definition in wrong order.
Expand Down
Expand Up @@ -3,6 +3,7 @@ assignment.inner.avoid=Innere Zuweisungen sollten vermieden werden.
avoid.clone.method=Die Methode clone() sollte vermieden werden.
avoid.double.brace.init=Vermeiden Sie doppelte geschweifte Klammern.
avoid.finalizer.method=Die Methode finalize() sollte vermieden werden.
constructors.declaration.grouping=Alle Konstruktoren sollten zusammengefasst werden. Der vorherige Konstruktor befand sich in der Zeilennummer ''{0}''.
covariant.equals=Kovariante Definition von equals(), ohne equals(java.lang.Object) zu überschreiben.
declaration.order.access=Fehlerhafte Deklarationsreihenfolge für diesen Scope.
declaration.order.constructor=Der Konstruktor steht an der falschen Stelle.
Expand Down
Expand Up @@ -3,6 +3,7 @@ assignment.inner.avoid=Deben evitarse las asignaciones internas.
avoid.clone.method=Evite el uso de método clone.
avoid.double.brace.init=Evite la inicialización de llaves dobles.
avoid.finalizer.method=Evite el uso de método de finalizador.
constructors.declaration.grouping=Todos los constructores deben agruparse. El constructor anterior estaba en la línea número ''{0}''.
covariant.equals=equals covariante sin sobrescribir equals(java.lang.Object).
declaration.order.access=Definición de acceso a variable en orden incorrecto.
declaration.order.constructor=Definición de constructor en orden incorrecto.
Expand Down
Expand Up @@ -3,6 +3,7 @@ assignment.inner.avoid=Älä käytä sisäkkäisiä sijoituksia.
avoid.clone.method=Vältä klooni menetelmää.
avoid.double.brace.init=Vältä kaksinkertaista ahdintuen alustamista.
avoid.finalizer.method=Vältä Finalizer menetelmää.
constructors.declaration.grouping=Kaikki rakentajat tulee ryhmitellä yhteen. Edellinen rakentaja oli rivillä ''{0}''.
covariant.equals=covariant vastaa ilman painavaa tasavertaisten (java.lang.Object).
declaration.order.access=Muuttuja pääsy määritelmä väärässä järjestyksessä.
declaration.order.constructor=Rakentaja määritelmä väärässä järjestyksessä.
Expand Down