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

nitsmell : Add code smell detection #2445

Merged
merged 1 commit into from
Jun 12, 2017
Merged

nitsmell : Add code smell detection #2445

merged 1 commit into from
Jun 12, 2017

Conversation

Delja
Copy link
Contributor

@Delja Delja commented May 15, 2017

Adding code smell detection :

  • Long class
  • Long method parameter list
  • Long size method
  • Feature envy

Adding a visitor to analyse the contents of the methods

@privat privat requested a review from Morriar May 15, 2017 15:44
@privat
Copy link
Member

privat commented May 15, 2017

ok to test

Copy link
Contributor

@lbajolet lbajolet left a comment

Choose a reason for hiding this comment

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

Although there's a base that could be merged, there is still some work to do to arrive at that point, could you fix all the issues in your code and re-submit? Thanks!

module codesmells_metrics

# We usualy need specific phases
# NOTE: `frontend` is sufficent in most case (it is often too much)
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment feels out of place, is it a copy/paste from an example phase?

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module codesmells_metrics
Copy link
Contributor

Choose a reason for hiding this comment

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

The module header should be detached from the license, else nitdoc might consider it as part of the documentation

class Antipatterns
super BadConceptions
# create all Antipatterns
init do
Copy link
Contributor

Choose a reason for hiding this comment

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

This init could be removed if it is empty

end

#Collection
fun collect(classe : AClassdef)do
Copy link
Contributor

Choose a reason for hiding this comment

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

A common way to avoid synatx errors when using class is to use clazz instead, but if it bothers no one, classe is also OK, albeit a bit too french

Copy link
Contributor

Choose a reason for hiding this comment

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

For AClassdef, aclassdef and n_classdef (the latter for historical reasons) are the most common variable names used in the Nit compiler.


class BadConceptions
#Code smell list
var badConceptionElement = new Array[BadConception]
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally when declaring attributes or variables, the preferred Nit style is snake_case instead of camelCase

var badConceptionElement = new Array[BadConception]

# Print all element conception
fun printAll do
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel the print concerns should be handled by the nitmetrics command-line client instead of the library.
If it is the case, at some point we may add a dependance on console to add some command-line eye-candy, which would make no sense in the library itself

var visits = callvisiteMethodAnalyse(aclasse)

for visit in visits do
if visit.lineDetail.length > 30 then
Copy link
Contributor

Choose a reason for hiding this comment

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

Hard-coded magic number is hard-coded, could you move it to an attribute should we wish to change it at some point?

import mclasses_metrics
import semantize

fun callvisiteMethodAnalyse(aclasse : AClassdef) : Array[MethodAnalyse] do
Copy link
Contributor

Choose a reason for hiding this comment

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

fun analyze_methods? Or analyse_methods would be OK too, I'm not angry with en_GB.

end
end
end
end
Copy link
Contributor

Choose a reason for hiding this comment

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

Same newline issue here


# The body of the specific work.
# The main entry point is provided by `test_phase`,
# This function is then automatically (unless errors where found).
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems out of place too, copy/paste issue?
There's a typo here as well

@Delja Delja force-pushed the master branch 2 times, most recently from 5a4540f to 517a880 Compare May 31, 2017 18:38
Copy link
Member

@Morriar Morriar left a comment

Choose a reason for hiding this comment

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

A lot of changes required. But aside from the code the real problem is the theory behind it.

In my opinion, you really need more tests on this. At least to find the edge cases (like the one shown in test_prog.

# See the License for the specific language governing permissions and
# limitations under the License.

# Detect the code smells and antipatterns in the code.
Copy link
Member

Choose a reason for hiding this comment

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

If this is the documentation of the module it should be attached to the module declaration. Remove the blank line between them.



redef class ToolContext
var codesmells_metrics_phase: Phase = new CodeSmellsMetricsPhase(self, null)
Copy link
Member

Choose a reason for hiding this comment

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

I'm not convinced that the static type is really required here.



redef fun process_mainmodule(mainmodule, given_mmodules)
do
Copy link
Member

Choose a reason for hiding this comment

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

Most of your dos are inlined with the method declaration, keep it consistent.

redef fun process_mainmodule(mainmodule, given_mmodules)
do
print toolcontext.format_h1("--- Code Smells Metrics ---")
var mclazzs = mainmodule.flatten_mclass_hierarchy
Copy link
Member

Choose a reason for hiding this comment

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

Did you mean mclasses ?

print toolcontext.format_h1("--- Code Smells Metrics ---")
var mclazzs = mainmodule.flatten_mclass_hierarchy
var model_builder = toolcontext.modelbuilder
var model_view = model_builder.model.private_view
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 really good question. Do we take private/generated things into account when running mertrics?

-quit
Long method: Average 1 lines
Affected method:
-total_strengh has 2 lines
Copy link
Member

Choose a reason for hiding this comment

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

Well, the example itself shows how reliable this metric is... :/

I'm not convinced that a 2-lines method can really be viewed as large. Even if the average is 1 line.

*** CODE SMELLS METRICS ***
--- Code Smells Metrics ---
-------------------
Class: Character
Copy link
Member

Choose a reason for hiding this comment

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

You should display the class full name (full_name) or/and the class location.

Class: Character
Feature envy : true
Affected method:
-quit
Copy link
Member

Choose a reason for hiding this comment

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

Feature envy: A method accesses the data of another object more than its own data.

Here the quit code:

fun quit do
    career = null
end

I really don't understand the result.

-total_endurance has 2 lines
-total_intelligence has 2 lines
-------------------
Class: Character
Copy link
Member

Choose a reason for hiding this comment

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

If it appears twice for the same program, you are not talking about Class but Classdef

Class: Character
Feature envy : true
Affected method:
-hit_points
Copy link
Member

Choose a reason for hiding this comment

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

Again:

redef fun hit_points do return health

Here health is introduced by Character but in another mclassdef.

fun print_result is abstract
end


Copy link
Member

Choose a reason for hiding this comment

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

Caca


var number_method = 0

redef fun name do
Copy link
Member

Choose a reason for hiding this comment

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

Inlining


redef fun name do
return "LONGPL"
end
Copy link
Member

Choose a reason for hiding this comment

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

Caca

return result
end


Copy link
Member

Choose a reason for hiding this comment

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

Caca

end
end


Copy link
Member

Choose a reason for hiding this comment

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

Caca

number_attribut = mclassdef.collect_intro_and_redef_mattributes(model_builder.model.private_view).length
# get the number of methods and subtract the get and set of attibutes (numberAtribut*2)
number_method = mclassdef.collect_intro_and_redef_methods(model_builder.model.private_view).length - (number_attribut*2)
if number_method.to_f > phase.average_number_of_method and number_attribut.to_f > phase.average_number_of_attribute then
Copy link
Member

Choose a reason for hiding this comment

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

return "the big ass condition"

bad_methods.add(meth)
result = true
end
return result
Copy link
Member

Choose a reason for hiding this comment

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

return bad_methods.not_empty

result = true
bad_methods.add(mmethoddef)
end
return result
Copy link
Member

Choose a reason for hiding this comment

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

return bad_methods.not_empty

return "Long method"
end

redef fun collect(mclassdef, model_builder): Bool do
Copy link
Member

Choose a reason for hiding this comment

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

Factorize this method

var result = false
var mmethoddefs = call_analyze_methods(mclassdef,model_builder)
for mmethoddef in mmethoddefs do
if mmethoddef.line_number <= phase.average_number_of_lines.to_i then continue
Copy link
Member

Choose a reason for hiding this comment

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

Redefine this part from the factorization

@@ -0,0 +1,2 @@
Error: cannot find module `../TestNitsmells/LargeClass/`.
Copy link
Member

Choose a reason for hiding this comment

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

LOL

@@ -0,0 +1,2 @@
Error: cannot find module `../TestNitsmells/LongMethod/`.
Copy link
Member

Choose a reason for hiding this comment

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

LOL

@@ -0,0 +1,2 @@
Error: cannot find module `../TestNitsmells/LongParameterList/`.
Copy link
Member

Choose a reason for hiding this comment

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

LOL

return counter.avg + counter.std_dev
end

fun get_avg_LineNumber(model_builder: ModelBuilder): Float do
Copy link
Member

Choose a reason for hiding this comment

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

Weird case

@privat
Copy link
Member

privat commented Jun 6, 2017

tests are OK (modulo Java & race conditions).
A final(?) review is requested.

@Delja
Copy link
Contributor Author

Delja commented Jun 6, 2017

I just saw one last change. I need to replace the mclassdef.mpropdefs by mclassdef.collect ...

Long class
Long method parameter list
Long size method
Feature envy
Adding a visitor to analyse the contents of the methods

Signed-off-by: Florian Deljarry <deljarry.florian@gmail.com>
@Morriar Morriar dismissed lbajolet’s stale review June 9, 2017 17:04

Changes applied

@Morriar
Copy link
Member

Morriar commented Jun 9, 2017

Good job, ok to merge!

privat added a commit that referenced this pull request Jun 12, 2017
Adding code smell detection :

- Long class
- Long method parameter list
- Long size method
- Feature envy

Adding a visitor to analyse the contents of the methods

Pull-Request: #2445
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Jean-Christophe Beaupré <jcbrinfo.public@gmail.com>
@privat privat merged commit 3686625 into nitlang:master Jun 12, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants