A tool for measuring Python class cohesion.

Overview

Cohesion

Build Status Build Status Coverage Status Python Versions PyPI Version

Cohesion is a tool for measuring Python class cohesion.

In computer programming, cohesion refers to the degree to which the elements of a module belong together. Thus, cohesion measures the strength of relationship between pieces of functionality within a given module. For example, in highly cohesive systems functionality is strongly related.

When cohesion is high, it means that the methods and variables of the class are co-dependent and hang together as a logical whole.

  • Clean Code pg. 140

Some of the advantages of high cohesion, also by Wikipedia:

  • Reduced module complexity (they are simpler, having fewer operations).
  • Increased system maintainability, because logical changes in the domain affect fewer modules, and because changes in one module require fewer changes in other modules.
  • Increased module reusability, because application developers will find the component they need more easily among the cohesive set of operations provided by the module.

Installing

$ python -m pip install cohesion
$ cohesion -h

OR

$ git clone https://github.com/mschwager/cohesion.git
$ cd cohesion
$ PYTHONPATH=lib/ python -m cohesion -h

Using

Cohesion measures class and instance variable usage across the methods of that class.

$ cat example.py
class ExampleClass1(object):
    class_variable1 = 5
    class_variable2 = 6

    def func1(self):
        self.instance_variable = 6

        def inner_func(b):
            return b + 5

        local_variable = self.class_variable1

        return local_variable

    def func2(self):
        print(self.class_variable2)

    @staticmethod
    def func3(variable):
        return variable + 7

class ExampleClass2(object):
    def func1(self):
        self.instance_variable1 = 7
$ cohesion --files example.py --verbose
File: example.py
  Class: ExampleClass1 (1:0)
    Function: func1 2/3 66.67%
      Variable: class_variable1 True
      Variable: class_variable2 False
      Variable: instance_variable True
    Function: func2 1/3 33.33%
      Variable: class_variable1 False
      Variable: class_variable2 True
      Variable: instance_variable False
    Function: func3 0/3 0.00%
      Variable: class_variable1 False
      Variable: class_variable2 False
      Variable: instance_variable False
    Total: 33.33%
  Class: ExampleClass2 (23:0)
    Function: func1 1/1 100.00%
      Variable: instance_variable1 True
    Total: 100.00%

The --below and --above flags can be specified to only show classes with a cohesion value below or above the specified percentage, respectively.

Flake8 Support

Cohesion supports being run by flake8. First, ensure your installation has registered cohesion:

$ flake8 --version
3.2.1 (pyflakes: 1.0.0, cohesion: 0.8.0, pycodestyle: 2.2.0, mccabe: 0.5.3) CPython 2.7.12 on Linux

And now use flake8 to lint your file:

$ flake8 example.py
example.py:1:1: H601 class has low cohesion

Python Versions

If you would like to simultaneously run Cohesion on Python 2 and Python 3 code then you will have to install it for both versions. I.e.

$ python2 -m pip install cohesion
$ python3 -m pip install cohesion

Then use the corresponding version to run the module:

$ python2 -m cohesion --files python2_file.py
$ python3 -m cohesion --files python3_file.py

Developing

First, install development packages:

$ python -m pip install -r requirements-dev.txt

Testing

$ nose2

Linting

$ flake8

Coverage

