String extensions using LINQ

Some days ago, I encountered this C# code which defines some more or less standard string operations:

using System;
 
namespace My.Extensions
{
    public static class StringExtensions
    {
        public static string Left(this string s, int count) {
            if(count > s.Length) {
                return s;
            }
 
            return s.Substring(0, count);
        }
 
        public static string Right(this string s, int count) {
            if(s.Length <= count) {
                return s;
            }
 
            return s.Substring(s.Length - count, count);
        }
 
        public static string Shorten(this string s, int count) {
            if(s.Length < count) {
                return String.Empty;
            }
 
            return s.Substring(0, s.Length - count);
        }
    }
}

Personally, I don’t like this kind of implementation. It looks too technical to me. The string manipulations, the condition checks – it’s completely from a technical point of view.

There is a way to implement that functionality in a descriptive way which makes it look more aesthetic. The key is to understand a string as a sequence of characters. Let’s try to rewrite the extension methods using LINQ:

using System.Linq;
 
namespace My.Extensions
{
    public static class StringExtensions
    {
        public static string Left(this string characters, int number) {
            return new string(characters.Take(number).ToArray());
        }
 
        public static string Right(this string characters, int number) {
            return new string(characters.Skip(characters.Length - number).ToArray());
        }
 
        public static string Shorten(this string characters, int number) {
            return new string(characters.Take(characters.Length - number).ToArray());
        }
    }
}

Better? In my eyes, yes! The code is shorter, it looks more precise. The core part of the methods is extremely descriptive. The methods meet their respective purpose by taking or skipping a certain number of characters of the input string – nice.

Unfortunately, there is one thing that is not so nice. There is some noise in the above implementation. We are forced by the .NET framework to switch from IEnumerable to string making a detour via an array of char. But by introducing another extension method, we make our short code example a little bit more readable again:

using System.Collections.Generic;
using System.Linq;
 
namespace My.Extensions
{
    public static class StringExtensions
    {
        public static string Left(this string characters, int number) {
            return characters.Take(number).AsString();
        }
 
        public static string Right(this string characters, int number) {
            return characters.Skip(characters.Length - number).AsString();
        }
 
        public static string Shorten(this string characters, int number) {
            return characters.Take(characters.Length - number).AsString();
        }
    }
 
    public static class EnumerableExtensions
    {
        public static string AsString(this IEnumerable characters) {
            return new string(characters.ToArray());
        }
    }
}

And exactly this is the driving force behind all the code changes we made here: readability! Code should be simple, readable, and self-explanatory.

So, what’s left? With respect to self-explanation, we maybe can find better names for the string extension methods. As the saying goes, I leave this as an exercise for you…

Just to mention, the original implementation as well as the one developed here satisfies this specification:

using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace My.Extensions.Tests
{
    [TestClass]
    public class MyExtensionsSpecifications
    {
        private const string Abcdefghij = "abcdefghij";
 
        [TestMethod]
        public void The_first_5_characters_of_abcdefghij_are_abcde() {
            Assert.AreEqual("abcde", Abcdefghij.Left(5));
        }
 
        [TestMethod]
        public void The_first_50_characters_of_abcdefghij_are_abcdefghij() {
            Assert.AreEqual(Abcdefghij, Abcdefghij.Left(50));
        }
 
        [TestMethod]
        public void The_last_3_characters_of_abcdefghij_are_hij() {
            Assert.AreEqual("hij", Abcdefghij.Right(3));
        }
 
        [TestMethod]
        public void The_last_20_characters_of_abcdefghij_are_abcdefghij() {
            Assert.AreEqual(Abcdefghij, Abcdefghij.Right(20));
        }
 
        [TestMethod]
        public void abcdefghij_shortened_by_4_characters_is_abcdef() {
            Assert.AreEqual("abcdef", Abcdefghij.Shorten(4));
        }
 
        [TestMethod]
        public void abcdefghij_shortened_by_12_characters_is_an_empty_string() {
            Assert.AreEqual(string.Empty, Abcdefghij.Shorten(12));
        }
    }
}

When a debugger is needed

Something became clear to me when I attended a Code Review last week. The code we reviewed was written by a co-worker who is quite new in the business, i.e. who passed his academic studies not long ago and has never worked as a software developer before – so let’s call him a newbie. And of course, there was this unbelievable implementation of a class method: All concerns were present in that single method, and you may imagine the length of that method.

So I asked, why all these things are in one single method. His answer was that it is difficult when the debugger jumps from method to method and from class to class. So, it would be better to have it all in one method. Having the logic spread over the whole solution appeared too difficult to him. He needed the debugger to understand what was going on.

Now, what do I have learned from that?

A debugger is needed when the code rules us, when we don’t understand the code just by reading it. It should be vice versa: We should rule the code! Then, no debugger is needed to understand the code.

So, let’s rule the code! I am quite sure you know what you have to do to obtain that.

(Just a short note: A debugger may be used during talks or Code Reviews when you want to show some internal states or just to demonstrate something. Or as the last resort when hunting a bug. In my eyes, these are scenarios where a debugger is useful.)