Chapter 11. Coding Best Practice and Testing the Functional Code

We developed a functional application in the previous chapter. To create better code in the functional approach, we have to follow the best practice rules and implement them in our code. In this chapter, we are going to discuss the concept of the functional approach, which is a pure function and makes our function similar to a mathematical function. The topics that will be covered in this chapter are as follows:

  • Preventing dishonest signatures
  • Creating immutable classes
  • Avoiding Temporal Coupling
  • Dealing with the side-effects
  • Separating the code into a Domain Logic and the Mutable Shell
  • Testing the functional code

Coding best practices in functional C#

The functional approach has the concept of a pure function. This means that the function will produce the same result as long as we pass the exact same input. Now, let's start our discussion to create the better functional code by following the coding best practices outlined here.

Preventing dishonest signatures

As we discussed in Chapter 1, Tasting Functional Style in C#, we use the mathematical approach to constructing our code in functional programming. In other words, functional programming is programming with mathematical functions. There are two requirements that mathematical functions must fit, they are:

  • A mathematical function should always return the same result whenever we supply the same arguments.
  • The signature of the mathematical function should deliver all the information for the possible accepted input values and the possible produced output.

Now let's take a look at the following code snippet, which we can find in the HonestSignature.csproj project:

public partial class Program 
{ 
  public static int SumUp( 
    int a, int b) 
  { 
    return a + b; 
  } 
} 

By examining the preceding SumUp() function, we can say that we will retrieve the same output every time we pass the same inputs. Now let's examine the following GenerateRandom() function, which we can also find in the HonestSignature.csproj project:

public partial class Program 
{ 
  public static int GenerateRandom( 
    int max) 
  { 
    Random rnd = new Random( 
      Guid.NewGuid() 
      .GetHashCode()); 
    return rnd.Next(max); 
  } 
} 

From the preceding code, we can see that we will retrieve different output although we pass the same input continually. Suppose we have the following RunGenerateRandom() function:

public partial class Program 
{ 
  public static void RunGenerateRandom() 
  { 
    for (int i = 0; i < 10; i++) 
    { 
      Console.WriteLine( 
        String.Format( 
          "Number {0} = {1}", 
          i, 
          GenerateRandom(100))); 
    } 
  } 
} 

If we run the preceding RunGenerateRandom() function, we will get the following output on the console:

Preventing dishonest signatures

From the preceding code snippet, we invoke the GenerateRandom() function 10 times by passing the exact same argument, that is, 100. As you can see in the preceding figure, the function returns a different output for each of the 10 invocations. So, we have to avoid functions such as the GenerateRandom() function in order to create a pure function since it is not a mathematical function.

Now let's take a look at the following Divide() function, which will divide the first argument by the second argument:

public partial class Program 
{ 
  public static int Divide( 
    int a, int b) 
  { 
    return a / b; 
  } 
} 

The Divide() function looks similar to the SumUp() function since the signature of the Divide() function accepts any two integers and returns another integer. So if we pass the exact same argument, it will return the same output. However, how about if we pass 1 and 0 as input parameters? The Divide() function will throw a DivideByZeroException error instead of returning an integer value. In this case, we can conclude that the signature of the function does not deliver enough information about the result of the operation. It looks like the function can handle any two parameters of the integer type, but it actually cannot. To solve this problem, we can refactor the preceding Divide() function to the following one:

public partial class Program 
{ 
  public static int? Divide( 
    int a, int b) 
  { 
    if (b == 0) 
    return null; 
    return a / b; 
  } 
} 

As you can see in the preceding Divide() function, we add the nullable type by adding a question mark after int so that the return of the function can be null. We also add an if statement to make sure that DivideByZeroException error will never be thrown.

Refactoring a mutable class into an immutable one

Immutability is very important in functional programming, since a mutable operation will make our code dishonest. As we discussed previously, we need to prevent dishonest operations in order to create our pure function approach. Immutability is applied to a data structure - for instance, a class means that the objects of this class cannot be changed during their lifetime. In other words, we can say that a class is mutable if the instances of the class can be changed in some way, while it is immutable if we cannot modify the instance of that class once we create it.

Now, let's take a look at the following code, which can be found in the Immutability.csproj project to continue our discussion:

namespace Immutability 
{ 
  public class UserMembership 
  { 
    private User _user; 
    private DateTime _memberSince; 
    public void UpdateUser( 
      int userId, string name) 
    { 
      _user = new User( 
       userId, 
       name); 
    } 
  } 
  public class User 
  { 
    public int Id { get; } 
    public string Name { get; } 
    public User( 
      int id, 
      string name) 
    { 
      Id = id; 
      Name = name; 
    } 
  } 
} 

As you can see in the preceding code, we have a simple composition. The UserMembership class consists of the _user and _memberSince properties. We can also see that the User class is immutable since all the properties are defined as read-only. Because of immutability, the only way for the UserMembership method to update the _user field is to create a new User instance and replace the old one with it. Note that the User class itself doesn't contain the state here, whereas the UserMembership class does. We can say that the UpdateUser method leaves a side-effect by changing the object's state.

Now let's refactor the UpdateUser method and make it immutable. The following code is the result of refactoring the UpdateUser method:

namespace Immutability 
{ 
  public class UserMembership 
  { 
    private readonly User _user; 
    private readonly DateTime _memberSince; 
 
    public UserMembership( 
      User user, 
      DateTime memberSince) 
    { 
       _user = user; 
       _memberSince = memberSince; 
    } 
    public UserMembership UpdateUser(int userId,string name) { 
      var newUser = new User(userId,name);
      return new UserMembership(newUser,_memberSince);
    } 
  } 
 
  public class User 
  { 
    public int Id { get; } 
    public string Name { get; } 
    public User( 
      int id, 
      string name) 
    { 
      Id = id; 
      Name = name; 
    } 
  } 
} 

As you can see, the UpdateUser() method no longer updates the structure of the UserMembership class. Instead, it creates a new UserMembership instance and returns it as a result of the operation. By refactoring the UpdateUser method, we have eliminated the side-effect from the method. Now it's clear what the actual output of the operation is. The usage of immutable data makes the code more readable and also helps to provide a good understanding of what is going on right away without too much effort.

Avoiding mutability and temporal coupling

Sometimes, the use of the methods with side-effects will damage readability. The invocation of one method is coupled with the other's method invocation. To make things clear, let's take a look at the following code, which we can find in the TemporalCoupling.csproj project:

public class MembershipDatabase 
{ 
  private Address _address; 
  private Member _member; 
  public void Process( 
    string memberName, 
    string addressString) 
  { 
    CreateAddress( 
      addressString); 
 
    CreateMember( 
      memberName); 
    SaveMember(); 
  } 
 
  private void CreateAddress( 
    string addressString) 
  { 
    _address = new Address( 
      addressString); 
  } 
 
  private void CreateMember( 
    string name) 
  { 
    _member = new Member( 
    name, 
    _address); 
  } 
 
  private void SaveMember() 
  { 
    var repository = new Repository(); 
    repository.Save(_member); 
  } 
} 
 
public class Address 
{ 
  public string _addressString { get; } 
  public Address( 
    string addressString) 
  { 
    _addressString = addressString; 
  } 
} 
 
public class Member 
{ 
  public string _name { get; } 
  public Address _address { get; } 
 
  public Member( 
    string name, 
    Address address) 
  { 
    _name = name; 
    _address = address; 
  } 
} 
 
public class Repository 
{ 
  public static List<Member> customers { get; } 
 
  public void Save( 
    Member customer) 
  { 
    customers.Add(customer); 
  } 
} 

From the preceding code, you can see that we have a MembershipDatabase class, which processes a new member. It retrieves input parameters named memberName and addressString and uses them to insert a new member in the database. The Process() method in the MembershipDatabase class invokes the CreateAddress method first, which will create the address and then save it to the private field. The CreateMember() method then retrieves the address and uses it to instantiate a new Member parameter, which is saved in another private field named member. The last method, the SaveMember() method, saves the member to the database (in this example, we use list). There is a problem here. The invocations in the Process() method are coupled with temporal coupling. We have to always invoke these three methods in the right order for this code to work properly.