$ nose2 --with-coverage
Comments
  • Replace assertEquals with assertEqual in tests/test_module.py

    Replace assertEquals with assertEqual in tests/test_module.py

    Running py.test throws a DeprecationWarning w/r/t assertEquals

    (gsoc_env) ➜  cohesion git:(master) py.test
    ============================================= test session starts ==============================================
    platform linux -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
    rootdir: /home/jaskaran/packages/cohesion
    collected 62 items                                                                                             
    
    tests/test_filesystem.py ...                                                                             [  4%]
    tests/test_flake8_extension.py .....                                                                     [ 12%]
    tests/test_module.py ................                                                                    [ 38%]
    tests/test_parser.py ......................................                                              [100%]
    
    =============================================== warnings summary ===============================================
    tests/test_module.py::TestModule::test_module_function_variable
      /home/jaskaran/packages/cohesion/tests/test_module.py:105: DeprecationWarning: Please use assertEqual instead.
        self.assertEquals(result, expected)
    
    -- Docs: https://docs.pytest.org/en/latest/warnings.html
    ==================================== 62 passed, 1 warnings in 0.12 seconds =====================================
    

    Replacing assertEquals with assertEqual in tests/test_module.py passes tests without the warning above.

    opened by jajajasalu2 5
  • flake8 does not raise any errors

    flake8 does not raise any errors

    For some reason flake8 does not produce any warnings. Even if it should.

    Reproduction:

    1. create example.py from the readme
    2. run flake8 --select=H --cohesion-below=95 example.py

    This should raise one error, since the same file produces this output:

    » cohesion --below 95 -f example.py
    File: example.py
      Class: ExampleClass1 (1:0)
        Function: func1 2/3 66.67%
        Function: func2 1/3 33.33%
        Function: func3 staticmethod
        Total: 33.33%
    

    But, nothing happens. Versions I am using:

    {
      "dependencies": [
        {
          "dependency": "setuptools",
          "version": "39.0.1"
        }
      ],
      "platform": {
        "python_implementation": "CPython",
        "python_version": "3.6.5",
        "system": "Darwin"
      },
      "plugins": [
        {
          "is_local": false,
          "plugin": "cohesion",
          "version": "0.9.1"
        },
        {
          "is_local": false,
          "plugin": "mccabe",
          "version": "0.6.1"
        },
        {
          "is_local": false,
          "plugin": "pycodestyle",
          "version": "2.3.1"
        },
        {
          "is_local": false,
          "plugin": "pyflakes",
          "version": "1.6.0"
        }
      ],
      "version": "3.5.0"
    }
    
    opened by sobolevn 5
  • Unable to specify `cohesion-below` option via `setup.cfg`

    Unable to specify `cohesion-below` option via `setup.cfg`

    Hi, thanks for this plugin. I am working on integrating it into our linter: https://github.com/wemake-services/wemake-python-styleguide/issues/134

    But, currently there's an issue with configuration.

    _______________ FLAKE8-check(ignoring N802 D100 D104 D106 D401) ________________
    multiprocessing.pool.RemoteTraceback: 
    """
    Traceback (most recent call last):
      File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/multiprocessing/pool.py", line 119, in worker
        result = (True, func(*args, **kwds))
      File "/Users/sobolev/Documents/github/wemake-python-styleguide/.venv/lib/python3.6/site-packages/flake8/checker.py", line 648, in _run_checks
        return checker.run_checks()
      File "/Users/sobolev/Documents/github/wemake-python-styleguide/.venv/lib/python3.6/site-packages/flake8/checker.py", line 579, in run_checks
        self.run_ast_checks()
      File "/Users/sobolev/Documents/github/wemake-python-styleguide/.venv/lib/python3.6/site-packages/flake8/checker.py", line 493, in run_ast_checks
        for (line_number, offset, text, check) in runner:
      File "/Users/sobolev/Documents/github/wemake-python-styleguide/.venv/lib/python3.6/site-packages/cohesion/flake8_extension.py", line 44, in run
        file_module.filter_below(self.cohesion_below)
      File "/Users/sobolev/Documents/github/wemake-python-styleguide/.venv/lib/python3.6/site-packages/cohesion/module.py", line 79, in filter_below
        self._filter(predicate)
      File "/Users/sobolev/Documents/github/wemake-python-styleguide/.venv/lib/python3.6/site-packages/cohesion/module.py", line 43, in _filter
        for class_name, class_structure in self.structure.items()
      File "/Users/sobolev/Documents/github/wemake-python-styleguide/.venv/lib/python3.6/site-packages/cohesion/module.py", line 44, in <dictcomp>
        if predicate(class_name)
      File "/Users/sobolev/Documents/github/wemake-python-styleguide/.venv/lib/python3.6/site-packages/cohesion/module.py", line 77, in predicate
        return operator.le(class_percentage, percentage)
    TypeError: '<=' not supported between instances of 'float' and 'str'
    """
    
    The above exception was the direct cause of the following exception:
    .venv/lib/python3.6/site-packages/pluggy/hooks.py:258: in __call__
        return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
    .venv/lib/python3.6/site-packages/pluggy/manager.py:67: in _hookexec
        return self._inner_hookexec(hook, methods, kwargs)
    .venv/lib/python3.6/site-packages/pluggy/manager.py:61: in <lambda>
        firstresult=hook.spec_opts.get('firstresult'),
    .venv/lib/python3.6/site-packages/_pytest/runner.py:111: in pytest_runtest_call
        item.runtest()
    .venv/lib/python3.6/site-packages/pytest_flake8.py:117: in runtest
        self.statistics)
    .venv/lib/python3.6/site-packages/py/_io/capture.py:150: in call
        res = func(*args, **kwargs)
    .venv/lib/python3.6/site-packages/pytest_flake8.py:193: in check_file
        app.run_checks([str(path)])
    .venv/lib/python3.6/site-packages/flake8/main/application.py:310: in run_checks
        self.file_checker_manager.run()
    .venv/lib/python3.6/site-packages/flake8/checker.py:319: in run
        self.run_parallel()
    .venv/lib/python3.6/site-packages/flake8/checker.py:288: in run_parallel
        for ret in pool_map:
    /usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/multiprocessing/pool.py:735: in next
        raise value
    E   TypeError: '<=' not supported between instances of 'float' and 'str'
    

    Here's how to reproduce it.

    1. install flake8 and cohesion
    2. create setup.cfg with the following contents:
    [flake8]
    cohesion-below = 100.0
    
    1. Run flake8 on any file with a class inside, for example the one from the readme.

    Here's related flake8 information:

    {
      "dependencies": [
        {
          "dependency": "setuptools",
          "version": "39.0.1"
        }
      ],
      "platform": {
        "python_implementation": "CPython",
        "python_version": "3.6.5",
        "system": "Darwin"
      },
      "plugins": [
        {
          "is_local": false,
          "plugin": "cohesion",
          "version": "0.8.0"
        },
        {
          "is_local": false,
          "plugin": "pycodestyle",
          "version": "2.3.1"
        },
        {
          "is_local": false,
          "plugin": "pyflakes",
          "version": "1.6.0"
        }
      ],
      "version": "3.5.0"
    }
    
    opened by sobolevn 5
  • `--below` argument works incorrectly

    `--below` argument works incorrectly

    After updating to 0.9.1 I have faced a new issue:

    1. create an example.py file from the readme
    2. run cohesion -f example.py, output will be:
    File: example.py
      Class: ExampleClass1 (1:0)
        Function: func1 2/3 66.67%
        Function: func2 1/3 33.33%
        Function: func3 staticmethod
        Total: 33.33%
      Class: ExampleClass2 (22:0)
        Function: func1 1/1 100.00%
        Total: 100.00%
    
    1. then run cohesion --below 100 -f example.py, output will not change
    File: example.py
      Class: ExampleClass1 (1:0)
        Function: func1 2/3 66.67%
        Function: func2 1/3 33.33%
        Function: func3 staticmethod
        Total: 33.33%
      Class: ExampleClass2 (22:0)
        Function: func1 1/1 100.00%
        Total: 100.00%
    

    However, 100 is not below 100. It is equal.

    Just for the reference, running cohesion --below 99 -f example.py:

    File: example.py
      Class: ExampleClass1 (1:0)
        Function: func1 2/3 66.67%
        Function: func2 1/3 33.33%
        Function: func3 staticmethod
        Total: 33.33%
    

    It works well.

    opened by sobolevn 1
  • Add cohesion as a flake8 extension

    Add cohesion as a flake8 extension

    http://flake8.pycqa.org/en/2.5.5/extensions.html

    This will also provide some added community visibility to this project. We'll probably need some kind of filter capability in order to perform linting. I.e. we'll want to only show classes whose cohesion is less than 50%, etc.

    opened by mschwager 1
  • Meaning of result returned by Cohesion

    Meaning of result returned by Cohesion

    I ran the lib cohesion over my code and return me the result.. what the lib analyze ?

     Function: __eq__ 1/3 33.33%
        Function: __init__ 1/3 33.33%
        Function: __repr__ 2/3 66.67%
        Function: change_bit 2/3 66.67%
        Function: is_valid 1/3 33.33%
        Total: 46.67%
      Class: Hamming
        Function: __init__ 1/13 7.69%
        Function: __repr__ 2/13 15.38%
        Function: bits_verified_by 3/13 23.08%
        Function: calculate_parity 2/13 15.38%
        Function: check 4/13 30.77%
        Function: divisors 1/13 7.69%
        Function: encode 8/13 61.54%
        Function: fix 3/13 23.08%
        Function: is_power 2/13 15.38%
        Function: wrong_position 4/13 30.77%
        Total: 23.08%
    
    opened by Marlysson 1
  • Error on downloading package

    Error on downloading package

    I ran the command:

    pip install cohesion

    and show me result :

    Downloading/unpacking cohesion
      Could not find any downloads that satisfy the requirement cohesion
    No distributions at all found for cohesion
    Storing complete log in /home/cliente/.pip/pip.log
    

    Seeing the pip.log file:

    Downloading/unpacking cohesion
    
      Getting page http://pypi.python.org/simple/cohesion
      URLs to search for versions for cohesion:
      * http://pypi.python.org/simple/cohesion/
      Getting page http://pypi.python.org/simple/cohesion/
      Analyzing links from page https://pypi.python.org/simple/cohesion/
        Skipping link https://pypi.python.org/packages/9d/56/536846a677d672f6176eccbc66d4da05101960ef755e865d36df3a3fbc25/cohesion-0.5.0-py2.py3-none-any.whl#md5=f735e4fa689b77313edb46c6434e7df5 (from https://pypi.python.org/simple/cohesion/); unknown archive format: .whl
        Skipping link https://pypi.python.org/packages/e1/db/2b29b55958681a994a52a06767465426cf1952ea9218a73f9c00926694c3/cohesion-0.5.1-py2.py3-none-any.whl#md5=0b5fc31e76753afb20c7e320318295a5 (from https://pypi.python.org/simple/cohesion/); unknown archive format: .whl
      Could not find any downloads that satisfy the requirement cohesion
    
    No distributions at all found for cohesion
    
    Exception information:
    Traceback (most recent call last):
      File "/home/cliente/Dev/Apps/redes/tests/local/lib/python2.7/site-packages/pip-1.1-py2.7.egg/pip/basecommand.py", line 104, in main
        status = self.run(options, args)
      File "/home/cliente/Dev/Apps/redes/tests/local/lib/python2.7/site-packages/pip-1.1-py2.7.egg/pip/commands/install.py", line 245, in run
        requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle)
      File "/home/cliente/Dev/Apps/redes/tests/local/lib/python2.7/site-packages/pip-1.1-py2.7.egg/pip/req.py", line 978, in prepare_files
        url = finder.find_requirement(req_to_install, upgrade=self.upgrade)
      File "/home/cliente/Dev/Apps/redes/tests/local/lib/python2.7/site-packages/pip-1.1-py2.7.egg/pip/index.py", line 157, in find_requirement
        raise DistributionNotFound('No distributions at all found for %s' % req)
    DistributionNotFound: No distributions at all found for cohesion
    
    opened by Marlysson 1
  • Cohesion does not produce the same results with debug on and off

    Cohesion does not produce the same results with debug on and off

    For example:

    $ cat example.py 
    class ExampleClass1(object):
        class_variable1 = 5
        class_variable2 = 6
    
        def func1(self):
            self.instance_variable = 6
    
            def inner_func(b):
                return b + 5
    
            local_variable = self.class_variable1
    
            return local_variable
    
        def func2(self):
            print(self.class_variable2)
    
        @staticmethod
        def func3(variable):
            return variable + 7
    
    class ExampleClass2(object):
        def func1(self):
            self.instance_variable1 = 7
    

    Without the --debug flag:

    $ PYTHONPATH=lib/ python -m cohesion -f example.py 
    File: example.py
      Class: ExampleClass2 (22:0)
        Function: func1 1/1 100.00%
        Total: 100.00%
      Class: ExampleClass1 (1:0)
        Function: func3 staticmethod
        Function: func2 1/3 33.33%
        Function: func1 2/3 66.67%
        Total: 33.33%
    

    With the --debug flag:

    $ PYTHONPATH=lib/ python -m cohesion --debug -f example.py 
    {
        "ExampleClass2": {
            "cohesion": null,
            "variables": [
                "instance_variable1"
            ],
            "lineno": 22,
            "col_offset": 0,
            "functions": {
                "func1": {
                    "classmethod": false,
                    "variables": [
                        "instance_variable1"
                    ],
                    "bounded": true,
                    "staticmethod": false
                }
            }
        },
        "ExampleClass1": {
            "cohesion": null,
            "variables": [
                "class_variable2",
                "class_variable1",
                "instance_variable"
            ],
            "lineno": 1,
            "col_offset": 0,
            "functions": {
                "func3": {
                    "classmethod": false,
                    "variables": [],
                    "bounded": false,
                    "staticmethod": true
                },
                "func2": {
                    "classmethod": false,
                    "variables": [
                        "class_variable2"
                    ],
                    "bounded": true,
                    "staticmethod": false
                },
                "func1": {
                    "classmethod": false,
                    "variables": [
                        "class_variable1",
                        "instance_variable"
                    ],
                    "bounded": true,
                    "staticmethod": false
                }
            }
        }
    }
    

    Notice the cohesion value is null with debugging turned on. It looks like this value is calculated differently depending on if debugging is turned on. We should remedy this.

    opened by mschwager 0
  • Add mechanism for filtering cohesion results

    Add mechanism for filtering cohesion results

    E.g. if we only want to see classes or modules that meet some criteria, like high cohesion percentage than X%.

    We'll probably want a way to perform this via library calls and a way to specify it from the command line.

    opened by mschwager 0
  • Function and class parsing think member methods are member variables

    Function and class parsing think member methods are member variables

    $ cat example.py 
    class ExampleClass1(object):
        def func1(self):
            print("HAI")
    
        def func4(self):
            self.func1()
    
    $ cohesion -f example.py -x
    {
        "ExampleClass1": {
            "variables": [
                "func1"
            ],
            "functions": {
                "func1": {
                    "classmethod": false,
                    "variables": [],
                    "bounded": true,
                    "staticmethod": false
                },
                "func4": {
                    "classmethod": false,
                    "variables": [
                        "func1"
                    ],
                    "bounded": true,
                    "staticmethod": false
                }
            }
        }
    }
    
    opened by mschwager 0
  • Skip empty classes

    Skip empty classes

    The cohesion calculation should also be for classes that contain either no methods or only static, class or abstract methods, because the cohesion will be 0 %. This is annoying when dealing with abstract classes that will implement methods on subclasses.

    opened by sasanjac 0
  • Shouldn't we expand calls to class methods when evaluating cohesion?

    Shouldn't we expand calls to class methods when evaluating cohesion?

    Suppose you have this module:

    class Data(object):
        def __init__(self, data):
            if not isinstance(data, list):
                raise ValueError('Data: Input arg must be list')
            self.data = data
    
        def get_value_at_start(self):
            return self.get_value_at_index(0)
    
        def get_value_at_index(self, index):
            return self.data[index]
    
    data = Data([1, 3, 5, 7])
    print 'Data 0: {}'.format(data.get_value_at_start())
    print 'Data 2: {}'.format(data.get_value_at_index(2))
    

    cohesion gives 0.00% for Function: get_value_at_start. IMO this is incorrect. By the definition you have in your documentation (extracted from wikipedia): "[...] cohesion refers to the degree to which the elements of a module belong together". Getting a cohesion of 0% would mean that the method does not belong to the class. However, it does; it's only that we're avoiding code repetition. If the function did 'return self.data[0]' we'd get cohesion score=100%, but this would go against good practices, because if later we change something in how we access data points, we need to change it in two places. What do you think?

    opened by portusato 1
