Consider your behaviour and desires as a customer when you visit:

a) your favourite local coffee shop - you specify what you'd like, rather than how it's made. You care about the outcome, but rarely care about the process/sequences/steps that are followed as it's being constructed.
b) a Subway/Quiznos shop - you care equally about the outcome and the construction. Meats, veggies, bread type, order of placement of each, precise amount of mustard, pickles, etc.

baristaThe difference here is: you aren't instructing the coffee barista on how to steam the milk, or when to start brewing the espresso. You don't remind them that their level of ground beans is getting low, or where to store the milk. You are happy to assume they know their job best, and order of operations is properly under their control. They have their efficiencies to care about, and you're happy to let them manage that.

Consider now your desires as a programmer when your task is to find customers with a condition. Let's say we want to use this contrived example:

    Find the customers whose account balance owing is over $5,000. Find the youngest customer in that set. 
 

The Sandwich Model of Algorithms

//find all customers with the appropriate account balance
var owingOver5000 = new List<Customer>();
foreach (Customer c in myCustomers)
{
if (c.TotalAmountOwing > 5000)
{
owingOver5000.Add(c);
}
}

var youngestCust;
DateTime youngestBirthdate=null;

foreach(Customer c in owingOver5000)
{
//initialize on the first go-round; many ways to do this.
if (youngestBirthdate==null)
{
youngestBirthdate=c.BirthDate;
}

//find the youngest by their age
if (c.Birthdate < youngestBirthdate) //pretend nobody has the same birthdate ;)
{
youngestCust = c;
}
}
//you now have youngestCust populated (in most cases)


Very fine-grain operations are explicitly laid out by the developer, and execution path follows exactly what the developer wrote. Defects and all! The number of defects is up to you!

The Coffee Shop Model of Algorithms

Consider now the Coffee Shop model of this algorithm. We'll use LINQ.

var youngestCust = myCustomers
.Where(c=>c.TotalAmountOwing > 5000)
.OrderBy(c=>c.BirthDate)
.SingleOrDefault();

It should be obvious to you now, if it wasn't at the start: the LINQ extension methods are doing all the looping for you, and taking care of all the small bits and housekeeping. You, as the customer, don't want to care about how it's found, but rather, you declare what you want.

Outsource Your Loops

I hate to steal Eric Lippert's thought on this, but it's worth saying once more in a different way:

Avoid loops. They’re becoming a code smell. Let built-in methods and functionality do the boring non-value added logic for you.

You should be focused on YOUR business logic or end-goals (i.e. eating your sandwich and drinking your coffee), and less on syntax + language constructs. Take advantage of more declarative constructs provided in your language/framework. LINQ is a perfect example of this.

*this post is a mashup of Luca Bolognese's PDC 2008 F# metaphor and Eric Lippert's post on loops. Apologies to both!


Posted in: development , best practices , linq  Tags:
Actions: E-mail | Permalink | Comments (0) |
Phil posted on December 11, 2009 03:06

Here’s a class that’ll make your life easier when you want to deal with saving information in cookies on your user’s browser. Everyone needs a wrapper class for all those external data-stores – session, cookies, file system, web.config and app.config, registry, log files, etc. Here’s a class usable in ASP.NET Web Forms and ASP.NET MVC.

Wrapper Class

Here’s a static class that you can simply include in your web project, and refer to its static properties to get to your cookies. Any and all simple datatypes can be used, and heck, even serialized versions of your POCO objects can be saved/retrieved here. Image if you wanted to save those shopping cart items, a collection of user prefs, or whatever, you could simply override the .toString() method in your custom class.

Just Make Properties

The key pattern here is that you purposefully create new properties for each piece of data that you want to save/retrieve. This solves the problem of:

  • having to remember strings all over your project.
  • ensuring no duplicates exist – imagine if multiple developers created a defect by using the same string indexer for their cookie, and ended up stomping each other’s value?
  • typos in cookie names.

Instead, the data access cookie-retrieval is done through named properties. This solution solves all those potential problems. Here’s a peek at one of these properties.

public static string UserFullName
{
get { return GetCookieVal(CookieItem.UserFullName); }
set { UpdateCookieVal(CookieItem.UserFullName, value, 365); }
}

Enums Help

With the aforementioned ‘remembering strings’ problem, the pattern that this class uses relies internally on an enum to handle the naming of the value in the cookie. The enum will boil down to an integer, but really we don’t care what the key’s is actually stored as in the cookie. We really only care to access/read/save the values constantly and easily from our calling code.

