metrics: add some Mendel metrics for classes
[nit.git] / src / metrics / mendel_metrics.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # The mndel model helps to understand class hierarchies
18 #
19 # It provides metrics to extract interesting classes:
20 #
21 # * Large classes that have a lot of local mproperties
22 # * Budding classes that provide more mproperties than their superclasses
23 # * Blooming classes that are both large and budding
24 #
25 # Also, this model helps to understand inheritance behviours between classes.
26 # It provide metrics to categorize classes as:
27 #
28 # * pure overriders that contain only redefinitions
29 # * overriders that contain more definitions than introductions
30 # * pure extenders that contain only introductions
31 # * extenders that contain more introduction than redefinitions
32 #
33 # Finally, this model can characterize overriding behaviors
34 #
35 # * pure specializers that always call to super in its redefinitions
36 # * specializers that have more redefinitions that call super than not calling it
37 # * pure replacers that never call to super in its redefinitions
38 # * replacers that have less redefinitions that call super than not calling it
39 #
40 # For more details see
41 # Mendel: A Model, Metrics and Rules to Understan Class Hierarchies
42 # S. Denier and Y. Gueheneuc
43 # in Proceedings of the 16th IEEE International Conference on Program Comprehension (OCPC'08)
44 module mendel_metrics
45
46 import metrics_base
47 import mclasses_metrics
48 import mmodules_metrics
49 import modelize
50
51 redef class ToolContext
52 # Compute MENDEL metrics.
53 #
54 # See `mendel_metrics` module documentation.
55 var mendel_metrics_phase: Phase = new MendelMetricsPhase(self, null)
56 end
57
58 private class MendelMetricsPhase
59 super Phase
60 redef fun process_mainmodule(mainmodule, given_mmodules)
61 do
62 if not toolcontext.opt_mendel.value and not toolcontext.opt_all.value then return
63 var csv = toolcontext.opt_csv.value
64 var out = "{toolcontext.opt_dir.value or else "metrics"}/mendel"
65 out.mkdir
66
67 print toolcontext.format_h1("\n# Mendel metrics")
68
69 var vis = protected_visibility
70 var model = toolcontext.modelbuilder.model
71
72 var mclasses = new HashSet[MClass]
73 for mclass in model.mclasses do
74 if mclass.visibility < vis then continue
75 if mclass.is_interface then continue
76 mclasses.add(mclass)
77 end
78
79 var cnblp = new CNBLP(mainmodule, vis)
80 var cnvi = new CNVI(mainmodule)
81 var cnvs = new CNVS(mainmodule)
82
83 var metrics = new MetricSet
84 metrics.register(cnblp, cnvi, cnvs)
85 metrics.collect(mclasses)
86 if csv then metrics.to_csv.save("{out}/mendel.csv")
87
88 var threshold = cnblp.threshold
89 print toolcontext.format_h4("\tlarge mclasses (threshold: {threshold})")
90 for mclass in cnblp.sort do
91 var val = cnblp.values[mclass]
92 if val.to_f < threshold then break
93 print toolcontext.format_p("\t {mclass.name}: {val}")
94 end
95
96 threshold = cnvi.threshold
97 print toolcontext.format_h4("\tbudding mclasses (threshold: {threshold})")
98 for mclass in cnvi.sort do
99 var val = cnvi.values[mclass]
100 if val.to_f < threshold then break
101 print toolcontext.format_p("\t {mclass.name}: {val}")
102 end
103
104 threshold = cnvs.threshold
105 print toolcontext.format_h4("\tblooming mclasses (threshold: {threshold})")
106 for mclass in cnvs.sort do
107 var val = cnvs.values[mclass]
108 if val.to_f < threshold then break
109 print toolcontext.format_p("\t {mclass.name}: {val}")
110 end
111
112 if csv then
113 var csvh = new CsvDocument
114 csvh.format = new CsvFormat('"', ';', "\n")
115 csvh.header = ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
116 for mclass in mclasses do
117 var povr = mclass.is_pure_overrider(vis).object_id
118 var ovr = mclass.is_overrider(vis).object_id
119 var pext = mclass.is_pure_extender(vis).object_id
120 var ext = mclass.is_extender(vis).object_id
121 var pspe = mclass.is_pure_specializer(vis).object_id
122 var spe = mclass.is_pure_specializer(vis).object_id
123 var prep = mclass.is_pure_replacer(vis).object_id
124 var rep = mclass.is_replacer(vis).object_id
125 var eq = mclass.is_equal(vis).object_id
126 csvh.add_record(povr, ovr, pext, ext, pspe, spe, prep, rep, eq)
127 end
128 csvh.save("{out}/inheritance_behaviour.csv")
129 end
130 end
131 end
132
133 # Class Branch Mean Size
134 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
135 class CBMS
136 super MClassMetric
137 super FloatMetric
138 redef fun name do return "cbms"
139 redef fun desc do return "branch mean size, mean number of introduction available among ancestors"
140
141 # Mainmodule used to compute class hierarchy.
142 var mainmodule: MModule
143
144 redef fun collect(mclasses) do
145 for mclass in mclasses do
146 var totc = mclass.collect_accessible_mproperties(protected_visibility).length
147 var ditc = mclass.in_hierarchy(mainmodule).depth
148 values[mclass] = totc.to_f / (ditc + 1).to_f
149 end
150 end
151 end
152
153 # Module Branch Mean Size
154 # mbms(module) = |mclassdefs(module)| / (DIT(module) + 1)
155 class MBMS
156 super MModuleMetric
157 super FloatMetric
158 redef fun name do return "mbms"
159 redef fun desc do return "branch mean size, mean number of class definition available among ancestors"
160
161 redef fun collect(mmodules) do
162 for mmodule in mmodules do
163 var totc = mmodule.collect_intro_mclassdefs(protected_visibility).length
164 totc += mmodule.collect_redef_mclassdefs(protected_visibility).length
165 var ditc = mmodule.in_importation.depth
166 values[mmodule] = totc.to_f / (ditc + 1).to_f
167 end
168 end
169 end
170
171 # Class Novelty Index
172 # cnvi = |LocS(class)| / cbms(parents(class))
173 class CNVI
174 super MClassMetric
175 super FloatMetric
176 redef fun name do return "cnvi"
177 redef fun desc do return "class novelty index, contribution of the class to its branch in term of introductions"
178
179 # Mainmodule used to compute class hierarchy.
180 var mainmodule: MModule
181
182 redef fun collect(mclasses) do
183 var cbms = new CBMS(mainmodule)
184 for mclass in mclasses do
185 # compute branch mean size
186 var parents = mclass.in_hierarchy(mainmodule).direct_greaters
187 if parents.length > 0 then
188 cbms.clear
189 cbms.collect(new HashSet[MClass].from(parents))
190 # compute class novelty index
191 var locc = mclass.collect_accessible_mproperties(protected_visibility).length
192 values[mclass] = locc.to_f / cbms.avg
193 else
194 values[mclass] = 0.0
195 end
196 end
197 end
198 end
199
200 # Module Novelty Index
201 # mnvi = |LocS(module)| / mbms(parents(module))
202 class MNVI
203 super MModuleMetric
204 super FloatMetric
205 redef fun name do return "mnvi"
206 redef fun desc do return "module novelty index, contribution of the module to its branch in term of introductions"
207
208 redef fun collect(mmodules) do
209 var mbms = new MBMS
210 for mmodule in mmodules do
211 # compute branch mean size
212 var parents = mmodule.in_importation.direct_greaters
213 if parents.length > 0 then
214 mbms.clear
215 mbms.collect(new HashSet[MModule].from(parents))
216 # compute module novelty index
217 var locc = mmodule.collect_intro_mclassdefs(protected_visibility).length
218 locc += mmodule.collect_redef_mclassdefs(protected_visibility).length
219 values[mmodule] = locc.to_f / mbms.avg
220 else
221 values[mmodule] = 0.0
222 end
223 end
224 end
225 end
226
227 # Class Novelty Score
228 # cnvs = |LocS(class)| x nvi
229 class CNVS
230 super MClassMetric
231 super FloatMetric
232 redef fun name do return "cnvs"
233 redef fun desc do return "class novelty score, importance of the contribution of the class to its branch"
234
235 # Mainmodule used to compute class hierarchy.
236 var mainmodule: MModule
237
238 redef fun collect(mclasses) do
239 var cnvi = new CNVI(mainmodule)
240 cnvi.collect(mclasses)
241 for mclass in mclasses do
242 var locc = mclass.collect_local_mproperties(protected_visibility).length
243 values[mclass] = cnvi.values[mclass] * locc.to_f
244 end
245 end
246 end
247
248 # Module Novelty Score
249 # mnvs = |LocS(module)| x nvi
250 class MNVS
251 super MModuleMetric
252 super FloatMetric
253 redef fun name do return "mnvs"
254 redef fun desc do return "module novelty score, importance of the contribution of the module to its branch"
255
256 redef fun collect(mmodules) do
257 var mnvi = new MNVI
258 mnvi.collect(mmodules)
259 for mmodule in mmodules do
260 var locc = mmodule.collect_intro_mclassdefs(protected_visibility).length
261 locc += mmodule.collect_redef_mclassdefs(protected_visibility).length
262 values[mmodule] = mnvi.values[mmodule] * locc.to_f
263 end
264 end
265 end
266
267 redef class MClass
268 # the set of redefition that call to super
269 fun extended_mproperties(min_visibility: MVisibility): Set[MProperty] do
270 var set = new HashSet[MProperty]
271 for mclassdef in mclassdefs do
272 for mpropdef in mclassdef.mpropdefs do
273 if mpropdef.mproperty.visibility < min_visibility then continue
274 if not mpropdef.has_supercall then continue
275 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
276 end
277 end
278 return set
279 end
280
281 # the set of redefition that do not call to super
282 fun overriden_mproperties(min_visibility: MVisibility): Set[MProperty] do
283 var set = new HashSet[MProperty]
284 for mclassdef in mclassdefs do
285 for mpropdef in mclassdef.mpropdefs do
286 if mpropdef.mproperty.visibility < min_visibility then continue
287 if mpropdef.has_supercall then continue
288 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
289 end
290 end
291 return set
292 end
293
294 # pure overriders contain only redefinitions
295 private fun is_pure_overrider(min_visibility: MVisibility): Bool do
296 var news = collect_intro_mproperties(min_visibility).length
297 var locs = collect_local_mproperties(min_visibility).length
298 if news == 0 and locs > 0 then return true
299 return false
300 end
301
302 # overriders contain more definitions than introductions
303 private fun is_overrider(min_visibility: MVisibility): Bool do
304 var rdfs = collect_redef_mproperties(min_visibility).length
305 var news = collect_intro_mproperties(min_visibility).length
306 var locs = collect_local_mproperties(min_visibility).length
307 if rdfs >= news and locs > 0 then return true
308 return false
309 end
310
311 # pure extenders contain only introductions
312 private fun is_pure_extender(min_visibility: MVisibility): Bool do
313 var rdfs = collect_redef_mproperties(min_visibility).length
314 var locs = collect_local_mproperties(min_visibility).length
315 if rdfs == 0 and locs > 0 then return true
316 return false
317 end
318
319 # extenders contain more introduction than redefinitions
320 private fun is_extender(min_visibility: MVisibility): Bool do
321 var rdfs = collect_redef_mproperties(min_visibility).length
322 var news = collect_intro_mproperties(min_visibility).length
323 var locs = collect_local_mproperties(min_visibility).length
324 if news > rdfs and locs > 0 then return true
325 return false
326 end
327
328 # pure specializers always call to super in its redefinitions
329 private fun is_pure_specializer(min_visibility: MVisibility): Bool do
330 var ovrs = overriden_mproperties(min_visibility).length
331 var rdfs = collect_redef_mproperties(min_visibility).length
332 if ovrs == 0 and rdfs > 0 then return true
333 return false
334 end
335
336 # specializers have more redefinitions that call super than not calling it
337 private fun is_specializer(min_visibility: MVisibility): Bool do
338 var spcs = extended_mproperties(min_visibility).length
339 var ovrs = overriden_mproperties(min_visibility).length
340 var rdfs = collect_redef_mproperties(min_visibility).length
341 if spcs > ovrs and rdfs > 0 then return true
342 return false
343 end
344
345 # pure replacers never call to super in its redefinitions
346 private fun is_pure_replacer(min_visibility: MVisibility): Bool do
347 var spcs = extended_mproperties(min_visibility).length
348 var rdfs = collect_redef_mproperties(min_visibility).length
349 if spcs == 0 and rdfs > 0 then return true
350 return false
351 end
352
353 # replacers have less redefinitions that call super than not calling it
354 private fun is_replacer(min_visibility: MVisibility): Bool do
355 var spcs = extended_mproperties(min_visibility).length
356 var ovrs = overriden_mproperties(min_visibility).length
357 var rdfs = collect_redef_mproperties(min_visibility).length
358 if ovrs > spcs and rdfs > 0 then return true
359 return false
360 end
361
362 # equals contain as redifinition than introduction
363 private fun is_equal(min_visibility: MVisibility): Bool do
364 var spcs = extended_mproperties(min_visibility).length
365 var ovrs = overriden_mproperties(min_visibility).length
366 var rdfs = collect_redef_mproperties(min_visibility).length
367 if spcs == ovrs and rdfs > 0 then return true
368 return false
369 end
370 end