I <3 Steve McConnell*
Coding Horror
programming and human factors
by Jeff Atwood


26 posts from October 2004

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.

Posted by Jeff Atwood    3 Comments

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:

screenshot of novalogic product update

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:

screenshot of MySQL browser error

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.

Posted by Jeff Atwood    0 Comments

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:

  1. 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.
  2. 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.
  3. 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?
  4. 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.
  5. 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.
So why use Stored Procedures at all? Conventional wisdom says we do it because:
  • 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:

  1. The amount of bandwidth + time it takes to transmit the dynamic SQL text to the database.
  2. The amount of time it takes to calculate the hash of the dynamic SQL text to look up the cached execution plan.
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.

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".

Posted by Jeff Atwood    130 Comments

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.

Posted by Jeff Atwood    2 Comments

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

Posted by Jeff Atwood    12 Comments

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:

What Dogs Hear

My intent is not to make fun of users, but to illustrate that there are far more effective ways to communicate with your dog. Essentially, any time you're asking the user to make a choice they don't care about, you have failed the user. Well designed software takes care of "teching the tech tech" all by itself, and leaves the user free to worry about things relevant to the work they are doing. Dialog boxes are almost always detrimental. The goal should be to write an entire application free of dialog boxes.

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?

Posted by Jeff Atwood    17 Comments

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.

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.

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!

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.

Posted by Jeff Atwood    12 Comments

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.

Posted by Jeff Atwood    10 Comments

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."

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.

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.

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.

Posted by Jeff Atwood    11 Comments

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:

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:

Microsoft Media Center Edition UI

This is hideously painful to use. What number do I press? What character will I get? Ugh. Compare with the Tivo alternative:

Tivo UI

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:

Tivo UI

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?

Posted by Jeff Atwood    5 Comments
Read older entries »
Content (c) 2009 Jeff Atwood. Logo image used with permission of the author. (c) 1993 Steven C. McConnell. All Rights Reserved.