Download

Download the cookie class, or copy/paste from below. You can see that I pre-loaded it with some amusing properties for your entertainment!

Be sure to change the ApplicationName const at the top.

Special thanks to Special-K!

using System;
using System.Web;

namespace MyNamespace
{
public class Cookies
{
private const string ApplicationName = "MyCoolApplication";

private enum CookieItem
{
UserGuid,
UserFullName,
UserLoginExpiry,
UserHadForBreakfast,
UserTimezone
}
/**************
All cookie values are accessible by public static methods.
No typos/duplicates are possible from calling code!
**************/

public static string UserFullName
{
get { return GetCookieVal(CookieItem.UserFullName); }
set { UpdateCookieVal(CookieItem.UserFullName, value, 365); }
}

public static Guid UserGuid
{
get { return new Guid(GetCookieVal(CookieItem.UserGuid)); }
set { UpdateCookieVal(CookieItem.UserGuid, value.ToString(), 365); }
}

public static DateTime UserLoginExpiry
{
get { return DateTime.Parse(GetCookieVal(CookieItem.UserLoginExpiry)); }
set { UpdateCookieVal(CookieItem.UserLoginExpiry, value.ToString(), 365); }
}

public static string UserHadForBreakfast
{
get { return GetCookieVal(CookieItem.UserHadForBreakfast); }
set { UpdateCookieVal(CookieItem.UserHadForBreakfast, value, 1); }
}

private static string GetCookieVal(CookieItem item)
{
HttpCookie cookie = GetAppCookie(false); //get the existing cookie
return (cookie != null && (cookie.Values[item.ToString()] != null)) //value or empty if doesn't exist
? cookie.Values[item.ToString()]
: string.Empty;
}

private static void UpdateCookieVal(CookieItem item, string val, int expireDays)
{
//get the existing cookie (or new if not exists)
HttpCookie cookie = GetAppCookie(true);

//modify its contents & meta.
cookie.Expires = DateTime.Now.AddDays(expireDays);
cookie.Values[item.ToString()] = val;

//add back to the http response to send back to the browser
HttpContext.Current.Response.Cookies.Add(cookie);
}

private static HttpCookie GetAppCookie(bool createIfDoesntExist)
{
//get the cookie or a new one if indicated
return HttpContext.Current.Request.Cookies[ApplicationName] ?? ((createIfDoesntExist) ? new HttpCookie(ApplicationName) : null);
}

}
}


Posted in: c# , asp.net , best practices , code blowout , development  Tags:
Actions: E-mail | Permalink | Comments (0) |
Phil posted on November 2, 2009 01:10

I recently had the privilege of working on a contract where the main goal of the project was to port/convert the web project and associated assemblies (business and data layers) from one .NET language to another. The other tasks were around fixing a handful of defects, and adding a few bits of functionality. The project was hard-capped at a set quoted number of hours, so there was little room for error.

The solution consisted of three .NET 3.5 projects:

  • ASP.NET web project – this project had roughly 10 pages and a handful of helper classes in the App_Code directory. This project already had VB as its language.
  • Business Layer Assembly – 6 classes brokering access to the data layer. Very much pass-through code with not a lot of business logic in between the web layer and data layer. There were moderate amounts of looping and updating of the business object properties.
  • Data Layer Assembly – the DL included the matching DL classes for each of the 6 classes above. The most challenging (code for the converter here was the Linq-To-Sql code.

Sizing Up the Task

The largest part of the project for me was the conversion task. All told, the conversion/translation involved:

  • 12 .cs files
  • ~55 methods
  • ~500 lines of code

I had previously figured the smartest way to spend my time was not rewriting code by hand. If there’s anything a developer should be good at, it’s evaluating and choosing to use the right tool for the job. That’s a major part of your job as a developer!

Enter the Telerik Batch Converter Tool

A quick Bing search brought me to a few options: online-in-the-browser-cut-and-paste style, or full file upload.

I first noticed that Telerik had a batch converter tool. This allows the user to upload a handful of source code files, and the converter will do its magic on the server. This was right up my alley, as it meant that I didn’t have to laboriously open each code file, copy, paste, and make a new source code file.

Telerik Batch Code Converter Upload

The initial load of the page shows the user 3 input boxes where you can browse to the location of your code file. Obviously for those ASP.NET web projects needing conversion, you don’t need to upload your .aspx files, but rather your code-behind files. This particular project was using code-behind files, rather than inline <script>.

Aside: It’s amazing to me that some developers are using <script> tags to hold their code which belongs in a code behind  Page.aspx.cs or .vb file. Obviously it’s a style or configuration issue, but to me it just feels wrong. I like the separation of .NET code and HTML markup. Perhaps it’s just a side effect of trawling the web for source code, and people are choosing to format their .aspx to include code within, instead of posting separate files.

Back to the task. The process is easy:

  • Browse one-by-one for the files you need converted. Make sure to remember to pickup your Linq-To-Sql’s .designer.vb or .cs class. That’s the file where your SQL Server tables are mapped to POCO [Plain Old CLR Objects ;) ]
  • Upload, Convert and Download a .zip containing the artifacts of the conversion. Included are all the converted files, and a Report.txt file.

