November 30, 2004
Classic ASP
I just went to the Radio Shack website to look for something, and after every click on the main page, I was greeted with this:
If I was running a giant corporation, I think I'd hire coders who could develop a rational error handling strategy for our production website.
Of course, that's much easier to do in ASP.NET than cruddy old classic ASP. I'm not a big fan of the ultra-leaky "winforms on the web" abstraction model, but there's no denying that ASP.NET-- and the .NET framework-- is an order of magnitude improvement over ASP. That's why I was appalled that in the otherwise interesting How Microsoft Lost the API War, Joel wrote:
And personally I still haven't had time to learn .NET very deeply, and we haven't ported Fog Creek's two applications from classic ASP and Visual Basic 6.0 to .NET because there's no return on investment for us. None. It's just Fire and Motion as far as I'm concerned: Microsoft would love for me to stop adding new features to our bug tracking software and content management software and instead waste a few months porting it to another programming environment, something which will not benefit a single customer and therefore will not gain us one additional sale, and therefore which is a complete waste of several months, which is great for Microsoft, because they have content management software and bug tracking software, too, so they'd like nothing better than for me to waste time spinning cycles catching up with the flavor du jour, and then waste another year or two doing an Avalon version, too, while they add features to their own competitive software. Riiiight.
It's difficult to comprehend how a smart guy like Joel could be so blind to the massive, obvious benefits of a straight port to ASP.NET. Coherent global error handling is only the tip of the iceberg. At this point, you could not pay me enough to write in ASP.*
* Ok, you could. But it would be really expensive.
November 29, 2004
Because I love the smell of compilation in the morning
As McConnell notes in Code Complete:
If you haven't spent at least a month working on the same program -- working 16 hours a day, dreaming about it during the remaining 8 hours of restless sleep, working several nights straight through truing to eliminate that "one last bug" from the program -- then you haven't really written a complicated computer program. And you may not have the sense that there is something exhilarating about programming.-- Edward Yourdon
This lusty tribute to programming machismo is pure B.S. and an almost certain recipe for failure. Those all-night programming stints make you feel like the greatest programmer in the world, but then you have to spend several weeks correcting the defects you installed during your blaze of glory. By all means, get excited about programming. But excitement is no substitute for competency. Remember which is more important.
Enthusiasm is important-- I'd rather work with someone who is gonzo than someone who is apathetic-- but there's definitely a point where it becomes a negative. Although I try to avoid it, I find myself veering into "macho coder" territory sometimes.
Don't invest tons of hours for the wrong reasons. As any EverQuest player will tell you, time does not always equal value.
November 28, 2004
Custom AssemblyInfo Attributes
To complement my previous post bemoaning the lack of respect for AssemblyInfo, I wanted to illustrate just how easy it is to add a few custom attributes to our AssemblyInfo file:
Imports System
Imports System.Reflection
<Assembly: AssemblyTitle("ASPUnhandledException")>
<Assembly: AssemblyDescription("ASP.NET unhandled exception handling library")>
<Assembly: AssemblyCompany("Atwood Heavy Industries")>
<Assembly: AssemblyCompanyEmail("jatwood@atwoodheavyindustries.com")>
<Assembly: AssemblyCompanyUrl("http://www.atwoodheavyindustries.com")>
<Assembly: AssemblyProduct("Exception Handling Framework")>
<Assembly: AssemblyCopyright(" 2004, Atwood Heavy Industries")>
<Assembly: AssemblyTrademark("All Rights Reserved")>
<Assembly: CLSCompliant(True)>
<Assembly: AssemblyVersion("2.1.*")>
To get the custom attributes AssemblyCompanyUrl and AssemblyCompanyEmail working, just add these two classes to your solution:
<AttributeUsage(AttributeTargets.Assembly)> _
Public Class AssemblyCompanyEmailAttribute
Inherits System.Attribute
Private _strCompanyEmail As String
Public Sub New(ByVal email As String)
_strCompanyEmail = email
End Sub
Public Overridable ReadOnly Property CompanyEmail() As String
Get
Return _strCompanyEmail
End Get
End Property
End Class
<AttributeUsage(AttributeTargets.Assembly)> _
Public Class AssemblyCompanyUrlAttribute
Inherits System.Attribute
Private _strCompanyUrl As String
Public Sub New(ByVal url As String)
_strCompanyUrl = url
End Sub
Public Overridable ReadOnly Property CompanyUrl() As String
Get
Return _strCompanyUrl
End Get
End Property
End Class
Once you've compiled your assembly, the obvious question is, how do we get these attributes (custom or standard) back out? I do it with a reflection loop into a NameValueCollection:
Private Shared Function GetAssemblyAttribs(ByVal a As Reflection.Assembly) _
As Specialized.NameValueCollection
Dim attribs() As Object
Dim attrib As Object
Dim Name As String
Dim Value As String
Dim nvc As New Specialized.NameValueCollection
attribs = a.GetCustomAttributes(False)
For Each attrib In attribs
Name = attrib.GetType().ToString()
Value = ""
Select Case Name
Case "System.Reflection.AssemblyTrademarkAttribute"
Name = "Trademark"
Value = CType(attrib, AssemblyTrademarkAttribute).Trademark.ToString
Case "System.Reflection.AssemblyProductAttribute"
Name = "Product"
Value = CType(attrib, AssemblyProductAttribute).Product.ToString
Case "System.Reflection.AssemblyCopyrightAttribute"
Name = "Copyright"
Value = CType(attrib, AssemblyCopyrightAttribute).Copyright.ToString
Case "System.Reflection.AssemblyCompanyAttribute"
Name = "Company"
Value = CType(attrib, AssemblyCompanyAttribute).Company.ToString
Case "System.Reflection.AssemblyTitleAttribute"
Name = "Title"
Value = CType(attrib, AssemblyTitleAttribute).Title.ToString
Case "System.Reflection.AssemblyDescriptionAttribute"
Name = "Description"
Value = CType(attrib, AssemblyDescriptionAttribute).Description.ToString
Case Else
'Console.WriteLine(Name)
End Select
If Value <> "" Then
If nvc.Item(Name) = "" Then
nvc.Add(Name, Value)
End If
End If
Next
Return nvc
End Function
But I am sure there are other ways.
November 27, 2004
Populate your AssemblyInfo
All too often, I download sample code with AssemblyInfo files that look like this:
//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
//
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.*")]
So, basically, you're compiling into an assembly DLL or EXE with nothing but crappy, default metadata and versioning.
Please don't do this.
If you're going to the effort of making your code publically available, take an additional 60 seconds to fill out the AssemblyInfo.
My only regret with assembly attributes is that they really should have included URL and email attributes by default. Instead we have to define custom attributes to get that..
November 26, 2004
WebFileManager
I posted a new CodeProject article, Web File Manager:
I often deploy ASP.NET websites to servers that I don't control. In these situations, I can't get to the underlying filesystem to do any file maintenance, because I don't have direct access to the server. For various reasons, I often need to access the filesystem indirectly, through the website that I am deploying. Rather than writing a bunch of special purpose pages to deal with file management, I developed a generic WebFileManager page than can be dropped into any ASP.NET website. This page performs the most common file and folder operations.
Nothing earth shattering, but I find it very useful. I will respond to any feedback left on the article page.
November 25, 2004
Giving of thanks, and tech support
Next week, millions of college students and young professionals will head home for the Thanksgiving holidays. We'll sit with our families in warm, candle-lit dining rooms eating stuffed turkey, reminiscing over old photographs, preparing holiday shopping lists and … Please. Let's be frank. We are going home to fix our parents' computers.
It's funny because it's true.
November 24, 2004
On American Programmers
Paul Graham posted an interesting new essay about American culture and software development:
This works well in some fields and badly in others. I suspect it works in movies and software because they're both messy processes. "Systematic" is the last word I'd use to describe the way good programmers write software. Code is not something they assemble painstakingly after careful planning, like the pyramids. It's something they plunge into, working fast and constantly changing their minds, like a charcoal sketch.In software, paradoxical as it sounds, good craftsmanship means working fast. If you work slowly and meticulously, you merely end up with a very fine implementation of your initial, mistaken idea. Working slowly and meticulously is premature optimization. Better to get a prototype done fast, and see what new ideas it gives you.
In other words, good programmers get off their butts.*
I could say "asses", yes, but I respect the delicate, tender sensibilities of my readership.
November 23, 2004
Trapped in a Bitmapped World
In a recent blog entry, Don Park waxed poetic about 1600x1480 15" LCDs. That's more of a microfiche reader than an actual screen. High DPI displays, though, aren't the root of the problem. As Scoble points out, the real issue is Windows itself:
Turns out his screen was set to a non-native resolution. This is a common thing I see on LCD screens. Most LCD owners don't realize that there is only one resolution that their screen should be set to. Unfortunately Windows doesn't tell you what that resolution is and the tools to let you control your resolution are confusing at best.Why is this bad? Well, for one, if you don't run an LCD screen at the "right" resolution then the ClearType font sharpening technology won't work (it'll actually be even worse on a screen that isn't set to the right resolution).
So, I asked Dave [Winer] if I could set his screen to the proper resolution. "Sure."
After I did, I showed him the screen and he promptly said "I can't read that, it's too small."
"How old are you?" he asked.
"Almost 40."
"In another 10 years you won't be able to read that screen either."
Unfortunately I didn't have the right setting for making everything on the screen appear bigger while retaining the sharpness of a well-set screen... the settings that are built into Windows XP today really are inadequate to deal with high resolution screens. Even now, some things on my page are way too big and others are way too small.
The sad truth is that Windows is hard coded to specific pixel sizes. It doesn't scale because bitmaps don't scale. Oh sure, you can pretend that it scales by flipping on Large Fonts, or modifying the DPI setting in control panel, but these are just hacky workarounds with major side effects. Windows is a bitmapped UI. If you run Windows, like it or not, you live in a bitmapped world.
Nothing short of a full-blown UI redesign-- something far more radical than the change from Windows 3.1 to Windows 95-- will fix this problem. That's exactly what the Avalon technology is supposed to do. And thank God Microsoft decided to port Avalon to XP and reach a much larger audience with the blessing of backwards compatibility. I seriously doubt we'd ever get to use Avalon if it was tied to a new OS release.
Now, I'm sure this will come as no surprise to Flash developers, who have had vector primitives for nearly ten years. Steve Jobs' ill-fated NeXTstep OS used Display PostScript to drive its windowing engine-- and that was fifteen years ago. Interestingly, the current Mac OS X inherits this choice. Why is it taking Windows so long to catch up? It sucks to be trapped in a bitmapped world when vectors are more powerful, more flexible, more elegant: just plain better. Bring on the Avalon!
November 22, 2004
Full Threaded Shellicious
I couldn't resist adding some features to Shellicious. You can now run shell commands either asynchronously (as before) or synchronously, like so:
Private WithEvents _s As New Shell
Private _IsExecutionComplete As Boolean = False
Public Sub Main()
_s.UseNewThread = True
_s.Execute("C:\LongRunningConsoleApp.exe")
Do While Not _IsExecutionComplete
'-- do other work here..
Thread.Sleep(20)
Loop
Console.WriteLine("Exiting Sub Main()..")
Console.ReadLine()
End Sub
Private Sub OutputLine(ByVal LineText As String) Handles _s.OutputLine
Console.WriteLine(LineText)
End Sub
Private Sub ExecutionComplete(ByVal TimedOut As Boolean) _
Handles _s.ExecutionComplete
_IsExecutionComplete = True
Console.WriteLine("execution complete; did we time out? " & TimedOut)
If _s.ExitCode <> 0 Then
Console.WriteLine(_s.Error)
End If
Console.WriteLine(_s.ExecutionTime)
Console.WriteLine(_s.ExitCode)
End Sub
I updated the code in the original post. And this time I remembered to give the threads names, which always helps in debugging:
The thread 'ShellErrorThread' (0xca4) has exited with code 0 (0x0). The thread 'ShellOutputThread' (0x934) has exited with code 0 (0x0). The thread 'ShellLaunchThread' (0x5c0) has exited with code 0 (0x0).
So far so good. The synchronous behavior respects the same .MaximumWaitSeconds property as before, and there's a new .CancelExecution method if you want to bail out on demand.
November 21, 2004
Good Programmers Get Off Their Butts
I searched for this citation, and like Wes, I remember reading it, but I can't remember the exact place I read it:
This echoes another comment from a recently read blog article, the author of which I cannot recall. Good programmers get off their butts. Typically, programmers won't write code until they have resolved some design issues, but in the process time can go by with very little advancement in the design. Productive developers will write some code, even if the design is vague, because software development is an iterative process.
I have lost count of the number of times I've set out to design software, then during implementation had to throw out or radically alter my design, because..
- I forgot something really important
- I found another, easier approach
- What I'm doing doesn't make sense
- I am reinventing the wheel, and should be looking for a download
- Hey, I don't even need to do this in the first place!
I am not proposing a code-like-hell methodology. I am merely observing that, in my experience, coding without planning is just as futile as coding with too much planning. Software development is a wicked problem; you should never make planning decisions without some kind of code prototype to ensure that you're making informed decisions. If you plan too far ahead of the code, I guarantee you are doing work that will be thrown away or altered until it is unrecognizable.
The most destructive symptom of over-planning is the wrongheaded idea that being a Software Architect(tm) means drawing a lot of UML diagrams and handing them off to a group of developers in Bangalore. UML is great if you don't want to do any work; you just draw pictures of what it would look like if work was actually done. This is not only borderline laziness, it's also a recipe for disaster. You can't architect a real world application on a whiteboard. You must prototype it in code to gather data on the performance and design implications of the choices you are making. Otherwise you really have no idea if you're creating something that makes sense-- or if it's even possible! As noted in Robert Glass' Facts and Fallacies of Software Engineering, in software design, being hands on is mandatory:
Far from being a predictable, structurable, routinizable process, design-- according to the findings of Curtis and Soloway (1987)-- is a messy, trial-and-error thing. And remember, these findings are the result of studying top designers at work. One can imagine less than top designers using an even messier process. Probably the worst possible design approach, and yet one that is tempting to most design novices, is "easy-part-first." Although it is easy to get the design process started with this approach, it all too often leads to solutions-in-the-small that won't integrate into an overall solution-in-the-large. As a result, those solutions-in-the-small must often be discarded.It is easy to see from all of this that design is complex and iterative. (This thought is explicit in Wiegers [1996].) In fact, it is probably the most deeply intellectual activity of the software development process. It is also easy to see that the initial design solution is quite likely to be wrong. And what about optimal? Well, certainly initial design solutions will rarely be optimal. But that word raises another interesting question-- is there such a thing as an optimal design solution?
As software developers-- and especially if we have pretensions of being so-called "architects"-- we should always make decisions based on experience and data. And like it or not, that means getting off our butts and writing code.
