Home > Software engineering >  Reduce Cognitive Complexity
Reduce Cognitive Complexity

Time:01-18

I'm having big trouble with reducing cognitive complexity in given piece of code. Could you please give some tips on how to fix this issue? I could reduce it from 24 to 16 using switch, but it is still 16 and I have no options left

 protected override bool Compare(object valueToValidate, object valueToCompare)
    {
        if (RaUtils.IsBlankValue(valueToValidate) || RaUtils.IsBlankValue(valueToCompare))
        {
            return true;
        }

        switch (Type.GetTypeCode(valueToCompare.GetType()))
        {
            case TypeCode.DateTime:
                if (DateTime.TryParse(valueToValidate.ToString(), out var valueToValidateDt)
                    && DateTime.TryParse(valueToCompare.ToString(), out var valueToCompareDt))
                {
                    return valueToValidateDt >= valueToCompareDt;
                }

                break;
            case TypeCode.Double:
                if (double.TryParse(valueToValidate.ToString(), out var valueToValidateDouble)
                    && double.TryParse(valueToCompare.ToString(), out var valueToCompareDouble))
                {
                    return valueToValidateDouble >= valueToCompareDouble;
                }

                break;
            case TypeCode.Decimal:
                if (decimal.TryParse(valueToValidate.ToString(), out var valueToValidateDecimal)
                    && decimal.TryParse(valueToCompare.ToString(), out var valueToCompareDecimal))
                {
                    return valueToValidateDecimal >= valueToCompareDecimal;
                }

                break;
            case TypeCode.Int32:
                if (int.TryParse(valueToValidate.ToString(), out var valueToValidateInt32)
                    && int.TryParse(valueToCompare.ToString(), out var valueToCompareInt32))
                {
                    return valueToValidateInt32 >= valueToCompareInt32;
                }

                break;
            case TypeCode.Int64:
                if (long.TryParse(valueToValidate.ToString(), out var valueToValidateInt64)
                    && long.TryParse(valueToCompare.ToString(), out var valueToCompareInt64))
                {
                    return valueToValidateInt64 >= valueToCompareInt64;
                }

                break;

            default: throw new NotImplementedException();
        }

        return false;
    }

CodePudding user response:

You could try to move the code inside the case blocks into their own methods:

private bool CompareDateTime(string value1, string value2)
    {
        if (DateTime.TryParse(value1, out var valueToValidateDt)
                    && DateTime.TryParse(value2, out var valueToCompareDt))
                {
                    return valueToValidateDt >= valueToCompareDt;
                }
        return false;
    }

Your Comapre would simplify to this:

protected override bool Compare(object valueToValidate, object valueToCompare)
    {
        if (RaUtils.IsBlankValue(valueToValidate) || RaUtils.IsBlankValue(valueToCompare))
        {
            return true;
        }

        switch (Type.GetTypeCode(valueToCompare.GetType()))
        {
            case TypeCode.DateTime:
                return CompareDateTime(valueToValidate.ToString(), valueToCompare.ToString());
            case TypeCode.Double:
                 return CompareDouble(valueToValidate.ToString(), valueToCompare.ToString());
            case TypeCode.Decimal:
                 return CompareDecimal(valueToValidate.ToString(), valueToCompare.ToString());
            case TypeCode.Int32:
                 return CompareInt32(valueToValidate.ToString(), valueToCompare.ToString());
            case TypeCode.Int64:
                 return CompareInt64(valueToValidate.ToString(), valueToCompare.ToString());

            default: throw new NotImplementedException();
        }
    }

Because all your methods have the same signature, you could also use a Dictionary<TypeCode,Func<string,string,bool>> instead of a switch block:

protected override bool Compare(object valueToValidate, object valueToCompare)
    {
        if (RaUtils.IsBlankValue(valueToValidate) || RaUtils.IsBlankValue(valueToCompare))
        {
            return true;
        }
    var methods = new Dictionary<TypeCode,Func<string,string,bool>>
    {
       { TypeCode.DateTime, CompareDateTime },
       { TypeCode.Double, CompareDouble },
       { TypeCode.Decimal, CompareDecimal },
       { TypeCode.Int32, CompareInt32 },
       { TypeCode.Int64, CompareInt64 }
    };
    if(methods.TryGetValue(Type.GetTypeCode(valueToCompare.GetType()), out var method))
    {
       return method.Invoke(valueToValidate.ToString(), valueToCompare.ToString());
    }
    else
    {
       throw new NotImplementedException();
    }
}

CodePudding user response:

You may be able to entirely eliminate the switch and the associated redundancy if you can pass concrete types to a generic function like the following. The class contains a perfunctory test in Main.

In general, switches over types should raise suspicions because they try to manually re-invent polymorphism, either compile-time (as here) or runtime polymorphism. It's often better to use the language mechanisms for that.

public class VariableCompare
{
    public static bool Compare<N>(N valueToValidate, N valueToCompare) where N: IComparable<N>, IParsable<N>
    {
        N lhs, rhs;

        if (    N.TryParse( valueToValidate.ToString(), 
                            null, 
                            out lhs )
             && N.TryParse( valueToCompare.ToString(), 
                            null,                            
                            out rhs) )
        {
            return lhs.CompareTo(rhs) >= 0;
        }
        return false;
    }

    static int Main()
    {
        int i1 = 1, i2 = 2;
        Console.WriteLine(Compare(i1, i2));

        float f1 = 2, f2 = 1;
        Console.WriteLine(Compare(f1, f2));

        DateTime d1 = DateTime.Now;
        DateTime d2 = DateTime.Today;
        Console.WriteLine(Compare(d1, d2));

        return 0;
    }
}

CodePudding user response:

I think for this method formatting and short names can make a world of difference. Here are two versions that I think are very easy to read and to maintain:

Version 1

bool Compare(object valueToValidate, object valueToCompare)
{
    if (RaUtils.IsBlankValue(valueToValidate) || RaUtils.IsBlankValue(valueToCompare))
    {
        return true;
    }

    var a = valueToValidate.ToString();
    var b = valueToCompare.ToString(); 

    return Type.GetTypeCode(valueToCompare.GetType()) switch
    {
        TypeCode.DateTime =>      
                   DateTime.TryParse(a, out var x) 
                && DateTime.TryParse(b, out var y)
                && x >= y,
        TypeCode.Double =>
                   double.TryParse(a, out var x)
                && double.TryParse(b, out var y)
                && x >= y,
        TypeCode.Decimal =>  
                   decimal.TryParse(a, out var x)
                && decimal.TryParse(b, out var y)
                && x >= y,
        TypeCode.Int32 => 
                   int.TryParse(a, out var x)
                && int.TryParse(b, out var y)
                && x >= y,           
        TypeCode.Int64 =>
                   long.TryParse(a, out var x)
                && long.TryParse(b, out var y)
                && x >= y,
        
        _ =>  throw new NotImplementedException();
    };
}

Version 2

bool Compare(object valueToValidate, object valueToCompare)
{
    if (RaUtils.IsBlankValue(valueToValidate) || RaUtils.IsBlankValue(valueToCompare))
    {
        return true;
    }

    var a = valueToValidate.ToString();
    var b = valueToCompare.ToString(); 

    switch (Type.GetTypeCode(valueToCompare.GetType()))
    {
        case TypeCode.DateTime:
        {
            return DateTime.TryParse(a, out var x) 
                && DateTime.TryParse(b, out var y)
                && x >= y;
        }
        case TypeCode.Double:
        {
            return double.TryParse(a, out var x)
                && double.TryParse(b, out var y)
                && x >= y;
        }
        case TypeCode.Decimal:
        {
            return decimal.TryParse(a, out var x)
                && decimal.TryParse(b, out var y)
                && x >= y;
        }
        case TypeCode.Int32:
        {
            return int.TryParse(a, out var x)
                && int.TryParse(b, out var y)
                && x >= y;           
        }
        case TypeCode.Int64:
        {
            return long.TryParse(a, out var x)
                && long.TryParse(b, out var y)
                && x >= y;
        }
        default: throw new NotImplementedException();
    }
}

CodePudding user response:

Could you use IComparable?

For example:

bool Compare(object valueToValidate, object valueToCompare)
{
    if (RaUtils.IsBlankValue(valueToValidate) || RaUtils.IsBlankValue(valueToCompare))
    {
        return true;
    }

    if( valueToValidate is IComparable x && valueToCompare is IComparable y)
        return x.CompareTo(y) > 0;

    throw new NotImplementedException();
}  

If you want/need keep the switch checking the types can be made simpler (which also removes the ToString/Parse):

bool Compare(object valueToValidate, object valueToCompare)
{
    ...
    return (valueToValidate, valueToCompare) switch
    {
        (DateTime a, DateTime b) => a >= b,      
        (double a, double b) => a >= b,             
        //...
        
        _ =>  throw new NotImplementedException()
    };
}

If you really really really have to round trip of ToString->Parse then in .NET7 you could use the IParseable...er...I mean IParsable interface, part of the Generic Math feature. Could something like this work?

using System.Numerics;

bool CustomCompare<T>(object a, object b) where T: IComparable<T>, IParsable<T>
{
    return T.TryParse(a.ToString(),System.Globalization.CultureInfo.CurrentCulture, out var x)
        && T.TryParse(b.ToString(), System.Globalization.CultureInfo.CurrentCulture, out var y)
        && x.CompareTo(y) > 0;     
} 

This could then be used in you switch.

  • Related