Whither VB? The withering away of a category killer

Rogers Cadenhead posts about something that has been on my mind recently, the ending of support for Visual Basic 6. A while ago Scoble and Dan Appleman blogged the opposing viewpoint: making customers move their apps off VB6 is the right thing to do because VB.NET is a far superior language.

Let me illustrate a real world perspective on the debate, rather than the strawman that Appleman provides. Say you are a small software company that makes a product that is written in a variety of languages, and a core part of the application suite having to do with automatically sending e-mails is written as a Visual Basic 6 application.

Now why would someone write an email notification process in VB6? I don’t know. Why would someone write the standard procurement system for the Department of Defense in PowerBuilder? This happens all the time: mission critical apps get written in less than mission critical languages, and then get maintained, more or less, forever.

So fast forward about eight years from the creation of this app. The mail landscape has changed, and the architecture of the mailing process, which used to leverage client apps for sending mails, needs to change too. Email viruses and spammers have made old approaches to writing mail functionality painful; organizations are abandoning POP/SMTP based mail and retreating to MAPI. Meanwhile your VB app, which relied on a once attractive piece of third party code to provide MAPI support, is stagnating, and has started to show issues with age (like memory leaks, and interop issues with the security measures added across the Microsoft mail stack). In other words, it’s time to freshen the app. So the natural thing to do is to look at Visual Basic.NET, the successor to VB6, right?

Um, wrong. VB.NET famously no longer supports the programming interfaces used by VB6. And the VB6 to VB.NET migration tools bite; in fact, the tool blows up prior to successfully migrating the VB6 project. And no one in your development team has expertise in the .NET framework. In the years since VB, the team has moved on to C++ and Java. So which language will be the natural choice for migrating the mail app? Not VB.NET. What might have been a no-brainer to move up the ladder to the next version of VB has turned into a major nightmare for our small software company, and the only clear loser is Microsoft.

SQL refactoring: replace insert cursor with table variable

I lost my copy of the classic programmer’s cookbook Refactoring several years ago, alas, but its philosophy of careful replacement of smelly code with clean code to improve the performance and maintainability of a software program is one that has stayed with me long after I ceased being an active programmer. One regret I had about the book was that it primarily addressed refactoring for object oriented languages (all the examples were in Java). But one of the biggest opportunities for cleaning up code is in non-object oriented languages such as Transact SQL. Recently I had an opportunity to clean up some legacy stored procedure code that I was adapting for an integration project I was working on, and one particular refactoring struck me as especially useful.

A common mistake when writing stored procedures is to overuse cursors. The cursor provides a way to operate on one row of data at a time, which is logical to a programmer used to thinking about working with arrays by looping through them. But SQL is fundamentally a language that is about set operations, and you can realize tremendous gains if you can stop operating on one row at a time and instead operate on a bunch of them at once. Microsoft provided a way to do that in SQL Server 2000 by introducing the table datatype.

Table variables help us by giving us an entire rowset in memory that acts precisely like a table in the database. An article by Alex Grinberg on SQL Server Central illustrates some of the applications of the table variable. I used one of the ideas to replace a cursor that was being used to import data into a table in our system using a counter table.

In fact, the counter table probably caused the use of the cursor in the first place, because a developer had helpfully written a stored procedure to get the next ID value from the table (a common construct for database structures that need to be cross-platform and therefore can’t use features like ). Such a function is a good idea in a client application because it enforces a consistent method for creating IDs for new records, but because it enforces getting only one ID at a time it leads inexorably to row-at-a-time inserts and other abuses of SQL.

So the first step in converting this insert cursor into a sensible insert was to create a new procedure that allowed me to get a bunch of IDs out at once. Where the original procedure had as inputs the table for which the counter was being incremented and the new ID as output, this one also took the number of keys, the starting key value, and the ending key value. Thus even for a very large number of rows, I was only performing a single transaction to get a block of keys to work with.