Telerik Batch Code Converter Done

Results & Conversion Report

The real answer you’re looking for is right here: the results of the conversion with the Telerik tool, for me, were 100%. Absolutely no problems were had in compiling a new project with these new files added to it. I can’t believe it was so easy. Here’s the report that Telerik included in the .zip (irrelevant or repetitive bits snipped):

Conversion Error Report (Created [datetime])

Every time a possible conversion problem is located, the file name, problem severity , and problem description are recorded. There are three levels of severity:

    Warning    = code will convert and will likely work, but conversion may need manual improvement
    Minor    = code will convert, but it will likely need manual correction to work
    Major    = code will not convert. It must be modified before used with converter

There are also general issues to remember when converting web sites:
    - Events connected with the Handles syntax in VB will not work in C#. Events must be connected in code or in the control markup.

Report Details:
================================================
1: TEST.designer.cs    Minor    Your region may not convert from C# to VB if quotes have not been used to name the region. Make sure       region name is surrounded in quotes before using code.
2: TEST.designer.cs    Minor    Your region may not convert from C# to VB if quotes have not been used to name the region. Make sure       region name is surrounded in quotes before using code.

End Report (11 total matches)

Easy & Accurate Results

I can’t believe it was so easy to convert the code. It really meant that I could spend more time ADDING VALUE to the project by way of adding features and fixing defects than by the drudgery of converting source between languages. The price to pay for the Telerik online batch tool was the included comments at the bottom of each source file. That’s an easy price to pay! Can I say now a BIG THANK YOU to Telerik, NRefactory & SharpDevelop, and Todd Anglin @ Telerik for making this tool free and available for the world at large!

TelerikBatchCodeConverter_Comments

Stitching your Converted Files Back Into a Project & Solution

The tricky work is to then take the output of the converter and create a new project. For each of the assembly projects, I:

  • copied all the reusable non-.NET files from the old solutions - *.xml, *.config, etc.
  • made new .vbproj projects in Visual Studio
  • for the web project, Add Existing Files –> Select all your converted files.

For those converting a web project, just create a new Web Project. For each of your pages, modify your .aspx  to have its Page directive to have the appropriate configurations:

<%@ Page Language="VB" MasterPageFile="~/SomeMaster.master" CodeFile="SomeFile.aspx.vb"

Then do the same Add Existing Files routine for all your converted .aspx.vb or .cs files.

Quick Online .NET VB and C# Converters

In some cases, you’ll find snippets online (yes, the bathroom wall of code) where you actually like the snippet, but rather want it in the language of your choice. I recently searched for and used both these tools when needing to convert a large snippet of VB to C#.

Try DeveloperFusion’s online code conversion tool - http://www.developerfusion.com/tools/convert/vb-to-csharp/

Telerik’s Code Converter is of course, likely to be mostly used for its quick online code converter - http://converter.telerik.com/


Posted in: linq-to-sql , c# , vb , shortcuts , best practices  Tags:
Actions: E-mail | Permalink | Comments (0) |

The Setup

Recently I was involved in a conversation with a co-worker relating to the customer experience at Subway. The premise was that Subway communicates to the customer the status of their order at every step of the sales process.

Have you ever noticed how slowly Subway sandwiches are made? As each order is taken and prepared, the customer gawks into the glass prep area and watches as each pickle is precariously placed, by an expert sandwich maker, onto a parmesan toasted bun.

Sandwiches are prepared one at a time. One customer, one cash transaction at time.

On a unit production and volume comparison, Subway’s assembly line cannot compare to the mass preparation techniques of McD’s/others. Subway utilizes a serial production system while Big Macs pumped out, en mass, in parallel, efficiently, dozens at a time.  They even pour your drink at McD’s.

