Thursday, September 29, 2005

Omaha Ruby User's Group

It's time to hold another great meeting of the Omaha Ruby User's Group. We'll be discussing Rails, Ruby, dynamic languages, and anything else we can think of. So bring your favorite pieces of Ruby code and your curiousity. Hope to see a lot of people there! Make sure you sign up on the mailing list. Here's the information of the when and where:

    When:October 3, 2005
    Where:Panera @ Eagle Run Shopping Center
    13410 West Maple Road
    Omaha, NE 68164

Wednesday, September 28, 2005

What He Said

I couldn't have said it better myself.
From Vincent Foley:
I compared dynamic languages and Java with riding a bike; in dynamic languages, you ride on two wheels and you get where you want pretty quickly. You can fall if the road is slippery, if you hit a rock or something, but with a little experience, these cases rarely occur. On the other hand, riding a Java bike is like having 8 wheels to make sure you don’t fall over, 10 people constantly around you, ready to catch you should you fall. You may be safer, but you’re not getting places faster than the dynamic biker.

Right on! Can I get an amen?

Monday, September 26, 2005

How not to make money

On the first visit to your site, I get this:
Mod_python error: "PythonHandler mod_python.servlet"

Traceback (most recent call last):

File "/usr/local/lib/python2.4/site-packages/mod_python/", line 299, in HandlerDispatch
result = object(req)

File "/usr/local/lib/python2.4/site-packages/mod_python/", line 1464, in handler

File "/home/hosting/users/metald/web/index.mps", line 23, in prep

File "/home/hosting/users/metald/lib/", line 42, in prep

File "/usr/local/lib/python2.4/site-packages/mod_python/", line 593, in prep

File "/usr/local/lib/python2.4/site-packages/mod_python/", line 389, in Session
timeout=timeout, lock=lock)

File "/usr/local/lib/python2.4/site-packages/mod_python/", line 294, in __init__
timeout=timeout, lock=lock)

File "/usr/local/lib/python2.4/site-packages/mod_python/", line 132, in __init__
Cookie.add_cookie(self._req, self.make_cookie())

File "/usr/local/lib/python2.4/site-packages/mod_python/", line 160, in make_cookie
c.path = dirpath[len(docroot):]

TypeError: unsubscriptable object

Ouch...Talk about bad first impressions. This is what happened when I tried to go to Metal Direct. I guess they don't like customers.

Sunday, September 25, 2005

Private: The Enemy of Extensibility

I was shocked to find out tonight that junit.runner.ClassPathTestCollector didn't account for tests in jars. OK, easy enough. I look through the class and find a methood called gatherFiles(File,String,result). It takes a file and determines if it is a test class. Well, a simple check to see if the file is a jar, add the class names of the entries which are tests to the result, and we were in business, right? Well, that's what I thought! So, I overrode gatherFiles, added super to let it do its job, and then, added my jar functionality. Simple right?! It didn't compile. It seems gatherFiles is a private method and I didn't have any visibility for the super. OK, I then just copied the original gatherFiles in place of the super and a little gentle refactoring. Everything compiled, but it worked like the old one. It then struck me. You can't override private methods. GRRRRRR! So, I wound up copying the remaining methods that I needed and removed ClassPathTestCollector as my superclass. It shocked me to know what should have been a simple refactoring, turned into a lot more, and it forced me to commit the worst of all sins: Duplicate code. Oh, did you want to see the jar code? Here you go:

