Ralf Westphals Newsletter

Share this post

Functional Dependencies

ralfwestphal.substack.com
Clean Code Development

Functional Dependencies

What they are, how to get rid of them

Ralf Westphal
Feb 9, 2023
Share

Functional dependencies are the bane of programming. They just cause problems: testability decreases, understandability decreases, function size increases the more functional dependencies proliferate. Hence in one word: Don’t! Don’t mess up your codebase with functional dependencies.

Here’s how they look in a nutshell:

There is some function containing logic (red area). Logic is the code where the action happens.

Ralf Westphals Newsletter
Logic Makes the Software Turn Around
Logic is a term strangely missing from many discussions about clean code development or software architecture. Yet it is such an import concept because without it many a heated argument about principles and structures does not make sense. By logic I mean the basic code you learn to write when you start programming. Here's an excerpt from the textbook on …
Read more
10 months ago · 1 like · Ralf Westphal

And at the same time the function is also calling another function (green area). That means, the calling function (CountDistinctWordsInFile) is depending on the called function (LoadTextFile).

If logic and calls to other functions are colocated in the same function, then that’s a functional dependency.

Because in essence not the containing function depends on the other function, but the logic. The logic only makes sense/can do its job, if the other function (or several of them) are called around the logic.

Why is this a problem?

  • For one, it’s a violation of the Single Level of Abstraction (SLA) principle. The logic is on a much lower level of abstraction than the line where the function is called. That makes understanding the composing function harder.

  • Most importantly, though, now the logic cannot be tested on its own. It can only be tested by calling the composing function which also calls another function whose logic then will also be tested. That makes reasoning about errors harder, that might lower the performance of the test.

  • And finally, mixing logic and calls to other functions rather sooner than later suggests to extract some logic into yet another function. That sounds great until it leads to bloated functions, because whenever you want to add more logic to a function, you can extract some. The result are functions with more and more logic interspersed with function calls. 100, 500, even 5000 lines of code in a function are quite common — and make reasoning and debugging of code very, very hard.

Here’s a visualization of this kind of evolution: A function starts out with just logic. Then more logic is added and some is extracted to another function. Then even more logic is added and more is extracted and so on…

A function slowly growing by adding functional dependencies.

There is no limit to how big functions can and will grow as long as functional dependencies are not considered harmful.

Commonly of these problems only one is attacked in a pragmatic way: the degradation of testability. The recommended remedy is Dependency Injection (DI).

Indeed this solves the problem of isolating the logic during testing: A surrogate can be injected which implements IPersistence and does only very little so that in case of a negative test result the source of the bug is limited to the logic (red area).

Unfortunately this approach leads to another kind of code bloat. In order to increase testability in all sorts of areas with functional dependencies, interfaces are added and dependency injection infrastructure is added. As Robert C. Martin himself said about this when he showed to what it leads with this image:

Diagram from “Clean Architecture” by Robert C. Martin

„Much of the complexity in that diagram was intended to make sure that the dependencies between the components pointed in the correct direction.“

Defusing functional dependencies by adding complexity! Seriously? To me that’s counterproductive advice. This graph I found on LinkedIn shows nicely what fighting functional dependencies with abstractions leads to. And fortunately it also shows, there’s hope beyond that:

To be able to precisely attach tests to logic — because logic is the part of code that’s hard to get right —, all functional dependencies have to be defused with DIP. That’s a lot of additional effort not clearing up a codebase but adding noise.

Logic interspersed with functional simply is a pain in the butt.

There has to be another solution!

And there is. The answer is simple:

Don’t try to defuse functional dependencies, but get rid of them altogether by applying the Integration Operation Segregation Principle (IOSP).

Ralf Westphals Newsletter
IOSP 2.0
To me the Integration Operation Segregation Principle (IOSP) is maybe the most important principle of clean code development. The reason: It’s so simple to follow. Because you can recognize if it’s been applied by looking at the code alone. IOSP code has a certain form. A form that supports understandability and testability…
Read more
7 months ago · Ralf Westphal

Is it really possible to have no functional dependencies as described/defined above in your codebase? Yes. Is it worth to strive for “zero functional dependencies”? Yes. Will you ever reach that point? No.

Even though you should really, really try hard to get rid of functional dependencies there’s always a trade-off. Sometimes the benefit does not warrant the effort. There are always some functions with some logic in them calling other functions where testability and understandability are not seriously impacted by the functional dependency. And that’s fine.

Nevertheless for most functions, probably 95%, functional dependencies should and can be get rid of by following the IOSP.

Share
Previous
Next
Comments
Top
New
Community

No posts

Ready for more?

© 2023 Ralf Westphal
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing