How to piss off a developer

Friday, March 27, 2009

This is something I whipped up years ago to brighten up a coworker's sour mood. Enjoy!

Want to piss off developers? Aggravate them? Drive them toward crowbar-bashing road rage when driving home? Follow these steps for guaranteed asshole-ness:

1. Field a call from the customer. Say YES to everything. Promise it in 5 minutes

2. Run (do not walk) to the developer's desk. Huff and Puff and breathe like you just ran a marathon. If you have trouble affecting such respiratory dynamics, hold your breath for a minute and run in circles.

3. Say something urgent like "This is urgent". Sweat is good. Tears are better.

4. Explain your hands were tied and it's a production emergency and if the client doesn't get [insert thing they want here] right away we're going to lose all their business and we'll be closing up shop tomorrow. For added effect, say something like "Think of the children"

5. DO NOT....LISTEN UP...THIS IS IMPORTANT.... DO NOT give the developer any documentation, specification, guidance, or explanation.

6. Say "They want this [again, insert thing here] right now. Please get it to me. It's urgent" (repetition is key, as you may be sensing)

1. Super-Duper Ladder-Climber Tip: when you get sufficiently good (or are certified by a professional certifying agent), you can just forward emails from clients directly to developer without any additional need for commentary. Certificates generally confer upon the sender some degree of telepathic skill. Little known fact! 2. All 9th graders learn that a 1 must have a 2. Here's my 2.

7. Then, start to whisper to the developer so no managers hear the conversation. God forbid they know what you're working on

8. Thank them profusely, effusively, gushingly, for the work they're about to do.

9. Return to seat. Surf internet. Play some games. But do set your timer.

10. In 3 minutes, email the developer. Use this template: "Dear [insert lackey's name here], Thanks so much again for getting this done on such short notice. It really means a lot to me, and to the client, that we continue to provide such world class service. You're a gem!"

11. In 6 minutes, email the developer. use this template: "Dear [insert lackey's name here], Can you give me a report on your status? YOU'RE THE BEST!!!!". The timing of your use of ALL CAPS is a finely honed skill. Guidelines are thus:

1. IN GENERAL, USE THEM WHENEVER POSSIBLE. THERE IS NO MORE FORCEFUL MEANS OF PUNCTUATING YOUR POINTS THAN WITH CAPITAL LETTERS.
2. EXCEPT WITH EXCLAMATION POINTS!!!!!!!!!!!!!!!!!!!!!!!!!!
3. As the time between the initial client request and the current time increases (now() - InitialClientRequest --> Infinity), so too should the percentage of CAPITAL letters to lowercase ones.
4. Post-issue communications, however, should use lowercase except in cases where use of lowercase violates standard written English. Like so:
1. Initial issue letter: I CANNOT STRESS ENOUGH THE IMPACT THAT NOT MEETING THIS DEADLINE WILL HAVE ON OUR FUTURE RELATIONS WITH THIS CLIENT
2. Post-issue followup letter: "Received rpt. Pls forward to [insert list of people that you are too busy to forward this to yourself here]. tks." (See below for proper use of tks).

12. Repeat that step every 5 minutes. Each time, add another upper level manager to the CC list. In addition, BCC your colleagues so they can see how hard you're working and how slow the developer is and how it's so hard to deal with people who just don't get things done. This facilitates post-issue commiseration and is a critical team-building tool. Do not discount the importance of this step.

13. Upon completion of task, forward report to client. Bask in your glory

14. Email all management explaining what a good job the team did. Pepper with phrases like "here's another example of how our team provides world class service". Be liberal in your use of phrases such as "team" and "we". Third-person plural is GOLD. If it's good enough for the Queen of England, it's good enough for you.

15. DO NOT DOCUMENT THIS. It's imperative that this go down the memory hole so that next time it comes up, the entire process can be started from scratch. Bug trackers are the devil's playground: avoid them.

16. In 2 weeks, when client finally reads said report and complains that the headings are in the wrong font, send email to developer requesting a new report. use this template: "Dear [lackey], Remember that one report? The font was wrong. Please fix. This is urgent. Tks." Click the little red exclamation point in outlook. CC management. BCC colleagues. Also BCC your friends so they can see how tough you are. Make 50 printouts and leave at printer for all to see.