I believe that Subway has nailed the customer formula because of three things: Transparency of the work, the proximity to the customer and the behaviour of others waiting in line.

I absolutely agree with the conclusions of the last paragraph, and it is comparable to software development. In a successful software developer’s life, you have to be in constant contact with your customer. Proximity is another important attribute to make those softer, subtler human connections.

Back to the Subway example: Absolutely every detail of the product is in plan view. At each step, the customer knows:

  • what is happening with the order right now
  • whether the order being produced to the customer's standards
  • how long it will be until the goods are received.

The important part: the customer is always informed, and has the latest information that they need.

I think about the last time I bought something from an online vendor (eBay, Amazon, Chapters, etc.) The best vendors will show the status of your order, and give you/make available feedback on your order. I know *I* love seeing my shipment journey its way over to me. I sometimes check a few times a day. Now where are my OCD pills…?

The Takeaway

Give your customers feedback. Make it the feedback that you would expect as a consumer. Keep the updates fresh.

Make your development plans transparent. Doing this will build trust between you and customers/managers and your peers. Post it at your workspace/cubicle/office door. Keep it updated! Take the manual route by printing/posting your dev plan (from Excel or what-have-you). Take the digital route by posting it on a blog, wiki, SharePoint, email, or any other way that you can.

How do *YOU* keep your customers informed? Do you have any success stories?


Posted in: best practices , communication , customer  Tags:
Actions: E-mail | Permalink | Comments (0) |
Phil posted on April 27, 2009 06:33

Got wind of a great idea. Some organizations are making an effort to reduce printing, print devices and printing costs. Please note, this isn’t my original idea, and IANAEA (I am not an Exchange admin). I am just being loud about it, perhaps you'll turn out to be a green hero where you work. This will work for any SMB or large organization.

Some organizations work like this: someone is assigned the fax machine, and part of their job is to take care of the incoming faxes. Holy hum-drum, Batman!

Lessen the burden on those fax-gathering-folk

  • Acquire a fax machine that will transform an incoming fax to an email with an attachment (pdf, tif or whatever). Obviously the smaller filesize the better.
  • Have that incoming fax document emailed to an account that only deals with email.
  • Be nice though. Setup their email client with a new folder and rule so that emails from fax@mycompany.org go to only one folder.
  • Perhaps have that account checked only X times a day to limit the interruptions to the human.
  • They can then filter and send to the recipients.
  • This is a human-powered time-consuming spam filtering mechanism as well. Just imagine all the restaurants, travel agents, and sundry fax spammers whose hopes and dreams are crushed by this system.
  • Encourage the end-recipients not to print that fax.

Level Up

Now that you are saving paper, go the extra step. Save some human time. Let’s assume you are using Microsoft Exchange and Outlook. Any version will work.

  • Create yourself a new Public Folder - one per fax line that you have. Do you have 'private' fax lines for the HR types, or director types? Lock it down with Active Directory permissions! Simple stuff!
  • Modify the ‘to’ email address on the fax machine to send to that new Public Folder.
  • Direct people to visit that Public Folder when they're expecting a fax.
  • Build your own set of business rules around when to remove items from this Public Folder. i.e. If the fax is meant for you, and only you, then delete or move it to your personal Inbox as you will. Fwd to anyone concerned is also appropriate. Standard email rules apply here as well (etiquette, attachment size, etc).
  • Have people add that Fax folder to their Favorites in Outlook Public Folders.

Write a nice how-to document for people on how to visit that Public Folder. How to Move/Copy to their inbox. Set the business rules here. Obviously management buy-in will be important.

"So What?! It's just a few pieces of paper!"

Well let's do the math. Here's my quick and dirty formula.

  • 20 faxes per day.
  • .0055 hours (20 seconds) to analyze a fax and determine who to redirect it to, and forward the email. (20 seconds /3600 seconds per hour = .0055 hours)
  • $15 per hour for a human
  • 10 cents per page printed. Assume that includes TCO items: paper, printer toner (galaxies more for ink), printer maintenance, amortized cost of the printer.

20 x (($15 x .0055) + $.10) = $3.65 per day

$3.65 x 255 work days per year = $930.75

My example company is spending $930 a year on receiving faxes. I know this number is dwarfed by the time-costs of email, but it's something to look at. I know the fax-hunter-gatherer would love to have that task melt away.


Actions: E-mail | Permalink | Comments (0) |