<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://devlicio.us/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Anne Epstein : VB.Net</title><link>http://devlicio.us/blogs/anne_epstein/archive/tags/VB.Net/default.aspx</link><description>Tags: VB.Net</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP1 (Build: 31106.3070)</generator><item><title>VB.Net and the Case of the Iffy Ifs</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/05/12/vb-net-and-the-case-of-the-iffy-ifs.aspx</link><pubDate>Tue, 12 May 2009 05:49:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:46615</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=46615</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/05/12/vb-net-and-the-case-of-the-iffy-ifs.aspx#comments</comments><description>&lt;p&gt;Previous Items in Series:&lt;a href="http://devlicio.us/blogs/anne_epstein/archive/2009/01/12/intro-to-series-on-quality-vb-net.aspx"&gt;&lt;br /&gt;Intro to series on quality VB.Net&lt;br /&gt;&lt;/a&gt;&lt;a href="http://devlicio.us/blogs/anne_epstein/archive/2009/01/20/vb-net-and-the-spaghetti-ball-of-doom.aspx"&gt;VB.Net and the Spaghetti Code Of Doom&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;a href="http://devlicio.us/blogs/anne_epstein/archive/2009/01/20/vb-net-and-the-spaghetti-ball-of-doom.aspx"&gt;previous item in this series&lt;/a&gt;, we looked at a pretty
typical class, discussed some of its problems, and started in on fixing
those problems.&amp;nbsp; We finished with smaller, easier-to-understand pieces
of code, but it still exhibits some notable problems.&amp;nbsp; Most visible for
me are all those branching if/else statements, some of them nested.&amp;nbsp;
This is a potential problem for several reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each time we add/remove/change the customer levels, we&amp;#39;re going
to need to change both the OrderTotalService and the ShippingService.&amp;nbsp;&amp;nbsp;
That&amp;#39;s one core change we have to make, and now we&amp;#39;re going to have to
update two different pieces of code.&amp;nbsp; Hopefully, we remember to do that!&lt;/li&gt;
&lt;li&gt;Each time we go in and mess with any customer level, we have to
change these classes.&amp;nbsp; Hopefully, we change the classes safely and
don&amp;#39;t accidentally destabilize other customer levels when we&amp;#39;re in
there!&amp;nbsp; This is starting to feel a little risky...&lt;/li&gt;
&lt;li&gt;Are all these tangled ifs really readable?&amp;nbsp; they take some mental walking through to see what&amp;#39;s going on.&lt;/li&gt;
&lt;li&gt;It looks like constructing test data that matches the pattern
required to drill through all these conditions could be tricky.&amp;nbsp;
Testing is important, but it&amp;#39;s a lot harder to find the motivation when
it seems like it&amp;#39;s going to be an overwhelming task.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, one way to handle the mess is to try some &lt;a href="http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming"&gt;polymorphism&lt;/a&gt;.&amp;nbsp;
The basic idea we&amp;#39;re going to use is that instead of switching on a property of a class,
create a set of classes based on that property that share an interface,
and move the switched behavior over to the classes. Let&amp;#39;s give that a
try.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;The Customer class has been subclassed into three different classes
to reflect the Gold, Silver, and Basic customer levels. Then, the
customer level-specific product price and shipping calculations have
been moved into the subclasses: &lt;/p&gt;
&lt;pre class="vb.net" name="code"&gt;Public Interface ICustomer
    Property CompanyName() As String
    Property ContactName() As String
    Property Orders() As IList(Of Order)
    Sub AddOrder(ByVal order As Order)
    Sub RemoveOrder(ByVal order As Order)
    Function CalculateProductPrice(ByVal prod As Product) As Decimal
    Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal
End Interface

Public Class GoldCustomer
    Inherits Customer
    Implements ICustomer

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then
            Return 11.5D
        Else
            Return prod.Price * 0.8
        End If
    End Function
    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        Return 0D
    End Function
End Class

Public Class SilverCustomer
    Inherits Customer
    Implements ICustomer

    Public Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then
            Return prod.Price * 0.7
        Else
            Return prod.Price * 0.9
        End If
    End Function

    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        Return 3.5D
    End Function
End Class

Public Class BasicCustomer
    Inherits Customer
    Implements ICustomer

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then
            Return prod.Price * 0.9
        Else
            Dim ddService = New DealDayService()
            If ddService.IsDealDays() Then
                Return (prod.Price - 3)
            End If
        End If
    End Function
    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        If orderTotal &amp;lt; 20 Then
            Return 1.4D
        ElseIf orderTotal &amp;lt; 40 Then
            Return 2.5D
        ElseIf orderTotal &amp;lt; 70 Then
            Return 5D
        Else
            Return 8D
        End If
    End Function
End Class

Public MustInherit Class Customer
    Implements ICustomer

    ... (original methods) ...


    Public MustOverride Function CalculateProductPrice(ByVal prod As Product) As Decimal _
        Implements ICustomer.CalculateProductPrice
    Public MustOverride Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal _
        Implements ICustomer.CalculateShipping
End Class
&lt;/pre&gt;
&lt;p&gt;&lt;i&gt;(A note: we&amp;#39;re seeing the effects of a VB.Net &amp;quot;feature&amp;quot; here: in VB.Net-methods implementing an interface *must* state what it&amp;#39;s implementing from that interface.  Customer shouldn&amp;#39;t really need to implement ICustomer-and thus wouldn&amp;#39;t need the two MustOverride methods. The problem is that if Customer didn&amp;#39;t implement ICustomer, then when GoldCustomer, etc used Customer&amp;#39;s methods to implement ICustomer, we&amp;#39;d get a compile error because those method/properties in Customer wouldn&amp;#39;t have the &amp;quot;implements&amp;quot; mapping.  We can avoid this issue by making GoldCustomer contain a Customer instead of inheriting from it, but that&amp;#39;s a discussion for a different post)&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;This shrinks the OrderTotalService to just a few lines, and shrinks
the ShippingService to nothing-it would be reasonable to completely
remove the ShippingService class in the next refactoring pass. 
&lt;/p&gt;
&lt;pre class="vb.net" name="code"&gt;Public Class OrderTotalService
    Public Function CalculateOrderTotal(ByVal ord As Order) As Decimal
        &amp;#39; this first part adds up the cost of all the products
        Dim total As Decimal = 0D
        For Each prod In ord.Products
            total += ord.OrderedBy.CalculateProductPrice(prod)
        Next

        Dim shipService As New ShippingService
        Return shipService.CalculateShipping(ord.OrderedBy, total) + total
    End Function
End Class

Public Class DealDayService
    Public Function IsDealDays() As Boolean
        Return DateTime.Now &amp;gt; New DateTime(2009, 1, 18) AndAlso DateTime.Now &amp;lt; New DateTime(2009, 2, 18)
    End Function
End Class

Public Class ShippingService
    Public Function CalculateShipping(ByVal cust As ICustomer, ByVal total As Decimal) As Decimal
        &amp;#39; this next section adds in the shipping
        Return cust.CalculateShipping(total)
    End Function
End Class
&lt;/pre&gt;
&lt;p&gt;The last change here is in the Product class-I made a decision that
though there were only two hardcoded MegaDeal products, this felt like
a group that could change, and I wanted to get those hardcoded values
out of there. Instead of being hardcoded, those values are now going to
be stored as a flag in Product. (It very well may be that MegaDeals
would be better implemented as some way other than as a property the
first priority was getting out the hardcoded values-that can be
revisited) &lt;/p&gt;
&lt;pre class="vb.net" name="code"&gt;Private _isMegaDeal As Boolean
Public Property IsMegaDeal() As Boolean
     Get
        Return _isMegaDeal
     End Get
     Set(ByVal value As Boolean)
         _isMegaDeal = value
     End Set
End Property
&lt;/pre&gt;
&lt;p&gt;Next, I&amp;#39;m going to do a simple change on those inner ifs just to make them a bit easier to visually scan:&lt;/p&gt;
&lt;pre class="vb.net" name="code"&gt;Public Class GoldCustomer
    Inherits Customer
    Implements ICustomer 

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then Return 11.5D
        Return prod.Price * 0.8
    End Function 

    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        Return 0
    End Function
End Class

 
Public Class SilverCustomer
    Inherits Customer
    Implements ICustomer

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then Return prod.Price * 0.7
        Return prod.Price * 0.9
    End Function

    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        Return 3.5D
    End Function
End Class

 
Public Class BasicCustomer
    Inherits Customer
    Implements ICustomer

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then Return prod.Price * 0.9
        Dim ddService = New DealDayService()
        If ddService.IsDealDays() Then Return (prod.Price - 3)
    End Function

    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        If orderTotal &amp;lt; 20 Then Return 1.4D
        If orderTotal &amp;lt; 40 Then Return 2.5D
        If orderTotal &amp;lt; 70 Then Return 5D
        Return 8D
    End Function
End Class
&lt;/pre&gt;
&lt;p&gt;Conclusion:&lt;/p&gt;
&lt;p&gt;Getting rid of those ifs made a big difference.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Now,
if we need to modify a customer level, we can do it in one place, in
that level&amp;#39;s class.&amp;nbsp; No more hoping we got all the places we branch
over customer level types!&amp;nbsp; In addition, if we need to add a new level,
*none* of this code needs to change-we just add a new class for that
new level that implements ICustomer, and everything that&amp;#39;s here works
as-is.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Related to the item #1, when we change a customer level we
don&amp;#39;t have to change the places that level is used.&amp;nbsp; We can test the
heck out of what actually *had* to change, the modified or new
level, and since nothing else was touched, we won&amp;#39;t have to consider
any other exiting code destabilized.&lt;/li&gt;
&lt;li&gt;Unfortunately, there are still ifs, but we&amp;#39;ve split them out
some, so they&amp;#39;re not so nested.&amp;nbsp; OrderTotalService and ShippingService
are a lot cleaner and easier to look at.&lt;/li&gt;
&lt;li&gt;Testing these classes is a little easier now.&amp;nbsp; we don&amp;#39;t have to
feed as many combinations CalculateOrderTotal now-it treats all
customer types the same.&amp;nbsp; We still have to test that logic in the
inheritors of ICustomer, but without that extra layer of ifs, it should
be a little clearer what conditions need to be tested.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;This code can definitely still be improved!&amp;nbsp; For one thing, we&amp;#39;ll
have to keep an eye on the logic inside the customer level subtypes.&amp;nbsp;
Though the differing computation logic is now split off and close to
what it differs on, I&amp;#39;ve got a feeling that code could get complex and
overwhelm those classes over time.&amp;nbsp; The DealDays logic and dates seem
to be fixed to a single datespan, but that&amp;#39;s the sort of thing clients
want changed regularly, and we also don&amp;#39;t have a way to calculate the
order total without the shipping right now. We also still haven&amp;#39;t
addressed the upcoming desire to be able to handle pricing for
different shipping vendors either.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=46615" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/VB.Net/default.aspx">VB.Net</category></item><item><title>Intro to series on quality VB.Net</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/01/12/intro-to-series-on-quality-vb-net.aspx</link><pubDate>Tue, 13 Jan 2009 04:41:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:43739</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>16</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=43739</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/01/12/intro-to-series-on-quality-vb-net.aspx#comments</comments><description>&lt;p&gt;Recently, there was a discussion on twitter regarding the sophistication of VB.Net development.&amp;nbsp; It&amp;#39;s worth noting that there is a very firmly held belief among C# developers that VB developers care less about the quality of their own work and that they are not interested in good techniques or self-improvement.&amp;nbsp; Interestingly, there is a related belief that VB.Net as a language is so inadequate that it is literally impossible to code using what is generally understood to be good practices.&amp;nbsp; For those of you out there that may be primarily VB.Net developers, I am NOT attacking you, (continue reading to see that) but it&amp;#39;s worth knowing that in the greater .Net world, your reputation isn&amp;#39;t so good, and that may be affecting your job prospects-strategize accordingly.&amp;nbsp; Practices increasingly well accepted in Java and C# are barely making a dent in common VB.Net development at this point.&amp;nbsp; As it is, VB programmers trying these accepted ideas may feel like trailblazers... and frankly, that&amp;#39;s pretty tiring work that not everyone is up to.&amp;nbsp; Convenient that in the C# world, the path is getting fairly clear.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Personally, I have done a fair amount of both C# and VB.Net, and find that it&amp;#39;s definitely possible to write code that follows widely accepted principles in VB.Net.&amp;nbsp; There&amp;#39;s nothing in the language that actually prevents use of ORMs like NHibernate, IoCs like Windsor, or SOLID principles.&amp;nbsp; Admittedly, I feel VB.Net is wordy to the point of being more difficult to scan visually, has some silliness (see AndAlso vs And) is missing some nice featues (see auto properties and lambdas), and tool support is weaker than for C#(see Resharper, and even Visual Studio itself).&amp;nbsp; I find C# to be an overall lower-friction coding exerience.&amp;nbsp; However, there are many developers who still code in VB.Net, whether because of preference or because of the situation on a particular project. They&amp;#39;re doing the same job as the C# programmers, in a language that&amp;#39;s fundamentally pretty similar to C# and can even interact directly with C#.&amp;nbsp; I want to let developers of both stripes know that VB doesn&amp;#39;t mean having to give up good practices.&amp;nbsp; &lt;/p&gt;&lt;p&gt;In that spirit, I&amp;#39;ll be doing a series of as-yet indeterminate length on implementing these practices, focused primarily on VB.Net, with C# for clarification or contrast where helpful.&amp;nbsp; &lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=43739" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/VB.Net/default.aspx">VB.Net</category></item></channel></rss>