1. Note use of 'tks' in template
2. This is a crucial communication technique. Your abbreviation is a cue that any socially adept recipient will immediately recognize as a manifestation of your disapproval of their performance. Most will respond by working harder. All people crave unabbreviation. It's human nature.
3. If you feel you're dealing with a less emotionally intelligent recipient, start removing subjects from your sentences. This will serve to punctuate said disapproval. To wit: NOT "The font was wrong" but "Font was wrong". better yet: "Font wrong. Will follow up. Urgent. Tks."
1. Super-Duper Ladder-Climber Tip: do not perform this subject-cide when communicating with those whose position you wish to assume someday...only lackeys and those too inferior to be graced by your pronouns. Conserve your verbiage for those who matter.
 
17. One final basking. Hooooold it..... Hoooooold it...... Wait for it........... Ah, yeah. That was good. Good job

Debugging WebService errors in ColdFusion

Tuesday, March 24, 2009

Overview

This is an article about the process of debugging a strange error I received while trying to execute an external webservice using ColdFusion 8. The external service is a java service, using Axis2, running in WebSphere. But if you're just searching for an answer b/c maybe you've hit the same problem, scroll to the bottom. Otherwise, enjoy the show.

I was executing the service like so:
<cfset ws = createObject("webservice","http://XXXXX/webservices/services/WFIService?wsdl")>
<cfset result = ws.initiateOrder(IlexCode="enr",OrderXML="<orders/>")>

It Started with this Error Here's the error:

Cannot perform web service invocation initiateOrder. The fault returned when invoking the web service operation is: AxisFault faultCode: {http://www.w3.org/2003/05/soap-envelope}Server.userException faultSubcode: faultString: org.xml.sax.SAXParseException: Invalid encoding name "UTF-8; ACTION="URN:INITIATEORDERRESPONSE"". faultActor: faultNode: faultDetail: {http://xml.apache.org/axis/}stackTrace:org.xml.sax.SAXParseException: Invalid encoding name "UTF-8; ACTION="URN:INITIATEORDERRESPONSE"". at org.apache.xerces.util.ErrorHandlerWrapper.createSAXParseException(Unknown Source) (for the lone bleary-eyed googler trying desperately to solve the same problem, i give you these keywords: faultString: org.xml.sax.SAXParseException: Invalid encoding name "UTF-8; ACTION=" )

Here's what it looked like so you can bask in its awesomeness: Running the service from outside ColdFusion

First, I wanted to be sure that the problem wasn't with the service itself, so I needed to execute it outside of ColdFusion. For me, the simplest way to do this is with the "Web Services Explorer" that comes with Eclipse WST (Google it, it's handy). So I fired that up, saw there were 3 different "Bindings", and I hit the first SOAP one (the Soap11 porttype) just fine.

The SOAP12 binding was unsupported, so I ignored it. I also ignored the HTTP Post binding. So in my head, what I'm thinking is "I can hit the service successfully from outside of CF. Now I'm in black box hell."

Trying to view the SOAP messages with TCPMon