Now the cursor replacement. The original logic of the stored procedure declared a select cursor against the staging table containing the data to be imported, then opened the cursor, got a new id, and inserted the contents of the cursor plus the ID into the destination table. So for each row of data to be imported we performed two transactions, an update on the counter table and an insert to the destination table. For 500 rows, this was taking about 25 seconds—not huge, but definitely a place where there could be a big improvement. Here are the steps I used to replace the insert cursor:

  1. Declare a table variable, @tbl, with the same columns as the cursor, plus
    an ID column defined with the IDENTITY property.
  2. Populate @tbl with an INSERT INTO…SELECT FROM statement that draws data from the staging table. Now each row in @tbl has the values from the staging
    table plus a temporary ID, ranging from 1 to the number of rows. (We didn’t specify a seed for the ID field; more on that in a second.
  3. Count the number of rows in @tbl (select count(*) from
    @tbl
    ).
  4. Call the updated counter procedure with the number of rows in @tbl, getting
    back the first ID in the range (call it @startid.
  5. Finally, insert into the destination table, selecting from @tbl and adding
    @startid to the identity column in @tbl. Since the IDENTITY column
    started at 1, we want to subtract 1 from @startid for each row as well, or else
    we’ll exceed the range of keys reserved.

Here’s some sample code:

DECLARE @tbl TABLE (col1 int, col2 int, col3 int, temp_id IDENTITY (1,1))

INSERT INTO @tbl (col1, col2, col3)
SELECT stagecol1, stagecol2, stagecol3 FROM staging_table WHERE ...

DECLARE @startid int
DECLARE @total int

SELECT @total = COUNT (*) FROM @tbl

exec p_increment_counter_multi 'dest_table', @total, @startid OUTPUT

INSERT INTO dest_table ( dest_id, destcol1, destcol2, destcol3 )
SELECT temp_id + @startid - 1, col1, col2, col3 FROM @tbl

So instead of two transactions for each row in the staging table, we end up with four transactions total, one of which is a select to populate the table variable and one which just gets the total number of rows. The performance benefits can be substantial: In my particular procedure, for 500 source rows, the time to execute the procedure went from 25 seconds to 6 seconds.

Of course, you could have done the same thing using a temporary table, though it appears that table variables are slightly more efficient in memory. But the basic principle is the same: an application-level counter column is no reason to insert one row at a time.

The same principle, with a twist, can be applied to update cursors as well; more on that in a while.

MSN Toolbar Tabs – first reactions

screen shot of tab widgets from MSN toolbar

There are other tabbed browsing add-ons for IE, but when I saw that the MSN toolbar had added a tabbed browsing enhancement, I decided to check it out. After all, I still know people at MSN I can yell at if there’s something wrong. And, actually, yeah, there’s a few things I would change.

First things: I can’t stress how glad I am to have tabs rather than the damned taskbar group (multiple browsers collapsed into one toolbar button with a number on it). There’s no good way to do blogging and newsreading with toolbar groups. Tabs are a hell of a lot more usable. I also appreciate that the toolbar supports the standard CTRL-T keyboard shortcut for creating a new tab.

But there are quite a few missing features from the MSN implementation. For one thing, there’s no option to make new links open automatically as a tab in an existing browser window. So if you click a link from email or another application, it still spawns a new browser window. And links defined to open in new windows still do; there’s no way to override that behavior to make the new window open in a tab instead, as there is with Firefox or with Safari. Also, there is no “open in new tab” on the right-click context menu, which renders the tab feature a lot less useful.

Verdict: on a scale of 1 to 5, a 2. The new tab support is better than having no tabs at all, but to call it half baked is too generous. It feels like the team focused on tabs as a feature, rather than looking at the customer problem, which is window clutter and impaired productivity, and thinking about what is required to address that in a tabbed browser implementation. Microsoft is traditionally good at thinking through user scenarios; I look forward to the next version.

Schooled by Scoble, and my response

Scoble commented on my piece yesterday on MSN Virtual Earth and gently points out, through a link to the Channel 9 interview with the team behind Virtual Earth, that there’s considerably more to the new offering than following what Google did with Google Maps. I agree; certainly the eagle-eye view is impressive (if not destined for the first release; it would be rude to call it vaporware, though), as are the hybrid view and the UI work. I probably misspoke in calling this a “me-too” release; several of the features are brand-new to the market. I’m not sure that changes the main point I made, though.

Launching a product isn’t just features, it’s time to market. Shimon commented that there’s no question that Microsoft will keep innovating in this space and lap the competition. My question, as in my first post, is what took so long? Certainly the first feature, combining satellite and map in the same interface, is something that Microsoft could have done years ago. But from all appearances it took the arrival of competition for the company to deliver that value to customers.

My point is that the competition is good—for customers, for the company, and for its shareholders. And that brings me dangerously close to a hobbyhorse that I’ve been on and off for a long time. Microsoft can’t be the only company in a space and still deliver maximum value, because it generally does its best work in response to competition. That’s not a reflection on the company’s technical skills but on its great organizational strength: the way it responds to a challenge.

Mapping: When being a smart follower isn’t enough

Microsoft announced that they will debut a new mapping service, MSN Virtual Earth, this summer (thanks to Slashdot for the link). The service combines satellite images with map data, provides Sims-like isometric views, and allows layering information about businesses and services atop the search results.

This isn’t a surprising move. After all, MSN Maps have been around for a while, and Microsoft has had Terraserver since 1998. What’s different is that Microsoft’s announcement has a feel of desperation and me-too-ness about it, coming several months after Google debuted satellite images in their slick Google Maps service.

Integration of maps and satellite images is a natural incremental feature that provides radical amounts of value to users. It’s just the sort of software that you used to expect Microsoft to release. Embarrassing, then, that they got beaten to their own punch by a company that had no prior competence in mapping or imaging.

The good news in this scenario is that customers are getting a choice, as Microsoft feels the sting of competition. The bad news—for customers and for its investors— is that the most highly capitalized software company in the world isn’t capable of turning all its resources into bringing products like this to the market faster.

Correction: InfoCard federates

Johannes Ernst, whom I linked from my piece on InfoCard last week, wrote in to point out that I erred in my quick description of the service. He says that in InfoCard:

…the PC does not actually store the identity information, only pointers to it. The actual identity information is stored by identity providers, who are the “3rd party” in the system (the other ones being the relying party, such as a website, and the PC component).

This makes InfoCard much less like Apple’s Keychain (or for that matter the existing Windows saved password feature) and more like, well, a federated identity system. Interestingly, this is consistent with what I remember from the discussion of the future of Passport back in 2001 with MSN execs.

Reinstalling again

Another morning lost to rebuilding the machine. I asked the IT guys at my office to allow me to upgrade to Windows XP from 2000, and they did—alas, something went awry and the install created an entirely new system directory. So I’m reinstalling services and trying to find installation disks, and basically just trying to get things back to normal. It is nice to be back on XP, though.

Transforming XML with XPath in SQL Server

Since the next version of SQL Server (2005, aka Yukon) is at least nominally coming out this year, it seems a bit late to write a tip about using the XML features in SQL Server 2000. However, better late than never.

As part of a project I’m working on, I needed to bring XML data into our production database for subsequent processing. The XML document that our partner’s application provides is what I would consider “loosely typed”—all the useful information about the data contained within is defined outside the actual schema. For an example, say the canonical sample XML document is written like this:


<ROOT>

<Customer CustomerID="VINET" ContactName="Paul Henriot">
 <Order CustomerID="VINET" EmployeeID="5" OrderDate="1996-07-04T00:00:00">
  <OrderDetail OrderID="10248" ProductID="11" Quantity="12"/>
  <OrderDetail OrderID="10248" ProductID="42" Quantity="10"/>
 </Order>
</Customer>
<Customer CustomerID="LILAS" ContactName="Carlos Gonzlez">
 <Order CustomerID="LILAS" EmployeeID="3" OrderDate="1996-08-16T00:00:00">
  <OrderDetail OrderID="10283" ProductID="72" Quantity="3"/>
 </Order>
</Customer>
</ROOT>

In the flavor of data description used by our partner, the same document would be written like this:


<ROOT>
<Customer CustomerID="VINET" ContactName="Paul Henriot">
 <Order>
  <OrderAttribute rawname="EmployeeID">"5"</OrderAttribute>
  <OrderAttribute rawname="OrderDate=">"1996-07-04T00:00:00"</OrderAttribute>
   <OrderDetail>
    <OrderDetailAttribute rawname="OrderID">"10248"</OrderDetailAttribute>
    <OrderDetailAttribute rawname="ProductID">"11"</OrderDetailAttribute>
    <OrderDetailAttribute rawname="Quantity">"12"</OrderDetailAttribute>
   </OrderDetail>
...
<ROOT>

That’s only slightly unfortunate; after all, XML is meant to be transformed, and if you wanted to turn the sample into sensible row-and-column data for a relational database you could use the features provided in OPENXML to do so.

Ah, but it turns out the XPATH syntax needed to transform the data (using the ColPattern mapping in the OPENXML) is slightly nontrivial. To extract the text of the elements, you need the text() function, which must be called after the predicate specifier. That is, if your OPENXML statement looks at the /Root/Customer/Order/OrderDetail branch, rather than writing OrderDetailAttribute/text()[@rawname="Quantity"] to get the value of the quantity of the order, you need to write child::OrderDetailAttribute[@rawname="Quantity"]/text(). (Note: If you wanted to include details from both the order and orderdetail branch, you actually have to specify child:: rather than SQL Server’s shorthand, otherwise you get a crossproduct.) (see below)

Anyway, once you have the correct XPath sequence, the rest is relatively trivial. The stored procedure below accepts the XML document as a parameter (you need to do this, generally speaking, unless you know that the XML document will be smaller than 4000 characters in size, since SQL Server 2000 doesn’t let you use local variables of type TEXT) and transforms it into a rowset, ready to be inserted into the database or otherwise manipulated:


CREATE PROCEDURE dbo.import_xml
	@xmldoc text
as
	DECLARE @hdoc int

EXEC sp_xml_preparedocument @hdoc OUTPUT, @xmldoc

--let's peek at the rowset

SELECT * FROM 
OPENXML (@hdoc,'/Root/Customer/Order',1)
 WITH (CustomerID varchar(300) 
'../@CustomerID', EmployeeID varchar(30)
'OrderAttribute[@rawname="EmployeeID"]', OrderID varchar(300)
'OrderDetail/OrderDetailAttribute[@rawname="OrderID"]', ProductID varchar(30)
'OrderDetail/OrderDetailAttribute[@rawname="ProductID"]', Quantity varchar(30)
'OrderDetail/OrderDetailAttribute[@rawname="Quantity"]') exec sp_xml_removedocument @hdoc GO

which produces, for the sample file excerpted above,

CustomerID EmployeeID OrderID ProductID Quantity
VINET 5 10248 11 12

This is probably old hat to those of my readers who know what the hell I’m talking about, but it was eye opening and nonobvious to me.

Update: Corrected after some input from a reader who knew better than I what the hell I was talking about (thanks, Michael Rys). As it turns out, you don’t need the text() function; the XPath shown above will correctly pull the text in the node once you’ve written the predicate correctly. Nothing to see here; move along.

Solving ASP.NET application problems

I have been working with a partner of ours to get an ASP.NET web application running on my Windows 2000 computer at the office. We had no joy for several hours last night trying to figure out why none of the .aspx pages in the application could be contacted. We were getting an interesting error message:


Server Error in '/BogusAppname' Application.
----------------------------------------------------

The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.

Requested Url: /BogusAppname/login.aspx

----------------------------------------------------
Version Information: Microsoft .NET Framework Version:1.1.4322.2032; ASP.NET Version:1.1.4322.2032

I had to give up with the guy from our partner company at 8 PM my time last night. This morning, I did some Googling and found an all-too-simple-sounding solution: the application wasn’t correctly registered as a virtual directory. I went to the IIS manager, created a new virtual directory with the same name as the directory I was trying to hit, and lo and behold the application started working.

The difference was only apparent in the properties dialog on the application directory. The virtual directories on the server had “Virtual Directory” as the first tab on the properties dialog, but our application directory had “Directory”.

Perhaps this will save someone’s thinning hair.

Expansion begins at home, in Redmond

microsoft campus buildings 1 - 6

CNet: After looking around, Microsoft decides to expand at home. That the travel across the 520 bridge to get to hypothetical Seattle offices should be viewed as an insurmountable obstacle to development should surprise no one. Microsoft doesn’t know how to build software (broad generalization) unless all the people are within two minutes’ walk of each other. For all its high-techness, the most surprising thing about the Microsoft culture is how meeting-centric it is.

The thing that caught my eye was this sentence in the third paragraph: “The company also plans to demolish and rebuild 600,000 square feet of older buildings that lack the power and cooling capacity needed for modern computer equipment.” I wonder if that means a final sayonara for buildings 1 through 6? These were the original Microsoft buildings on the Redmond campus, and while they’re earthquake damaged, mold infested, and confusing as hell (check out the satellite photo here and then imagine navigating around the corridors inside those Xs), they’s historic. They also fit sympathetically into the wooded ravine landscape in a way that none of the later buildings manage. Maybe one of my Microsoftie friends can comment on this?

Email productivity tips

Now that I’m back in the real world (that is, not blogging all day long), I am definitely feeling the need to revisit some of the recommendations for time management at 43 Folders. Fortunately Merlin posted a roundup of email and task management recommendations today, including the following (drawn from the three individual posts):

  • Shut off auto-check, or set it to something reasonable like every 20 minutes.
  • Pick off the easy mails—if you can reply to something with a 1-2 line response, do it.
  • Write less.
  • Be honest—delete or archive the mails you’ll never do anything about.
  • Process each piece of incoming email as: delete, archive, defer for later response, generate an action, or respond immediately. Then go back to the response and action items and do them in batches.
  • Outlook and Entourage allow you to categorize task items. Use categories to provide the context around task items. Merlin suggests using functional categories (“chores,” “errand,” “write,” “calls”), computer-related categories, and categories like “agenda” to prevent items from falling off the plate.

Microsoft Blog Portal 2.0

Jana’s Joint: Blog OPML. The updated version of the Microsoft.com Blog Portal (which I worked on right before I left the company—I was there to ship the 1.0 version) brings OPML for collections of Microsoft blogs out of the realm of “easter egg” and into the user interface in an incredibly intuitive way:

The fun part is you can go create your own OPML feeds by using the search function on the page. Each product search for blogs will generate a feed.

So, cool stuff. Next: incorporate the blog search results into regular Microsoft.com search. Right, guys? sideways smiley

Microsoft ties the knot with Groove

My former employer purchased Groove today, making official what was already a very close working relationship. I’d like to be optimistic about what the acquisition will mean for the information worker part of Microsoft’s business.

But let’s look at the track record that the Information Worker business unit has in bringing innovative products into the Office mainstream. Live Meeting? Kind of integrated, still largely a standalone product, but it’s out there and fighting for market share with WebEx. PowerPoint? Visio? FrontPage? OK. All standalone apps, all acquired, that fill a niche in the information worker workspace.

But what about XDocs? This brave internal project came out of ashes of NetDocs as a “smart client alternative to Office.” Where is it now? InfoPath, which is being marketed primarily as a forms app.

Will Microsoft tap the benefits of Groove and make them available in a rich way throughout the desktop? Or will Groove just end up looking like the next version of SharePoint, which currently looks like the next version of a generic company intranet tool?

Excellent additional coverage from Robert Scoble, John Evdemon, Scott Rosenberg, Ross Mayfield, and Alex Barnett.

And incidentally: Alex points to Jef Raikes talking about a product announcement that I missed earlier this week, the launch of something called “Microsoft Office Communicator 2005.” Sounds interesting. Go try to find something about it on Microsoft’s Office site. Did you find it? Did you try searching? Did you try changing the search dropdown from “All Office Online” to “All Microsoft.com?” Ah, there we go. Hint to our friends on the Office web site: If you want to sell a product as part of the Office family, it would be a good idea to make it findable from the Office web page.

Welcome back, my friends

I’d officially like to congratulate the IE team for getting the IE 7 release decoupled from Longhorn, and welcome Microsoft back to the modern browser landscape. It’s a big deal that Microsoft has awakened to the threat to its browser dominance from Firefox and other alternative browsers. We can only hope that IE 7 doesn’t bring its own slate of nasty CSS bugs that have to be worked around with yet another bunch of skanky CSS hacks.

The mote in your neighbor’s eye

Alex Barnett, erstwhile Microsoft UK online marketer, now in corporate at Redmond, is today’s designated lightning rod with Firefox is secure, FUD?:

How many times have you heard, “hey drop IE, it is full of security holes. Try Firefox, it is secure.”? I’m not saying IE hasn’t had its own problems, but Firefox has had security holes in past, has security holes today and will in the future. To say Firefox is secure is simply untrue.

There are certainly arguments to be made that Firefox is not the be-all and end-all in secure browsers, but picking on this flaw is probably not the right way to go about it. Especially given that IE never bothered to implement the standard in question. And the large numbers of vulnerabilities in everything from graphics handling to hyperlinks that were patched yesterday.

I think there’s a positive story to be told about Microsoft’s security response efforts. I also think that the company and its representatives have a long way to go before they can be credible calling another software effort insecure.