Linked Source Code: An Anti-pattern

I was going through some code the other day and I noticed that there were several linked source files.  This was very frustrating because the files existed outside the project that I was working in and not part of the solution.  What other projects did this source code file belong to?  If I made changes to the code in this code file, what side effects could I be introducing?  The contents of this file were a couple of classes that included business logic.

I believe that this kind of coding practice came from an incorrect interpretation of the Do Not Repeat Yourself (DRY) software pattern.  Indeed, this developer did not repeat themselves when they wrote the class.  By linking the source code file into several different projects, they ensured that only one copy of code exists.

The Sample

To illustrate my point, I will be creating a sample where the classes responsible for reading a credit card number are being linked or shared between many different projects.
Credit Card Reading Activity Diagram
This credit card reader will read the card information, process the card, and send it to the appropriate payment server or gateway.  How this is accomplished it not important for the purposes of this example.

The Credit Card class contained in the CreditCard.cs source file is inherited from the Card abstract base class.
Credit Card Class Diagram
The CreditCard.cs and Card.cs files have been included in the Alpha, Bravo, and Delta projects.

I have created a simple Visual Studio solution help illustrate the problem clearly.  The original source files were developed in the Alpha Project.  The other projects are linking to the original source files.
Single Solution Explorer Sample
Let us look on the file system for further clarification.
Linked Files Missing
Notice that Project Bravo does not contain any artifact of the source code files that exist in Project Alpha.

That is because linked files only modify the project file, Project Bravo.csproj.

<Project Sdk=Microsoft.NET.Sdk>

 

  <PropertyGroup>

    <TargetFramework>netstandard2.0</TargetFramework>

    <RootNamespace>Project_Bravo</RootNamespace>

  </PropertyGroup>

 

  <ItemGroup>

    <Compile Include=..\Project Alpha\Card.cs Link=Card.cs />

    <Compile Include=..\Project Alpha\CreditCard.cs Link=CreditCard.cs />

  </ItemGroup>

 

</Project>

Both files are referenced from within the Project Alpha folder.  Below is a project diagram that shows the overall solution.

Linked Classes Diagram

The Problem

If the code changes in either of these files, it will affect all three projects in the solution.  The problem is exacerbated if the source code file has been linked from a different solution.

Multi Solution Linked Classes Diagram

In this case, a change to either file could have disastrous results if you do not compile and test both solutions.  The margin for causing errors or unintended consequences it too high.  If a developer is performing any kind of maintenance on these files, there is simply no way to test for any ripple effects the changes may have.

While I can understand how this happened, I do not agree with the implementation.  How then would be a better way of accomplishing the same thing?  There are several changes that need to be made to fix this and make it both testable and maintainable.  Let us look at those now.

Solution

Let us talk about solutions to the problem.

Separation of Concerns

Separation of Concerns (SoC) is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern.

  • I want to break out the Credit Card Reading from the rest of the application.

First, extract out the code that performs the reading of the credit card from the rest of the projects.  Currently each project performs other work other than reading the credit cards.

The only business logic that you want these other projects to contain is the one of their concern, and not worry about the business logic of reading a credit card or any other logic.

Extracted Interface Class Diagram

I created an interface in my new project that exposes all the public methods in my CreditCard.cs class.  This helps accomplish two principles of SOLID.  The Interface segregation principle and the Dependency inversion principle.

Interface Segregation

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.  In this post, we will only explore one consumer of the interface.

  • Depending on how the item is consumed, it could have multiple interfaces to allow multiple consumers to only access those methods and properties of their concern. This approach is using Role Interfaces.  Martin Fowler better explains this concept here.

Dependency Inversion

The dependency inversion principle (DIP) is a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details.  We will see how this is done later when we cover unit testing.

Once the code has been excised from the existing project, you will need to add a project reference to the new project.  The solution should look like this now.

Extracted Interface Solution

And the solution explorer should look similar to this.

Solution Explorer - Seperate Credit Card API

Introduce Unit Tests

I had to refactor the alpha class in order to use the new interface.   I do not create or new up the object directly in my alpha class anymore.  Instead, I have created a constructor that accepts an object which implements ICreditCard.  This allows me to inject the object into the class.  This is critical for me to properly create unit tests for the alpha class.  Injecting the ICreditCard into the AlphaClass constructor allows me to satisfy the dependency inversion principle of SOLID.

AlphaClass.cs

using CreditCard.API;

namespace Project_Alpha
{
    class AlphaClass
    {
        private readonly ICreditCard creditCard;

        public AlphaClass(ICreditCard creditCard)
        {
            this.creditCard = creditCard;
        }

        public bool PerformSomeWork(double value)
        {
            try
            {
                creditCard.Read();

                if (creditCard.ReadSuccessful == false)
                    return false;

                ChargeCard(creditCard.CardNumber, value);

                return true;
            }
            finally
            {
                creditCard.Reset();
            }
        }

        private void ChargeCard(string creditCardCardNumber, double value)
        {
            //  Does Nothing for now.
        }
    }
}

Extracting an interface will allow you to create a mock instead of the real object for unit testing.  Here is some test code for this sample.  The newly created Credit Card API directly interfaces with the credit card reader so we will not create unit tests there.  Manual testing is required for that since hardware is involved.  I have created tests in the consumer of the Credit Card API, Project Alpha, instead.

AlphaClassSpecs.cs

using CreditCard.API;
using NUnit.Framework;
using Rhino.Mocks;

namespace Project_Alpha.Unit_Tests
{
    abstract class AlphaClassSpecs : SpecificationBase
    {
        protected AlphaClass sut = null;
        protected bool readSuccess = true;
        protected bool actualResult = true;
        protected double workValue = 123.55;

        protected override void Given()
        {
            var creditCardMock = BuildCreditCardReader();
            sut = new AlphaClass(creditCardMock);
        }

        protected override void When() => actualResult = sut.PerformSomeWork(workValue);

        protected virtual ICreditCard BuildCreditCardReader()
        {
            var reader = MockRepository.GenerateMock();
            reader.Stub(m => m.ReadSuccessful).Repeat.Any().Return(readSuccess);
            reader.Stub(m => m.CardNumber).Repeat.Any().Return("1234");
            reader.Stub(m => m.Read());
            reader.Stub(m => m.Reset()).Repeat.Any().Return(true);

            return reader;
        }
    }

    class when_card_is_read_successfully : AlphaClassSpecs
    {
        [Test]
        public void it_should_return_false() => Assert.IsTrue(actualResult);
    }

    class when_card_is_not_read_successfully : AlphaClassSpecs
    {
        protected override void Given()
        {
            readSuccess = false;
            base.Given();
        }

        [Test]
        public void it_should_return_false() => Assert.IsFalse(actualResult);
    }
}

You can create your unit tests in the same project as I have, or you can create a dedicated test project for your production code to keep the testing dependencies out of your production code.  Since this is just a test project, I show them in the same folder.

For production code, it is critical to have some unit tests around business logic describing the expected behavior.  You should write tests before you write your code.  This is another good software development process called Test Driven Development (TDD).  The idea is that your work in the following manner.

  • Red – The tests all fail because none of your logic is written yet.
  • Green – You have written just enough code to allow your tests to pass.
  • Clean – Now you can go back and clean up, optimize, and refactor your code.

You can run the unit tests, as many times as you want to, and the outcome should be the same.

Unit Tests Successful Run

Authors Rant

I am going off on a tangent to rant a bit on Unit Testing.  Properly designed unit tests are a real pet peeve of mine.  Too often, I have run across developers who have written a test around a behavior and call it a unit test.   Many times, these tests are not restricted to a unit of work but reach down into the data layers of an application.  Just because you place the [TestFixture] or [Test] attributes on a class does not make it a unit test.  It simply makes it a test!

Unit testing is a level of software testing where individual units or components of a software application are tested.  The purpose is to validate that each unit of the software performs as designed.  A unit is the smallest testable part of any software.

Unit Tests should not go beyond the object that you are trying to test.  It should not touch the database, the file system, or any other peripheral that exists outside your object.  If it does, then you do not have a unit test, you have an integration test.  You know that because it “integrates” with something else.  So, for heaven’s sake, if it is not a unit test don’t call it one!

For me it is like the prefix, Pre.  The most overused prefix in all the English language.  The most egregious example of this would be pre-pay.  You cannot pay for something before you pay for it.  This, however, is the topic for another post.

  • End of Rant

Introduce Package Management

While segmented the code off into its own project will work for Solution A described above, it will not work for Solution B.  Remember that Solution B had a link to the files outside the solution.  At this point in our example, that solution will not compile.  We need to make a reusable component that could be used for the rest of our developers.

In order to fix that, we need to refactor the code a bit further.  We need to create a reusable package out of it.  I will give you my example using NuGet, but you can use the package manager of your choice.

Nuspec File

For NuGet packages, you can create what is called a NuGet Specification or nuspec file.  This file contains the instructions for what you want to include or pack into your NuGet file.  Here is the nuspec file that we will use for our example.

