Practical .NET

Coding Without a Plan

I don't believe in coding design tools. I've been programming for more than 30 years now (40 years if you go back to my first class in programming). I think in code. Code is my design language and procrastination is my friend.

Every once in a while, someone asks me how I plan out my code, incorporating all the programming principles and design patterns that I've talked about in these columns. I say that I have two rules:

  1. I design my code by writing my code.
  2. I put off making any decision as long as I possibly can. In fact, I recommend this as a practice. Let me explain why.

Design Tools
Back at the dawn of history, when I went to school, my instructors spent a lot of time giving me tools that would help me design code. Neither myself or anyone else in my class used them.

For example, we had a long run of exercises where we were supposed to turn a flowchart in along with our applications. The whole class quickly discovered that we got marks deducted if the final version of our code didn't match our flowchart -- so we got our code working and then we wrote the flowchart to match the code. I think our instructors must have known what we were doing because we usually drew our flowchart on our code's printout. Certainly, none of us found flowcharting helpful in designing our applications.

Of course, back then, we were programming in assembler and Fortran, writing programs on punched cards that were, perhaps -- a long program consisted of 40 lines. We were also purely procedural programmers.

Despite everything I've been through, I have to say that my practices haven't changed much since then. And why would they? Between Visual Studio and object-oriented programming my tools have gotten infinitely better. It's hard to believe that I need to compensate for better tools with more planning.

Since I left school, I've had several code design tools proposed to me, many of them visual in nature (class design tools seem to be especially popular). Of all the tools that I've been introduced to, the one that has made the least sense to me is pseudo-code. In Visual Studio, I have a perfectly wonderful code editor that will work in any language … except, of course, for the language called "pseudo." It's never been clear to me why I would design my programs in some language that wouldn't compile when I can use a language that will compile.

There are real benefits to using some compile-able language as a design tool. With Visual Studio I have a perfectly wonderful compiler that will ensure that my design is consistent. I also have (with the right add-ins) all sorts of refactoring tools that will help me improve the design of my code (as long as I don't write in "pseudo," of course). When I get tired of looking at the red wavy lines marking classes/methods/properties I've called but haven't written yet, I can use those tools to generate an interface or class definition that makes the errors go away.

Architecture First
There's a couple of things that I need to explain, though. First, I distinguish between "architecture" and "code." If you've read my articles on domain-driven design then you've seen some of what I consider when I think about architecture. I begin by dividing the problem up into individual domains, determining what operations will be performed in which domains, and deciding on the messages that the domains will use to communicate between each other. Effectively, I've determined what my "back-end" and "front-end" components are and how functionality is to be divided up among those components.

I'll be considering a lot of non-technical issues as I make these decisions. Typically, for example, my client is interested in organizing the workload into sprints/phases that will deliver something useful in the foreseeable future (sometimes we're starting from scratch and aiming to deliver a minimally viable product). I'll also be interested in leveraging existing teams and expertise within the organization; I'll also want to make sure that the domains align with the organization's structure (it's hard enough to get approval to do things when you have one department head to appease -- it's impossible with two or more heads involved). It's not clear to me how a coding design tool can be helpful with these problems.

However, I'll be making these decisions at a very high level and without putting in much detail. While I have a good idea what messages I'll need to pass between domains, I won't have decided on all of the details for those messages. At this stage, I might just be confident that some particular message will include a sales order Id, perhaps a customer Id … and some other stuff to be decided later.

Further, I will prefer to defer finalizing that interface until I write the actual code that sends and receives the message. Sometimes that's not possible: A team working in another domain that I communicate with might need to finalize that interface ASAP so that they can continue. While I'd prefer to defer, I recognize that playing well with others is also important.

At most, inside the domains, I will define an object that will handle communication with other domains. Even then, however, I won't specify if that communication is to be handled by writing to a message queue, calling a Web Service or using a service bus (though, of course, if the client has a service bus in place I'll be inclined to use it). I also won't settle on which operations in related domains will be completed immediately or eventually. I certainly won't have settled on the object model for any of these domains.

If you're interested in the kinds of principles that I apply at this stage, Timothy Guay's post, "Is Agile Design an Oxymoron?," on the Learning Tree blog summarizes some of the tools that I do apply at this stage. (In the interests of full disclosure: I have both taught and written courses for Learning Tree International.)

Designing the UX
With that architecture in place, I start working within one or more of the domains. I start by writing the application/UI code and, as I write that code, I specify the interfaces that I'd prefer my UIs to work with. Here, I'm following the dependency inversion principle: As I write my code, I decide what interfaces with which methods and properties I would like to have -- what design would make problems in my UX easy to solve? When I'm happy with the part of the UI I'm currently working, I repeat the process for the code behind those interfaces until I arrive at the end of the line (typically, data storage and retrieval).

This process will start to reveal problems with my original design and I usually find myself cycling back to re-architect my domains or move operations between domains. During this process, I occasionally get the impression from my clients that they feel I should have spent more time in the architectural phase so that I wouldn't have to re-visit and revise those decisions. I'm willing to admit that it's possible that more time in defining the architecture might eliminate later "redesignings."

But, while I admit that it's possible, I don't think it's true. I find that the more that I work with a problem, the more I understand it (to paraphrase Joe Walsh: "The worker I get, the smarter I know"). And the more different ways I work with a problem, the more I understand it. So, moving to building out the UI gives me a different way of thinking about the problem and gives me new insights that cause me to re-think earlier decisions.

By the way, wouldn't it be awful if that wasn't true -- that we knew no more about a problem when we finished working on it than when we started?

More important, at this point, the cost of making a change is low: Everything is probably just lines on a whiteboard (virtual or physical) or high-level code in an editor. And it's not like I've got a lot of details to be changed.

Design Patterns
But where do design patterns fit in? As I lay out the interfaces in each layer, I often build in some design patterns. Certainly, the various builder patterns make an appearance at this stage. But, as often as not, I refactor my way into a design pattern.

For example, it's not out of the question that the first draft of my code might look like the block in Listing 1.

Listing 1: Typical First-Draft Code
Public Function studentSearch() 
  Dim localUrl As String
  If oname <> "" Then
    localUrl = url + "ByOffice" + "/" + oname
  ElseIf sId <> "" Then
    localUrl = url + "ById" + "/" + oname
  Else
    If fname <> "" Then
      fname = "_"
    End If
    localUrl = url + "ByName" + "/" + fname + "/" + lname
  End If
  Return ProcessData(localUrl); 
End Function

There's much wrong with this code … but that wouldn't have stopped me from writing it. Once I had written it, though, I would realize that (among other failures) this method violates the Single Responsibility Principle because it's doing at least three things:

  1. It's choosing which method to build a URL.
  2. It's building the URL (three different ways).
  3. It's using the URL.

A change to any one of those three operations would force me to rewrite this method, in the process of which I'd probably insert a bug into one of the other two operations.

So, having written the first draft of the code and figured out what's wrong, I would refactor it to a combination of design patterns. That would probably give me:

  • A base UrlBuilder class that would handle the common part of each URL, implementing the template method pattern.
  • Three separate UrlBuilder classes, each of which builds one of the URL, a version of the strategy pattern.
  • A UrlFactory that would choose between the three classes and return the right one, implementing the factory method pattern.

Mostly, though, I'm implementing the Single Responsibility Principle (for someone else's opinion on the SRP, see Lafe Low's interview with Scott Allen). The refactored studentSearch method might look like this:

Public Function studentSearch(oname As String, sId As String, fname As String) 
  Dim localUrl As String
  localUrl = UrlFactory.BuildLocalUrl(oname, sId, fname)
  Return ProcessData(localUrl); 
End Function

Will I, potentially, end up with a lot of methods with just a single line? Sure. But, it turns out that I'm OK with that. Besides, it usually turns out that I end up having to revisit those "one-line" methods and incorporate more code into them.

I suppose that it sounds like I stumble my way to a good design and I wouldn't object to this description. However, I think there's a great deal to be said for what I call the Deferral Principle: "Defer all decisions until you can't avoid making them." By the time you do have to make them, you'll be better informed than at any earlier point in the process. If that sounds too negative, phrase it as, "We'll make all decisions as early as we can, and no earlier."

Really, why would you work any other way?

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube