1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 # Detect the code smells and antipatterns in the code.
16 module codesmells_metrics
20 import mclasses_metrics
22 import method_analyze_metrics
23 import mclassdef_collect
25 redef class ToolContext
26 var codesmells_metrics_phase
= new CodeSmellsMetricsPhase(self, null)
29 class CodeSmellsMetricsPhase
31 var average_number_of_lines
= 0.0
32 var average_number_of_parameter
= 0.0
33 var average_number_of_method
= 0.0
34 var average_number_of_attribute
= 0.0
36 redef fun process_mainmodule
(mainmodule
, given_mmodules
) do
37 print toolcontext
.format_h1
("--- Code Smells Metrics ---")
38 self.set_all_average_metrics
39 var mclass_codesmell
= new BadConceptonController
40 var collect
= new Counter[MClassDef]
41 var mclassdefs
= new Array[MClassDef]
43 for mclass
in mainmodule
.flatten_mclass_hierarchy
do
44 mclass_codesmell
.collect
(mclass
.mclassdefs
,self)
46 mclass_codesmell
.print_top
(10)
49 fun set_all_average_metrics
do
50 var model_builder
= toolcontext
.modelbuilder
51 var model_view
= model_builder
.model
.private_view
52 self.average_number_of_lines
= model_view
.get_avg_linenumber
(model_builder
)
53 self.average_number_of_parameter
= model_view
.get_avg_parameter
54 self.average_number_of_method
= model_view
.get_avg_method
55 self.average_number_of_attribute
= model_view
.get_avg_attribut
59 class BadConceptonController
61 var bad_conception_elements
= new Array[BadConceptionFinder]
63 # Print all element conception
65 for bad_conception
in bad_conception_elements
do
66 bad_conception
.print_all
70 # Print number of top element conception
71 fun print_top
(number
: Int) do
72 var test
= self.get_numbers_of_elements
(number
)
73 for bad_conception
in test
do
74 bad_conception
.print_all
79 fun collect
(mclassdefs
: Array[MClassDef],phase
: CodeSmellsMetricsPhase) do
80 for mclassdef
in mclassdefs
do
81 var bad_conception_class
= new BadConceptionFinder(mclassdef
,phase
)
82 bad_conception_class
.collect
83 bad_conception_elements
.add
(bad_conception_class
)
87 fun sort
: Array[BadConceptionFinder]
89 var res
= bad_conception_elements
90 var sorter
= new BadConceptionComparator
95 fun get_numbers_of_elements
(number
: Int) : Array[BadConceptionFinder]do
96 var return_values
= new Array[BadConceptionFinder]
99 if list
.length
<= number
*2 then min
= list
.length
101 var t
= list
[list
.length-i-1
]
108 class BadConceptionFinder
109 var mclassdef
: MClassDef
110 var array_badconception
= new Array[BadConception]
111 var phase
: CodeSmellsMetricsPhase
114 var bad_conception_elements
= new Array[BadConception]
115 bad_conception_elements
.add
(new LargeClass(phase
))
116 bad_conception_elements
.add
(new LongParameterList(phase
))
117 bad_conception_elements
.add
(new FeatureEnvy(phase
))
118 bad_conception_elements
.add
(new LongMethod(phase
))
119 for bad_conception_element
in bad_conception_elements
do
120 if bad_conception_element
.collect
(self.mclassdef
,phase
.toolcontext
.modelbuilder
) then array_badconception
.add
(bad_conception_element
)
125 if array_badconception
.length
!= 0 then
127 print
"{mclassdef.full_name}"
128 for bad_conception
in array_badconception
do
129 bad_conception
.print_result
136 var phase
: CodeSmellsMetricsPhase
139 fun name
: String is abstract
142 fun desc
: String is abstract
145 fun collect
(mclassdef
: MClassDef, model_builder
: ModelBuilder): Bool is abstract
147 # Show results in console
148 fun print_result
is abstract
153 var number_attribut
= 0
155 var number_method
= 0
157 redef fun name
do return "LARGC"
159 redef fun desc
do return "Large class"
161 redef fun collect
(mclassdef
, model_builder
): Bool do
162 number_attribut
= mclassdef
.collect_intro_and_redef_mattributes
(model_builder
.model
.private_view
).length
163 # get the number of methods (subtract the get and set of attibutes with (numberAtribut*2))
164 number_method
= mclassdef
.collect_intro_and_redef_methods
(model_builder
.model
.private_view
).length
165 return number_method
.to_f
> phase
.average_number_of_method
and number_attribut
.to_f
> phase
.average_number_of_attribute
168 redef fun print_result
do
169 print phase
.toolcontext
.format_h2
("{desc}: {number_attribut} attributes and {number_method} methods ({phase.average_number_of_attribute}A {phase.average_number_of_method}M Average)")
173 class LongParameterList
175 var bad_methods
= new Array[MMethodDef]
177 redef fun name
do return "LONGPL"
179 redef fun desc
do return "Long parameter list"
181 redef fun collect
(mclassdef
, model_builder
): Bool do
182 for meth
in mclassdef
.collect_intro_and_redef_mpropdefs
(model_builder
.model
.private_view
) do
183 # check if the property is a method definition
184 if not meth
isa MMethodDef then continue
185 # Check if method has a signature
186 if meth
.msignature
== null then continue
187 if meth
.msignature
.mparameters
.length
<= 4 then continue
188 bad_methods
.add
(meth
)
190 return bad_methods
.not_empty
194 redef fun print_result
do
196 if bad_methods
.length
>= 1 then
197 print
" Affected method(s):"
198 for method
in bad_methods
do
199 print
" -{method.name} has {method.msignature.mparameters.length} parameters"
207 var bad_methods
= new Array[MMethodDef]
209 redef fun name
do return "FEM"
211 redef fun desc
do return "Feature envy"
213 redef fun collect
(mclassdef
, model_builder
): Bool do
214 var mmethoddefs
= call_analyze_methods
(mclassdef
,model_builder
)
215 for mmethoddef
in mmethoddefs
do
216 if mmethoddef
.total_extern_call
<= mmethoddef
.total_self_call
then continue
217 bad_methods
.add
(mmethoddef
)
219 return bad_methods
.not_empty
222 redef fun print_result
do
224 if bad_methods
.length
>= 1 then
225 print
" Affected method(s):"
226 for method
in bad_methods
do
227 print
" -{method.name} {method.total_self_call}/{method.total_call}"
235 var bad_methods
= new Array[MMethodDef]
237 redef fun name
do return "LONGMETH"
239 redef fun desc
do return "Long method"
241 redef fun collect
(mclassdef
, model_builder
): Bool do
242 var mmethoddefs
= call_analyze_methods
(mclassdef
,model_builder
)
243 for mmethoddef
in mmethoddefs
do
244 if mmethoddef
.line_number
<= phase
.average_number_of_lines
.to_i
then continue
245 bad_methods
.add
(mmethoddef
)
247 return bad_methods
.not_empty
250 redef fun print_result
do
251 print
"{desc}: Average {phase.average_number_of_lines.to_i} lines"
252 if bad_methods
.length
>= 1 then
253 print
" Affected method(s):"
254 for method
in bad_methods
do
255 print
" -{method.name} has {method.line_number} lines"
261 redef class ModelView
262 fun get_avg_parameter
: Float do
263 var counter
= new Counter[MMethodDef]
264 for mclassdef
in mclassdefs
do
265 for method
in mclassdef
.collect_intro_and_redef_mpropdefs
(self) do
266 # check if the property is a method definition
267 if not method
isa MMethodDef then continue
268 #Check if method has a signature
269 if method
.msignature
== null then continue
270 if method
.msignature
.mparameters
.length
== 0 then continue
271 counter
[method
] = method
.msignature
.mparameters
.length
274 return counter
.avg
+ counter
.std_dev
277 fun get_avg_attribut
: Float do
278 var counter
= new Counter[MClassDef]
279 for mclassdef
in mclassdefs
do
280 var number_attributs
= mclassdef
.collect_intro_and_redef_mattributes
(self).length
281 if number_attributs
!= 0 then counter
[mclassdef
] = number_attributs
283 return counter
.avg
+ counter
.std_dev
286 fun get_avg_method
: Float do
287 var counter
= new Counter[MClassDef]
288 for mclassdef
in mclassdefs
do
289 var number_methodes
= mclassdef
.collect_intro_and_redef_methods
(self).length
290 if number_methodes
!= 0 then counter
[mclassdef
] = number_methodes
292 return counter
.avg
+ counter
.std_dev
295 fun get_avg_linenumber
(model_builder
: ModelBuilder): Float do
296 var methods_analyse_metrics
= new Counter[MClassDef]
297 for mclassdef
in mclassdefs
do
300 for mmethoddef
in call_analyze_methods
(mclassdef
,model_builder
) do
301 result
+= mmethoddef
.line_number
302 if mmethoddef
.line_number
== 0 then continue
305 if not mclassdef
.collect_local_mproperties
(self).length
!= 0 then continue
306 if count
== 0 then continue
307 methods_analyse_metrics
[mclassdef
] = (result
/count
).to_i
309 return methods_analyse_metrics
.avg
+ methods_analyse_metrics
.std_dev
313 class BadConceptionComparator
315 redef type COMPARED: BadConceptionFinder
316 redef fun compare
(a
,b
) do return a
.array_badconception
.length
<=> b
.array_badconception
.length