Fixing Static Connascence

Introduction

In the previous post we described connascence and explained why it’s so important to understand for software developers and architects. In this post we will show practical approach on how to fix different levels of static connascence. As to summarize, connascence has nine levels (five static and 4 dynamic). The higher level the less is code quality.

Levels of Connascence

Connascence has three properties. Think of connascence properties as toggles that we can be turned in order to improve code quality withing the same level of connascence. All three properties contribute to connascence at the same time. For example, connascence of position withing private members of a class is not as bad as connascence of position that spans through entire codebase.

Properties of connascence

Connascence of Name

Connascence of name is the least evil level. It’s also hard to avoid as it happens every time we have a named construct that is referenced by other code. Simple examples, invoke a function by name, instantiate a class by name, pass named parameter, reference global constant or variable. The main idea captured by connascence of name is when a name changes, all code that reference the name needs to change as well.

Code below shows different variations of connascence of name, comments explain which is where.

ublic class ScheduleItem
{
    public string Flight { get; set; }
    public string Origin { get; set; }
    public string Destination { get; set; }
    public DateTimeOffset DepartureDt { get; set; }
    public DateTimeOffset ArrivalDt { get; set; }

    public string Print()
    {
        // 1. We are referring properties here.
        //    If a property name chages, we have to change it
        //    in this line as well.
        return $"Flight {Flight} from {Origin} on {DepartureDt} to {Destination} on {ArrivalDt}";
    }
}

public class Itinerary
{
    // 2. If we rename ScheduleItem class we will have to change
    //    the class name here to make code compilable.
    // 3. We may also need to change _scheduleItems field names
    //    to keep it consistent with the ScheduleItem class name.
    private readonly IList<ScheduleItem> _scheduleItems;

    public void Print()
    {
        foreach (var item in _scheduleItems)
        {
            // 4. If we change Print methond name, we will have to
            //    change it in this line as well.   
            Console.WriteLine(item.Print());
        }
    }
}

Most of modern IDEs support renaming capabilities therefore changing name is least of a problem. For statically typed languages like C#, Golang, C++ and other connascence of name is not a problem because of compiler. If we introduce an error while renaming then compiler will not be able to resolve the name and compilation will fail. We will know about the error right away and the error will not make its way into production.

Different story is for dynamically typed languages like Python, JavaScript and other alike. Unless rigorous linters and extensive automated testing is used a trivial renaming may introduce an error that may end up in production and cause a failure on runtime.

Fix Connascence of Name

This is very basic level of connascence that does not have a fix and inevitable. The only thing we can do is to work with connascence properties, e.g. increase locality, reduce degree and strenght.

Connascence of Type

This connascence level appears quite often when code makes statements or assumptions about the types it operates on. Simple example, a function takes an argument of an integer type, so we can’t pass a string value to the function.

void Sum(int a, int b);

