d09e76939066421a7da75608a9189d7d237e6f3e
[nit.git] / src / metrics / codesmells_metrics.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
15
16 module codesmells_metrics
17
18 import frontend
19 import metrics_base
20 import mclasses_metrics
21 import semantize
22 import method_analyze_metrics
23 import mclassdef_collect
24
25 redef class ToolContext
26 var codesmells_metrics_phase = new CodeSmellsMetricsPhase(self, null)
27 end
28
29 class CodeSmellsMetricsPhase
30 super Phase
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
35
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]
42
43 for mclass in mainmodule.flatten_mclass_hierarchy do
44 mclass_codesmell.collect(mclass.mclassdefs,self)
45 end
46 mclass_codesmell.print_top(10)
47 end
48
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
56 end
57 end
58
59 class BadConceptonController
60 # Code smell list
61 var bad_conception_elements = new Array[BadConceptionFinder]
62
63 # Print all element conception
64 fun print_all do
65 for bad_conception in bad_conception_elements do
66 bad_conception.print_all
67 end
68 end
69
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
75 end
76 end
77
78 # Collection
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)
84 end
85 end
86
87 fun sort: Array[BadConceptionFinder]
88 do
89 var res = bad_conception_elements
90 var sorter = new BadConceptionComparator
91 sorter.sort(res)
92 return res
93 end
94
95 fun get_numbers_of_elements(number : Int) : Array[BadConceptionFinder]do
96 var return_values = new Array[BadConceptionFinder]
97 var list = self.sort
98 var min = number
99 if list.length <= number*2 then min = list.length
100 for i in [0..min[ do
101 var t = list[list.length-i-1]
102 return_values.add(t)
103 end
104 return return_values
105 end
106 end
107
108 class BadConceptionFinder
109 var mclassdef: MClassDef
110 var array_badconception = new Array[BadConception]
111 var phase: CodeSmellsMetricsPhase
112
113 fun collect do
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)
121 end
122 end
123
124 fun print_all do
125 if array_badconception.length != 0 then
126 print "-----------"
127 print "{mclassdef.full_name}"
128 for bad_conception in array_badconception do
129 bad_conception.print_result
130 end
131 end
132 end
133 end
134
135 class BadConception
136 var phase: CodeSmellsMetricsPhase
137
138 # Name
139 fun name: String is abstract
140
141 # Description
142 fun desc: String is abstract
143
144 # Collection method
145 fun collect(mclassdef: MClassDef, model_builder: ModelBuilder): Bool is abstract
146
147 # Show results in console
148 fun print_result is abstract
149 end
150
151 class LargeClass
152 super BadConception
153 var number_attribut = 0
154
155 var number_method = 0
156
157 redef fun name do return "LARGC"
158
159 redef fun desc do return "Large class"
160
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
166 end
167
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)")
170 end
171 end
172
173 class LongParameterList
174 super BadConception
175 var bad_methods = new Array[MMethodDef]
176
177 redef fun name do return "LONGPL"
178
179 redef fun desc do return "Long parameter list"
180
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)
189 end
190 return bad_methods.not_empty
191 end
192
193
194 redef fun print_result do
195 print "{desc}:"
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"
200 end
201 end
202 end
203 end
204
205 class FeatureEnvy
206 super BadConception
207 var bad_methods = new Array[MMethodDef]
208
209 redef fun name do return "FEM"
210
211 redef fun desc do return "Feature envy"
212
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)
218 end
219 return bad_methods.not_empty
220 end
221
222 redef fun print_result do
223 print "{desc}:"
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}"
228 end
229 end
230 end
231 end
232
233 class LongMethod
234 super BadConception
235 var bad_methods = new Array[MMethodDef]
236
237 redef fun name do return "LONGMETH"
238
239 redef fun desc do return "Long method"
240
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)
246 end
247 return bad_methods.not_empty
248 end
249
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"
256 end
257 end
258 end
259 end
260
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
272 end
273 end
274 return counter.avg + counter.std_dev
275 end
276
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
282 end
283 return counter.avg + counter.std_dev
284 end
285
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
291 end
292 return counter.avg + counter.std_dev
293 end
294
295 fun get_avg_linenumber(model_builder: ModelBuilder): Float do
296 var methods_analyse_metrics = new Counter[MClassDef]
297 for mclassdef in mclassdefs do
298 var result = 0
299 var count = 0
300 for mmethoddef in call_analyze_methods(mclassdef,model_builder) do
301 result += mmethoddef.line_number
302 if mmethoddef.line_number == 0 then continue
303 count += 1
304 end
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
308 end
309 return methods_analyse_metrics.avg + methods_analyse_metrics.std_dev
310 end
311 end
312
313 class BadConceptionComparator
314 super Comparator
315 redef type COMPARED: BadConceptionFinder
316 redef fun compare(a,b) do return a.array_badconception.length <=> b.array_badconception.length
317 end