funk-neon-sign-brick-wall-background-87058206.jpg

Two of the three break-out talks on Tuesday were with Jeremy Clark (@jeremybytes) and since the two talks bridge each other nicely, I've decided to make this blog post a distillation of those two talks. 

The first talk was about Func and Action, which was something I was not quite as comfortable with, but the talk gave a few aha-moments, that made things fall into place. The second talk was about a concept that we use frequently as developers: lambdas. I had a fairly good grasp of how they work but put together with the knowledge from the first talk, I got a much deeper understanding of how to use lambdas.

Func and Action

Func and Action are both constructs that build on delegates. But instead of declaring the delegate and hooking it up manually, we can do it all with a Func/Action. They are pretty much the same thing, except that Func returns a value; an Action does not. To determine which input (and output for a Func), we can take a look at the signature of the delegate. 

Action<int, string> for example takes two arguments. The first is an int and the second is a string. 

Func<int, string> takes an int argument and returns a string. Func<int, int, string> takes two int arguments and returns a string. In short, the last type in the brackets is the return type and all the preceding types are the input parameters.

The cool thing about Func and Action is that you can have a method that takes a Func/Action and this way can defer the behavior until runtime. An example of this could be an overload of the ToString method in a class.

public class Person

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

 

    public string ToString(Func<Person, string> formatter)

    {

        return formatter(this);

    }

}

To use this we can call the ToString method on a Person object with either a delegate with the Func<Person, string> signature or an anonymous lambda like this:

var person = new Person(){FirstName = "John", LastName = "Johnson" };

Console.WriteLine(person.ToString(p => $"{p.FirstName} {p.LastName}"));

This method of passing in some logic (or another type of dependency) is called "method dependency injection". It can be used in many different ways. Jeremy Clark has a good little example of this in his sample code 

In his example he has a GetFormatter() method, that will return the correct Func, based on which type of formatting has been chosen in the UI.

Lambdas

We use lambdas very often as C# programmers. The most common scenario is when we're working on IEnumerable<T>.  When you use LINQ you use lambdas! LINQ is essentially a set of extension methods on IEnumerable<T>, that provide a lot of functionality in a very succinct syntax. If we look a little closer on the signature of a LINQ method, it is a Func or an Action. Usually we use an anonymous delegate (Func or Action) when invoking the LINQ method, but it could just as well be a delegate defined somewhere else.

On a IEnumerable<Person> we can use the Where extension method to get a subset of the list, that matches a certain predicate.

IEnumerable<Person> persons = new List<Person>()

{

    new Person(){ FirstName = "John"},

    new Person(){ FirstName = "Foo"},

    new Person(){ FirstName = "Bar"}

};

var matches = persons.Where((Person p) => p.FirstName == "John");

 

This will return a new list of all the objects where the first name equals "John". In this example we're passing in an anonymous delegate and I've used a syntax that is a bit more explicit compared to what you usually see. I've added the type in front of the variable name 'p', to clearly indicate what the type is. It shows that the variable p is of type Person, so when we go to the right side of => we know that p is a Person object and that we can access the public interface (ie. properties, methods) of each of the Person objects in the list. 

 

However, we do not need to implicit declare what the type that we are working on is. The compiler can infer this automatically and this leads to the syntax that is the most normal:

var matches = persons.Where(p => p.FirstName == "John");

 

There are no rules about how the argument on the left of => should be named. But normally we use either 'x' or the first letter of the type that we are working on (here 'p'). Now, to know what type the expression to the right of => expects, we can look at the method signature of Where. In this case, the argument to where is Func<Person, bool>. As we just learned above this means that the argument is a Person object (this first type in the brackets) and that it is expecting the code to the right of => to evaluate to bool. In other words Where will return all the elements from the list, that satisfy the boolean expression on the right side of =>. In this case, it is all the Person objects where the first name property equals "John".

Another commonly used LINQ method is OrderBy (and OrderByDescending). If we take a look at the method signature of OrderBy, we can see that it is Func<Person, TKey>. This can be a little hard to understand since TKey is not a type per se, but instead signifies a function that the list should be ordered by. In most instances TKey is simply a type that implements the IComparable interface, so that two objects can be compared and placed correctly to each other in the lsit. All simple types  (ie. int, string) implement this out of the box and if you have your own complex types, it is fairly easy to implement the interface. So essentially what TKey means is that we have to tell the OrderBy method, which property it should order by.

var orderedList = persons.OrderBy(p => p.FirstName);

OrderBy returns the type IOrderedEnumerable<People> and not IEnumerable<People> as many think. This is to enable us to do a secondary sorting. IOrderedEnumerable has an extension method called ThenBy (and ThenByDescending) that allows us to sort first on one property (by using OrderBy) and after that by a second property. An example of this could be sorting by the last name first and after that by the first name.

var orderedList = persons.OrderBy(p => p.LastName).ThenBy(p => p.FirstName);

Summary

To summarize Func and Action are not as complex to understand, once you get the hang of it. As soon as you crack what a delegate is and how to read and understand the signature, it is actually quite easy to use. Check the types in the brackets, then you know what type the input arguments should have (and what type the return type is, if it is a Func).

 

A few last thing to note:

Func with no input arguments that return string looks like this :

Func<string>

() => ""

 

Func with more than one input argument looks like this:

Func<string, string, string>

(a, b) => $"{a} {b}"

 

Jules Mays full-day workshop called "Coding like your life depends on it" covered a lot of ground. The main subject is how we, as developers, should tackle everyday work. It does not matter if it is a new feature or a bug fix, we have to "code like our lives depends on it".

 

There were a lot of very complex topics - too complex to cover in a summary-style blog post like this (and there is definitely some things that I will have to investigate further, before fully understanding it). However, there were a few points that really struck home for me. The points that Jules May made about software are absolutely spot on. We cannot take quality for granted!

 

Software quality

It is in the nature of the beast that we as humans are flawed. This also applies to programmers. All programmers introduce bugs in their code, no matter how skilled they are. The question is how we handle those bugs and the measures we take to discover bugs as soon as possible. We must realize that the earlier in the development process we find and fix bugs, the cheaper and faster it is. 

 

As a developer, we face many challenges. Our main responsibility is to implement features described by the business. This is something that we are comfortable doing. But we also have the responsibility to demand of the business that bug fixing and technical debt must be prioritized. All too often the business only have focus on which features should be implemented next, so that the sales department has a new shiny gadget they can sell. However if the business does not embrace the fact that all software systems have technical debt - and that the technical debt is exponential over time - the software will suffer a slow and painful death. Each new change will be more and more time-consuming to implement, eventually making it impossible to keep working on.

 

To prevent the total collapse of a codebase, it is empirical that the developers take responsibility for the code base and clearly communicate that the quality of the code base must be prioritized on equal terms with new features. The developers must do this since the business does not have the skill set required to recognize the importance of eliminating technical debt and improving the quality of the codebase.

 

"Software quality is not a luxury that we can take for granted - Jules May"

 

Keep asking 'why'?

In criminology, the broken window theory, states that visible signs of decay eg. a broken window that is not replaced in a building, will create an environment that encourages additional disorder. The same concept applies to a codebase. If there's no structure and/or the quality of the existing code is not up to par, chances are that the next time someone makes changes to the codebase, the new code will be of equal or worse quality. This will make the overall quality of the code deteriorate more and more over time.

 

To mitigate this, developers should be encouraged to take responsibility for the changes they make. One of the ground pillars of this is understanding the general architecture and adhering to it when adding new features. It is also important to find the root cause of a bug when trying to fix it.

 

"Find the defect that is causing the malfunction, instead of just patching up the malfunction - Jules May"

 

A good way to try to get to the button of a malfunction and thereby find the defect that is the culprit, is to open the debugger and keep asking 'Why did this happen?'. Keep asking 'why' until it is not possible to ask the question anymore; then you have found the defect that should be fixed. Doing this will improve the quality of the codebase immensely.