For statically strongly typed languages (C#, C++, Golang and alike) compiler takes care of most issues that can occur as result of type violations. The code below is in C# and shows few examples of connascence of type and how compiler helps to catch the issues.

public class ScheduleItem
{
    public string Flight { get; set; }
    public string Origin { get; set; }
    public string Destination { get; set; }
    public DateTimeOffset DepartureDt { get; set; }
    public DateTimeOffset ArrivalDt { get; set; }

    public string Print()
    {
        return $"Flight {Flight} from {Origin} on {DepartureDt} to {Destination} on {ArrivalDt}";
    }
}

public class Itinerary
{
    private readonly IList<ScheduleItem> _scheduleItems;

    public string Print()
    {
        List<string> printedSchedules = new List<string>();
        foreach (var item in _scheduleItems)
        {
            // COMPILE ERROR
            // printedSchedules is a collection of strings,
            // therefore we can not add ScheduleItem instance
            // into the collection.
            printedSchedules.Add(item);

            // FIXED
            // A fix is simple, developer just forgot to call Print() on item.
            // Print() returns a string that can be added to printedSchedules.
            printedSchedules.Add(item.Print());
        }

        return string.Join(Environment.NewLine, printedSchedules);
    }
}

In dynamically typed languages (Python, JavaScript and other) the responsibility falls on a developer, so she needs to follow the rules, write automated tests and ensure linters run before code gets deployed. Below is functionally similar code, but re-written in Python. If compared to the above we can see differences of how statically and dynamically typed languages handle issues caused by connascence of type.

class ScheduleItem:
    def __init__(self):
        self.flight = ""
        self.origin = ""
        self.destination = ""
        self.departure_dt = None
        self.arrival_dt = None

    def print(self):
        return (
            f"Flight {self.flight} from {self.origin} on {self.departure_dt} "
            f"to {self.destination} on {self.arrival_dt}"
        )


class Itinerary:
    def __init__(self):
        self._schedule_items = []

    def print(self):
        printed_schedules = []
        for item in self._schedule_items:
            # No error, item is added to a collection that is supposed
            # to be a collection of strings. When code runs user will see that
            # itinerary is not printed correctly.
            printed_schedules.append(item)

            # A fix is simple, developer just forgot to call print() on an item.
            # However the error was discovered after the code was run.
            printed_schedules.append(item.print())

        return "\n".join(printed_schedules)

Fix Connascence of Type

Connascence of type is thought to be a little stronger than connascence of name, however it is very basic level of connascence that does not have a direct fix and also hard to avoid. The only thing we can do to improve connascence is to work with properties: locality, strength and degree.

Connascence of Meaning

Connascence of meaning appears when specific meaning is assigned to a value. Example, a function returns string “SUCCESS” when it is executed successfully. Code needs to check the function return value and compare it to “SUCCESS” string value. If we ever need to change the value “SUCCESS” we will have to change it everywhere in the code.
The code below is full of examples of connascence of meaning.

  1. “SUCCESS” string value is returned when function BookFlight() executes successfully.
  2. 0 is returned from BookItinerary() when entire itinerary is booked.
public class ScheduleItem
{
    public string Flight { get; set; }
    public string Origin { get; set; }
    public string Destination { get; set; }
    public DateTimeOffset DepartureDt { get; set; }
    public DateTimeOffset ArrivalDt { get; set; }
}

public class FlightBookingService
{
    private static readonly Random _rand = new Random();

    public string BookFlight(string flight, DateTimeOffset departureDt)
    {
        if (_rand.NextDouble() > 0.95)
        {
            return "FAILURE";
        }

        return "SUCCESS";
    }
}

public class Itinerary
{
    private readonly IList<ScheduleItem> _scheduleItems;

    public int BookItinerary()
    {
        var bookingService = new FlightBookingService();
        foreach (var scheduleItem in _scheduleItems)
        {
            var status = bookingService.BookFlight(
                scheduleItem.Flight,
                scheduleItem.DepartureDt);
            // Check if BookFlight completed successfully by
            // comparing retuned result against string value.
            if (status != "SUCCESS")
            {
                return 1;
            }
        }
        return 0;
    }
}

How to fix connascence of meaning

Usually a fix is very simple. We can assign a value to global constant with proper name, or create an enum to map group of values to proper enum members. This way we reduce level of connascence by converting connascence of meaning to connascence of name.

Connascence of Position

Connascence of position appears when order of elements assumes certain meaning. Example, user entity is represented as an array where position of an element carries meaning: [FirstName, LastName, PhoneNumber]. Another example, a function takes a long list of arguments where of course the order of arguments matters.

lass UserFactory
{
    public string[] CreateUser(
        string firstName,
        string lastName,
        string phoneNumber)
    {
        // User is represented as an array where
        // each element position has meening.
        return new string[] {
            firstName,
            lastName,
            phoneNumber
        };
    }
}

How to fix connascence of position

Connascence of position can be converted to connascence of name by employing proper data types, e.g. classes to represent domain entities.

Functions that take long list of arguments are most likely too large in size and are responsible for too many things. In this case refactoring to multiple functions that are more focused will not only reduce connascence but also improve code readability and maintainability. Also, there may be a proper type hiding behind a long list of arguments, so grouping arguments into a proper data type may be a good solution that will reduce level of connascence to connascence of type.

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PhoneNumber { get; set; }
}

public class UserFactoryFixed
{
    public User CreateUser(
        string firstName,
        string lastName,
        string phoneNumber)
    {
        return new User
        {
            FirstName = firstName,
            LastName = lastName,
            PhoneNumber = phoneNumber
        };
    }
}

Connascence of Algorithm

Connascence of algorithm appears when components need to agree upon algorithm being used. Example, component A encrypts data and component B decrypts it. Components A and B need to agree upon encryption algorithm, otherwise component B will not be able to decrypt data that’s encrypted by component A. We can also think of string encoding (utf8, base64 and etc.) and serialization format (JSON, XML, protobuf and etc.) that need to be agreed by both sides.

Connascence of algorithm to be fixed

How to fix connascence of algorithm

The best solution is to extract an algorithm into a separate component and share the component with other. To fix the example above we can extract encryption and decryption logic into a component C so that components A and B would use component C to encrypt and decrypt data.

Connascence of algorithm fixed

Summary

We walked through all five static levels of connascence and discussed possible fixed for each of them. Most of the fixes can be distilled down to two categories:

  1. Refactor code to reduce connascence level where possible. For example, convert connascence of position to connascence of type.
  2. Refactor code to adjust connascence properties, increase locality and decrease degree and strength.
Posts created 28

One thought on “Fixing Static Connascence

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top