Using TryXXX pattern to avoid exceptions I have been using the TryXXX and GetXXX pattern lately to give clients a choice whether to trap an exception or to read a boolean. An example of this concept would be System.Integer.TryParse and System.Integer.Parse.

It is my understanding that you should get two benefits from this: 


using an if statement in calling code instead of try-catch  
a performance benefit from not causing an exception.


In this class, I am attempting to do this, but I think I am getting only benefit 1, not benefit 2. I am drawing a blank as how to not throw any exception without code duplication.  

       Public Function ConvertFractionToDecimal(ByVal fraction As String) As Decimal

        If String.IsNullOrWhiteSpace(fraction) Then Throw BuildBadFractionException(fraction)

        'will accept fractions of the form:
        'X-Y/Z
        'X Y/Z
        'Y/Z
        'will not accept negative signs in numerator, denominator, or whole number

        Dim whole As String
        Dim numen As String
        Dim denom As String

        Dim loc1 As Integer
        Dim loc2 As Integer

        fraction = fraction.Trim
        If fraction Like "*?-*?/?*" Then
            loc1 = InStr(fraction, "-")
            loc2 = InStr(fraction, "/")
        ElseIf fraction Like "*? *?/?*" Then
            loc1 = InStr(fraction, " ")
            loc2 = InStr(fraction, "/")
        ElseIf fraction Like "*?/?*" Then
            loc2 = InStr(fraction, "/")
        Else
            Throw BuildBadFractionException(fraction)
        End If

        If loc1 &gt; 0 Then
            whole = Mid(fraction, 1, loc1 - 1).Trim
        Else
            whole = "0"
        End If
        numen = Mid(fraction, loc1 + 1, loc2 - loc1 - 1).Trim
        denom = Mid(fraction, loc2 + 1).Trim

        Dim Uwhole As UInt32
        Dim Unumen As UInt32
        Dim Udenom As UInt32
        If Not UInt32.TryParse(whole, Uwhole) Then Throw BuildBadFractionException(fraction)
        If Not UInt32.TryParse(numen, Unumen) Then Throw BuildBadFractionException(fraction)
        If Not UInt32.TryParse(denom, Udenom) Then Throw BuildBadFractionException(fraction)

        Return CDec(Uwhole + Unumen / Udenom)

    End Function

    Public Function TryConvertFractionToDecimal(ByVal fraction As String, ByRef result As Decimal) As Boolean
        Try
            result = ConvertFractionToDecimal(fraction)
            Return True
        Catch ex As Exception
            result = 0
            Return False
        End Try
    End Function

    Private Function BuildBadFractionException(ByVal fraction As String) As InvalidOperationException
        Return New InvalidOperationException(String.Format("'{0}' is not a recognisable fraction.", fraction))
    End Function
I was going to edit my first answer, but as I wrote the example code I wanted to show, and refactored, and refactored again, ...the result went so far away from the original code that I thought it warranted a separate answer.



So I copied your code into Visual Studio, and started by implementing the changes I suggested in my other answer. Quickly though, I felt the need to extract a FractionInfo type, a Structure that would hold the Whole, Numerator and Denominator parts of a fraction, so that I could write the regex part into a separate function that would return an instance of this FractionInfo type.

Being a Structure, I wanted to make that type immutable, so I implemented an Empty property that returned a Shared default instance... and then I tried doing If result = FractionInfo.Empty and noticed the = operator needed to be implemented - I'm lazy, so instead I just implemented Equals, and since when you override Equals you also need to override GetHashCode, I implemented GetHashCode as well.

So the ConvertFractionToDecimal function started like this:

Dim info As FractionInfo = GetFractionInfo(value)
If info.Equals(FractionInfo.Empty) Then Throw BuildBadFractionException(value)


And then I thought "great, now I can get rid of pretty much the whole rest of the code!" ...and then it struck me: you're not showing where your Convert.../TryConvert... methods are written, but I'm assuming they're not written in a Structure called Fraction.

And to follow the single responsibility principle, the only logical place to put such conversion methods would be in a value type called Fraction.

I suggest you take a look at this question and answers (disclaimer: it's one of my questions on this site) - it's C#, but the basic idea is essentially the same.

After much refactoring, this is what I ended up with - I'm not fully satisfied with it because it will throw a FormatException whenever parsing fails, even when one would expect an OverflowException. It does throw an ArgumentNullException when you give it Nothing, but the produced stack trace might be a little surprising.

Private Shared Function TryParseMatchGroups(ByVal groups As Match, ByRef result As Fraction) As Boolean

    Dim success As Boolean
    Dim wholePart As String = groups.Groups("whole").Value
    Dim numeratorPart As String = groups.Groups("numerator").Value
    Dim denominatorPart As String = groups.Groups("denominator").Value

    Dim whole As Integer
    Dim numerator As Integer
    Dim denominator As Integer

    success = Integer.TryParse(IIf(String.IsNullOrEmpty(wholePart), "0", wholePart), whole) _
        And Integer.TryParse(IIf(String.IsNullOrEmpty(numeratorPart), "0", numeratorPart), numerator) _
        And Integer.TryParse(IIf(String.IsNullOrEmpty(denominatorPart), "0", denominatorPart), denominator)

    If success Then
        result = New Fraction(whole, numerator, denominator)
    Else
        result = Fraction.Empty
    End If

    Return success

End Function

Public Shared Function Parse(ByVal value As String) As Fraction

    Dim result As Fraction
    Dim match As Match = regexp.Match(value)

    If match.Success And TryParseMatchGroups(match, result) Then
        Return result
    Else
        Throw New FormatException()
    End If

End Function

Public Shared Function TryParse(ByVal value As String, ByRef result As Fraction) As Boolean

    If value Is Nothing Then Return False

    Dim match As Match = regexp.Match(value)
    Return match.Success And TryParseMatchGroups(match, result)

End Function


These functions are Shared, exactly like Decimal.Parse and Decimal.TryParse are. Here's the rest of the type:

Public Structure Fraction

    Private Const pattern As String = "^((?&lt;whole&gt;[0-9]+)?\s+?)?(?&lt;numerator&gt;[0-9]+)\s?/\s?(?&lt;denominator&gt;[0-9]+)\s*?$"
    Private Shared ReadOnly regexp As Regex = New Regex(pattern)
    Private Shared ReadOnly emptyValue As Fraction = New Fraction()

    Private ReadOnly WholePart As Integer
    Private ReadOnly NumeratorPart As Integer
    Private ReadOnly DenominatorPart As Integer

    Public Sub New(ByVal whole As Integer, ByVal numerator As Integer, ByVal denominator As Integer)
        WholePart = whole
        NumeratorPart = numerator
        DenominatorPart = denominator
    End Sub

    Public ReadOnly Property Whole() As Integer
        Get
            Return WholePart
        End Get
    End Property

    Public ReadOnly Property Numerator As Integer
        Get
            Return NumeratorPart
        End Get
    End Property

    Public ReadOnly Property Denominator As Integer
        Get
            Return DenominatorPart
        End Get
    End Property

    Public Shared ReadOnly Property Empty As Fraction
        Get
            Return emptyValue
        End Get
    End Property

    Public Function ToDecimal() As Decimal
        Return Whole + CDec(Numerator) / CDec(Denominator)
    End Function

    Public Overrides Function Equals(obj As Object) As Boolean
        Dim other As Fraction = DirectCast(obj, Fraction) 'let it blow up on InvalidCastException
        Return other.Whole = Me.Whole _
            And other.Denominator = Me.Denominator _
            And other.Numerator = Me.Numerator
    End Function

    Public Overrides Function GetHashCode() As Integer
        'ok this is a bit like cheating.. but so much easier than implementing the real thing
        Return Tuple.Create(Me.Whole, Me.Numerator, Me.Denominator).GetHashCode()
    End Function

    Public Overrides Function ToString() As String
        If Me.Whole &lt;&gt; 0 Then
            Return String.Format("{0} {1}/{2}", Me.Whole, Me.Numerator, Me.Denominator)
        Else
            Return String.Format("{0}/{1}", Me.Numerator, Me.Denominator)
        End If
    End Function


You'll notice this implementation doesn't support negative fractions either - I'll leave that for you to explore and implement yourself. Note that vb.net isn't my primary language, so there might be stylistic errors in here.

If you looked at my A fraction of the code post, you've noticed there's many, many members missing here, mostly operators; you'll probably want a Fraction to at least support the = and &lt;&gt; operators, but the Equals implementation might be good enough as well. To make the type more usable you'll want to add MinValue and MaxValue, and perhaps a Zero value (0/1) and a Simplify() method. And of course you'll want to support negative fractions.

Basically, what this answer is suggesting that no answer has suggested yet, is to implement a [full-blown?] Fraction type, and to encapsulate your parsing logic in there: putting it anywhere else will break SRP.