September 29, 2004
Don't Devalue the Address Bar
I was reading an interesting entry in Rocky Lhotka's blog when something in the url caught my eye:
http://www.lhotka.net/WeBlog/PermaLink.aspx?guid=b28971dc-ac4b-4494-8a21-7a5105a39b07I guess it's a DasBlog thing, but good lord: a globally unique ID in a blog hyperlink? Has it really come to this?
Dim g As Guid = Guid.NewGuid
Console.WriteLine(g.ToString)
2ac6857d-6fa4-43f6-9145-dfffaf00fd7a
This is my GUID. There are many like it but this one is mine. My GUID is my best friend. It is my life. I must master it as I must master my life. Without me, my GUID is useless. Without my GUID I am useless.
It's bad enough that so many websites devalue the address bar with nonsensical geek-speak URLs...
- http://www.bestbuy.com/site/olspage.jsp?id=cat03037&type=category&navLevel=4&navHistory=cat00000%2Bcat03000%2Bcat03030
- http://www.amazon.com/exec/obidos/tg/detail/-/0821277545/ref=pd_nfy_b_nr/104-4632089-1596752?v=glance&s=books&n=283155
- http://listings.ebay.com/_W0QQa14Z1752QQalistZa14QQgcsZ1135QQpfidZ1413QQsocmdZListingItemList
- http://www.cnn.com/2004/WEATHER/09/11/hurricane.ivan/index.html
- http://www.subversivecrossstitch.com/lifesucks.html
- http://www.techreport.com/etc/2004q3/3dmark05/index.x?pg=1*
Building crappy URLs is just plain laziness. With the rich set of tools provided by IIS and ASP.NET, we should be leveraging 404 handlers and URL Rewriting to build URLs that are simple for people to understand, instead of taking the low road and building URLs that are easy for machines to understand.
My favorite technique is to map the 404 handler in IIS, per website, to /404.aspx. I can then intercept page not found errors with ASP.NET code and Server.Transfer them as I see fit. If you want a more robust solution at the ISAPI level, we've also used ISAPI Rewrite with great success.
* They should simplify this even further by making the default folder handler "index.x", then just.. http://www.techreport.com/etc/2004q3/3dmark05/?pg=1
September 28, 2004
Why Your Code Sucks... and Mine Doesn't
OK, the title is just Why Your Code Sucks, but you know you were thinking it. The article may not be as gramatically (sp) correct as I would like, but it's got some solid advice. My favorite is rejection of dogma:
Your code sucks if it dogmatically conforms to a trendy framework at the cost of following good design and implementation practices.For example, Bob Martin recently raised the issue of dogmatically using private fields and getters/setters for a simple data structure (e.g. a DTO). If a field is transparently readable and writable why not simply make the field public? In most languages you can do that. Granted, in some you can't. For example, traditionally in Smalltalk all fields are private and all methods are public.
In general it's a good thing whenever you can throw out, or avoid writing, some code. Using a heavy framework generally requires that you must write a significant amount of code that has no business value. There are a variety of lightweight frameworks for Java that are a response to the heavyweight frameworks (e.g. EJB) that have become matters of dogma lately. O'Reilly has a new book out on this topic, coauthored by Bruce Tate.
When making framework decisions, consider if a lighter framework will do the required job. Using something like Hibernate, Prevayler, Spring, PicoContainer, NakedObjects, etc. can be a real win in many situations. Never blindly adopt a heavy framework just because it's the current bandwagon. Likewise, don't blindly adopt a lightweight framework in defiance. Always give due consideration to your choices.
Of course, the real problem with software development is the users. It's unbelievable. They've caused problems with every program I've ever written.
September 27, 2004
The Rise and Fall of Homo Logicus
Of all the professional hubris I've observed in software developers, perhaps the greatest sin of all is that we consider ourselves typical users. We use the computer obsessively, we know a lot about how it works, we even give advice to friends and relatives. We are experts. Who could possibly design software better than us superusers? What most developers don't realize is how freakishly outside the norm we are. We're not even remotely average-- we are the edge conditions. I've often told program managers: if you are letting me design your software, your project is in trouble.
In The Inmates Are Running the Aslum, Alan Cooper labels this phenomenon Homo Logicus:
Homo logicus desires to have control over things that interest them, and the things that interest them are complex, deterministic systems. People are complex, but they don't behave in a logical and predictable way, like machinery. The best machinery is digital, because it can be the most complex, sophisticated, and easily changed by the programmer.The price of control is always more effort and increased complexity. Most people are willing to make a moderate effort, but what differentiates programmers from most people is their willingness and ability to master extreme complexity. It is a satisfying part of the programmer's job to know and manage systems composed of many interacting forces. Flying airplanes is the archetypal programmer's avocation. The cockpit control panel of an airplane is packed with gauges, knobs, and levers, but programmers thrive on those daunting complexities. Homo logicus finds it fun and engaging, despite (because of!) the months of rigorous study required. Homo sapiens would rather ride along as passengers.
For Homo logicus, control is their goal and complexity is the price they will pay for it. For normal humans, simplicity is their goal, and relinquishing control is the price they will pay. In software-based products, control translates into features. For example, in Windows 95, the "Find File" function gives me lots of control over the procedure. I can specify which area of my disk to search, the type of file to search for, whether to search by file name or by file contents, and several other parameters. From a programmer's point of view, this is very cool. For some extra up-front effort and understanding, he gets to make the search faster and more efficient. Conversely, the user's point of view is less rosy because he has to specify the area of the search, the type of file to search for, and whether to search by name or contents. Homo sapiens would gladly sacrifice the odd extra minute of compute time if they didn't have to know how the search function works. To them, each search parameter is just another opportunity to enter something incorrectly. The probability of making a mistake and the search function failing is higher, not lower, with the added flexibility. They would gladly sacrifice all that unnecessary complexity, control, and understanding in order to make their job simpler.
Homo logicus are driven by an irresistible desire to understand how things work. By contrast, Homo sapiens have a strong desire for success. While programmers also want to succeed, they will frequently accept failure as the price to pay for understanding. There's an old joke about engineers that gives some insight into this need to understand.
Three people are scheduled for execution: a priest, an attorney, and an engineer. First, the priest steps up to the gallows. The executioner pulls the lever to drop the hatch, but nothing happens. The priest claims divine intervention and demands his release, so he is set free. Next, the attorney takes a stand at the gallows. The executioner pulls the lever, but again nothing happens. The attorney claims another attempt would be double jeopardy and demands release, so he is set free. Finally, the engineer steps up to the gallows, and begins a careful examination of the scaffold. Before the executioner can pull the lever, he looks up and declares, "Aha, here's your problem."
Cooper goes on to list a few more traits of Homo Logicus:
- trades simplicity for control
- exchanges success for understanding
- focuses on what is possible to the exclusion of what is probable
- acts like a jock
Anybody can build a complex application that nobody can figure out how to use. That's easy. Building an application that's simple to use.. well, now that takes actual skill. I'm not sure you need high priced interaction designers to achieve this goal, but you do have to stop thinking like Homo Logicus-- and start thinking like Homo Sapiens.
September 22, 2004
Weeding out the Weak Developers with J2EE
I got into an interesting discussion today about that recently published Comparing Microsoft .NET and IBM WebSphere/J2EE report. If you haven't read it, there's a summary at eWeek, but I definitely recommend downloading the full report for the details. If you're too busy to do either of those things, well, I'll just tell you: in this particular study, VS.NET is about twice as productive as either of the two IBM J2EE environments, at approximately one-tenth the cost. It's also slightly faster and performs more reliably, but after the key productivity and cost results, those things are just icing on the cake.
This is significant enough that I felt like sharing it with a few fellow developers and some management. Predictably, I got some pushback from some of the Java oriented developers: "Well, what do you expect from a Microsoft commissioned report." I don't consider that a valid criticism. Microsoft didn't conduct the study, they just commissioned it. And the report has this disclaimer on the third page:
1.4 Does a "sponsored study" always produce results favorable to the sponsor?No.
Our arrangement with sponsors is that we will write only what we believe, and only what we can stand behind, but we allow them the option to prevent us from publishing the study if they feel it would be harmful publicity. We refuse to be influenced by the sponsor in the writing of this report. Sponsorship fees are not contingent upon the results. We make these constraints clear to sponsors up front and urge them to consider the constraints carefully before they commission us to perform a study.
Disregarding the report sight unseen because Microsoft sponsored it is like disregarding someone's opinion based on their ethnicity:
Of course we expect that from Bob, he's an Eskimo. And everyone knows Eskimos are liars!
If you want valid criticisms, you have to disagree with the actual substance of the report. So after actually reading it (one would hope) this is the criticism I got back from a fellow developer:
Visual Studio, like Visual Basic and other Microsoft development tools and languages, provide ease of use and a low learning curve at a price: They don't impose any kind of discipline or framework, making it too easy to crank out poorly designed apps and horrid code. This is not helped by all the amateur Visual Basic/Studio "developers" out there who have no understanding of basic comp sci concepts. (Luckily, we don't hire those kind of developers, but I'm sure we've all worked with many of them in the past.)Let me get this straight. The difficulty of developing applications in J2EE is, paradoxically, a good thing? Oh yes! J2EE weeds out the weak developers! It rampages through the land, leaving a wake of crushed and broken developers weeping in its path! You want the productivity? You can't handle the productivity!On the other hand, J2EE by its nature imposes some rigid rules and forces one to use some kind of a framework to deliver an enterprise level app. This takes more time, planning, and skill. As a result, though, J2EE apps, by comparison, tend to be more robust, maintainable, and scalable. This is not to say that .NET apps cannot have those qualities as well -- it just takes a lot more discipline and some self-imposed rules to achieve this -- Visual Studio doesn't give you that out of the box.
Obviously, I think this is a complete load of crap. Consider all the projects you've worked on in your career as a software developer. At any point in any of those projects, can you ever remember saying to yourself:
Developing applications is far too easy! If only my development could be made more difficult and challenging!
Dan Appleman also refutes this ridiculous argument in one of his blog posts:
The reason that so much bad VB6 code was written was not because VB6 was RAD, but because it was easy. In fact, VB6 made writing software so easy that anyone could be a programmer, and so everyone was. Doctors, Lawyers, Bankers, Hobbyists, Kids – everyone was writing VB6 code with little or no training.Now, I don't know about you, but I still have copies of a few of the programs I wrote when I was just starting out, before I'd actually gone to school to learn a thing or two about software development. There was some BASIC, some Pascal, and looking at it now, it's all pretty ugly.
So let's get real. Bad programmers write bad code. Good programmers write good code. RAD lets bad programmers write bad code faster. RAD does NOT cause good programmers to suddenly start writing bad code.
RAD tools can make a good programmer more productive, because they speed up the coding process without compromising the level of quality that a good programmer is going to achieve.
What I find amusing is that someone would actually try to invert this argument, proposing that bad tools are good because they force you to produce better code. And if they don't, well-- that's because you aren't smart enough to use them correctly, stupid!
September 21, 2004
POPFile vs. POPFile
In my previous blog entry on some plan(s) for spam, I mentioned that I didn't care for challenge/response "human-only" whitelists. I couldn't put my finger on exactly why I felt that way.. until I happened upon this John Graham-Cumming PowerPoint presentation:
I don't "do" Challenge/Response. If I mail you and you challenge me I hit delete, because, as Dan Quinlan put it: "Challenge/Response is the ultimate email diss. By using it you are saying, 'my time is more important than yours.'"That about sums it up for me.
John Graham-Cumming is the author of POPFile, so naturally his presentation goes on to.. describe ways to defeat POPFile? It's actually titled How to beat an Adaptive Spam Filter. A fascinating read, with a disturbing conclusion: when pitting "evil" POPFile against good POPFile, the good guys lose. In other words, spammers can use bayesian filters to defeat bayesian filters-- if they get feedback about what mails are getting through!
This makes me very, very happy that Windows XP Service Pack 2 turned off HTML rendering in Outlook Express by default:
Pictures and images embedded in HTML e-mail messages can be adapted to secretly send a message back to the sender. These are often referred to as Web beacons. Spammers rely on information returned by these images to confirm active e-mail addresses. Some spam messages contain Web beacon images so small that they are invisible to the human eye -- but not to Outlook Express.Putting images in HTML seems innocent enough, but retrieving any image results in a direct request from your computer to the spammer's webserver. With this tiny bit of feedback, they could concievably defeat any anti-spam technology. Scary stuff!An improved defense against Web beacons is to stop pictures from downloading until you've had a chance to review the message. Outlook Express in Windows XP SP2 will now block images automatically in messages from people who are not in your address book. This goes a long way in preventing the verification of your e-mail address for spammers. It makes your e-mail name less useful to spammers and may result in your getting less spam over time.
Turn your application Inside-Out
How I wish developers would spend more time obsessing over user interface patterns instead of design patterns. To that end, here's another great UI Patterns and Techniques site to bookmark alongside Welie's GUI Design patterns.
This link arrives courtesy of Andy's unicode obsessed weblog. It's rare to find a Java developer who is enthused about GUI development; rarer still is the Java developer who defends smart client development in the face of "browser is good enough" criticisms from the likes of Joel Spolsky, Tim Bray, and John Gruber:
Gmail's threading and searching are indeed nice, but its overall look-and-feel is far inferior to that of a real desktop mail client. What it has going for it is what all webmail apps have -- zero installation, zero maintenance, access from any computer, anywhere (including from work, a major factor for personal email). Gmail is simply better than the other major web-based mail apps; but Yahoo and Hotmail and the others are still ragingly popular.What these guys fail to grasp is that once the .NET runtime becomes ubiquitous-- and it's only a matter of time until that happens-- that's exactly how you install a smart client app! You simply type the URL, and the app launches. No dependencies, no installation, and most importantly, none of the tedious glass ceiling restrictions the browser places on development. All the assemblies are automatically downloaded via HTTP, and all communication between the app and the server occurs via HTTP. When is a browser not a browser? When it's a smart client application.What I missed when I dismissed them a decade ago is that web apps don't need to beat desktop apps on the same terms. What's happened is that they're beating them on an entirely different set of terms. It's all about the fact that you just type the URL and there's your email.
It's a have your cake (rich UI) and eat it too (trivial deployment) solution-- as long as you're running Windows. The reality is, though, that most organizations don't give a damn about OS independence; what they do care about is no-hassle, zero footprint deployment of applications that make the user's life easier. Now that the dot-com bubble euphoria has completely worn off, we can be more objective about how crappy the browser's UI is for a lot of user tasks. Let's turn our applications inside-out: steal the UI metaphors you like from the web browser and harness it as an embedded windows forms control. Don't let the browser's limitations determine what you can do.
September 20, 2004
Why Objects Suck, Revisited
I recently blogged about how pure object oriented programming is oversold. Well, evidently Paul Graham agrees with me:
Object-oriented programming generates a lot of what looks like work. Back in the days of fanfold, there was a type of programmer who would only put five or ten lines of code on a page, preceded by twenty lines of elaborately formatted comments. Object-oriented programming is like crack for these people: it lets you incorporate all this scaffolding right into your source code. Something that a Lisp hacker might handle by pushing a symbol onto a list becomes a whole file of classes and methods. So it is a good tool if you want to convince yourself, or someone else, that you are doing a lot of work.I've found that a little object orientation goes a long way. Pushing too far into "everything must be an object" territory leads to, well, exactly what Paul describes above-- giant masses of repetitive code that someone is going to have to maintain. I like to err on the side of simplicity, and that typically means the approach that produces the least volume of source code.
September 19, 2004
Some Plan(s) for Spam
After struggling with spam e-mail for years the old fashioned way-- highlight, DEL-- I finally succumbed and installed POPFile on my server. POPFile uses a Bayesian Filter technique and it is amazingly effective. Within a day I had 95% accuracy; within a week I had 97% accuracy. Two months later, I'm up to nearly 99% accuracy:
It's interesting that bayesian filtering is so effective, yet most people never heard of it until mid 2002. Spam has been around seemingly forever; why wasn't this technique adopted sooner? I did some digging and came up with Paul Graham's A Plan For Spam. Paul is an interesting guy with a LISP background, and although he probably wasn't the first person to think of using Bayesian techniques to fight spam, he was definitely the first person to stump for a workable algorithm:
I don't know why I avoided trying the statistical approach for so long. I think it was because I got addicted to trying to identify spam features myself, as if I were playing some kind of competitive game with the spammers. (Nonhackers don't often realize this, but most hackers are very competitive.) When I did try statistical analysis, I found immediately that it was much cleverer than I had been. It discovered, of course, that terms like "virtumundo" and "teens" were good indicators of spam. But it also discovered that "per" and "FL" and "ff0000" are good indicators of spam. In fact, "ff0000" (html for bright red) turns out to be as good an indicator of spam as any pornographic term.I know what you're thinking now: say I'm a spammer. How would I beat a Bayesian Filter? Well, it's possible, but it's hard:But the real advantage of the Bayesian approach, of course, is that you know what you're measuring. Feature-recognizing filters like SpamAssassin assign a spam "score" to email. The Bayesian approach assigns an actual probability. The problem with a "score" is that no one knows what it means. The user doesn't know what it means, but worse still, neither does the developer of the filter. How many points should an email get for having the word "sex" in it? A probability can of course be mistaken, but there is little ambiguity about what it means, or how evidence should be combined to calculate it. Based on my corpus, "sex" indicates a .97 probability of the containing email being a spam, whereas "sexy" indicates .99 probability. And Bayes' Rule, equally unambiguous, says that an email containing both words would, in the (unlikely) absence of any other evidence, have a 99.97% chance of being a spam.
Because it is measuring probabilities, the Bayesian approach considers all the evidence in the email, both good and bad. Words that occur disproportionately rarely in spam (like "though" or "tonight" or "apparently") contribute as much to decreasing the probability as bad words like "unsubscribe" and "opt-in" do to increasing it. So an otherwise innocent email that happens to include the word "sex" is not going to get tagged as spam.
Assuming they could solve the problem of the headers, the spam of the future will probably look something like this:Digging through today's email for examples-- what about messages with no text, only HTML images?
Hey there. Thought you should check out the following:
http://www.27meg.com/foobecause that is about as much sales pitch as content-based filtering will leave the spammer room to make. (Indeed, it will be hard even to get this past filters, because if everything else in the email is neutral, the spam probability will hinge on the url, and it will take some effort to make that look neutral.)
Spammers range from businesses running so-called opt-in lists who don't even try to conceal their identities, to guys who hijack mail servers to send out spams promoting porn sites. If we use filtering to whittle their options down to mails like the one above, that should pretty much put the spammers on the "legitimate" end of the spectrum out of business; they feel obliged by various state laws to include boilerplate about why their spam is not spam, and how to cancel your "subscription," and that kind of text is easy to recognize.
Received: from host-122-195.firstpointsecure.com ([69.42.122.195]) by server.mydomain.com with Microsoft SMTPSVC(6.0.3790.0); Sun, 19 Sep 2004 14:32:43 -0400 From: "Good News" <rodfournier@moquije.remarkablenews.com> To: me <me@mydomain.com> Subject: Single? Date: Sun, 19 Sep 2004 11:32:56 -0800 MIME-Version: 1.0 Content-type: text/html; charset="ISO-8859-1" Content-transfer-encoding: 7bit Message-Id: <0771687B7E76766B477E707A6C346C697C7A70756C7A7A356A7674$4df803ge2@moquije.remarkablenews.com> Return-Path: rodfournier@moquije.remarkablenews.com <html> </head> <body> <p align="center"><a href="http://quugot.deliveredsavings.com/date3/?i=iog0771687b7e76766b4v&vj=jzv77e707a6c346c697c7ig&n=ksia70756c7a7a356a7674k&pq=vtyk&winner&_m01"> <img border="0" src="http://quugot.deliveredsavings.com/date3/at.gif" width="383" height="210"></a><br> <br> <br> <br> <br> </p> <p align="center"> <a href="http://quugot.deliveredsavings.com/date3/rd.cgi?i=iog0771687b7e76766b4v&vj=jzv77e707a6c346c697c7ig&n=ksia70756c7a7a356a7674k&pq=vtyk&winner&_m01"> <img border="0" src="http://quugot.deliveredsavings.com/date3/5.gif" width="502" height="59"></a></p> <p align="center"></p> <img src="http://quugot.deliveredsavings.com/date3/logogen.img?i=iog0771687b7e76766b4v&vj=jzv77e707a6c346c697c7ig&n=ksia70756c7a7a356a7674k&pq=vtyk" border=0> </body> </html>
Or messages with non-spam spoof text?
<font size="2" face="Verdana">Stop this <a href="http://www.muss4267pinn.com/a.ddd">please</a>!</font><br> <br> christy passport nocturnal director cargoes corrigendum sicklewort doria polaroid <br> <br>
Interestingly, POPFile has no problem at all correctly categorizing these messages as spam. That's the value of parsing the headers and the HTML, something early researchers failed to do. Graham cites this as the primary reason why Bayesian filtering wasn't used prior to 2002. They didn't think it was effective enough!
98.6 percent accuracy is good, one of the best available, but it's not 100 percent. Can we do better with other spam fighting techniques? I agree with Graham's position that blacklists are both a bad idea and a losing battle, so I won't even go there. Whitelists, on the other hand, are more interesting. Take a service like SpamArrest, for example. This works like so:
- Joe sends you an email.
- An auto-generated response is sent to Joe, explaining that you are fighting spam, and that his email won't be delivered until he visits the provided URL.
- Joe clicks the URL, enters the CAPTCHA value, and clicks OK.
- Joe's email address is now verified human, and his email is delivered to you.
- All further emails from Joe will arrive without this additional step.
Some people love whitelists. I'm not a fan. Putting the burden of verification on the sender seems kind of onerous to me. Even though it's a one time thing, it is an additional hurdle for every person that wants to communicate with me. On the other hand, this type of anti-machine whitelist is a reasonable approach to an intractable problem. Bayes will always let some spam slip through, so it is arguably the only way to get an ironclad "100 percent" effective spam blocking.
September 18, 2004
The Jack Principles
As a student of UI design, I was always intrigued by the user interface used in You Don't Know Jack. If you're not familiar with the game, it's a demented in-your-face quiz show game. The first version was released circa 1995, and at the time, I don't think I had ever experienced anything quite like it on the PC. If you haven't played a version of You Don't Know Jack, do yourself a favor and download the demo to see what I'm talking about. Plus, it's fun.
Evidently the guys at Jellyvision think they're come up with a unique UI design, too. On Jellyvision's website, you can download a copy of The Jack Principles which describes the iCi-- the Interactive Conversation Interface:
Shared control also manifests itself in the way the program limits the options it gives you. A television program gives you no options at all. The Web and multimedia programs usually allow you to go anywhere at any time you want. An iCi program falls between these extremes. It will only allow you to do a relatively small number of things at any one time (like responding to a single question). What the program allows you to do at any moment is up to the designers of the program, not you. Reciprocally, as you can see from the example above, how you respond to the program will then influence what other things the character in the program asks you to do and possibly the order in which he asks you.If this reminds you of the well known "wizard" metaphor, it should. In both cases the user is being guided through a specific series of steps, some of which he or she can influence. This is also the central metaphor used in Microsoft's Inductive User Interface, something I think will figure heavily in Longhorn.So, you are not without influence over what you will experience, although you cannot completely decide what you will experience.
This models the dynamic of talking to a human being. In a conversation, you can't unilaterally decide what gets discussed. The other person is not a machine. He can place his own limits on the conversation. He can steer the conversation in one direction, just as much as you can. The control of the conversation is shared.
For iCi, the sharing happens between the creative design team and the individual user. The design team arranges for all the possible experiences. The individual's actions determine which experience actually transpires.
September 16, 2004
The Delusion of Reuse
I'm currently reading Facts and Fallacies of Software Engineering by Robert Glass. It's definitely a worthwhile book, although I do have two criticisms:
- Someone really, really needs to buy Robert Glass a copy of Strunk and White's Elements of Style. Or at least get him a decent editor. There's some great information here, but his overly florid writing style gets in the way.
- Quite a few of the 55 facts and fallacies presented here will be old news to anyone familiar with the most popular books on the reading list. For example, "Adding people to a late project makes it later." That's well understood, and Glass doesn't cover enough new ground with these chestnuts to justify the two or three pages each one adds to the book.
There are two "Rules of Three" in reuse: (a) It is three times as difficult to build reusable components as single use components, and (b) a reusable component should be tried out in three different applications before it will be sufficiently general to accept into a reuse library.
I have found this to be universally true in the projects I've worked on. If anything, I think this rule underestimates the cost: I believe writing a truly reusable class is an order of magnitude harder than writing a single use class. Sometimes the right thing to do is resist the urge to write "general purpose" solutions. Sure, it's better in the long run to have a widget library you can use forever, but that doesn't get your current project done any faster. Furthermore, how confident are you that the so-called "general purpose" solution you built will actually work for these.. unknown future projects? Have you tried it?
You can't know if you have a strong case for reuse unless you've tried-- and possibly failed-- to use that same bit of code on at least three different projects.
Until you've invested the additional effort to implement that "reusable" code with different developers and different problem domains, all you have is the delusion of reuse. Be careful, because I've seen too many developers fall into this trap. Myself included.
