October 31, 2004
Full Trust can't be trusted
Microsoft gets blamed for a lot of security problems, and for the most part, they deserve it. There's no excuse for the irresponsible "on by default" policy that resulted in so many vulnerable Windows 2000 IIS installations. That's why Nimda was so devastating. Windows 2003 has a great security record, mostly because of Microsoft's new "off by default" policy. I expect Windows XP SP2 to be similarly successful.
Here's what disturbs me, though. Even if we eliminate all the system vulnerabilities, what about the biggest vulnerability of all-- the user? The fastest growing virus vector is complicit users:
In the past year, spyware problems have become especially pernicious, leaving companies scrambling to respond to customers who don't necessarily realize they have spyware. Companies are concerned about the cost of dealing with such calls. But perhaps more worrisome, they fear customers will wrongly blame them.Spyware generally refers to programs that land on computers without their owners' knowledge. They can deliver hordes of pop-up ads, redirect people to unfamiliar search engines or, in rare cases, steal personal information. Users most often get them by downloading free games or file-sharing software -- and consenting to language buried deep within a licensing agreement. And because they consented, "in some ways it ties our hands because we can't legally interfere," said Mike George, head of Dell's U.S. consumer business.
It's a thorny problem. How do you protect users from themselves? Unix users will quickly respond with "users should not run as root." And they're right:
Unfortunately, running as root (or Administrator) is common in the Windows world. In fact, Microsoft is still engaging in this risky behavior. Windows XP, supposed Microsoft's most secure desktop operating system, automatically makes the first named user of the system an Administrator, with the power to do anything he wants to the computer. The reasons for this decision boggle the mind. With all the lost money and productivity over the last decade caused by countless Microsoft-borne viruses and worms, you'd think the company could have changed its procedures in this area, but no.
Windows, unlike Unix, started life as a single user system. So running as Administrator is deeply ingrained into Windows users. While you can run as a regular user under XP, the User Accounts section of control panel practically begs you not to:
Users with limited accounts cannot always install programs. Depending on the program, a user might need administrator privileges to install it. Also programs designed prior to Windows XP or Windows 2000 might not work properly with limited accounts. For best results, choose programs bearing the Designed for Windows XP logo, or, to run older programs, choose the "computer administrator" account type.
If that doesn't scare the crap out of the average user, nothing will. The correct, Unix-y idea that users should not run as Administrator will be adopted by the Windows world, albeit exruciatingly slowly. Microsoft has 10 years of history to overcome, and some non-trivial usability hurdles to address. Security is complexity, and users don't like complexity: they just want to do their thing. To some degree, security and convenience are mutually exclusive.
Although running as a regular user would be a definite improvement in security-- at the cost of convenience-- it's still something of an illusion. If users want software bad enough they will jump through any arbitrary hoop to get it, including switching to an Administrator account. Never underestimate the power of a free copy of the latest Linkin Park album. Malware vendors will be more than happy to document the "installation" process for their free p2p file sharing software-- File, Run As, Administrator. And once that door is open, it's open for everyone.
So we're back where we started: how do you protect users from themselves in an increasingly exploitative world, where malware and phishing grow by double digits every year? Maybe the only answer is something like is Dan Appleman's education effort. While we clearly need to continue attacking the technology part of this problem, it's unrealistic to think we can 'solve' security through technology alone.
October 30, 2004
UI Follies, Volume I
Occasionally I run into UI elements so boneheaded, I have to wonder what the programmers were thinking.
It's a standard convention for installers to show (estimate, really) how long the install will take. That way users have some idea how long they'll be waiting, and whether they can safely go do something else while the install proceeds. This installer bravely bucks that trend, choosing to show elapsed time:
Not only elapsed time, but elapsed time down to the tenth of a second. Way to take a useful installer convention, reverse it, and utterly ruin it. Bravo!
I've talked before about how windows apps can benefit from adopting web conventions. Well, the converse is not true: web apps should not pretend to be windows apps; they should follow standard web conventions. Well, this MySQL error page avoids those standards, choosing instead to build its own FUI:
On top of that, it's a poor error message. I doubt "threads" or "OS-dependent bugs" mean anything to most users browsing xbitlabs articles.
October 28, 2004
Who Needs Stored Procedures, Anyways?
It's intended as sarcasm, but I believe this Daily WTF entry on Stored Procedures should be taken at face value:
I'm sure we've all heard, over and over, that inline SQL is generally a bad practice, and that we should use Stored Procedures when possible. But let's be realistic for a minute. Who wants to write a stupid stored procedure for every stupid little simple query needed.
Have you ever worked on a system where someone decreed* that all database calls must be Stored Procedures, and SQL is strictly verboten? I have, and this decision leads to incredible development pain:
- Stored Procedures are written in big iron database "languages" like PL/SQL (Oracle) or T-SQL (Microsoft). These so-called languages are archaic, and full of the crazy, incoherent design choices that always result from the torturous evolution of ten years of backwards compatibility. You really don't want to be writing a lot of code in this stuff. For context, JavaScript is a giant step up from PL/SQL or T-SQL.
- Stored Procedures typically cannot be debugged in the same IDE you write your UI. Every time I isolate an exception in the procs, I have to stop what I am doing, bust out my copy of Toad, and load up the database packages to see what's going wrong. Frequently transitioning between two totally different IDEs, with completely different interfaces and languages, is not exactly productive.
- Stored Procedures don't provide much feedback when things go wrong. Unless the proc is coded interally with weird T-SQL or PL/SQL exception handling, we get cryptic 'errors' returned based on the particular line inside the proc that failed, such as Table has no rows. Uh, ok?
- Stored Procedures can't pass objects. So, if you're not careful, you can end up with a zillion parameters. If you have to populate a table row with 20+ fields using a proc, say hello to 20+ parameters. Worst of all, if I pass a bad parameter-- either too many, not enough, or bad datatypes-- I get a generic "bad call" error. Oracle can't tell me which parameters are in error! So I have to pore over 20 parameters, by hand, to figure out which one is the culprit.
- Stored Procedures hide business logic. I have no idea what a proc is doing, or what kind of cursor (DataSet) or values it will return to me. I can't view the source code to the proc (at least, without resorting to #2 if I have appropriate access) to verify that it is actually doing what I think it is-- or what the designer intended it to do. Inline SQL may not be pretty, but at least I can see it in context, alongside the other business logic.
- Stored procedures generally result in improved performance because the database can optimize the data access plan used by the procedure and cache it for subsequent reuse.
- Stored procedures can be individually secured within the database. A client can be granted permissions to execute a stored procedure without having any permissions on the underlying tables.
- Stored procedures result in easier maintenance because it is generally easier to modify a stored procedure than it is to change a hard-coded SQL statement within a deployed component.
- Stored procedures add an extra level of abstraction from the underlying database schema. The client of the stored procedure is isolated from the implementation details of the stored procedure and from the underlying schema.
- Stored procedures can reduce network traffic, because SQL statements can be executed in batches rather than sending multiple requests from the client.
And many people buy into this philosophy, lock stock and barrel:
At just about every talk I give I always try to make several consistent statements. One of which is: ‘Whenever possible use stored procedures to access your data'.
However, there's one small problem: none of these things are true in practice. The benefits are marginal, but the pain is substantial. And I'm not the only person that feels this way. John Lam also adds:
As a guy who dabbles in low-level bit twiddling stuff from time-to-time, the performance claims are quite interesting to me. The new (as of SQL Server 7.0) cached execution plan optimization in SQL Server looks to me a lot like JIT compilation. If this is, in fact, the case it seems to me that the only overhead that would be associated with dynamic SQL would be:
I can imagine quite a few scenarios where the above overhead would disappear into the noise of the network roundtrip. What upsets me are the folks who spout forth anecdotal arguments that claim stored procedures have "much better" performance than dynamic SQL.
- The amount of bandwidth + time it takes to transmit the dynamic SQL text to the database.
- The amount of time it takes to calculate the hash of the dynamic SQL text to look up the cached execution plan.
For modern databases and real world usage scenarios, I believe a Stored Procedure architecture has serious downsides and little practical benefit. Stored Procedures should be considered database assembly language: for use in only the most performance critical situations. There are plenty of ways to design a solid, high performing data access layer without resorting to Stored Procedures; you'll realize a lot of benefits if you stick with parameterized SQL and a single coherent development environment.
* "the man".
October 27, 2004
The Antidote to ASP.NET Smart Navigation
One of the issues I have with ASP.NET is that it is postback crazy. Virtually nothing of significance can be done in pure browser client code with ASP.NET out of the box*. You have to Submit() the specially formed ASP.NET HTML form to the server, and all the event magic happens server side.
While this is nice from an abstraction standpoint, it's kind of a pain for the client. Having tons of postbacks in the middle of the form causes "flicker" and loss of page scroll position. It can also be a serious performance issue if you have tons of viewstate that gets submitted to the server over and over. I am definitely not a fan of doing a lot of stuff in JavaScript and DOM on the browser-- been there, done that-- but we almost have the opposite extreme in ASP.NET.
One of the tricks MS introduced to combat the loss of page scroll position on postback is something called SmartNavigation. Unfortunately, it's horribly broken. A friend recently referred me to an article by Brad McCabe which outlines the most elegant workaround I've seen so far-- it lets you retain page position between postbacks in a very generic way. And not a single named anchor in sight!
Better workarounds are on the horizon with ASP.NET 2.0. I hear we'll be able to use XMLHTTP requests to do "background" postbacks in some scenarios. We've gone this route before, too. It works, but it's still painful, mostly because the browser is a hideous development platform. If ASP.NET 2.0 can abstract away the pain, and retain browser compatibility, then I'm for it.
* Now, there are some third party ASP.NET server controls which expose a lot of exotic client side behaviors. But none of the default controls do.
October 26, 2004
Creating Even More Exceptional Exceptions
In response to a previous post decrying the lack of a master list of Exception classes for .NET, a helpful reader pointed out a clever little utility buried in the .NET SDK:
Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\Bin\wincv.exe
Wincv works well, but it doesn't allow me to sort or even copy out the class names. After reflecting on this a bit, I generated the following console app:
Sub Main()
ReflectionSearch(".*exception$")
Console.ReadLine()
End Sub
Sub ReflectionSearch(ByVal strPattern As String)
Dim a As Reflection.Assembly
Dim m As Reflection.Module
Dim t As Type
Dim al As New ArrayList
Dim sl As New SortedList
Dim strAssemblyName As String
For Each strAssemblyName In DefaultAssemblyList()
a = Reflection.Assembly.Load(strAssemblyName)
For Each m In a.GetModules
For Each t In m.GetTypes
al.Add(t)
Dim strFullName As String = t.FullName
If Regex.IsMatch(strFullName, strPattern, RegexOptions.IgnoreCase) Then
sl.Add(strFullName, Nothing)
End If
Next
Next
Next
Dim de As DictionaryEntry
For Each de In sl
Console.WriteLine(de.Key)
Next
Console.WriteLine(sl.Count.ToString & " matches for " & strPattern)
End Sub
Function DefaultAssemblyList() as ArrayList
Dim al As New ArrayList
With al
.Add("mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
.Add("System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
.Add("System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
.Add("System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
.Add("System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
.Add("System.Xml, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
.Add("System.Design, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.DirectoryServices, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.Drawing.Design, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.Messaging, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.Runtime.Serialization.Formatters.Soap, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.Security, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.ServiceProcess, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.Web, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.Web.Services, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
.Add("System.Management, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
End With
Return al
End Function
This console app produces the following (mostly) sorted list of 141 classes in the framework that end in Exception. Now there's really no reason to throw generic System.Exception, because one of these likely to be a better match-- or, failing that, a custom Exception. Thanks for the tips, guys.
p.s. I really am lazy.
System.AppDomainUnloadedException System.ApplicationException System.ArgumentException System.ArgumentNullException System.ArgumentOutOfRangeException System.ArithmeticException System.ArrayTypeMismatchException System.BadImageFormatException System.CannotUnloadAppDomainException System.ComponentModel.Design.CheckoutException System.ComponentModel.Design.Serialization.CodeDomSerializerException System.ComponentModel.InvalidEnumArgumentException System.ComponentModel.LicenseException System.ComponentModel.WarningException System.ComponentModel.Win32Exception System.Configuration.ConfigurationException System.ContextMarshalException System.Data.ConstraintException System.Data.DataException System.Data.DBConcurrencyException System.Data.DeletedRowInaccessibleException System.Data.DuplicateNameException System.Data.EvaluateException System.Data.ExprException System.Data.InRowChangingEventException System.Data.InvalidConstraintException System.Data.InvalidExpressionException System.Data.MissingPrimaryKeyException System.Data.NoNullAllowedException System.Data.Odbc.OdbcException System.Data.OleDb.OleDbException System.Data.ReadOnlyException System.Data.RowNotInTableException System.Data.SqlClient._ValueException System.Data.SqlClient.SqlException System.Data.SqlTypes.SqlNullValueException System.Data.SqlTypes.SqlTruncateException System.Data.SqlTypes.SqlTypeException System.Data.StrongTypingException System.Data.SyntaxErrorException System.Data.TypedDataSetGeneratorException System.Data.VersionNotFoundException System.DivideByZeroException System.DllNotFoundException System.Drawing.Printing.InvalidPrinterException System.DuplicateWaitObjectException System.EntryPointNotFoundException System.Exception System.ExecutionEngineException System.FieldAccessException System.FormatException System.IndexOutOfRangeException System.InvalidCastException System.InvalidOperationException System.InvalidProgramException System.IO.DirectoryNotFoundException System.IO.EndOfStreamException System.IO.FileLoadException System.IO.FileNotFoundException System.IO.InternalBufferOverflowException System.IO.IOException System.IO.IsolatedStorage.IsolatedStorageException System.IO.PathTooLongException System.Management.ManagementException System.MemberAccessException System.Messaging.MessageQueueException System.MethodAccessException System.MissingFieldException System.MissingMemberException System.MissingMethodException System.MulticastNotSupportedException System.Net.CookieException System.Net.ProtocolViolationException System.Net.Sockets.SocketException System.Net.WebException System.NotFiniteNumberException System.NotImplementedException System.NotSupportedException System.NullReferenceException System.ObjectDisposedException System.OutOfMemoryException System.OverflowException System.PlatformNotSupportedException System.RankException System.Reflection.AmbiguousMatchException System.Reflection.CustomAttributeFormatException System.Reflection.InvalidFilterCriteriaException System.Reflection.ReflectionTypeLoadException System.Reflection.TargetException System.Reflection.TargetInvocationException System.Reflection.TargetParameterCountException System.Resources.MissingManifestResourceException System.Runtime.InteropServices.COMException System.Runtime.InteropServices.ExternalException System.Runtime.InteropServices.InvalidComObjectException System.Runtime.InteropServices.InvalidOleVariantTypeException System.Runtime.InteropServices.MarshalDirectiveException System.Runtime.InteropServices.SafeArrayRankMismatchException System.Runtime.InteropServices.SafeArrayTypeMismatchException System.Runtime.InteropServices.SEHException System.Runtime.Remoting.MetadataServices.SUDSGeneratorException System.Runtime.Remoting.MetadataServices.SUDSParserException System.Runtime.Remoting.RemotingException System.Runtime.Remoting.RemotingTimeoutException System.Runtime.Remoting.ServerException System.Runtime.Serialization.SerializationException System.Security.Cryptography.CryptographicException System.Security.Cryptography.CryptographicUnexpectedOperationException System.Security.Policy.PolicyException System.Security.SecurityException System.Security.VerificationException System.Security.XmlSyntaxException System.ServiceProcess.TimeoutException System.StackOverflowException System.SystemException System.Threading.SynchronizationLockException System.Threading.ThreadAbortException System.Threading.ThreadInterruptedException System.Threading.ThreadStateException System.Threading.ThreadStopException System.TypeInitializationException System.TypeLoadException System.TypeUnloadedException System.UnauthorizedAccessException System.UriFormatException System.Web.HttpApplication+CancelModuleException System.Web.HttpCompileException System.Web.HttpException System.Web.HttpParseException System.Web.HttpRequestValidationException System.Web.HttpUnhandledException System.Web.Services.Discovery.InvalidContentTypeException System.Web.Services.Discovery.InvalidDocumentContentsException System.Web.Services.Protocols.SoapException System.Web.Services.Protocols.SoapHeaderException System.Windows.Forms.AxHost+InvalidActiveXStateException System.Xml.Schema.XmlSchemaException System.Xml.XmlException System.Xml.XPath.XPathException System.Xml.Xsl.XsltCompileException System.Xml.Xsl.XsltException
October 25, 2004
Teaching Users to Read
I've talked about irresponsible use of dialog boxes before, but a few pages I've read recently highlighted an interesting aspect of this topic that I hadn't considered. First, Joel Spolsky:
This may sound a little harsh, but you'll see, when you do usability tests, that there are quite a few users who simply do not read words that you put on the screen. If you pop up an error box of any sort, they simply will not read it. This may be disconcerting to you as a programmer, because you imagine yourself as conducting a dialog with the user. Hey, user! You can't open that file, we don't support that file format! Still, experience shows that the more words you put on that dialog box, the fewer people will actually read it.
And Mike dug up a fascinating Eric Lippert comment on the same topic:
It's not that users are morons or that they "forget" to think. It's that users are trained to not think. Users very quickly learn from experience that:
- Dialog boxes are modal. But users do not think of them as "modal", they think of them as "preventing me from getting any work done until I get rid of them."
- Dialog boxes almost always go away when you click the leftmost or rightmost button.
- Dialog boxes usually say "If you want to tech the tech, you need to tech the tech with the teching tech tech. Tech the tech? Yes / No"
- If you press one of those buttons, something happens. If you press the other one, nothing happens. Very few users want nothing to happen -- in the majority of cases, whatever happens is what the user wanted to happen. Only in rare cases does something bad happen.
In short, from a user perspective, dialog boxes are impediments to productivity which provide no information. It's like giving shocks or food pellets to monkeys when they press buttons -- primates very quickly learn what gives them the good stuff and avoids the bad.
Well, I couldn't help thinking of this classic Gary Larson strip:
Well designed software avoids asking the user questions by...
- Anticipating user needs (wizards, templates, autocomplete, IUI)
- Remembering past preferences and using that to better anticipate future needs
- Silently and automatically protecting the user from the consequences of any negative actions (versioning, undo)
It's amazing how few software packages even try to meet these goals, even with simple, common things. For example, why does the file save dialog always default to My Documents even though I saved to Desktop the last time I used the application?
October 24, 2004
Creating More Exceptional Exceptions
I find myself throwing plain old System.Exception far too often. If only I had a complete reference of the many default Exception classes Microsoft provides, like the one Chris Sully provides in his article. That's good as a starting point, but I don't see things like System.Data.DataException in there. Does anyone know of a more comprehensive list of *Exception classes for all the common .NET namespaces?
While searching for this, I also found some interesting commentary on System.ApplicationException. I always wondered what the heck that was for, and a linked Microsoft page confirms my suspicions:
Designing exception hierarchies is tricky. Well-designed exception hierarchies are wide, not very deep, and contain only those exceptions for which there is a programmatic scenario for catching. We added ApplicationException thinking it would add value by grouping exceptions declared outside of the .NET Framework, but there is no scenario for catching ApplicationException and it only adds unnecessary depth to the hierarchy. You should not define new exception classes derived from ApplicationException; use Exception instead. In addition, you should not write code that catches ApplicationException.
Well, so much for that.
There's also some discussion about the merits of error codes vs. exceptions. Opinions vary, but the determining factor seems to be performance. The first entry in MSDN's Performance Tips and Tricks in .NET Applications talks about exceptions:
Throwing exceptions can be very expensive, so make sure that you don't throw a lot of them. Use Perfmon to see how many exceptions your application is throwing. It may surprise you to find that certain areas of your application throw more exceptions than you expected. For better granularity, you can also check the exception number programmatically by using Performance Counters.At some point in the development of your project, I suggest you turn on "Break on all exceptions" using the VS.NET Exceptions menu. This will expose any loops where you are catching thrown exceptions. That's how I found out we are using a a third party tree control which throws an exception on every row paint!Finding and designing away exception-heavy code can result in a decent perf win. Bear in mind that this has nothing to do with try/catch blocks: you only incur the cost when the actual exception is thrown. You can use as many try/catch blocks as you want. Using exceptions gratuitously is where you lose performance. For example, you should stay away from things like using exceptions for control flow.
That's a big deal, because throwing an exception is literally slower than making a database call. This really surprised me, because a DB query is incredibly slow. But it's true:
Yes, that's right - the decimal point is in the right place for function #2! The code path through the exception throwing route took almost 3 orders of magnitude longer than the raw code. This is why, for this article, I'm just not interested in minor optimisations of the source code since the impact of exceptions dwarfs them.
This sounds really bad, but in practice, it shouldn't matter. If you are using exceptions properly, they should rarely be occurring and therefore any performance cost is moot. Eric Gunnerson puts it best:
So, if you are not a programming God like those OS developers, you should consider using exceptions for your application errors. They are more powerful, more expressive, and less prone to abuse than error codes. They are one of the fundamental ways that we make managed programming more productive and less error prone. In fact, the CLR internally uses exceptions even in the unmanaged portions of the engine. However, there is a serious long term performance problem with exceptions and this must be factored into your decision.
October 22, 2004
We're Building the Space Shuttle
Today's dose of YAGNI comes from a recent Anders Hejlsberg interview:
If you ask beginning programmers to write a calendar control, they often think to themselves, "Oh, I'm going to write the world's best calendar control! It's going to be polymorphic with respect to the kind of calendar. It will have displayers, and mungers, and this, that, and the other." They need to ship a calendar application in two months. They put all this infrastructure into place in the control, and then spend two days writing a crappy calendar application on top of it. They'll think, "In the next version of the application, I'm going to do so much more."Once they start thinking about how they're actually going to implement all of these other concretizations of their abstract design, however, it turns out that their design is completely wrong. And now they've painted themself into a corner, and they have to throw the whole thing out. I have seen that over and over. I'm a strong believer in being minimalistic. Unless you actually are going to solve the general problem, don't try and put in place a framework for solving a specific one, because you don't know what that framework should look like.
I've run into so many software developers who delude themselves into believing they're building the space shuttle-- instead of the crappy little business apps they're actually building.
One sure way to make your crappy little business app even crappier is to build it like you're building the space shuttle. I know, because I spend far too much time refactoring these spectacularly failed space shuttle missions into something resembling a supportable business application.
October 21, 2004
KISS and YAGNI
Microsoft performance guy Rico touches on a topic near and dear to my heart
I hardly think that one can make any conclusions about which vendor has the edge in performance from my article on Performance Tidbits. If I was to summarize my advice in that blog in a few words it would be "don't use OOP features that you don't need."Don't use fancy OOP features just because you can. Use fancy OOP features because they have specific, demonstrable benefit to the problem you're trying to solve. You laugh, but like Rico, I see this all the time. Most programmers never met an object they didn't like. I think it should be the other way around: these techniques are guilty until proven innocent in the court of KISS.This is not to say that you should shun virtual functions, inheritance, or other features of modern programming languages. Far from it, often they not only add clarity and maintainability they also improve performance. But, as often, I find that people have written their code in some elaborate way when a much simpler model would have been equally servicable and more performant. Whatever programming religion you may have I think you'll agree that more complex language abstractions do not inherently help your design -- rather each more sophisticated feature starts at a net negative and must somehow earn its way to positiveness with benefits such as clarity, ease of maintenance, performance, and so forth.
So when I say things like "don't use a delegates if regular polymorphism would do" I don't mean that you should avoid delegates I mean that you should not use them if they are overkill.
As developers, I think we also tend to be far too optimistic in assessing the generality of our own solutions, and thus we end up building elaborate OOP frameworks around things that may not justify that level of complexity. To combat this urge, I suggest following the YAGNI (You Aren't Gonna Need It) doctrine. Build what you need as you need it, aggressively refactoring as you go along; don't spend a lot of time planning for grandiose, unknown future scenarios. Good software can evolve into what it will ultimately become.
October 20, 2004
10 Foot Interface Showdown
Courtesy of MSDN Universal, I downloaded and installed Windows Media Center Edition 2005 on my home theater PC yesterday. It looks like the original reports were correct-- MCE 2005 is more widely available at retail, too. Here are the relevant product links at newegg:
- Windows XP Media Center Edition 2005 $140
- Remote Control for Microsoft Windows XP Media Center with Receiver- OEM $40
- Hauppauge PCI Video Recorder, TV/FM Tuner Card, Model "WinTV-PVR250MCE" -RETAIL $130
Yes, you do need a hardware MPEG-2 encoding card. Even the fastest 3.4ghz+ CPUs struggle with realtime video encoding-- at least, if you want to have any hope of multitasking without dropping massive numbers of frames in your recordings. I know the Hauppauge is a little expensive, but I've used the cheaper brands (I'm looking at you, Avermedia) and it isn't worth it. Hauppauge has the most extensive third party support, so it'll be useful outside MCE and have a longer life. Hauppauge is also introducing a new dual-tuner card if you absolutely, positively must record two shows at the same time-- or watch live TV while recording another show.
MCE 2005 is the mythical third version of a Microsoft product, so at least in theory, this is as good as it's going to get for a while. The improvements over 2004 are mostly incremental, but solid. Here are a few I consider significant:
- Built in video and audio CD/DVD burning
- Vastly improved movie browsing (automatic internet ratings, images, etc)
- Multiple tuner support
- Easily add network shares as music / video sources
MCE's 10 Foot User Interface got a touch-up, too, but it's still disappointing compared to the Tivo. I have my Tivo Series 2 directly under the MCE, and we use both systems on a daily basis, so a direct comparison is inevitable. Let me illustrate with two glaring examples.
My biggest complaint is MCE's inexplicable use of telephone keypad entry for all alphanumeric fields:
This is hideously painful to use. What number do I press? What character will I get? Ugh. Compare with the Tivo alternative:
Cursoring to the letter you want is incredibly intuitive. Obvious even. Everything that numeric keypad entry is not. Why couldn't Microsoft copy an interface that actually works? Any time you're cribbing UI from a telephone, you are in deep, deep trouble.
The other criticism I have is that Microsoft forces me to use an additional Back button on my remote. This is particularly onerous coming from the elegance and simplicity of Tivo's design:
In the Tivo UI, cursor left takes you back a page, and cursor right takes you forward into your selection. There are screens that violate this rule (eg, the search page, above) but it is quite intuitive. You start to associate the left side of the screen with "backwards", and the right side with "forward", rather like a Wizard interface, or a VCR. The only side effect-- and I think it's a positive one-- is that most Tivo UI screens are a simple vertically scrolling list of selections.
I don't mean to be overly critical of MCE. It's a fantastic product. It's just hard to stomach these obvious lost opportunities for copying UI elements that are so stunningly effective on the Tivo. Why reinvent a square wheel?
