The Mendel model helps to understand class hierarchies.

It provides metrics to extract interesting classes:

  • Large classes that have a lot of local mproperties
  • Budding classes that provide more mproperties than their superclasses
  • Blooming classes that are both large and budding

Also, this model helps to understand inheritance behviours between classes. It provide metrics to categorize classes as:

  • pure overriders that contain only redefinitions
  • overriders that contain more definitions than introductions
  • pure extenders that contain only introductions
  • extenders that contain more introduction than redefinitions

Finally, this model can characterize overriding behaviors

  • pure specializers that always call to super in its redefinitions
  • specializers that have more redefinitions that call super than not calling it
  • pure replacers that never call to super in its redefinitions
  • replacers that have less redefinitions that call super than not calling it

For more details see “Mendel: A Model, Metrics and Rules to Understand Class Hierarchies,” by S. Denier and Y. Gueheneuc, in Proceedings of the 16th IEEE International Conference on Program Comprehension (OCPC'08).

Introduced classes

class CBMS

nitc :: CBMS

Class Branch Mean Size
class CNVI

nitc :: CNVI

Class Novelty Index
class CNVS

nitc :: CNVS

Class Novelty Score
class MBMS

nitc :: MBMS

Module Branch Mean Size
class MNVI

nitc :: MNVI

Module Novelty Index
class MNVS

nitc :: MNVS

Module Novelty Score

Redefined classes

redef class MClass

nitc :: mendel_metrics $ MClass

A named class
redef class ToolContext

nitc :: mendel_metrics $ ToolContext

Global context for tools

All class definitions

class CBMS

nitc $ CBMS

Class Branch Mean Size
class CNVI

nitc $ CNVI

Class Novelty Index
class CNVS

nitc $ CNVS

Class Novelty Score
class MBMS

nitc $ MBMS

Module Branch Mean Size
redef class MClass

nitc :: mendel_metrics $ MClass

A named class
class MNVI

nitc $ MNVI

Module Novelty Index
class MNVS

nitc $ MNVS

Module Novelty Score
redef class ToolContext

nitc :: mendel_metrics $ ToolContext

Global context for tools
package_diagram nitc::mendel_metrics mendel_metrics nitc::mclasses_metrics mclasses_metrics nitc::mendel_metrics->nitc::mclasses_metrics nitc::mmodules_metrics mmodules_metrics nitc::mendel_metrics->nitc::mmodules_metrics nitc\>modelize\> modelize nitc::mendel_metrics->nitc\>modelize\> nitc::metrics_base metrics_base nitc::mclasses_metrics->nitc::metrics_base nitc::model_collect model_collect nitc::mclasses_metrics->nitc::model_collect nitc::mmodules_metrics->nitc::metrics_base nitc::mmodules_metrics->nitc::model_collect nitc nitc nitc\>modelize\>->nitc ...nitc::metrics_base ... ...nitc::metrics_base->nitc::metrics_base ...nitc::model_collect ... ...nitc::model_collect->nitc::model_collect ...nitc ... ...nitc->nitc nitc::metrics metrics nitc::metrics->nitc::mendel_metrics nitc::nitmetrics nitmetrics nitc::nitmetrics->nitc::metrics nitc::api_metrics api_metrics nitc::api_metrics->nitc::metrics nitc::nitmetrics... ... nitc::nitmetrics...->nitc::nitmetrics nitc::api_metrics... ... nitc::api_metrics...->nitc::api_metrics

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module annotation

nitc :: annotation

Management and utilities on annotations
module array

core :: array

This module introduces the standard array structure.
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module console

console :: console

Defines some ANSI Terminal Control Escape Sequences.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module counter

counter :: counter

Simple numerical statistical analysis and presentation
module csv

csv :: csv

CSV document handling.
module digraph

graph :: digraph

Implementation of directed graphs, also called digraphs.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module ini

ini :: ini

Read and write INI configuration files
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module lexer

nitc :: lexer

Lexer and its tokens.
module lexer_work

nitc :: lexer_work

Internal algorithm and data structures for the Nit lexer
module list

core :: list

This module handle double linked lists
module literal

nitc :: literal

Parsing of literal values in the abstract syntax tree.
module loader

nitc :: loader

Loading of Nit source files
module location

nitc :: location

Nit source-file and locations in source-file
module math

core :: math

Mathematical operations
module mdoc

nitc :: mdoc

Documentation of model entities
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module metrics_base

nitc :: metrics_base

Helpers for various statistics tools.
module mmodule

nitc :: mmodule

modules and module hierarchies in the metamodel
module mmodule_data

nitc :: mmodule_data

Define and retrieve data in modules
module model

nitc :: model

Classes, types and properties
module model_base

nitc :: model_base

The abstract concept of model and related common things
module model_collect

nitc :: model_collect

Collect things from the model.
module model_examples

nitc :: model_examples

Examples for Model entities
module modelbuilder_base

nitc :: modelbuilder_base

Load nit source files and build the associated model
module modelize_class

nitc :: modelize_class

Analysis and verification of class definitions to instantiate model element
module modelize_property

nitc :: modelize_property

Analysis and verification of property definitions to instantiate model element
module more_collections

more_collections :: more_collections

Highly specific, but useful, collections-related classes.
module mpackage

nitc :: mpackage

Modelisation of a Nit package
module native

core :: native

Native structures for text and bytes
module nitpm_shared

nitc :: nitpm_shared

Services related to the Nit package manager
module numeric

core :: numeric

Advanced services for Numeric types
module opts

opts :: opts

Management of options on the command line
module ordered_tree

ordered_tree :: ordered_tree

Manipulation and presentation of ordered trees.
module parse_annotations

nitc :: parse_annotations

Simple annotation parsing
module parser

nitc :: parser

Parser.
module parser_nodes

nitc :: parser_nodes

AST nodes of the Nit language
module parser_prod

nitc :: parser_prod

Production AST nodes full definition.
module parser_work

nitc :: parser_work

Internal algorithm and data structures for the Nit parser
module phase

nitc :: phase

Phases of the processing of nit programs
module poset

poset :: poset

Pre order sets and partial order set (ie hierarchies)
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module tables

nitc :: tables

Module that interfaces the parsing tables.
module template

template :: template

Basic template system
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module toolcontext

nitc :: toolcontext

Common command-line tool infrastructure than handle options and error messages
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O
module version

nitc :: version

This file was generated by git-gen-version.sh

Parents

module mclasses_metrics

nitc :: mclasses_metrics

Collect common metrics about mclasses
module mmodules_metrics

nitc :: mmodules_metrics

Collect common metrics about modules
module modelize

nitc :: modelize

Create a model from nit source files

Children

module metrics

nitc :: metrics

Various statistics about Nit models and programs

Descendants

module a_star-m

a_star-m

module api

nitc :: api

Components required to build a web server about the nit model.
module nitmetrics

nitc :: nitmetrics

A program that collects various metrics on nit programs and libraries
module nitweb

nitc :: nitweb

Runs a webserver based on nitcorn that render things from model.
# The Mendel model helps to understand class hierarchies.
#
# It provides metrics to extract interesting classes:
#
# * Large classes that have a lot of local mproperties
# * Budding classes that provide more mproperties than their superclasses
# * Blooming classes that are both large and budding
#
# Also, this model helps to understand inheritance behviours between classes.
# It provide metrics to categorize classes as:
#
# * pure overriders that contain only redefinitions
# * overriders that contain more definitions than introductions
# * pure extenders that contain only introductions
# * extenders that contain more introduction than redefinitions
#
# Finally, this model can characterize overriding behaviors
#
# * pure specializers that always call to super in its redefinitions
# * specializers that have more redefinitions that call super than not calling it
# * pure replacers that never call to super in its redefinitions
# * replacers that have less redefinitions that call super than not calling it
#
# For more details see
# “Mendel: A Model, Metrics and Rules to Understand Class Hierarchies,”
# by S. Denier and Y. Gueheneuc,
# in *Proceedings of the 16th IEEE International Conference on Program Comprehension* (OCPC'08).
module mendel_metrics

import metrics_base
import mclasses_metrics
import mmodules_metrics
import modelize

redef class ToolContext
	# Compute MENDEL metrics.
	#
	# See `mendel_metrics` module documentation.
	var mendel_metrics_phase: Phase = new MendelMetricsPhase(self, null)
end

private class MendelMetricsPhase
	super Phase
	redef fun process_mainmodule(mainmodule, given_mmodules)
	do
		if not toolcontext.opt_mendel.value and not toolcontext.opt_all.value then return
		var csv = toolcontext.opt_csv.value
		var out = "{toolcontext.opt_dir.value or else "metrics"}/mendel"
		out.mkdir

		print toolcontext.format_h1("\n# Mendel metrics")

		var model = toolcontext.modelbuilder.model
		var filter = new ModelFilter(min_visibility = protected_visibility)

		var mclasses = new HashSet[MClass]
		for mclass in model.collect_mclasses(filter) do
			if mclass.is_interface then continue
			mclasses.add(mclass)
		end

		var cnblp = new CNBLP(model, mainmodule, filter)
		var cnvi = new CNVI(model, mainmodule, filter)
		var cnvs = new CNVS(model, mainmodule, filter)

		var metrics = new MetricSet
		metrics.register(cnblp, cnvi, cnvs)
		metrics.collect(mclasses)
		if csv then metrics.to_csv.write_to_file("{out}/mendel.csv")

		var threshold = cnblp.threshold
		print toolcontext.format_h4("\tlarge mclasses (threshold: {threshold})")
		for mclass in cnblp.sort do
			var val = cnblp.values[mclass]
			if val.to_f < threshold then break
			print toolcontext.format_p("\t   {mclass.name}: {val}")
		end

		threshold = cnvi.threshold
		print toolcontext.format_h4("\tbudding mclasses (threshold: {threshold})")
		for mclass in cnvi.sort do
			var val = cnvi.values[mclass]
			if val.to_f < threshold then break
			print toolcontext.format_p("\t   {mclass.name}: {val}")
		end

		threshold = cnvs.threshold
		print toolcontext.format_h4("\tblooming mclasses (threshold: {threshold})")
		for mclass in cnvs.sort do
			var val = cnvs.values[mclass]
			if val.to_f < threshold then break
			print toolcontext.format_p("\t   {mclass.name}: {val}")
		end

		if csv then
			var csvh = new CsvDocument
			csvh.separator = ';'
			csvh.header = ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
			for mclass in mclasses do
				var povr = mclass.is_pure_overrider(filter).object_id
				var ovr = mclass.is_overrider(filter).object_id
				var pext = mclass.is_pure_extender(filter).object_id
				var ext = mclass.is_extender(filter).object_id
				var pspe = mclass.is_pure_specializer(filter).object_id
				var spe = mclass.is_pure_specializer(filter).object_id
				var prep = mclass.is_pure_replacer(filter).object_id
				var rep = mclass.is_replacer(filter).object_id
				var eq = mclass.is_equal(filter).object_id
				csvh.add_record(povr, ovr, pext, ext, pspe, spe, prep, rep, eq)
			end
			csvh.write_to_file("{out}/inheritance_behaviour.csv")
		end
	end
end

# Class Branch Mean Size
# cbms(class) = |TotS(class)| / (DIT(class) + 1)
class CBMS
	super MClassMetric
	super FloatMetric
	redef fun name do return "cbms"
	redef fun desc do return "branch mean size, mean number of introduction available among ancestors"

	redef fun collect(mclasses) do
		for mclass in mclasses do
			var totc = mclass.collect_accessible_mproperties(mainmodule, filter).length
			var ditc = mclass.in_hierarchy(mainmodule).depth
			values[mclass] = totc.to_f / (ditc + 1).to_f
		end
	end
end

# Module Branch Mean Size
# mbms(module) = |mclassdefs(module)| / (DIT(module) + 1)
class MBMS
	super MModuleMetric
	super FloatMetric
	redef fun name do return "mbms"
	redef fun desc do return "branch mean size, mean number of class definition available among ancestors"

	redef fun collect(mmodules) do
		for mmodule in mmodules do
			var totc = mmodule.collect_intro_mclassdefs(filter).length
			totc += mmodule.collect_redef_mclassdefs(filter).length
			var ditc = mmodule.in_importation.depth
			values[mmodule] = totc.to_f / (ditc + 1).to_f
		end
	end
end

# Class Novelty Index
# cnvi = |LocS(class)| / cbms(parents(class))
class CNVI
	super MClassMetric
	super FloatMetric
	redef fun name do return "cnvi"
	redef fun desc do return "class novelty index, contribution of the class to its branch in term of introductions"

	redef fun collect(mclasses) do
		var cbms = new CBMS(model, mainmodule, filter)
		for mclass in mclasses do
			# compute branch mean size
			var parents = mclass.in_hierarchy(mainmodule).direct_greaters
			if parents.length > 0 then
				cbms.clear
				cbms.collect(new HashSet[MClass].from(parents))
				# compute class novelty index
				var locc = mclass.collect_accessible_mproperties(mainmodule, filter).length
				values[mclass] = locc.to_f / cbms.avg
			else
				values[mclass] = 0.0
			end
		end
	end
end

# Module Novelty Index
# mnvi = |LocS(module)| / mbms(parents(module))
class MNVI
	super MModuleMetric
	super FloatMetric
	redef fun name do return "mnvi"
	redef fun desc do return "module novelty index, contribution of the module to its branch in term of introductions"

	redef fun collect(mmodules) do
		var mbms = new MBMS(model, mainmodule, filter)
		for mmodule in mmodules do
			# compute branch mean size
			var parents = mmodule.in_importation.direct_greaters
			if parents.length > 0 then
				mbms.clear
				mbms.collect(new HashSet[MModule].from(parents))
				# compute module novelty index
				var locc = mmodule.collect_intro_mclassdefs(filter).length
				locc += mmodule.collect_redef_mclassdefs(filter).length
				values[mmodule] = locc.to_f / mbms.avg
			else
				values[mmodule] = 0.0
			end
		end
	end
end

# Class Novelty Score
# cnvs = |LocS(class)| x nvi
class CNVS
	super MClassMetric
	super FloatMetric
	redef fun name do return "cnvs"
	redef fun desc do return "class novelty score, importance of the contribution of the class to its branch"

	redef fun collect(mclasses) do
		var cnvi = new CNVI(model, mainmodule, filter)
		cnvi.collect(mclasses)
		for mclass in mclasses do
			var locc = mclass.collect_local_mproperties(filter).length
			values[mclass] = cnvi.values[mclass] * locc.to_f
		end
	end
end

# Module Novelty Score
# mnvs = |LocS(module)| x nvi
class MNVS
	super MModuleMetric
	super FloatMetric
	redef fun name do return "mnvs"
	redef fun desc do return "module novelty score, importance of the contribution of the module to its branch"

	redef fun collect(mmodules) do
		var mnvi = new MNVI(model, mainmodule, filter)
		mnvi.collect(mmodules)
		for mmodule in mmodules do
			var locc = mmodule.collect_intro_mclassdefs(filter).length
			locc += mmodule.collect_redef_mclassdefs(filter).length
			values[mmodule] = mnvi.values[mmodule] * locc.to_f
		end
	end
end

redef class MClass
	# the set of redefition that call to super
	fun extended_mproperties(filter: ModelFilter): Set[MProperty] do
		var set = new HashSet[MProperty]
		for mclassdef in mclassdefs do
			for mpropdef in mclassdef.mpropdefs do
				if not filter.accept_mentity(mpropdef) then continue
				if not mpropdef.has_supercall then continue
				if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
			end
		end
		return set
	end

	# the set of redefition that do not call to super
	fun overriden_mproperties(filter: ModelFilter): Set[MProperty] do
		var set = new HashSet[MProperty]
		for mclassdef in mclassdefs do
			for mpropdef in mclassdef.mpropdefs do
				if not filter.accept_mentity(mpropdef) then continue
				if mpropdef.has_supercall then continue
				if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
			end
		end
		return set
	end

	# pure overriders contain only redefinitions
	private fun is_pure_overrider(filter: ModelFilter): Bool do
		var news = collect_intro_mproperties(filter).length
		var locs = collect_local_mproperties(filter).length
		if news == 0 and locs > 0 then return true
		return false
	end

	# overriders contain more definitions than introductions
	private fun is_overrider(filter: ModelFilter): Bool do
		var rdfs = collect_redef_mproperties(filter).length
		var news = collect_intro_mproperties(filter).length
		var locs = collect_local_mproperties(filter).length
		if rdfs >= news and locs > 0 then return true
		return false
	end

	# pure extenders contain only introductions
	private fun is_pure_extender(filter: ModelFilter): Bool do
		var rdfs = collect_redef_mproperties(filter).length
		var locs = collect_local_mproperties(filter).length
		if rdfs == 0 and locs > 0 then return true
		return false
	end

	# extenders contain more introduction than redefinitions
	private fun is_extender(filter: ModelFilter): Bool do
		var rdfs = collect_redef_mproperties(filter).length
		var news = collect_intro_mproperties(filter).length
		var locs = collect_local_mproperties(filter).length
		if news > rdfs and locs > 0 then return true
		return false
	end

	# pure specializers always call to super in its redefinitions
	private fun is_pure_specializer(filter: ModelFilter): Bool do
		var ovrs = overriden_mproperties(filter).length
		var rdfs = collect_redef_mproperties(filter).length
		if ovrs == 0 and rdfs > 0 then return true
		return false
	end

	# specializers have more redefinitions that call super than not calling it
	private fun is_specializer(filter: ModelFilter): Bool do
		var spcs = extended_mproperties(filter).length
		var ovrs = overriden_mproperties(filter).length
		var rdfs = collect_redef_mproperties(filter).length
		if spcs > ovrs and rdfs > 0 then return true
		return false
	end

	# pure replacers never call to super in its redefinitions
	private fun is_pure_replacer(filter: ModelFilter): Bool do
		var spcs = extended_mproperties(filter).length
		var rdfs = collect_redef_mproperties(filter).length
		if spcs == 0 and rdfs > 0 then return true
		return false
	end

	# replacers have less redefinitions that call super than not calling it
	private fun is_replacer(filter: ModelFilter): Bool do
		var spcs = extended_mproperties(filter).length
		var ovrs = overriden_mproperties(filter).length
		var rdfs = collect_redef_mproperties(filter).length
		if ovrs > spcs and rdfs > 0 then return true
		return false
	end

	# equals contain as redifinition than introduction
	private fun is_equal(filter: ModelFilter): Bool do
		var spcs = extended_mproperties(filter).length
		var ovrs = overriden_mproperties(filter).length
		var rdfs = collect_redef_mproperties(filter).length
		if spcs == ovrs and rdfs > 0 then return true
		return false
	end
end
src/metrics/mendel_metrics.nit:17,1--360,3