High Fan In, Low Fan Out

Software Engineering Series #1
In “Software Engineering Series”, I cover and discuss specific techniques and methods that are commonly used in large code bases. Software is a rapidly evolving field, and new ideas and techniques will always re-emerge over the years. This is by no means an exhaustive discussion. Opinions are welcomed, even more so when accompanied with actual experience.

Mon, Jun 22, 2020

Read in 3 minutes

High fan-in, low fan-out is a common technique that helps to consolidate dependencies and references, and encourages code re-use throughout large projects.

In software engineering, we are often encouraged to write code that is reusable, or write code that is “DRY” (i.e. Don’t Repeat Yourself). The most common way of achieving this is to collect commonly used functionality into an external “helper” module. Let’s examine the code example below:

class Animal
    def initialise(name)
        @name = name    
    end

    def name 
        @name.split(" ").map {|s| s.first + "." }.join("")
    end
end

class Owner
    def initialise(name)
        @name = name    
    end

    def name 
        @name.split(" ").map {|s| s.first + "." }.join("")
    end
end

Typically in cases like this, an engineer may realise there could be more cases where uppercasing names is a common business requirement, so this happens:

module Helpers
    def self.upper_initials(str)
        str.split(" ").map {|s| s.first + "." }.join("")
    end
end

class Animal
    # ...
    def name 
        Helpers.upper_initials(@name)
    end
end

class Owner
    # ...
    def name 
        Helpers.upper_initials(@name)
    end
end

High fan-in, low-fan out design

At this point we have 2 classes that are using the Helper module. let’s say the system grows to support many other classes as well where upper_initials method is used, so the Helper module is called at even more places.

This design approach is called “high fan-in, low fan-out”. This means many classes or modules are dependent on this Helper module, but the module itself doesn’t have many external dependences.

Why is this desirable

This design of funnelling common low-level functions or business concerns into a common module encourages code reuse and maintainability over a long run.

Should I then refactor all my code into smaller modules?

Well, this is not exactly a good practice either. If the above Helper module ends up being used into only 1-2 classes, then there is very low reuse as well. The disadvantage of stepping to into another piece of code abstraction outweighs the advantage of code reuse. We can describe this module as having a “low fan-in, low fan-out”.

Helper modules become problematic

The situation can quickly get out of hand when the Helpers module also starts to be polluted with other concerns and responsibilities.

module Helpers
    include SomeHttpLibrary
    include SomeFileLibrary
    include SomeMailerClass

    def self.upper_initials(str)
        str.split(" ").map {|s| s.first + "." }.join("")
    end

    def self.make_a_db_call
        SomeClass.where(name: "something")
    end

    def self.save_a_file(contents)
        SomeFileLibrary.write("something.txt", contents)
    end
    # ... many more functions ...
end

This phenomena is described as “high fan-out”, since it has many dependencies. This is a common mistake when engineers are over-enthusiatic about putting all everything in the same place.

The motivation itself isn’t wrong; it’s just that there is limited capacity for an engineer to hold the context of what this module is doing with so many things going on. The rule of thumb is to have at most 7 immediate dependecies in a class or module.

Interestingly, the fanout of a module is also proven to be directly correlated to the probability of a bug.

“The fanout of a module is the number of calls from that module. At least three studies have concluded that fanout squared is one component of a design metric that correlates well to probability of defect.” Grady, R.B., “Successfully applying software metrics,” in Computer , vol.27, no.9, pp.18-25, Sept. 1994.

In summary

“Low fan-in, low fan-out”

Maybe you don’t really need this module. Are you just refactoring for the sake of it?

“Low fan-in, high fan-out”

Do you want to relook at how to consolidate some common modules under a sub-module? There could be a fundamental error in the initial design.

“High fan-in, low fan-out”

This is good. The module you have built has high code reuse and easy to maintain.

“High fan-in, high fan-out”

You likely have a very brittle module. It is used everywhere, but also has many dependencies. There could be a fundamental error in the initial design.

Learning to write software? Subscribe now to receive tips on software engineering.

Back to home page