<?xmlversion="1.0"encoding="utf-8"?><packagexmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">

  <metadata>     <id>SampleCompany.CreditCard.API</id>     <version>$version$</version>     <title>Credit Card API</title>     <authors>Sample Company Limited</authors>     <owners>Sample Company Limited</owners>     <requireLicenseAcceptance>false</requireLicenseAcceptance>     <description>Credit Card Reader API</description>     <copyright>Copyright © 2020 Sample Company Limited, All Rights Reserved</copyright>   </metadata>   <files>     <file src="bin\release\*.dll" target="lib\net45"/>   </files> </package>

Here is a brief description of the sample nuspec file.

  • Id – This is what you will look up and install the package by, so it should be as descriptive as possible.
  • Version – The version number of your package. You can see that we have the variable $version$ in place of the version number.  That is so that I can pass the version number into it when I build the package.  This is important if you are building your package using a Continuous Integration or CI server as often it is that server that will generate the version number
  • Title – The title of your package. Normally this is a formatted version of the id.
  • Description – A short summary of what your package does.
  • Authors, Owners – The individual or company name. This allows the package to be identified later.
  • Copyright – If you have a copyright on your intellectual property, then you would label your project here. Obtaining a copyright for your software is not difficult, you must simply follow the instructions defined here.
  • Files – The files that you want to include in your package. You can see that in the sample I want to include the files from the bin\release folder are to be included and stored in the lib\net45 folder.

This is all that is required for our sample API project, but you can reference the documentation for here for further details.

We can then build the package manually or as part of our CI server.  Since I don’t have a CI server right now, I will simply give you the command line option.

Console> nuget.exe pack .\CreditCardApi.nuspec -Version 2.1.1.23

Attempting to build package from 'CreditCardApi.nuspec'.

Successfully created package 'C:\Development\LinkedSourceCode\CreditCard.API\SampleCompany.CreditCard.API.2.1.1.23.nupkg'.

Now you can view the contents with the NuGet package explorer tool.
Nuget Package Explorer Sample
Lastly, you will need to publish it to a NuGet package server that your company can see.  If it is internal like our project here, you can publish it to the local package server.  If you want to publish it for everyone to use, then you can push it to Nuget.org.  Here is a sample of how to push on the command line.

Console> nuget push SampleCompany.CreditCard.API.2.1.1.23.nupkg 49698256-9407-4eba-aa8c-b5e50d159805 -src https://server.company.com/nuget

Pushing SampleCompany.CreditCard.API.2.1.1.23.nupkg to 'https://server.company.com/nuget'...

PUT https://server.company.com/nuget/

You need to specify the NuGet package (nupkg) file that you built in the first step.  Next is the API key of your NuGet server.  The GUID value shown here is the API key for my internal server on my network.  Lastly, the source parameter (-src) specifies the Uniform Resource Locator (URL) where my server exists on the network.  This will push the package to the server, so it is ready for use.

Modify Solutions

Next, we will modify the solutions so that they can use the newly created package.  In order to do that we will need to modify Visual Studio slightly so that the NuGet Package Manager can see the internal server to pull our package.  Open Visual Studio and select Tools and Options from the menu along the top menu.  Search for NuGet in the options search bar and select Package Sources.
Visual Studio Nuget Package Manager
Click the green plus button to add a new package source.  Add the name “Company Package Source” and then the source “http://server.company.com/nuget” and click the update button.  This will allow Visual Studio to find your new package server.

Open Solution A and right click on the solution in the Solution Explorer.  Make sure that you have “All” or “Company Package Source” selected as the package source.
All Package Managers for Solution
Then type in the ID of your published package.  Your new package should show up (SampleCompany.CreditCard.API) in the left-hand selection list.  Select it and then select the projects that you wish to install the credit card API into on the right-hand side.
Select Projects To Install API
Click the install button and that should install the API into each of the selected projects.  Assuming, you have made all the other change to each of the other projects, then your solution should build successfully.  Repeat this process for Solution B and it should build correctly, as well.

I am sure that you will agree that this is a much better way to satisfy the original need, which was to keep the code performing a specific task limited to one place.  Unlike the source code linking anti-pattern, this is a much more reusable, expandable, and maintainable way to accomplish this task.  The refactored code is still DRY, but now it follows good programming principles that will keep the developer maintaining your code from wanting to kill you.

As always, thank you for your time and attention.   Remember to like, comment, share and subscribe.  What other patterns do you see when you are coding that make you say, “Why did that guy do that?!?!”  Let me know in the comments and I’ll try to help answer that.  Until next time, happy coding everyone.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s