protected void gatherFilesInJar(File jarFile, Hashtable result) {
try {
JarFile jar = new JarFile(jarFile);
try {
Enumeration enumeration = jar.entries();
while (enumeration.hasMoreElements()) {
JarEntry next = (JarEntry) enumeration.nextElement();
if (next.getName().endsWith(".class") && next.getName().indexOf('$') == -1) {
addToResultsIfTest(next.getName(), result);
} finally {
} catch (IOException ex) {

Nothing to it! Anyway, it was a fun bit of functionality to add, so that I can find and add all of my tests in my project now. I ran into these issues with private in Swing as well. I just think as designers we should not close our classes. The reason is because you never know what someone might need to do with your code eventually. Sealing means you know best. Just like I wanted to be able to handle jars in ClassPathTestCollector. If the original designer had thought of that, then he/she would have added that capability. Instead, they sealed off the class and forced me to sin. Don't make the same mistake.

Tuesday, September 20, 2005

Private Testing

David Buck and Charles Monteiro have been discussing how to make sure "private" methods in Smalltalk stay "private". Both have great techniques and it of course got me to thinking: "What a great use for unit tests!"

In all Smalltalks, we categorize methods of a class. Why not mark the private methods in some way? For example, use "private" somewhere in the category name (private, private-processing, private-accessing, etc). This way we know what should be private and what shouldn't be. It doesn't matter the convention just pick one. Now, that we know the private methods, create an object proxy that forwards all calls to the real object. This proxy would check if the method coming in is "private" and signal an exception if a method is "private" since only outside objects will call the proxy directly (Everything else will behind the proxy wall). Now, just pass the proxy around in the unit test as if it were the real object. Let the unit tests verify that we are not calling "private" methods and we have no run-time costs. Of course, we could get fancy and use method wrappers and #become:, but I will leave that as an exercise to the reader (or maybe a future blog entry?).

The main point of this post was to give yet another idea of how to make sure private methods stay private. I would love to hear more techniques. This technique that I just described is what I call meta-unit-tests. We are verifying meta-information about our program. I've used meta-unit-tests for making sure deprecated API calls are not sent in my code and that certain methods are implemented (ie don't have super that calls #shouldBeImplemented). We have a lot of information available to us at unit test time. We can take advantage of it!

Re: Dolphin's Command Framework

I got a comment from Anand that suggested that I change this code:
CommandMessageSend>>queryCommand: query 
| canSelector |
canSelector := ('can' , query command selector capitalized) asSymbol.
^(self receiver respondsTo: canSelector)
isEnabled: (self receiver perform: canSelector);
receiver: self receiver.
ifFalse: [super queryCommand: query]

CommandMessageSend>>queryCommand: query
| canSelector isEnabled |
canSelector := ('can' , query command selector capitalized) asSymbol.
[isEnabled := self receiver perform: canSelector]
on: MessageNotUnderstood
do: [:ex | ^super queryCommand: query].
isEnabled: isEnabled;
receiver: self receiver.

We got rid of the ifTrue:ifFalse: and the code is clearer. But, something bothered me about it. The MessageNotUnderstood could catch any MessageNotUnderstood further down in the stack (it might not be my canCommandSelector). What is a Smalltalker to do? How about this:
Object>>perform: aSelector onNotUnderstoodDo: aBlock
^[self perform: aSelector]
on: MessageNotUnderstood
do: [:ex | (ex receiver == self and: [ex selector == aSelector])
ifTrue: [aBlock value]
ifFalse: [ex pass]]

This message allows us to do the perform, but check to make sure it's the one we care about. Now, our CommandMessageSend code looks like this:
CommandMessageSend>>queryCommand: query
| canSelector isEnabled |
canSelector := ('can' , query command selector capitalized) asSymbol.
isEnabled := self receiver perform: canSelector onNotUnderstoodDo: [^super queryCommand: query].
isEnabled: isEnabled;
receiver: self receiver.

The code is about the same, but now we are only catching the MessageNotUnderstood exception that we care about and not any others. The code is still clean and we see another example of the power of Smalltalk exceptions. And I would like to thank Anand for the suggestion!

Monday, September 19, 2005

Oh Lucky Day

I pre-ordered Coheed and Cambria's "Good Apollo, I'm Burning Star IV" and it arrived a day early this morning. I immediately popped it into the player and was just floored. Great rock music from start to finish. I hear bits of The Cars, Led Zeppelin, Iron Maiden, and a whole bunch more. This is the way power pop is supposed to be. Oh yeah, they get the prog punk tag, but this is just wonderfully written music period. I haven't been this excited by a new band in a long time. I love the way they write incredibly catchy music that you never grow tired of hearing.

And then the mail man arrived with two more packages. I received Clutch's "Robot Hive/Exodus" and 3's "Wake Pig". Oh My God! I've never been blessed with this much good music in one day ever! Clutch is a mix of funk and stoner rock that pleases and 3 is like a blender of styles that just simply works. I guess progressive pop punk would be a tag, but you just have to listen to it.

All 3 albums are incredible slab of originality. Did I mention that they were great. Man, my ears are worn out! If anyone thinks that good rock music died in the 90's or you've been having a hard time looking for original great music. Look no further than these bands!

Great Advice

I have to apologize, but I have to pimp "Streamlined Object Modeling" yet again.
Personify Objects
Object model a domain by imagining its entities as active, knowing objects, capable of performing complex actions.
Talk Like an Object
To scope an object's responsibilities, imagine yourself as the object, and adopt the first-person voice when discussing it.
Do It Myself
Objects that are acted upon by others in the real world do the work themselves in the object world.

Live it, be it, model it! Let the design flow!

Smalltalk Unreadable? Huh?

Spotted this quote in JavaLobby via James Robertson:
Have you ever tried to go back an make significant changes to a large Smalltalk application that you haven't touched the code for in 3-5 years or maybe didn't even a part in writing? Try that some time if you haven't and then tell us what you think of Smalltalk. If a language can't pass that battle test, it sucks. IMO, Java passes that test very well, much better than Smalltalk or C/C++.

OK, I've been there and done that with both Java (revisited old code from 3 years ago) and Smalltalk (revisited old code from 5 years) code bases. Now, both of the code bases were written by me and I didn't have a problem getting back into the code with either one. If you have well-defined names and intention revealing code, then it's NEVER a problem.

But, to get up to speed on existing code that I didn't write is another issue. I find Smalltalk to be easier than any language I've ever dealt with. It generally takes me no time to get up to speed with unknown Smalltalk code. Hell, I've been having a blast going through all of the Dolphin code recently. But, I've had my fair share of problems understanding Java code written by other programmers. And most of the reasons have nothing to do with language. It's mostly a matter of writing intention revealing code. Something that most good coders do unconsciously. I've seen my share of bad code in Perl, C, C++, Java, etc. But, I will say this, I find bad code in Smalltalk is much easier to read and understand. Since there's less rules, there's less to get messed up.

Anyway, it's amazing me that we are still having these discussions. Pick the language you love and write code. Stop whining about everyone else's. Each language has its strengths and weaknesses. I choose Smalltalk because it makes me more productive. If Java or Perl makes you more productive, then go write code in it! I do try to sell Smalltalk, but I don't believe in doing it at the expense of putting down another. I think Ruby, Smalltalk, Lisp, etc attacts a different kind of programmer than Java. And that's cool. There's nothing wrong with it. Now, let the Smalltalk flow!

Sunday, September 18, 2005

Dolphin's Command Framework

When I first started to play with Dolphin's MVP framework for building GUIs, I thought it was interesting. It forced you to keep all of the controller code in the presenter. The view was only responsible for view things, the presenter was the glue, and the model was where all of the magic occured. But, there was one thing that bothered me and that's how you used the command framework. From the examples, you override the #queryCommand: method in your Shell. Well, you ended up with code like this which I absolutely hate (BTW, this is taken from my first MVP application):
queryCommand: aCommandQuery

| command |
command:=aCommandQuery command.
(#(File Agents Help about) includes: command)
ifTrue: [^aCommandQuery beEnabled].
command == #saveAgentTape
ifTrue: [^aCommandQuery isEnabled: self tape name isEmpty not].
command == #recordPressed
ifTrue: [^aCommandQuery isEnabled: self tape canRecord].
command == #stopPressed
ifTrue: [^aCommandQuery isEnabled: self tape canStop].
command == #playPressed
ifTrue: [^aCommandQuery isEnabled: self tape canPlay].
command == #pausePressed
ifTrue: [^aCommandQuery isEnabled: self tape canPause].
command == #generateJavaScript
ifTrue: [^aCommandQuery isEnabled: self tape canConvertToScript].
^super queryCommand: aCommandQuery.

YUCK! If you go searching through the code, you find CommandPolicy, Command, CommandQuery, etc and I thought to myself, "Why did they create so many objects if the code turns out like the above?" The answer is the framework is smart and has a lot of parts to it. So, I browsed the implementers of #queryCommand: and I found an interesting class that implemented it: AbstractMessageSend! Well, the interesting thing is that normally you add commands as symbols to the CommandDescription for things like Buttons and MenuItems. But, you don't have to. You can put any object that understands #forwardTo:! And if you add #queryCommand: to your object to use as your command, then you can allow the command to decide when to enable/disable itself! So, I whipped up a little CommandMessageSend that disables/enables itself by calling a canCommandName method if it exists to know when to disable/enable the command in the menu.

MessageSend subclass: #CommandMessageSend
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''

CommandMessageSend>>queryCommand: query
| canSelector |
canSelector := ('can' , query command selector capitalized) asSymbol.
^(self receiver respondsTo: canSelector)
isEnabled: (self receiver perform: canSelector);
receiver: self receiver.
ifFalse: [super queryCommand: query]


Now, instead of adding a symbol, I add this object. I now no longer override the #queryCommand: method in my Shell class! And the code from above no longer exists! No more case like if statements! It turns out that the Dolphin guys came up with really nice Command framework that works its way from the Command object itself and works it's way asking at each level of Presenters until it reaches the Shell. I can understand doing the simplest possible thing from the examples, but it doesn't show the power that they have given you. There's also a undo/redo framework for your code to take advantage of. Just browse references to the Command class. Fire up those browsers and start looking at that code! Dolphin has a lot of cool frameworks inside (look at DeferredValue for more fun).

Wednesday, September 14, 2005

I Got The Power

I've been spending an hour here and there on a mp3 file management system for myself in between on working on some of my other projects. It's basically so I can move files from my local file system, iRiver, and iShuffle. I've been writing it in Dolphin and having a great time. It's meant to be simple and I add features as I find things annoying. So, a production system it is not. Well, I decided to add drag/drop capability so I could do all my file operations within it. I've done drag/drop in several languages and environments in the past and I must admit Dolphin was the easiest. I looked at a few examples from the development environment and I had it all working within a couple of hours. WOW. I didn't even read any of the documentation. Most of my time was actually spent in the domain getting everything working just right. Most drag/drop frameworks I've used in the past are just a pain in the butt. I was simply amazed. Yet, another example of how nice Dolphin really is.

Ruby On Rails

Word on the street is that Sam Tesla gave a tasty sermon on Ruby On Rails at the Omaha Smalltalk User's Group last night. I couldn't make it because of work committments so I guess he's going to have to do it over at the next Ruby User's Group....=) He made a pdf available, so there's no reason not to enjoy its brilliance. Why do I always miss the good stuff?

Monday, September 12, 2005

Squeak Foundation

Towards an acting and communicating core for Squeak. I think this is a wonderful new development in the Squeak community. It's been a long time coming. I love the following quote too:
We will follow the chicken and pig Scrum metaphor: Doers or Core will have the power to do something and talkers will be less considered, even if we will listen to them.

It seems process is getting in place to fund and direct energy in the Squeak community. I think this is exciting! I would like to thank everyone involved in this. Let the Squeak flow!

Saturday, September 10, 2005

Omaha Smalltalk User's Meeting

This month we will be doing a two fisted meeting! First up, we will show the brilliant Avi Bryant Seaside demo from the Vancouver Lisp Meeting. Then, Sam Tesla has been kind enough to show us Ruby and Ruby On Rails. So, it's going to be a meeting not to be missed!

Here's all of the details:

When: September 13, 2005, 7pm - 9pm
Where: Offices of Northern Natural Gas
1111 S 103rd Street
Omaha Nebraska 68154

Office is at 103rd & Pacific. Guests can park in the Northern visitors parking area back of building, or across the street at the mall. Enter in front door, we'll greet you at the door at 7:00pm. If you arrive a bit later, just tell the guard at the reception desk you're here for the Smalltalk user meeting in the 1st floor training room.

SortedCollection, Block, and Polymorphism

I was having a discussion with a friend of mine tonight and they were having problems with serializing an object. After a few questions, I learned that they had a SortedCollection with a custom block. He was using Dolphin and I mentioned that there were other avenues to take. One such avenue is that he could use another object in place of the block as long at it understood #value:value: and returned a boolean. This is a common oversight because it's hard breaking "type thinking" and to step into "protocol thinking". The type of an object doesn't matter, it's the protocol it understands. Rubyists call this "duck typing". Call it what you want, it's powerful. As a side note, this is why interfaces make programming in Java nicer. So, I showed a quick example, say we take the following code:
| collection |
collection := Array
with: 'second' -> 'turbine'
with: 'third' -> 'earth'
with: 'fourth' -> 'good apollo'.
collection asSortedCollection: [:a :b | a key <= b key]

And it returns:

a SortedCollection(
'fourth' -> 'good apollo'
'second' -> 'turbine'
'third' -> 'earth')

In Dolphin, I can use Message instead of block since it understands the same protcol:
collection asSortedCollection: (Message selector: #key)

Or even write a method to do the comparison:
compare: anObjectA to: anObjectB
anObjectA key <= anObjectB key

collection asSortedCollection: (MessageSend receiver: someObject selector: #compare:to:)

Or we could implement #value:value: on an object and pass that in as the block into SortedCollection. Just remember, to use the polymorphism, luke. We can use these tricks anywhere we use a block (Depending on the number of arguments needed, you might been #value:, #value:value:value:, or #valueWithArguments:). And all of the above sample solutions are serializable...=)

It's great to work in a language that allows you the freedom to be creative and extend it in ways the creators never thought of (or perhaps they did). Let the Smalltalk FLOW!

Domain Specific Languages

I still see old world flat file specifications like the following:

9(8)Some DateDDMMYYYY
9(6)Some TimeHHMMSS
x(20)Lovely NameA Good Name

I thought I would do something different implement the parser as if I was a Lisper. So, here's what I came up with:

(9(8) someDate asDDMMYYYY)
(9(6) someTime asHHMMSS)
(x(20) lovelyName)
(x36) filler)

And what does the code look like to parse it? It's even simpler than I thought:

parse: aStream
self recordLayout do: [:eachLayout | | typeInfo typeLength fieldName converterSelector rawData data |
typeInfo := eachLayout first.
typeLength := eachLayout second first.
fieldName := eachLayout third.
converterSelector := eachLayout at: 4 ifAbsent: [#yourself].
rawData := aStream next: typeLength.
(typeInfo == 9) ifTrue: [self verifyIsNumeric: rawData].
data := rawData perform: converterSelector.
self perform: (self setterFor: fieldName) with: data]

WOW! I now have a method that shows the record layout that could be parsed for creating test records as well. I could even show it to a non-Smalltalker and they would know what's going on. I love the flexibility of Smalltalk! I love adding new tricks to my bag.

XML Generation In Smalltalk

Tim Jones showed a simple way to generate XML in Smalltalk. And then Michael Lucas-Smith showed an even simpler way which is very close to what Seaside does for HTML generation (using #doesNotUnderstand:). In fact, I wrote one for Squeak a long time ago for myself that does exactly what Michael and Tim did (my DNU code calls the Tim-like code and compiles it on the fly so to make debugging easy and future hits quick). Anyway, I found that I needed something different for attributes so I created the ability to churn out XML from arrays. Here's some code from one on my tests:
xml := BtbXmlRenderer on: writeStream.
xml render: #(first arg1: 'value1' arg2: 'value2'
(second (third '"fun"'))
(fifth #sixth: 56)).

And this generates the following XML:
<?xml version="1.0"?>
<first arg1="value1" arg2="value2">
<fifth sixth="56"/>

I made all objects implement #renderXMLOn: so that I can use Arrays, Blocks, or anything else to make it easier to output XML in the future. It also means I can use all three approaches to generate one XML document. I think this is a perfect example of polymorphism and the power of dynamic languages at its finest. So, we have three ways of generating XML and I found that each has its advantages at different times. My point to this post was to show yet another way to generate XML using Smalltalk. Keep the Smalltalk flowing!

Monday, September 05, 2005


Apparently, Avi Bryant made an interesting challenge to create a dynamic FTP server. I love the idea of using existing protocols in creative ways. What about a dynamic CSS or Subversion server? Or even automatically save code to a version control system based on submissions to the FTP server and tags things on a per session basis. I'm excited to hear more as this develops. Can you imagine using existing tools for your front-end? Then, people can use the tools that they are most comfortable with.

Good OO Thoughts

From Steve Dekorte's blog:
The amount to which the program is object oriented has an inverse relation to:

1. the number of arguments passed in message calls
2. The number of instance variables in the objects

I tend to use "7 +/- 2" rule. If I have 5 or less instance variables in my object, then everything is cool. I'm even OK with 7, but 9 is on the cusp of too many. The reason why is because too many instance variables is not only a code smell, but your object is getting away from having one responsbility and needs to be broken up.

Arguments are little bit more restrictive. I try to keep them below 5. Otherwise, you're writing big methods and well, your methods should do one thing and nothing more.

Of course, the rule above is excellent, as is the "DRY, Shy, and Tell The Other Guy" rule from the Pragmatic Programmers. They are just some things that I strive for when programming.

Sunday, September 04, 2005

Rule #67 in Design

NEVER clean up resources you did not create

I hate to pick on Java, but I found a great example of this in the Java API of all places in the javax.swing.text.html.parser.Parser class. I removed a lot of the code from the method to concentrate on the violation:

* Parse an HTML stream, given a DTD.
public synchronized void parse(Reader in) throws IOException {
try {
} finally {

Basically, you pass in a Reader object that obviously you have to CREATE. And what do they for you? They free your resource that you passed in. Heaven forbid, you would like to do something else with it. Besides, why would you do that? Well, that's not the point. In design, I NEVER clean up a resource I did not allocate. If it is passed into me, I do what I need to do with it and that's all. On the other hand, if I allocate it, I take pains to make sure I deallocate it. This is what I did in my C/C++ days and it was a great rule of thumb that prevented a lot of problems.

Friday, September 02, 2005

Is That All?

Taken from the comp.lang.smalltalk.dolphin:

> Besides the IdeaSpace, what else is new?

Well, off the top of my head, and in no particular order:
1) New look tools with sliding panes (imaginatively called Slidey Inny
Outey Things, SIOTs) :-)
2) New look Resource Browser with iconic categories and preview pane. nloads/6.0/images/d6rb.png
3) New look View Composer with sliding resource toolbox and Inspector
panes.  The View Composer now supports undo. nloads/6.0/images/d6vc.png
4) Improved garbage collector giving approximately 20% speed
improvement overall.
5) Compiler now supports true block closures and not just a
Smalltalk-80 block semantics.
6) All code editing is now done using the Scintilla code editor.  This
means we get away from the vagaries of the Windows Rich Text Control
and also we now have auto completion (IntelliSense). nloads/6.0/images/d6ac.png
7) New "literal filer" for storing views which opens up the resources
such that their contents can be refactored etc.  Storing resources in
this way also greatly simplifies the image stripping process at
8) New Sockets2 package that uses overlapped/blocking calls rather than
asynchronous messages.  The old package is provided for backwards
9) The Refactoring Browser rewrite tool has been included as a plug-in
in the class and system browsers. nloads/6.0/images/d6rwt.png
10) The class and system browsers now have a Code Mentor plug-in that
comments on the style of your code.  It runs the analysis in the
background as you work and is based on the SmallLint facility that
comes with the refactoring engine. nloads/6.0/images/d6cm.png
11) Views now support background colour inheritance.
12) New TabViewXP view that supports XP-style tab views in all
orientations (unlike the standard Windows control).
13) Dynamic syntax highlighting in all workspaces as you type.
14) Workspaces highlight compilation errors and warnings with squiggly
underlinesand will display help for these when the mouse hovers over
15) Various new view controls such as: SysLink, LinkButton,
MonthCalendarView, SpinButton.
16) Windows XP application manifest is now included as part of the
version resource in deployed executables.
17) Application deployment now creates a complete XML based contents
list of the entire application.  This can be browsed from within
Dolphin using a class browser to see exactly what methods and classes
are present in the application. nloads/6.0/images/d6cm.png
18) New Method Explorer which replaces the old Method Browser to
provide a hierarchical trail of browse requests.  The aim, like with
the IdeaSpace, is to reduce the number of open windows while working
with Dolphin. nloads/6.0/images/d6mex.png
There are dozens of other minor enhancements and bug fixes and probably
some other fairly major ones that I've forgotten but the above should
give you a flavour of what is coming in Dolphin 6.
Best regards,
Andy Bower
Dolphin Support

WOW! That sure is a lot of stuff to pack in for a release. I'm beyond excited. I'm so lusting for Dolphin 6.0. I feel like a kitten that can see the cream, but can't lap it up. Dolphin on!

Thursday, September 01, 2005

What kind of technology do you want to create?

From Donald Norman's "Things That Make Us Smart":
When I speak of the power and virtues of technology, I am referring to soft technology: technology that is flexible, that is under our control. Hard technology remains unheedful of the real needs and desires of the users. It is a technology that, rather than conforming to our needs, forces us to conform to its needs. Hard technology make us subservient; soft technology puts us in charge. Automating tends to be a hard use of technology. Informating tends to be soft.

So, when you are creating your next program or framework, think to yourself, "How would I want to be treated?" I bet you would prefer soft technology over hard. Let's make the user experience better all around. I promise to take the above quote to heart in my future endeavors. Take the oath and let's start delighting our users together.