Back when I made $20,000 a year I bought a used Honda Civic. I needed something to get me from point A to point B fairly reliably. I was looking for the best deal given my requirements and I found a Civic that seemed to fit the bill. I had a good advantage as a buyer, I knew what I wanted and I knew approximately what it should cost. The car lasted for several years, and I was always happy with the purchase.
The story could be much different. If I knew less about what I was buying I would have had to rely on someone else. If I ended up relying on someone unqualified I might have ended up with a Cadillac. Cadillacs are fine cars, but on a $20,000 annual salary I did not need the beauty and luxury features of a Cadillac.
The analogy probably breaks down quickly, but the same basic idea applies to business software. Time after time I see developers delivering Cadillacs when Civics would have been acceptable. The business has no idea what the difference in cost is between a Civic and a Cadillac so, as any ignorant customer does, they smile, thank you, and walk away worrying that they got screwed.
Imagine walking 5 miles to work every day while someone is building you a car. You want a Civic, which you think can be built in 2 weeks. Instead, you get a Cadillac in 4 weeks -- at a much higher cost. You'd be upset, and yet this happens in software all the time. Even worse, you might get a Civic in 4 weeks, but it comes with assurances that if you ever want to change the engine or make it a convertible it can be done in 15 minutes. You'd be asking yourself: when did I ever say I wanted a new engine or a convertible? You had to walk 10 miles a day for 2 extra weeks to get your "ultra configurable" Civic.
It's time to fire your mechanic.
This happens a lot with hackers, which is why businesses should employ developers, not hackers. But, this behavior isn't exclusive to hackers. I've seen plenty of developers who prefer to deliver Cadillacs. I know I always appreciate Cadillac software solutions, but I always find myself asking if the business knows the cost of the Cadillac.
The solution is simple: increase collaboration and change the definition of success. Developers should constantly be talking to the business, but they also need to think in terms of business value instead of solution beauty. Successfully delivered applications can be used by the business to make or save money. The amount of money made or saved is often determined by how much collaboration occurred between the developers and the business. High collaboration ensures the business gets the application they expect. The collaboration also ensures the developers understand their impact to the business. If they make or save the company money with their applications, they've succeeded.
Making or saving money, that's why a business employes you -- and that's how you earn your salary.
Thursday, July 31, 2008
Wednesday, July 30, 2008
Refactoring: Ruby Edition available on Amazon
It's been about 18 months since my first entry about Refactoring: Ruby Edition. It's been a long journey, but the book should be seeing day light in the next few months.
It's available for pre-order on Amazon now, and it should also be available on Safari in the near future.
Thanks to everyone who helped, and I hope the book is as fun to read as it was to write.
It's available for pre-order on Amazon now, and it should also be available on Safari in the near future.
Thanks to everyone who helped, and I hope the book is as fun to read as it was to write.
Tuesday, July 29, 2008
Developers needed; Hackers need not apply
Two developers interview for the same position. The position is software development for an internet advertising agency. The agency needs a developer to help create internal and external websites.
When the interview ends the candidates are given a simple assignment: the business needs to convert a csv file from one format to another. The candidates are given 24 hours to complete the task.
The first developer goes home and designs an amazing yet simple site where multiple files can be uploaded simultaneously and users can choose to be notified via SMS or email when the task is completed. It's great software.
The second developer receives the assignment and spends the next 30 minutes talking to the business to see how the software will be used and what value it provides. After he's gotten the information he needs he says thanks, but instead of letting him leave, the CTO offers him the job on the spot.
The first developer gets the "thanks, but no thanks" call the following day.
What the first developer didn't understand was that the assignment was designed to evaluate collaboration, not coding ability. Both candidates had previously submitted code samples and discussed their samples as part of the interview. Both developers could code equally well, but only candidate 2 had collaboration skills. Candidate 2 was clearly a better hire.
Developers love to talk about how software is about people. I know it's about people, you know it's about people, yet when I look around I rarely see developers interacting with people. It doesn't seem to make sense, and yet it happens the majority of the time. We're stuck in the ways of software development past.
Hackers put out code, often good code. That's great. Without hackers I wouldn't have messaging systems, proxies, web servers, etc. At least I wouldn't have such a good selection. But, being a good hacker doesn't make you valuable to a business.
Time after time I see requirements gathered and presented in a way that is totally disconnected from the business problem that's being addressed. There are two ways to handle the situation.
But, a developer can deliver what the business is looking for the first time. A(n often) quick conversation with the business ensures that the developer knows what to work on and how it will benefit the business. This dialog often leads to a superior solution implementation where the business gets what it wants and the developer is able to do it in the most efficient way (e.g. I don't need a beautiful website, I need a command line application).
A hacker can deliver the same value to the business (on the 2nd or 3rd try), but the time difference between when the developer delivers and when the hacker delivers is the cost of employing hackers. Hackers create waste (lost time), which the business must pay for. Good developers avoid waste by ensuring that the business gets what they are looking for as quickly as possible.
This idea isn't mine, I just see the application of the idea constantly so I felt the need to write about it. Kent Beck has been talking about it for years, most recently at QCon in London earlier this year. Look for the video when it becomes available at InfoQ -- it's relevant for anyone developing software for a business, and Kent puts it much better than I do. When Kent talks about it you think "well, yes, obviously", but look around work and see if Kent's ideas are being applied. If they are, consider yourself lucky to be working with good colleagues.
When the interview ends the candidates are given a simple assignment: the business needs to convert a csv file from one format to another. The candidates are given 24 hours to complete the task.
The first developer goes home and designs an amazing yet simple site where multiple files can be uploaded simultaneously and users can choose to be notified via SMS or email when the task is completed. It's great software.
The second developer receives the assignment and spends the next 30 minutes talking to the business to see how the software will be used and what value it provides. After he's gotten the information he needs he says thanks, but instead of letting him leave, the CTO offers him the job on the spot.
The first developer gets the "thanks, but no thanks" call the following day.
What the first developer didn't understand was that the assignment was designed to evaluate collaboration, not coding ability. Both candidates had previously submitted code samples and discussed their samples as part of the interview. Both developers could code equally well, but only candidate 2 had collaboration skills. Candidate 2 was clearly a better hire.
Developers love to talk about how software is about people. I know it's about people, you know it's about people, yet when I look around I rarely see developers interacting with people. It doesn't seem to make sense, and yet it happens the majority of the time. We're stuck in the ways of software development past.
Hackers put out code, often good code. That's great. Without hackers I wouldn't have messaging systems, proxies, web servers, etc. At least I wouldn't have such a good selection. But, being a good hacker doesn't make you valuable to a business.
Time after time I see requirements gathered and presented in a way that is totally disconnected from the business problem that's being addressed. There are two ways to handle the situation.
- Write the best code you can.
- Talk to the business.
But, a developer can deliver what the business is looking for the first time. A(n often) quick conversation with the business ensures that the developer knows what to work on and how it will benefit the business. This dialog often leads to a superior solution implementation where the business gets what it wants and the developer is able to do it in the most efficient way (e.g. I don't need a beautiful website, I need a command line application).
A hacker can deliver the same value to the business (on the 2nd or 3rd try), but the time difference between when the developer delivers and when the hacker delivers is the cost of employing hackers. Hackers create waste (lost time), which the business must pay for. Good developers avoid waste by ensuring that the business gets what they are looking for as quickly as possible.
This idea isn't mine, I just see the application of the idea constantly so I felt the need to write about it. Kent Beck has been talking about it for years, most recently at QCon in London earlier this year. Look for the video when it becomes available at InfoQ -- it's relevant for anyone developing software for a business, and Kent puts it much better than I do. When Kent talks about it you think "well, yes, obviously", but look around work and see if Kent's ideas are being applied. If they are, consider yourself lucky to be working with good colleagues.
Friday, July 25, 2008
StackOverflow.com Podcast: Subscribe for Education, Stay for Entertainment
I read Joel. Sure, I disagree with a lot that he says, but he also says some very thought provoking things. So, when he posted that he was going to start doing a podcast I was interested.
I listen to the StackOverflow podcast while I walk to work. During the first 2 episodes there were several times where I thought: you're kidding, right? I was set to post a blog entry about something I heard that I considered absolute stupidity, but I was walking to work. By the time I finished work, I lost all interest in the idea of pointing out how Jeff or Joel were wrong.
I considered unsubscribing, but I haven't found many podcasts I like*, and I walk over an hour a day. I had the time, so I decided to stick with it. As time went on I realized Jeff and Joel usually weren't wrong, they just live in a completely different world. Usually this isn't an issue for me. I have no problem admitting that I know nothing about game programming for example. But, Jeff and Joel appear to live in our enterprise development world. I guess they appear to live in our world because enterprise developers read their blogs, or maybe it's other reasons, who knows. Either way, they don't live in our world, which becomes very apparent if you stay tuned in.
Joel (as you probably already know) runs FogCreek. He has his own small company where they do whatever they want. Jeff (as far as I can tell) makes enough from his blog to support himself. They decided to create a website together and 3 programmers currently work on the codebase. That's not enterprise development. I'm sure they both did enterprise development in the past, but things (as they always do) have changed. That means a lot of what they say applies, and occasionally they mix in some complete nonsense.
Interestingly, the nonsense turns out to be quite entertaining once you realize that it's okay. And, of course, it's easy to criticize something they say unscripted that is probably often an incomplete thought. But, the unscripted and incompleteness is what makes the podcast work. They also come from different backgrounds with (often completely) different approaches -- which also makes for good entertainment.
So, if you're a fan of podcasts like me, give StackOverflow a shot. It's entertaining if nothing else, but you might learn something as well.
* I also subscribe to RailsEnvy and Alt.net
I listen to the StackOverflow podcast while I walk to work. During the first 2 episodes there were several times where I thought: you're kidding, right? I was set to post a blog entry about something I heard that I considered absolute stupidity, but I was walking to work. By the time I finished work, I lost all interest in the idea of pointing out how Jeff or Joel were wrong.
I considered unsubscribing, but I haven't found many podcasts I like*, and I walk over an hour a day. I had the time, so I decided to stick with it. As time went on I realized Jeff and Joel usually weren't wrong, they just live in a completely different world. Usually this isn't an issue for me. I have no problem admitting that I know nothing about game programming for example. But, Jeff and Joel appear to live in our enterprise development world. I guess they appear to live in our world because enterprise developers read their blogs, or maybe it's other reasons, who knows. Either way, they don't live in our world, which becomes very apparent if you stay tuned in.
Joel (as you probably already know) runs FogCreek. He has his own small company where they do whatever they want. Jeff (as far as I can tell) makes enough from his blog to support himself. They decided to create a website together and 3 programmers currently work on the codebase. That's not enterprise development. I'm sure they both did enterprise development in the past, but things (as they always do) have changed. That means a lot of what they say applies, and occasionally they mix in some complete nonsense.
Interestingly, the nonsense turns out to be quite entertaining once you realize that it's okay. And, of course, it's easy to criticize something they say unscripted that is probably often an incomplete thought. But, the unscripted and incompleteness is what makes the podcast work. They also come from different backgrounds with (often completely) different approaches -- which also makes for good entertainment.
So, if you're a fan of podcasts like me, give StackOverflow a shot. It's entertaining if nothing else, but you might learn something as well.
* I also subscribe to RailsEnvy and Alt.net
Ruby: Dwemthy's Array using Modules
Following my blog entry Underuse of Modules, an anonymous commenter asks
Note, I removed the comments and added the Rabbit code. The Rabbit is there to ensure the magic continues to function as expected.
The version using a module isn't significantly better, but I do slightly prefer it.
The above version is a bit clearer to me because it defines methods on a module and then extends the module. I know that if I extend a module from the context of a class definition the methods of that module will become class methods.
Conversely, the first example forces me to think about where a method goes if I do an instance_eval on a metaclass. By definition all class methods are instance methods of the metaclass, but there are times when you can be surprised. For example, using def changes based on whether you use instance_eval or class_eval, but define_method behaves the same (change metaclass.instance_eval in the original example to metaclass.class_eval and the behavior doesn't change). This type of thing is an easy concept that becomes a hard to find bug.
If you spend enough time with metaclasses it's all clear and easy enough to follow. However, modules are generally straightforward and get you the same behavior without the mental overhead. I'm sure someone will argue that metaclasses are easier to understand, which is fine. Use what works best for you.
However, there are other reasons why it might make sense to use modules instead, such as wanting to have an ancestor (and thus the ability to redefine and use
Again, it probably comes down to personal preference.
Can you show another example, then, of how one might implement the "magic" of Dwemthy's Array (http://poignantguide.net/dwemthy/) just using modules? I can never remember how to do this sort of thing, and if modules can make it conceptually simpler it would be most useful.I'm not sure exactly what magic they were referring to, but I'll assume they mean what allows creature habits to be defined in class definitions. Based on that assumption, I pulled out this code from the example.
class Creature
def self.metaclass; class << self; self; end; end
def self.traits( *arr )
return @traits if arr.empty?
attr_accessor(*arr)
arr.each do |a|
metaclass.instance_eval do
define_method( a ) do |val|
@traits ||= {}
@traits[a] = val
end
end
end
class_eval do
define_method( :initialize ) do
self.class.traits.each do |k,v|
instance_variable_set("@#{k}", v)
end
end
end
end
end
class Rabbit < Creature
traits :bombs
bombs 3
end
Rabbit.new.bombs # => 3
Note, I removed the comments and added the Rabbit code. The Rabbit is there to ensure the magic continues to function as expected.
The version using a module isn't significantly better, but I do slightly prefer it.
class Creature
def self.traits( *arr )
return @traits if arr.empty?
attr_accessor(*arr)
mod = Module.new do
arr.each do |a|
define_method( a ) do |val|
@traits ||= {}
@traits[a] = val
end
end
end
extend mod
define_method( :initialize ) do
self.class.traits.each do |k,v|
instance_variable_set("@#{k}", v)
end
end
end
end
class Rabbit < Creature
traits :bombs
bombs 3
end
Rabbit.new.bombs # => 3
The above version is a bit clearer to me because it defines methods on a module and then extends the module. I know that if I extend a module from the context of a class definition the methods of that module will become class methods.
Conversely, the first example forces me to think about where a method goes if I do an instance_eval on a metaclass. By definition all class methods are instance methods of the metaclass, but there are times when you can be surprised. For example, using def changes based on whether you use instance_eval or class_eval, but define_method behaves the same (change metaclass.instance_eval in the original example to metaclass.class_eval and the behavior doesn't change). This type of thing is an easy concept that becomes a hard to find bug.
If you spend enough time with metaclasses it's all clear and easy enough to follow. However, modules are generally straightforward and get you the same behavior without the mental overhead. I'm sure someone will argue that metaclasses are easier to understand, which is fine. Use what works best for you.
However, there are other reasons why it might make sense to use modules instead, such as wanting to have an ancestor (and thus the ability to redefine and use
super
).Again, it probably comes down to personal preference.
Labels:
extend,
include,
metaclass,
metaprogramming,
module
Thursday, July 24, 2008
Ruby: Underuse of Modules
When I began seriously using Ruby I noticed two things that I didn't like about the language.
In April 2005, Why gave us Seeing Metaclasses Clearly. I'm not sure the article actually helped me see metaclasses clearly, but I know I pasted that first block of code into several of my first Ruby projects. I used metaclasses in every way possible, several of which were probably inappropriate, and saw exactly what was possible, desirable, and painful.
I thought I had a good understanding of the proper uses for metaclasses, and then Ali Aghareza brought me a fresh point of view: defining methods on a metaclass is just mean.
We were on the phone talking about who knows what, and he brought up a blog entry I'd written where I dynamically defined delegation methods on the metaclass based on a constructor argument. He pointed out that doing so limited your ability to change the method behavior in the future. I created some simple examples and found out he was right, which lead to my blog entry on why you should Extend modules instead of defining methods on a metaclass.
Ever since that conversation and the subsequent blog entry, I've been using modules instead of accessing the metaclass directly. "Just in case someone wants to redefine behavior" isn't really a good enough reason for me if the level of effort increases, but in this case I found the code to be easier to follow when I used modules. In programming, there are few win-win situations, but Ali definitely showed me one on this occasion.
If you interact with a metaclass directly, do a quick spike where you introduce a module instead. I think you'll be happy with the resulting code.
Include modules instead of reopening classes
In January of 2007 I wrote a blog entry titled Class Reopening Hints. I didn't write it because I thought it was very valuable, I wrote it so developers afraid of open classes could get some sleep at night. Those guys think we are going to bring the end of the world with our crazy open classes, and I wanted to let them know we'd at least thought about the situation.
I'm really not kidding, I thought the entry was a puff piece, but it made some people feel better about Ruby so I put it together. I never followed the Use modules instead of adding behavior directly advice though, and I don't think many other Rubyists did either. It was extra effort, and I didn't see the benefit. In over two and an half years working with Ruby I've never once had a problem finding where behavior was defined. With that kind of experience, I couldn't justify the extra effort of defining a module -- until the day I wanted to change the behavior of Object#expects (defined by Mocha). I was able to work around the fact that Mocha defines the
It turns out, using modules instead of adding behavior directly to a reopened class has one large benefit: I can easily define new behavior on a class by including a new module. If you only need new behavior, then defining a new method on the class would be fine. But, if you want to preserve the original behavior, having it as an ancestor is much better.
Take the following example. This example assumes that a method
That code isn't terrible. In fact, there are a few different ways to redefine methods and access the original behavior, but none of them look as nice as the following example.
When you have an ancestor, the behavior is only a
note:Yes, I've also reopened the class to include the module, but when I talk about reopening the class I'm talking about defining the behavior directly on the reopened class. I could have also included the module by using
Prefer Modules to metaclasses and reopened classes
The previous example illustrates why you should prefer modules, it gives simple access to your method behavior to anyone who wishes to alter but reuse the original behavior. This fact applies to both classes that include modules and instances that extend modules.
So why didn't Matz give us first class access to the metaclass? Who cares. He probably knew extending modules was a better solution, but even if he didn't -- it is. He didn't give you a method to access the metaclass, and whether he knew it or not, you don't need it.
- There's no method to get the metaclass.
- Some people create modules as a hint that they reopened the class.
- I can't remember the last time I actually wanted a method to get the metaclass.
- If you use a module to add behavior your behavior becomes part of the ancestor tree, which is significantly more helpful than putting your behavior directly on the class.
In April 2005, Why gave us Seeing Metaclasses Clearly. I'm not sure the article actually helped me see metaclasses clearly, but I know I pasted that first block of code into several of my first Ruby projects. I used metaclasses in every way possible, several of which were probably inappropriate, and saw exactly what was possible, desirable, and painful.
I thought I had a good understanding of the proper uses for metaclasses, and then Ali Aghareza brought me a fresh point of view: defining methods on a metaclass is just mean.
We were on the phone talking about who knows what, and he brought up a blog entry I'd written where I dynamically defined delegation methods on the metaclass based on a constructor argument. He pointed out that doing so limited your ability to change the method behavior in the future. I created some simple examples and found out he was right, which lead to my blog entry on why you should Extend modules instead of defining methods on a metaclass.
Ever since that conversation and the subsequent blog entry, I've been using modules instead of accessing the metaclass directly. "Just in case someone wants to redefine behavior" isn't really a good enough reason for me if the level of effort increases, but in this case I found the code to be easier to follow when I used modules. In programming, there are few win-win situations, but Ali definitely showed me one on this occasion.
If you interact with a metaclass directly, do a quick spike where you introduce a module instead. I think you'll be happy with the resulting code.
Include modules instead of reopening classes
In January of 2007 I wrote a blog entry titled Class Reopening Hints. I didn't write it because I thought it was very valuable, I wrote it so developers afraid of open classes could get some sleep at night. Those guys think we are going to bring the end of the world with our crazy open classes, and I wanted to let them know we'd at least thought about the situation.
I'm really not kidding, I thought the entry was a puff piece, but it made some people feel better about Ruby so I put it together. I never followed the Use modules instead of adding behavior directly advice though, and I don't think many other Rubyists did either. It was extra effort, and I didn't see the benefit. In over two and an half years working with Ruby I've never once had a problem finding where behavior was defined. With that kind of experience, I couldn't justify the extra effort of defining a module -- until the day I wanted to change the behavior of Object#expects (defined by Mocha). I was able to work around the fact that Mocha defines the
expects
method directly on Object, but the solution was anything but pretty.It turns out, using modules instead of adding behavior directly to a reopened class has one large benefit: I can easily define new behavior on a class by including a new module. If you only need new behavior, then defining a new method on the class would be fine. But, if you want to preserve the original behavior, having it as an ancestor is much better.
Take the following example. This example assumes that a method
hello
has been defined on object. Your task is to change the hello method to include the original behavior and add a name.# original hello definition
class Object
def hello
"hello"
end
end
# your version with additional behavior
class Object
alias old_hello hello
def hello(name)
"#{old_hello} #{name}"
end
end
hello("Ali") # => "hello Ali"
That code isn't terrible. In fact, there are a few different ways to redefine methods and access the original behavior, but none of them look as nice as the following example.
# original hello definition
class Object
module Hello
def hello
"hello"
end
end
include Hello
end
# your version with additional behavior
class Object
module HelloName
def hello(name)
"#{super()} #{name}"
end
end
include HelloName
end
hello("Ali") # => "hello Ali"
When you have an ancestor, the behavior is only a
super
call away.note:Yes, I've also reopened the class to include the module, but when I talk about reopening the class I'm talking about defining the behavior directly on the reopened class. I could have also included the module by using
Object.send :include, HelloName
. Do whichever you like, it's not pertinent to this discussion.Prefer Modules to metaclasses and reopened classes
The previous example illustrates why you should prefer modules, it gives simple access to your method behavior to anyone who wishes to alter but reuse the original behavior. This fact applies to both classes that include modules and instances that extend modules.
So why didn't Matz give us first class access to the metaclass? Who cares. He probably knew extending modules was a better solution, but even if he didn't -- it is. He didn't give you a method to access the metaclass, and whether he knew it or not, you don't need it.
Wednesday, July 23, 2008
Move Mail.app RSS Feeds to NetNewsWire
Like Dave Thomas, I recently decided to give Mail.app a try as an RSS reader. Overall the experience hasn't been great, but it wasn't poor enough that I thought the effort to switch was justified.
And then I decided to quit my job*. Next week will by my last week with ThoughtWorks and I'll need to turn in my laptop. I tend to store as much as possible online so this type of situation isn't a big deal, but going with Mail.app seems to have been a decision in the wrong direction. Maybe Mail.app stores my RSS feeds online somewhere, but it wasn't immediately obvious to me.
A quick google search revealed a Mac OSX Hint on how to dump your RSS feeds to the command line. This was quite helpful, but what I really needed was an OPML file that I could send to NetNewsWire.
I wrote the following script for anyone that finds themselves in the same situation. This script will grab all the feeds and write them to an OPML file. I'm sure it can be cleaned up or done more easily, but this works.
note: This file works in NetNewsWire, but I had better success by logging on to newsgator.com and importing on their website. When you import on their website it pulls the feed names and other information. The NetNewsWire didn't seem to pull the additional information.
*For those interested, I'm joining DRW Trading.
And then I decided to quit my job*. Next week will by my last week with ThoughtWorks and I'll need to turn in my laptop. I tend to store as much as possible online so this type of situation isn't a big deal, but going with Mail.app seems to have been a decision in the wrong direction. Maybe Mail.app stores my RSS feeds online somewhere, but it wasn't immediately obvious to me.
A quick google search revealed a Mac OSX Hint on how to dump your RSS feeds to the command line. This was quite helpful, but what I really needed was an OPML file that I could send to NetNewsWire.
I wrote the following script for anyone that finds themselves in the same situation. This script will grab all the feeds and write them to an OPML file. I'm sure it can be cleaned up or done more easily, but this works.
feeds = %x[IFS=$'\n';for i in $(find ~/Library/Mail/RSS/ -name "Info.plist");do grep "<string>http://" $i | sed "s/.*\(http[^<]*\).*/\1/";done].split("\n")
feeds = feeds.collect { |element| element.gsub(/<string>/, "<outline xmlUrl=\"") }
feeds = feeds.collect { |element| element.gsub(/<\/string>/, "\"/>") }
xml = <<-eos
<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.1">
<head>
<title>mySubscriptions</title>
</head>
<body>
#{feeds.join("\n")}
</body>
</opml>
eos
File.open("mail_app_export.ompl", "w") { |file| file << xml }
note: This file works in NetNewsWire, but I had better success by logging on to newsgator.com and importing on their website. When you import on their website it pulls the feed names and other information. The NetNewsWire didn't seem to pull the additional information.
*For those interested, I'm joining DRW Trading.
Tuesday, July 22, 2008
ActionScript: Literal XML
One feature of ActionScript I really like is the ability to use XML within the language. In ActionScript there's no need to use strings to represent XML, you can use XML inline just as you would any other literal (numbers, strings, etc).
Recently I was working on some dynamic code that needed to update based on an XML response. We were just getting started on the card, so instead of worrying about the service, how it was triggered, etc, we added a button to the interface that called the response parsing method and passed in an XML literal as an argument. The code is similar to what's shown below.
Sure, we had to change the code later and make a real service call, but this quick solution let us keep focusing on the task at hand instead of how to get the XML. In a language like Ruby we could have used a builder or just a string, and given such a small amount of XML it would have been fine. However, the actual XML we were working with was significantly larger than the example and would have been a decent mess of a multiline string or several builder calls. Being able to write XML natively was significantly easier.
The beauty of literal XML is the simplicity. I don't have to represent it in any way other than what it actually is -- XML.
If you aren't a fan of all the angle brackets, that's okay, ActionScript has you covered there also. You can add elements and attributes as you would expect to be able to if you prefer method calls.
If you only ever use XML similar to the above syntax, then there may be little value in literal XML, but I don't think you're usage would be limited to the above syntax.
I write tests, a lot of them. I prefer to see the actual XML in my tests, instead of the builder versions of XML. I don't like angle brackets any more than the next programmer, but I strongly prefer testing with expected literals. I find my resulting tests to be more readable and reliable. If you've ever had a test fail because of whitespace issues in your XML, you should know what I mean by reliable.
Literal XML also ensures XML compliance at compile time and encourages IDEs to provide syntax highlighting. Two things that aren't essential, but are definitely nice to have.
ActionScript is the first language I've used with literal XML support, and I'm very happy with the experience. As programmers we spend a lot of time hiding other languages with object relational mappers (orm) and builders, but literal XML is a refreshing step in the other direction. The creators of ActionScript have embraced the fact that XML isn't going anywhere. They built first class support for XML in the language itself instead of hiding the problem with a framework, and that helps me significantly more than any orm or builder ever has.
Recently I was working on some dynamic code that needed to update based on an XML response. We were just getting started on the card, so instead of worrying about the service, how it was triggered, etc, we added a button to the interface that called the response parsing method and passed in an XML literal as an argument. The code is similar to what's shown below.
parseResponse(<user><firstName>Jay</firstName></user>);
Sure, we had to change the code later and make a real service call, but this quick solution let us keep focusing on the task at hand instead of how to get the XML. In a language like Ruby we could have used a builder or just a string, and given such a small amount of XML it would have been fine. However, the actual XML we were working with was significantly larger than the example and would have been a decent mess of a multiline string or several builder calls. Being able to write XML natively was significantly easier.
The beauty of literal XML is the simplicity. I don't have to represent it in any way other than what it actually is -- XML.
If you aren't a fan of all the angle brackets, that's okay, ActionScript has you covered there also. You can add elements and attributes as you would expect to be able to if you prefer method calls.
var request:XML = <smart_list/>;
request.sort.order = "highest";
request.sort.field = "Average Position";
request.max_results = 10;
request.toXMLString(); // "<smart_list><sort><order>highest</order><field>Average Position</field></sort><max_results>10</max_results></smart_list>"
If you only ever use XML similar to the above syntax, then there may be little value in literal XML, but I don't think you're usage would be limited to the above syntax.
I write tests, a lot of them. I prefer to see the actual XML in my tests, instead of the builder versions of XML. I don't like angle brackets any more than the next programmer, but I strongly prefer testing with expected literals. I find my resulting tests to be more readable and reliable. If you've ever had a test fail because of whitespace issues in your XML, you should know what I mean by reliable.
Literal XML also ensures XML compliance at compile time and encourages IDEs to provide syntax highlighting. Two things that aren't essential, but are definitely nice to have.
ActionScript is the first language I've used with literal XML support, and I'm very happy with the experience. As programmers we spend a lot of time hiding other languages with object relational mappers (orm) and builders, but literal XML is a refreshing step in the other direction. The creators of ActionScript have embraced the fact that XML isn't going anywhere. They built first class support for XML in the language itself instead of hiding the problem with a framework, and that helps me significantly more than any orm or builder ever has.
Thursday, July 17, 2008
Ruby: Redefine Method Behavior Conditionally
I was recently working on the integration between Mocha and expectations. Expectations promotes the idea that you should only have one expectation per test; therefore, I wanted to display a warning if you call Object#expects within an expected block.
For example, the following code will print a warning.
Usually, I'd use one of the various Alternatives for Redefining Methods, but redefining the Object#expects method had two additional constraints that complicated matters.
The actual implementation involves several moving parts, so here's a much simpler example. Start with some behavior defined on Object. This behavior will have been defined by another framework so you cannot (easily) alter the original method definition.
Next, you've decided to create a framework that says hello in Spanish also. You still want to be able to return "hello" when English is required, but you want the
Below is the output we are looking for.
Currently our code returns "hello" both in English and Spanish.
We can make the
Now we have Spanish working, but we've lost our English. Remember the actual implementation needed to preserve the original behavior in some circumstances. The
Now we have the original behavior of the
The final step is to define Object#say_hello in a way that delegates the
The final change was setting the
This isn't a technique that you'll use often, but it's a good trick to know when you need it. If you're interested in the actual application you can check out the expectations framework code.
For example, the following code will print a warning.
Expectations do
expect 1 do
Object.expects(:something).returns 1
Object.something
end
end
# >> Expectations allows you to to create multiple mock expectations, but suggests that you write another test instead.
# >> expects method called from /Users/jay/example.rb:6
# >>
# >> Expectations .
# >> Finished in 0.001 seconds
# >>
# >> Success: 1 fulfilled
Usually, I'd use one of the various Alternatives for Redefining Methods, but redefining the Object#expects method had two additional constraints that complicated matters.
- There's no (reasonable) way to extend all instances with a new module since the
expects
method is defined on Object. - I didn't want to unconditionally redefine the expects method. Within the framework I call Object#expects, and I don't want those calls to cause invalid warnings. I need a solution that prints a warning when you use Object#expects within an expectation block, but does not print a warning if Object#expects is called from anywhere else.
The actual implementation involves several moving parts, so here's a much simpler example. Start with some behavior defined on Object. This behavior will have been defined by another framework so you cannot (easily) alter the original method definition.
class Object
def say_hello
"hello"
end
end
Next, you've decided to create a framework that says hello in Spanish also. You still want to be able to return "hello" when English is required, but you want the
say_hello
method to return "hola" when you are expecting Spanish.Below is the output we are looking for.
in_english do
say_hello # => "hello"
end
in_spanish do
say_hello # => "hola"
end
Currently our code returns "hello" both in English and Spanish.
# Framework Object::say_hello
class Object
def say_hello
"hello"
end
end
# Your Object class
class Object
def in_english(&block)
instance_eval(&block)
end
def in_spanish(&block)
instance_eval(&block)
end
end
in_english do
say_hello # => "hello"
end
in_spanish do
say_hello # => "hello"
end
We can make the
say_hello
message sent to Object return "hola" by removing the say_hello
method, defining an InSpanish module, and including the InSpanish module.# Framework Object::say_hello
class Object
def say_hello
"hello"
end
end
# Your Object class
class Object
module InSpanish
def say_hello
"hola"
end
end
include InSpanish
remove_method :say_hello
def in_english(&block)
instance_eval(&block)
end
def in_spanish(&block)
instance_eval(&block)
end
end
in_english do
say_hello # => "hola"
end
in_spanish do
say_hello # => "hola"
end
Now we have Spanish working, but we've lost our English. Remember the actual implementation needed to preserve the original behavior in some circumstances. The
in_english
method is our circumstance where we need to preserve original behavior. This can be done easily enough by Moving the say_hello definition from Object to an InEnglish module.# Framework Object::say_hello
class Object
def say_hello
"hello"
end
end
# Your Object class
class Object
module InSpanish
def say_hello
"hola"
end
end
include InSpanish
module InEnglish
expects_method = Object.instance_method(:say_hello)
define_method :say_hello do |*args|
expects_method.bind(self).call(*args)
end
end
include InEnglish
remove_method :say_hello
def in_english(&block)
instance_eval(&block)
end
def in_spanish(&block)
instance_eval(&block)
end
end
in_english do
say_hello # => "hello"
end
in_spanish do
say_hello # => "hello"
end
Now we have the original behavior of the
say_hello
method, but we've lost our ability to speak Spanish.The final step is to define Object#say_hello in a way that delegates the
say_hello
message to the appropriate module instead of removing the method.# Framework Object::say_hello
class Object
def say_hello
"hello"
end
end
# Your Object class
class Object
module InSpanish
def say_hello
"hola"
end
end
include InSpanish
module InEnglish
expects_method = Object.instance_method(:say_hello)
define_method :say_hello do |*args|
expects_method.bind(self).call(*args)
end
end
include InEnglish
def say_hello
(@language || InEnglish).instance_method(:say_hello).bind(self).call
end
def in_english(&block)
@language = InEnglish
instance_eval(&block)
end
def in_spanish(&block)
@language = InSpanish
instance_eval(&block)
end
end
in_english do
say_hello # => "hello"
end
in_spanish do
say_hello # => "hola"
end
The final change was setting the
@language
instance variable to the module who's say_hello
method definition was required. As you can see from the printed output, our code works as desired.This isn't a technique that you'll use often, but it's a good trick to know when you need it. If you're interested in the actual application you can check out the expectations framework code.
Subscribe to:
Posts (Atom)