Owner
null
An extensible and friendly code review tool for projects and companies of all sizes.

Review Board Review Board is an open source, web-based code and document review tool built to help companies, open source projects, and other organiza

Review Board 1.5k Jan 2, 2023
A Python application for tracking, reporting on timing and complexity in Python code

A command-line application for tracking, reporting on complexity of Python tests and applications. wily [a]: quick to think of things, having a very g

Anthony Shaw 1k Dec 29, 2022
Various code metrics for Python code

Radon Radon is a Python tool that computes various metrics from the source code. Radon can compute: McCabe's complexity, i.e. cyclomatic complexity ra

Michele Lacchia 1.4k Jan 7, 2023
Inspects Python source files and provides information about type and location of classes, methods etc

prospector About Prospector is a tool to analyse Python code and output information about errors, potential problems, convention violations and comple

Python Code Quality Authority 1.7k Dec 31, 2022
McCabe complexity checker for Python

McCabe complexity checker Ned's script to check McCabe complexity. This module provides a plugin for flake8, the Python code checker. Installation You

Python Code Quality Authority 527 Dec 21, 2022
☀️ Measuring the accuracy of BBC weather forecasts in Honolulu, USA

Accuracy of BBC Weather forecasts for Honolulu This repository records the forecasts made by BBC Weather for the city of Honolulu, USA. Essentially, t

