metrics/mendel: sort results in console display
[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 modelize
49
50 redef class ToolContext
51 var mendel_metrics_phase: Phase = new MendelMetricsPhase(self, null)
52 end
53
54 private class MendelMetricsPhase
55 super Phase
56 redef fun process_mainmodule(mainmodule, given_mmodules)
57 do
58 if not toolcontext.opt_mendel.value and not toolcontext.opt_all.value then return
59 var csv = toolcontext.opt_csv.value
60 var out = "{toolcontext.opt_dir.value or else "metrics"}/mendel"
61 out.mkdir
62
63 print toolcontext.format_h1("\n# Mendel metrics")
64
65 var vis = protected_visibility
66 var model = toolcontext.modelbuilder.model
67
68 var mclasses = new HashSet[MClass]
69 for mclass in model.mclasses do
70 if mclass.visibility < vis then continue
71 if mclass.is_interface then continue
72 mclasses.add(mclass)
73 end
74
75 var cnblp = new CNBLP(mainmodule, vis)
76 var cnvi = new CNVI(mainmodule)
77 var cnvs = new CNVS(mainmodule)
78
79 var metrics = new MetricSet
80 metrics.register(cnblp, cnvi, cnvs)
81 metrics.collect(mclasses)
82 if csv then metrics.to_csv.save("{out}/mendel.csv")
83
84 var threshold = cnblp.threshold
85 print toolcontext.format_h4("\tlarge mclasses (threshold: {threshold})")
86 for mclass in cnblp.sort do
87 var val = cnblp.values[mclass]
88 if val.to_f < threshold then break
89 print toolcontext.format_p("\t {mclass.name}: {val}")
90 end
91
92 threshold = cnvi.threshold
93 print toolcontext.format_h4("\tbudding mclasses (threshold: {threshold})")
94 for mclass in cnvi.sort do
95 var val = cnvi.values[mclass]
96 if val.to_f < threshold then break
97 print toolcontext.format_p("\t {mclass.name}: {val}")
98 end
99
100 threshold = cnvs.threshold
101 print toolcontext.format_h4("\tblooming mclasses (threshold: {threshold})")
102 for mclass in cnvs.sort do
103 var val = cnvs.values[mclass]
104 if val.to_f < threshold then break
105 print toolcontext.format_p("\t {mclass.name}: {val}")
106 end
107
108 if csv then
109 var csvh = new CsvDocument
110 csvh.format = new CsvFormat('"', ';', "\n")
111 csvh.header = ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
112 for mclass in mclasses do
113 var povr = mclass.is_pure_overrider(vis).object_id
114 var ovr = mclass.is_overrider(vis).object_id
115 var pext = mclass.is_pure_extender(vis).object_id
116 var ext = mclass.is_extender(vis).object_id
117 var pspe = mclass.is_pure_specializer(vis).object_id
118 var spe = mclass.is_pure_specializer(vis).object_id
119 var prep = mclass.is_pure_replacer(vis).object_id
120 var rep = mclass.is_replacer(vis).object_id
121 var eq = mclass.is_equal(vis).object_id
122 csvh.add_record(povr, ovr, pext, ext, pspe, spe, prep, rep, eq)
123 end
124 csvh.save("{out}/inheritance_behaviour.csv")
125 end
126 end
127 end
128
129 # Class Branch Mean Size
130 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
131 class CBMS
132 super MClassMetric
133 super FloatMetric
134 redef fun name do return "cbms"
135 redef fun desc do return "branch mean size, mean number of introduction available among ancestors"
136
137 var mainmodule: MModule
138 init(mainmodule: MModule) do self.mainmodule = mainmodule
139
140 redef fun collect(mclasses) do
141 for mclass in mclasses do
142 var totc = mclass.all_mproperties(mainmodule, protected_visibility).length
143 var ditc = mclass.in_hierarchy(mainmodule).depth
144 values[mclass] = totc.to_f / (ditc + 1).to_f
145 end
146 end
147 end
148
149 # Class Novelty Index
150 # cnvi = |LocS(class)| / cbms(parents(class))
151 class CNVI
152 super MClassMetric
153 super FloatMetric
154 redef fun name do return "cnvi"
155 redef fun desc do return "class novelty index, contribution of the class to its branch in term of introductions"
156
157 var mainmodule: MModule
158 init(mainmodule: MModule) do self.mainmodule = mainmodule
159
160 redef fun collect(mclasses) do
161 var cbms = new CBMS(mainmodule)
162 for mclass in mclasses do
163 # compute branch mean size
164 var parents = mclass.in_hierarchy(mainmodule).direct_greaters
165 if parents.length > 0 then
166 cbms.clear
167 cbms.collect(new HashSet[MClass].from(parents))
168 # compute class novelty index
169 var locc = mclass.local_mproperties(protected_visibility).length
170 values[mclass] = locc.to_f / cbms.avg
171 else
172 values[mclass] = 0.0
173 end
174 end
175 end
176 end
177
178 # Class Novelty Score
179 # cnvs = |LocS(class)| x nvi
180 class CNVS
181 super MClassMetric
182 super FloatMetric
183 redef fun name do return "cnvs"
184 redef fun desc do return "class novelty score, importance of the contribution of the class to its branch"
185
186 var mainmodule: MModule
187 init(mainmodule: MModule) do self.mainmodule = mainmodule
188
189 redef fun collect(mclasses) do
190 var cnvi = new CNVI(mainmodule)
191 cnvi.collect(mclasses)
192 for mclass in mclasses do
193 var locc = mclass.local_mproperties(protected_visibility).length
194 values[mclass] = cnvi.values[mclass] * locc.to_f
195 end
196 end
197 end
198
199 redef class MClass
200 # the set of redefition that call to super
201 fun extended_mproperties(min_visibility: MVisibility): Set[MProperty] do
202 var set = new HashSet[MProperty]
203 for mclassdef in mclassdefs do
204 for mpropdef in mclassdef.mpropdefs do
205 if mpropdef.mproperty.visibility < min_visibility then continue
206 if not mpropdef.has_supercall then continue
207 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
208 end
209 end
210 return set
211 end
212
213 # the set of redefition that do not call to super
214 fun overriden_mproperties(min_visibility: MVisibility): Set[MProperty] do
215 var set = new HashSet[MProperty]
216 for mclassdef in mclassdefs do
217 for mpropdef in mclassdef.mpropdefs do
218 if mpropdef.mproperty.visibility < min_visibility then continue
219 if mpropdef.has_supercall then continue
220 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
221 end
222 end
223 return set
224 end
225
226 # pure overriders contain only redefinitions
227 private fun is_pure_overrider(min_visibility: MVisibility): Bool do
228 var news = intro_mproperties(min_visibility).length
229 var locs = local_mproperties(min_visibility).length
230 if news == 0 and locs > 0 then return true
231 return false
232 end
233
234 # overriders contain more definitions than introductions
235 private fun is_overrider(min_visibility: MVisibility): Bool do
236 var rdfs = redef_mproperties(min_visibility).length
237 var news = intro_mproperties(min_visibility).length
238 var locs = local_mproperties(min_visibility).length
239 if rdfs >= news and locs > 0 then return true
240 return false
241 end
242
243 # pure extenders contain only introductions
244 private fun is_pure_extender(min_visibility: MVisibility): Bool do
245 var rdfs = redef_mproperties(min_visibility).length
246 var locs = local_mproperties(min_visibility).length
247 if rdfs == 0 and locs > 0 then return true
248 return false
249 end
250
251 # extenders contain more introduction than redefinitions
252 private fun is_extender(min_visibility: MVisibility): Bool do
253 var rdfs = redef_mproperties(min_visibility).length
254 var news = intro_mproperties(min_visibility).length
255 var locs = local_mproperties(min_visibility).length
256 if news > rdfs and locs > 0 then return true
257 return false
258 end
259
260 # pure specializers always call to super in its redefinitions
261 private fun is_pure_specializer(min_visibility: MVisibility): Bool do
262 var ovrs = overriden_mproperties(min_visibility).length
263 var rdfs = redef_mproperties(min_visibility).length
264 if ovrs == 0 and rdfs > 0 then return true
265 return false
266 end
267
268 # specializers have more redefinitions that call super than not calling it
269 private fun is_specializer(min_visibility: MVisibility): Bool do
270 var spcs = extended_mproperties(min_visibility).length
271 var ovrs = overriden_mproperties(min_visibility).length
272 var rdfs = redef_mproperties(min_visibility).length
273 if spcs > ovrs and rdfs > 0 then return true
274 return false
275 end
276
277 # pure replacers never call to super in its redefinitions
278 private fun is_pure_replacer(min_visibility: MVisibility): Bool do
279 var spcs = extended_mproperties(min_visibility).length
280 var rdfs = redef_mproperties(min_visibility).length
281 if spcs == 0 and rdfs > 0 then return true
282 return false
283 end
284
285 # replacers have less redefinitions that call super than not calling it
286 private fun is_replacer(min_visibility: MVisibility): Bool do
287 var spcs = extended_mproperties(min_visibility).length
288 var ovrs = overriden_mproperties(min_visibility).length
289 var rdfs = redef_mproperties(min_visibility).length
290 if ovrs > spcs and rdfs > 0 then return true
291 return false
292 end
293
294 # equals contain as redifinition than introduction
295 private fun is_equal(min_visibility: MVisibility): Bool do
296 var spcs = extended_mproperties(min_visibility).length
297 var ovrs = overriden_mproperties(min_visibility).length
298 var rdfs = redef_mproperties(min_visibility).length
299 if spcs == ovrs and rdfs > 0 then return true
300 return false
301 end
302 end
303