First, I needed to find out what differed between the SOAP being sent by the Web Services Explorer and the SOAP being sent by ColdFusion. Having done a fair amount of development with Apache Axis back in 2002/2003, and more recently while developing the MXUnit Eclipse plugin, I was familiar with using TCPMon for doing this kind of debugging. However, I never tried to use it for debugging remote services, only local ones. (And after spending a half hour or so with it yesterday, I still don't know how to use it for debugging remote services. If you know, please tell me!). Eventually, because all I was really interested in was seeing the SOAP envelope and the http headers being sent, I just copied the wsdl from the remote services, stuck it in an xml file on my computer, and pointed my webservice call in Scribble pad to the new URL: http://localhost:8124/playground/WFIService.xml?wsdl. I then fired up TCPMon and added a listener at Port 8124

  1. from command line, navigate to C:\jrun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\lib
  2. type java -cp axis.jar org.apache.axis.utils.tcpmon
  3. The swing app pops up
  4. Type 8124 into Listen Port (this is arbitrary!)
  5. Target Port = 80 (though in my case, it doesn't matter anyway)
  6. Click "add"

I then executed my local version of the service like so:

<cfset ws = createObject("webservice","http://localhost:8124/playground/WFIService.xml?wsdl")>
<cfset result = ws.initiateOrder(IlexCode="enr",OrderXML="<orders/>")>
Again, This wouldn't actually "work work", but CF would at least try to send a soap request to this address, so I'd be able to see what was being sent to the remote server. Honestly, this didn't pan out. Based on the original error, I suspected the problem wasn't with the SOAP envelope but with the headers. If you'll remember from the original error message, it displayed: Invalid encoding name "UTF-8; ACTION="URN:INITIATEORDERRESPONSE"". I suspected the problem was with the Content-Type header being sent to the service, but when I looked at it in tcpmon, it showed up like so: POST /webservices/services/WFIService HTTP/1.0 Content-Type: application/soap+xml; charset=UTF-8 Accept: application/soap+xml, application/dime, multipart/related, text/* User-Agent: Axis/1.2.1 And I had expected to see this in that header: "UTF-8; ACTION="URN:INITIATEORDERRESPONSE"" For the screenshot lovers, it looked like this: So as of that moment, I was kind of perplexed. I'm sure folk with more experience consuming web services from a variety of external producers would know exactly what was going on. I, however, did not. Me + TCPMon = Fail.

Debugging with the Generated Java stubs

Some time back I had read Nathan Mische's excellent article in FusionAuthority Quarterly Update on using complex datatypes in CF web services. In the article, he mentions something new in CF8: the ability to easily view the generated java stubs created by axis (this is how CF works under the hood: it uses the wsdl2java tool from axis to dynamically generate bindings based on the WSDL). I change my web service invocation like so:

<cfset args = {refreshWSDL=true,savejava=true}>
<cfset ws = createObject("webservice","http://XXXXX/webservices/services/WFIService?wsdl",args)>
<cfset result = ws.initiateOrder(IlexCode="enr",OrderXML="<orders/>")>
So I run the service again, get the error, and traipse on down to the stubs directory (in my case: C:\jrun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\stubs) and open up the most recent directory. Navigate on down, and there are the java files. I decided that I wanted to see if I could execute the service from outside of CF but using these source files, so I opened up my java playground project in Eclipse (you DO have at least one playground/sandbox/scratch project, right?!), added a new package, and dropped the source files in there. The screenshot below shows the eclipse directory where I copied the generated java files on the left; on the right is the stubs directory that ColdFusion creates. Note that it's a mix of java and class files. Normally, the java files aren't in there, but the "savejava=true" key in the arguments struct that I'm now passing to CF in the createobject() call ensures the java files stay there. When I first copied the files over into my new Eclipse package, I naturally got a bunch of errors. First, I needed to change the package declaration in the source files (alternately, I could've simply renamed my package). Second, I needed to add jar files to the the build path so that the source files would compile. This is simple: In Eclipse, in the project's "lib" directory, I created a new subfolder called "axis12". I did this b/c I didn't want to pollute my existing lib directory, and at the time I had suspected that maybe the problem was with CF using axis 1.2 and not 1.4 (I use 1.4 for the Eclipse plugin, so I had thought maybe I'd end up running these sources files under both axis 1.2 and 1.4 and therein would've lied the bug... turned out that's not what it was at all). Then, in C:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\lib, I copied a whole bunch of jar files that looked like they'd be required. in the end, I copied all the ones you see highlighted in the "referenced libraries" from the screenshot below. I didn't need all of these to get the java to compile, but I did need them to get it to run without getting NoClassDefFound errors when actually running the code.

In addition, I created a new JUnit TestCase for executing the generated code (simply a start... no assertions, just something to run the code):

WFIServiceTest.java: package marc.axis; ...imports... public class WFIServiceTest extends TestCase { public void testCompleteOrder() throws RemoteException, ServiceException, MalformedURLException { WFIServiceLocator locator = new WFIServiceLocator(); System.out.println(locator.getWFIServiceSOAP11port_http().initiateOrder("enr", "<orders/>")); } }

After doing this stuff, the damn test case didn't error! Confound it all Samwise Gamgee! I had hoped against hope that by running the service using the stubs generated from CF that I'd hit the same error that I was hitting inside of CF. This way, I could at least step through it in the debugger and have a fighting chance of getting inside the black box to figure out how to fix this. Alas, this was not to be. It was time to take a break. Distance Every programmer knows that one of the worst things you can do when faced with a hard-to-get-at problem of this type is to stare and obsess over it endlessly. You get tired. Your eyes hurt. Your brain turns to mush. Your shoulders ache. I really, really wanted to keep at this. Thankfully, in the background I hear my wife yell at my daughter, my daughter has a low-grade conniption (she's 5 and of late prone to such outbursts...), and Daddy has to swoop in and save the day. Being a dad always comes first. About an hour after the meltdown, it's tubby time. Afterward, I'm drying my daughter's hair, my brain's still working on the problem, and it hits me: it's the other bindings! Remember the ones from when I executed the service successfully in the Web Services Explorer (the Soap11 binding worked fine)? And remember how the Soap12 binding was unsupported, and I ignored the HTTP Post binding? Well.... they stuck in my head. And they had to be in the generated sources. So as I'm drying my daughter's hair, I'm thinking... what if CF is using one of those other bindings, and that's why I'm getting the error? It's all about the PortName

Now, at this point, I can think of nothing else. I HAVE to get downstairs and see if that's it. Problem is, I have a wet-headed kid. So I race through that, hand her off to my sweet wife (who's busy with our littlest daughter), and hit the computer again. I change this line in my test case like so: from System.out.println(locator.getWFIServiceSOAP11port_http().initiateOrder("enr", "<orders/>")); to System.out.println(locator.getWFIServiceSOAP12port_http().initiateOrder("enr", "<orders/>"));

Sweet, sweet stack trace:

AxisFault faultCode: {http://www.w3.org/2003/05/soap-envelope}Server.userException faultSubcode: faultString: org.xml.sax.SAXParseException: Invalid encoding name "UTF-8; ACTION="URN:INITIATEORDERRESPONSE"". faultActor: faultNode: faultDetail: {http://xml.apache.org/axis/}stackTrace:org.xml.sax.SAXParseException: Invalid encoding name "UTF-8; ACTION="URN:INITIATEORDERRESPONSE"". at org.apache.xerces.util.ErrorHandlerWrapper.createSAXParseException(Unknown Source) at org.apache.xerces.util.ErrorHandlerWrapper.fatalError(Unknown Source) at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source) at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source) at org.apache.xerces.impl.XMLEntityManager.createReader(Unknown Source) at org.apache.xerces.impl.XMLEntityManager.setupCurrentEntity(Unknown Source) at org.apache.xerces.impl.XMLVersionDetector.determineDocVersion(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) .....

That was it... CF was using the Soap12 binding instead of the Soap11 binding. So all I needed to do was get CF to use the correct binding, and my problem would be solved. How to do that? I remember reading in the docs that you could use the "portname" attribute to force CF to use a specific binding, so that's what I needed to do. Now... what's the portname to use? It's simple to discover what is available to you. Just look in the WSDL, scroll to the bottom, and find stuff that looks like this:

<wsdl:service name="WFIService">
<wsdl:port name="WFIServiceSOAP11port_http" binding="ns0:WFIServiceSOAP11Binding">
<soap:address location="http://XXX/webservices/services/WFIService"/>
</wsdl:port>
<wsdl:port name="WFIServiceSOAP12port_http" binding="ns0:WFIServiceSOAP12Binding">
<soap12:address location="http://XXX/webservices/services/WFIService"/>
</wsdl:port>
<wsdl:port name="WFIServiceHttpport" binding="ns0:WFIServiceHttpBinding">
<http:address location="http://XXX/webservices/services/WFIService"/>
</wsdl:port>
</wsdl:service>
The names available to you for use in the portname attribute are the names in the wsdl:port elements. So I took the Soap11 portname, added it to the webservice invocation like so, and bam... problem solved:
<cfset ws = createObject("webservice","http://XXXXX/webservices/services/WFIService?wsdl","WFIServiceSOAP11port_http")>
<cfset result = ws.initiateOrder(IlexCode="enr",OrderXML="<orders/>")>
What I still don't understand According to the CF Documentation, if you don't specify the portname, then CF will pick the first one it finds. Looking at the WSDL, the first element is the Soap11 binding. Why is CF using the Soap12 binding? I don't get it. There's another lesson here, aside from the fact that portname is scalded into my brain. It's a lesson about struggle. I believe that struggle is key to learning deep. You have to be willing to fight for your answers. Sure, I tried googling for the answer first! But when I didn't find it, I didn't stop there. You have to do the same thing. If you resort to the mailing lists or forums and paste in your 1000 lines of code, you're not just annoying the people who would otherwise try to help you; you're doing yourself a great disservice. Learning is hard; it takes time; it's even worse when you're under deadline pressure and you just gotta get this thing done. Still, if you want to grow, you gotta welcome the pain.

Got Hard-to-Test code? We Want It!

Wednesday, March 18, 2009

Do you have CFML code that's hard to test? Maybe it's a legacy component written long before you tried out unit testing. Maybe it's something you wrote yesterday and even though you had testing on your mind, the test didn't flow out of you quite the way you hoped. Think back to some coding you've done recently. Did you have every intention of writing tests but then, when you started, said "Ahhhh, screw this", and gave up? If so... we want to hear about it! We're looking for CFCs, even CFM templates, that you think don't lend themselves to easy testing. We don't care what they do. Talk to databases? Fine. Delete 10 million files? Great! No need to give us any "setup" stuff like databases, test files, or whatever. We just want the code. Why, you ask? A few reasons. First, we're kicking into gear the development of the next gen MXUnit, and we want to get a good feel for the kinds of testing challenges people need to solve. That's longer-term. Short term: I'm presenting on designing-for-easy-testability at cfObjective this year and I'd like to dispense with the canned code and get real. I want to use real people's real code to demonstrate the techniques we've learned over the years for refactoring for easier-to-test code. Maybe you're thinking, "But I'll be embarassed." Don't be. We won't attach any names to anything. Email it to us at theguys at mxunit.org. Thanks in advance!

Are you using Twitter to update Facebook?

Monday, March 16, 2009

This will probably come off as quite presumptuous, possibly pedantic, and will surely piss at least some people off; after all, who am I to tell you how to conduct yourself in your own social network?

Good question.

I'm the guy who was glad to connect with you on Twitter. And then on Facebook. It was nice to get to know you. I could follow your sometimes useful thoughts on Twitter, and when appropriate, get more personal by looking at your pictures on Facebook. It was grand! We formed a typical "we're not hanging out right now drinking beers but I bet if we met at a conference we'd get along like old friends" type of relationship typical of the kind encouraged by the likes of Twitter and Facebook.

But now, though.... you know what? I think I'm getting too much of you. Don't take this the wrong way. You're swell! You're interesting! You're charming. But dammit, you're lazy. Sorry. I know you're busy. Let me rephrase that. You're thoughtless. No, wait. That's not right either. Ok, another shot: quite possibly, in the busyness of your life, you didn't give full attention to the potential downsides of updating your Facebook status with your Twitter status.

This is to be expected. I did it myself, about 3 seconds after I signed up on Facebook. It was basic Geek instinct: "Hey, I bet I can hook Twitter up to Facebook, thereby saving myself from typing the same thing twice." You've unlearned copy-paste well. Good for you! Except there's a problem. In doing so, you've contributed to the rampant noise problem on Facebook. Now, all the people who follow you on Twitter now have to see the same damn thing on Facebook.

You've saved a few seconds of redundant typing, but you've created a kind of reverse-copy/paste problem for the people who follow you.

You're not alone. Quite possibly a lot of the people you follow in both streams do the same thing. You're in good company, to be sure. How were you to know any better?

There's another problem. I bet you know about it, but you write it off as the unfortunate collateral damage of your dual-update glory: there are people who follow you on Facebook who have no clue what you mean when you tweet "RT @somedouche [insert completely noncontextual geek thing here]". Nope. Aunt Jenny has no clue. Cousin Sara... she now thinks you're a bigger dweeb than she did when you last kicked it at the family Christmas party. Also, she's learning to ignore you. You're teaching her that.

Do us all a favor. Disconnect Twitter and Facebook. Unshackle. It'll be good for you, your "real" relationships, your acquaintances, and the global technological noise pollution from which we all suffer. FB and Twitter are different applications, with different purposes and most likely different people attached to you. The people in both groups are giving you their time because they think you're worth it. Pay them the same respect in kind. Pretty Please. With a cherry on top. It might take you a few more minutes a week, but you'll get used to it. You'll continue to blather endlessly (we love it, we really do!) on Twitter. But you'll be more mindful about what you post on Facebook.

Your FB friends will thank you. Your tweeps won't know the difference.

Eclipse is wonky

Thursday, March 12, 2009

This is really just a self reminder of what to do when eclipse starts going nuts.
  1. Relax. Take your time. Screwing around aimlessly with plugin files often makes things wors.
  2. Create a new workspace.
  3. ... (some magic here)
  4. Profit!
Yes. After wrestling with a misbehaving subversive plugin for a few hours between yesterday and today, filing bug reports with eclipse and generally scouring the web for answers (strange that few people had the same issue I was having = here's a clue), someone made a comment on a website somewhere about having a corrupt workspace.
Doh!
15 minutes later. Problem solved and I was even able to import my existing projects.

Cannot can't open .svn\tmp\tempfile.tmp: access is denied

Wednesday, March 11, 2009

Yesterday I was trying to add a large directory of files to Subversion. I tried it my usual way, via Subclipse, and got this error: can't open .svn\tmp\tempfile.tmp: access is denied Then I tried with Tortoise. Same thing. I piddled for a bit, with no luck. Off to google I goes, and then I came across this thread on the tortoise list. No need to read it... the bottom line is that the Tortoise folks are blaming McAfee Antivirus for the problem. It just so happens, we use McAfee at work. And, naturally, it's locked down so that I can't do anything about it. Fortunately, our "network guy" here in the office is the model of pragmatism. He works extremely well with us... in short, he's the antithesis of Dilbert's Mordac, the "we will authenticate you by the sound of your scream" IT security dude. So I says to my network guy, "Can you disable McAfee for me so that I can confirm this is the problem". He does so. I try committing the files, and bam, they commit. So, if you're getting that error, your antivirus software may be to blame.

CFANT and ANT 1.7 on CF8

Monday, March 9, 2009

You probably know that CF ships with an undocumented tag for running ANT build files: CFANT. There's a writeup here. I tried getting it to work today and hit several snags. The steps I took to get it all working properly are these:
  1. First error: null pointer exception. This was b/c I stupidly did not include the anthome argument. However, as the link above states, it looks like this argument is required (probably so that the ant.home variable can be set internally, but it appears to be ignored when it comes to actually loading the ant jars
  2. Second error: I hit something like "attribute "else" is not supported on Condition tag". Since I was quite certain "else" was in fact a supported attribute, I immediately suspected an ant version problem. Maybe my ant home (which is the latest 1.7) was being ignored? So I did an <echoproperties/> in my build file, then hit snag #3
  3. optional task org.apache.tools.ant.taskdefs.optional.EchoProperties is not found (or something like that)
First, solving #2: the unsupported "else" Since echoproperties didn't work, I just did a normal echo on ${ant.home} and ${ant.version}. Sure enough, ant.version was 1.6. So this told me that CF was using the ant jar in its classpath. Makes sense, actually. So I stopped CF, dropped in the 1.7 jar, restarted CF, and that problem was solved. Next, solving #3: echoproperties not found I opened up ant.jar and drilled down and in fact the taskdefs/optional directory wasn't there. So where does this little bugger hide? It's in ant-nodeps.jar, which isn't bundled with CF. So I dropped that jar from my ant distribution into my cf lib directory (same lib directory where ant.jar lives), restarted CF, and that problem went away, too. With those problems solved, things *just worked*.