Max Halford 12 Oct 15, 2022
Measuring Coding Challenge Competence With APPS

Measuring Coding Challenge Competence With APPS This is the repository for Measuring Coding Challenge Competence With APPS by Dan Hendrycks*, Steven B

Dan Hendrycks 218 Dec 27, 2022
This code reproduces the results of the paper, "Measuring Data Leakage in Machine-Learning Models with Fisher Information"

Fisher Information Loss This repository contains code that can be used to reproduce the experimental results presented in the paper: Awni Hannun, Chua

Facebook Research 43 Dec 30, 2022
The coda and data for "Measuring Fine-Grained Domain Relevance of Terms: A Hierarchical Core-Fringe Approach" (ACL '21)

We propose a hierarchical core-fringe learning framework to measure fine-grained domain relevance of terms – the degree that a term is relevant to a broad (e.g., computer science) or narrow (e.g., deep learning) domain.

Jie Huang 14 Oct 21, 2022
Measuring and Improving Consistency in Pretrained Language Models

ParaRel ?? This repository contains the code and data for the paper: Measuring and Improving Consistency in Pretrained Language Models as well as the

Yanai Elazar 26 Dec 2, 2022
Scripts for measuring and displaying thermal behavior on Voron 3D printers

Thermal Profiling Measuring gantry deflection and frame expansion This script runs a series of defined homing and probing routines designed to charact

Jon Sanders 30 Nov 27, 2022
TruthfulQA: Measuring How Models Imitate Human Falsehoods

TruthfulQA: Measuring How Models Imitate Human Falsehoods

null 69 Dec 25, 2022
Conditional probing: measuring usable information beyond a baseline

Conditional probing: measuring usable information beyond a baseline

John Hewitt 20 Dec 15, 2022
Tools and data for measuring the popularity & growth of various programming languages.

growth-data Tools and data for measuring the popularity & growth of various programming languages. Install the dependencies $ pip install -r requireme

null 3 Jan 6, 2022
A simple stopwatch for measuring code performance with static typing.

A simple stopwatch for measuring code performance. This is a fork from python-stopwatch, which adds static typing and a few other things.

Rafael 2 Feb 18, 2022
Yuqing Xie 2 Feb 17, 2022
Tracking development of the Class Schedule Siri Shortcut, an iOS program that checks the type of school day and tells you class scheduling.

Class Schedule Shortcut Tracking development of the Class Schedule Siri Shortcut, an iOS program that checks the type of school day and tells you clas

null 3 Jun 28, 2022
Ffxiv-blended-job-icons - All action icons for each class/job are blended together to create new backgrounds for each job/class icon!

ffxiv-blended-job-icons All action icons for each class/job are blended together to create new backgrounds for each job/class icon! I used python to c

Jon Strutz 2 Jul 7, 2022
A simple scheduler tool that provides desktop notifications about classes and opens their meet links in the browser automatically at the start of the class.

This application provides desktop notifications about classes and opens their meet links in browser automatically at the start of the class.

Anshit 14 Jun 29, 2022
Cross-platform MachO/ObjC Static binary analysis tool & library. class-dump + otool + lipo + more

ktool Static Mach-O binary metadata analysis tool / information dumper pip3 install k2l Development is currently taking place on the @python3.10 branc

Kritanta 301 Dec 28, 2022