Connascence: A metric for coupling


This metric for software quality was invented by Meilir Page-Jones to allow evaluating the degree of coupling between the different actors in an object-oriented design. To do this, it categorizes coupling into different types, serving as an analysis tool to measure the impact of coupling and a guide to reduce it.

To measure the form of connascence there are three factors:

connascence vector
Strength (from low to high), degree (from low to high), and locality (from near to far).

Types of Connascence

The types of connascence are divided into dynamic and static, going in severity from strongest to weakest.

Dynamic

Static

Dynamic

Connascence of Identity

Multiple elements refer to the same element.

// Both controllers knows about ViewsCounter

class ViewsCounter { ... }

class HomeController {
  constructor(private viewsCounter: ViewsCounter) {}

  index() {
    this.viewsCounter.increment()
    ...
  }
}

class PostController {
  constructor(private viewsCounter: ViewsCounter) {}

  index() {
    this.viewsCounter.increment()
    ...
  }
}

Connascence of Value

Multiple values in different elements must change together.

// If PostState changes, so does Post and the test function

enum PostState {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED'
}

class Post {
  private body: string
  private state: PostState
  private title: string

  constructor(title: string) {
    this.body = ''
    this.state = PostState.DRAFT
    this.title = title
  }

  getState() {
    return this.state
  }

  ...
}

test('Post should be created as a draft', () => {
  assert(new Post('My first post').getState()).toEqual('DRAFT')
})

Connascence of Timing

The timing of execution of multiple elements is important.

// The render function needs the promise to be resolved for rendering the Post list

const getPosts = async (): Promise<Post[]> => {
  return posts
}

getPosts().then((posts) => render(posts))

Connascence of Execution

The order of execution of multiple elements is important.

// Changing the order of the if clauses in the main function will change its behavior

function isFizz(number: number): boolean { ... }
function isBuzz(number: number): boolean { ... }
function isFizzBuzz(number: number): boolean { ... }

function main(number: number): string {
  if (isFizzBuzz(number)) {
    return 'FizzBuzz'
  }
  if (isFizz(number)) {
    return 'Fizz'
  }
  if (isBuzz(number)) {
    return 'Buzz'
  }
  return number.toString()
}

Static

Connascence of Algorithm

Multiple elements must agree on a particular algorithm.

// Here the test function knows about the algorithm used for the user's password encription

test('User password should be properly encoded', () => {
  const password = 'my_completely_secure_password'
  const user = new User(password, 'John Doe')

  assert(user.getEncodedPassword()).toEqual(md5(password))
})

Connascence of Position

Multiple elements must agree on the order of values.

// Here any consumer of the getUserAddress function have to know the position of each piece of information in order to use it

function getUserAddress(user: User): (string | number)[] {
  return [
    user.getStreetName(),
    user.getStreetNumber(),
    user.getPostalCode(),
    user.getCity(),
    user.getCountry()
  ]
}

Connascence of Meaning

Multiple elements must agree on the meaning of certain values.

// Any consumer of the getRole function needs to know the meaning of its value.
// Does 1 mean Administrator; Customer; Manager; Moderator maybe?

const user = Database.getUserByName('John Doe')

if (user.getRole() === 1) {
  ...
}

Connascence of Type

Multiple elements must agree on the type of an entity (commonly found in weakly or dynamically typed languages).

// Which call will be correct?

function calculateAge(birthYear, birthMonth, birthDay) { ... }

calculateAge(1990, 9, 10)
calculateAge(90, 9, 10)
calculateAge('1990', '9', '10')
calculateAge('1990', 'September', '10')

Connascence of Name

Multiple elements must agree on the name of an entity (it’s inevitable).

// Locally changing a property name in one point will break the class.
// Also locally changing a public property or method will break users of Customer class

class Customer {
  constructor(private name: string, private familyName: string) {}

  fullName() {
    return `${this.name} ${this.familyName}`
  }
}

Reduce coupling

Now that we are able to catalog the degree of coupling at a given point in our system by identifying the type of connascence, we can refactor to a weaker type, thus reducing coupling.

Example: Passing from Connascence of Position to Connascence of Name

Before

function getUserAddress(user: User): (string | number)[] {
  return [
    user.getStreetName(),
    user.getStreetNumber(),
    user.getPostalCode(),
    user.getCity(),
    user.getCountry()
  ]
}

// Getting the city
const address = getUserAddress(user)
const city = address[3]

After

type UserAddress = {
  streetName: string
  streetNumber: number
  postalCode: number
  city: string
  country: string
}

function getUserAddress(user: User): UserAddress {
  return {
    streetName: user.getStreetName(),
    streetNumber: user.getStreetNumber(),
    postalCode: user.getPostalCode(),
    city: user.getCity(),
    country: user.getCountry()
  }
}

// Getting the city
const address = getUserAddress(user)
const city = address.city

Example: Passing from Connascence of Value to Connascence of Name

Before

enum PostState {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED'
}

class Post {
  private body: string
  private state: PostState
  private title: string

  constructor(title: string) {
    this.body = ''
    this.state = PostState.DRAFT
    this.title = title
  }

  getState() {
    return this.state
  }

  ...
}

// Test knows about Post internal implementation
test('Post should be created as a draft', () => {
  assert(new Post('My first post').getState()).toEqual('DRAFT')
})

After

enum PostState {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED'
}

class Post {
  private body: string
  private state: PostState
  private title: string

  constructor(title: string) {
    this.body = ''
    this.state = PostState.DRAFT
    this.title = title
  }

  isPublished(): boolean {
    return this.state === PostState.PUBLISHED
  }

  ...
}

// Test does not know about Post internal implementation
test('Post should be created as a draft', () => {
  assert(new Post('My first post').isPublished()).toBeFalsy()
})

Conclusion

Connascence gives us a common metric and vocabulary that can be used by a team to talk about coupling. It will also help us when making decisions about changes to the design, in order to improve it.

2023-12-06
Written by Samuel de Vega.
Tags