If we don't place the method in the right order - for instance, if we put the CreateAddress() method invocation, after the CreateMember() method invocation the resulting member instance will be invalid since the member will not retrieve the required dependency address. Likewise, if we put the SaveMember() method invocation above others, it will throw NullReferenceException because, when it tries to save a member, the member instance would still be null.

Temporal coupling is a consequence of the method's signature dishonesty. The CreateAddress() method has an output, creating an address instance, but this output is hidden under a side-effect because we mutate the Address field in the MembershipDatabase class. The CreateMember() method hides the result of the operation as well. It saves member to the private field, but it also hides some of its input. From the signature of the CreateMember() method, we might think that it needs only the name parameter in order to create member while it actually refers to a global state, the address field.

This happens to the SaveMember() method as well. To remove the temporal coupling, we have to specify all of the input and output in the method's signatures explicitly or, in other words, move all side-effects and dependencies to the signature level. Now, let's refactor the preceding side-effect - containing code to the following code:

public class MembershipDatabase 
{ 
  public void Process( 
    string memberName, 
    string addressString) 
  { 
    Address address = CreateAddress( 
      addressString); 
    Member member = CreateMember( 
      memberName, 
      address); 
    SaveMember(member); 
  } 
 
  private Address CreateAddress( 
    string addressString) 
  { 
    return new Address( 
      addressString); 
  } 
 
  private Member CreateMember( 
    string name, 
    Address address) 
  { 
    return new Member( 
      name, 
      address); 
  } 
 
  private void SaveMember( 
    Member member) 
  { 
    var repository = new Repository(); 
    repository.Save( 
      member); 
  } 
} 
 
public class Address 
{ 
  public string _addressString { get; } 
  public Address( 
    string addressString) 
  { 
    _addressString = addressString; 
  } 
} 
 
public class Member 
{ 
  public string _name { get; } 
  public Address _address { get; } 
  public Member( 
    string name, 
    Address address) 
  { 
    _name = name; 
    _address = address; 
  } 
} 
 
public class Repository 
{ 
  public static List<Member> customers { get; } 
 
  public void Save( 
    Member customer) 
  { 
    customers.Add(customer); 
  } 
} 

From the highlighted code, we can see that we have refactored the CreateAddress(), CreateMember(), SaveMember(), and Process() methods.

The CreateAddress() method now returns Address instead of saving it to the private field. In the CreateMember() method, we add a new parameter, address, and also change the returning type. For the SaveMember() method, instead of referring to the customer private field, we now specify it as a dependency in the method's signature. In the Process() method, we can now remove the fields, and we have successfully removed the temporal coupling with this change.

Now, it's impossible for us to put the CreateAddress() invocation method after the CreateMember() invocation method because the code will not be compiled.

Dealing with the side-effects

Although we need to create a pure function in functional programming, we cannot avoid the side-effects completely. As you can see in the preceding MembershipDatabase class, we have the SaveMember() method, which will save the member field into the database. The following code snippet will explain this clearly:

private void SaveMember( 
  Member member) 
{ 
  var repository = new Repository(); 
  repository.Save( 
    member); 
} 

To deal with the side-effects, we can use the command-query separation (CQS) principle to separate methods that generate side-effects and methods that don't. We can call commands for methods that incur side-effects and queries for methods that don't. If the method alters a piece of state, it should be the void type method. Otherwise, it should return something. Using this CQS principle, we can identify the purpose of a method by just looking at its signature. If the method returns a value, it will be a query and it won't mutate anything. If the method returns nothing, it must be a command and will leave some side-effects in the system.

From the preceding MembershipDatabase class, we now can identify that the Process() and SaveMember() methods are commands types and will leave some side-effects since they return nothing. In contrast, the CreateAddress() and CreateMember() methods are queries and won't mutate anything since they return something.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset