<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5003807027724289640</id><updated>2012-01-25T08:53:56.112-08:00</updated><category term='Agile Software Development'/><category term='PostSharp'/><category term='Autopilot Consulting'/><category term='Code Snippets'/><category term='Fail'/><category term='Natural'/><category term='HR-XML'/><category term='Adabas'/><category term='jQuery'/><category term='Research'/><category term='SQL'/><category term='IEnumerable'/><category term='Google Docs'/><category term='Mainframe Migration'/><category term='Bookmarklet'/><category term='dotPeek of the Week'/><category term='Pseudocode'/><category term='Security'/><category term='Aspect Oriented Programming'/><category term='Java'/><category term='It Takes an Engineer'/><category term='Programming'/><category term='Error Message of the Day'/><category term='WtfWare'/><category term='Business'/><category term='C#'/><category term='Book Reviews'/><category term='iPhone'/><category term='Mobile Magic Developers'/><category term='How to Get a Job'/><category term='IComparer'/><category term='Extension Methods'/><category term='Chuck Norris Facts Widget'/><category term='Google Apps Script'/><category term='Telephone Game'/><category term='Widget'/><category term='JavaScript'/><category term='Android'/><category term='Tips and Tricks'/><title type='text'>D. Patrick Caldwell on Software Engineering</title><subtitle type='html'>another programmer trying to enhance his craft through information sharing and socialization</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default?start-index=101&amp;max-results=100'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>120</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5420834509807679117</id><published>2011-11-26T12:29:00.001-08:00</published><updated>2011-11-26T13:45:45.917-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Autopilot Consulting'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><category scheme='http://www.blogger.com/atom/ns#' term='Widget'/><category scheme='http://www.blogger.com/atom/ns#' term='Mobile Magic Developers'/><title type='text'>Mobile Magic Developers Co-founded by Autopilot Founder</title><content type='html'>&lt;a href="http://mobilemagicdevelopers.com"&gt;&lt;img src="https://lh3.googleusercontent.com/-BEYs51xhegk/TtFLUo_TE4I/AAAAAAAABeo/1-4IzdOcbYo/s220/Mobile%252520Magic%252520Developers.png" alt="Mobile Magic Developers" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;&lt;p&gt;&lt;a href="http://autopilotllc.com"&gt;Autopilot Consulting, LLC&lt;/a&gt; founder Patrick Caldwell has joined forces with long time friend and coworker James Brechtel to found &lt;a href="http://mobilemagicdevelopers.com"&gt;Mobile Magic Developers, LLC&lt;/a&gt;.  &lt;/p&gt;&lt;p&gt;James and I worked together for a few years several years ago and have been close friends ever since.  It was an easy decision to get together and start developing mobile applications.&lt;/p&gt;&lt;p&gt;Mobile Magic Developers focuses on developing fun and exciting mobile products like &lt;a href="http://mobilemagicdevelopers.com/#kids"&gt;educational mobile applications for kids&lt;/a&gt;, &lt;a href="http://mobilemagicdevelopers.com/#widgets"&gt;amusing home screen widgets&lt;/a&gt;, and &lt;a href="http://mobilemagicdevelopers.com/#utilities"&gt;mobile utilities&lt;/a&gt;.  Autopilot Consulting focuses on &amp;hellip; well consulting.&lt;/p&gt;&lt;p&gt;When your company is ready for your own custom mobile application, come talk to &lt;a href="http://autopilotllc.com/services.html#mobile_applications"&gt;Autopilot&lt;/a&gt; about how we can help you wow your customers and outshine your competitors with a mobile application of your own.  We'll apply our mobile experience as well as the talents of Mobile Magic to make sure you get the biggest mobile bang for your buck. &lt;/p&gt;&lt;p&gt;Mobile applications come in several varieties.  Whether you're interested in native mobile applications or mobile skins for your web applications, we have the experience to get you there, save you money, and help you make the best decisions for your business, your customer, and your future.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5420834509807679117?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5420834509807679117/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/11/mobile-magic-developers-co-founded-by.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5420834509807679117'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5420834509807679117'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/11/mobile-magic-developers-co-founded-by.html' title='Mobile Magic Developers Co-founded by Autopilot Founder'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh3.googleusercontent.com/-BEYs51xhegk/TtFLUo_TE4I/AAAAAAAABeo/1-4IzdOcbYo/s72-c/Mobile%252520Magic%252520Developers.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5574013915272685443</id><published>2011-11-16T05:35:00.001-08:00</published><updated>2011-11-16T19:26:11.138-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Mainframe Migration'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Adabas'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='Natural'/><title type='text'>Mainframe Migrations: Source Code Translation Tools</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Mainframe_computer"&gt;&lt;img src="http://upload.wikimedia.org/wikipedia/commons/4/49/Ibm704.gif" alt="Mainframe" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;&lt;p&gt;I was working on a mainframe migration project a while back to move an application from &lt;a href="http://en.wikipedia.org/wiki/ADABAS"&gt;Adabas&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/NATURAL"&gt;Natural&lt;/a&gt; to Sql Server and C#.  &lt;a href="http://www.softwareag.com/corporate/default.asp"&gt;Software AG&lt;/a&gt; developed Adabas and Natural back in the 70's so I was very pleased to have some Software AG developers and managers on the team.&lt;/p&gt;&lt;p&gt;Shortly into the project, one of the managers recommended a product Software AG has that translates Natural code into C#.  Much to the surprise of my teammate, I recommended strongly against the software.  In fact, if you've read any of my other &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-tips-tricks-and.html"&gt;mainframe migration&lt;/a&gt; articles, you know I was recommending against a "migration" strategy altogether (a lift-and-shift my teammate called it) vs. a guided redevelopment strategy.&lt;/p&gt;&lt;p&gt;In the process of making my argument, I spent a fair amount of time positing that a source code translation tool is very little more than hiring a bad programmer because he's fast and cheap.  The customer did decide not to use the translation tool, but in the process of making my argument, my curiosity was piqued (and I do have something of a strong research background) so I put together the &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/09/what-really-makes-good-programmer.html"&gt;What Really Makes a Good Programmer Survey&lt;/a&gt;.&lt;br /&gt;&lt;span style="font-size: 10px; font-style: itallic; color: #666;"&gt;For an in depth discussion of the results, see my &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/10/results-of-what-really-makes-good.html"&gt;Results of the "What Really Makes a Good Programmer" Survey&lt;/a&gt; post.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;For this post, we'll be looking primarily at these data:&lt;script type="text/javascript" src="//ajax.googleapis.com/ajax/static/modules/gviz/1.0/chart.js"&gt; {"chartType":"LineChart","chartName":"What Really Makes a Good Programmer?","dataSourceUrl":"//spreadsheets.google.com/tq?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;range=A31%3AC48&amp;gid=2&amp;transpose=0&amp;headers=1&amp;pub=1","options":{"hAxis":{"showTextEvery":1,slantedText:true,slantedTextAngle:50},"reverseCategories":true,"pointSize":"2","is3D":true,"logScale":false,"wmode":"opaque","title":"What Really Makes a Good Programmer?","height":500,"pointSizeOther":"2","mapType":"hybrid","isStacked":false,"showTip":true,"displayAnnotations":true,"nonGeoMapColors":["#ff9900","#0000ff","#FF9900","#109618","#990099","#0099C6","#DD4477","#66AA00","#B82E2E","#316395"],"dataMode":"markers","titleY":"Importance","maxAlternation":1,"colors":["#ff9900","#073763","#FF9900","#109618","#990099","#0099C6","#DD4477","#66AA00","#B82E2E","#316395"],"smoothLine":true,"width":700,"lineWidth":"2","labelPosition":"right","hasLabelsColumn":true,"legend":"right","allowCollapse":false,"reverseAxis":true},"packages":"corechart","refreshInterval":5} &lt;/script&gt;&lt;br /&gt;If you can't view this chart, I managed to keep a copy of the &lt;a href="https://spreadsheets.google.com/oimg?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;oid=4&amp;zx=t7na7t-4scqjd"&gt;What Really Makes a Good Programmer Chart&lt;/a&gt; from the legacy charts API.&lt;/p&gt;&lt;p&gt;Because most of my respondents were programmers, I like to look at the group averages to eliminate selection bias.  I group the "traits" into the following three categories: &lt;ul&gt;  &lt;li&gt;   Traits that contribute to being a good programmer   &lt;ol&gt;    &lt;li&gt;Has good problem solving skills&lt;/li&gt;    &lt;li&gt;Learns from experience&lt;/li&gt;    &lt;li&gt;Interested in learning&lt;/li&gt;    &lt;li&gt;Passionate&lt;/li&gt;    &lt;li&gt;Sees the big picture&lt;/li&gt;    &lt;li&gt;Recognizes patterns&lt;/li&gt;    &lt;li&gt;Communicates effectively&lt;/li&gt;    &lt;li&gt;Tries new approaches&lt;/li&gt;    &lt;li&gt;Detail oriented&lt;/li&gt;    &lt;li&gt;Seeks help when needed&lt;/li&gt;    &lt;li&gt;Interested in helping others&lt;/li&gt;   &lt;/ol&gt;  &lt;/li&gt;  &lt;li&gt;   Traits that are nice to have if you can get them   &lt;ol&gt;    &lt;li&gt;Fast&lt;/li&gt;    &lt;li&gt;Co-located&lt;/li&gt;   &lt;/ol&gt;  &lt;/li&gt;  &lt;li&gt;   Traits with no effect on programmer quality   &lt;ol&gt;    &lt;li&gt;Has a college degree&lt;/li&gt;    &lt;li&gt;Has a computer science degree&lt;/li&gt;    &lt;li&gt;Cheap&lt;/li&gt;    &lt;li&gt;Has certifications&lt;/li&gt;   &lt;/ol&gt;  &lt;/li&gt; &lt;/ul&gt;&lt;/p&gt;&lt;p&gt;So, back to my original stipulation that "a source code translation tool is very little more than hiring a bad programmer because he's fast and cheap."  While I still believe that statement to be true, when I conducted my survey, I asked the question in a more positive way so I can't actually claim that the tool is a bad programmer.  I believe that it is analogous to a bad programmer; I just can't support that claim with these data.  I'll explain why, but first, for the pedants, statements like "the community represented in my responses appears to believe that having good problem solving skills contributes to being a good programmer" will be abbreviated to "having good problem solving skills contributes to being a good programmer."&lt;/p&gt;&lt;p&gt;That being said, the data suggest that having good problem solving skills contributes to being a good programmer.  If that stipulation is true, then the contrapositive must also be true that not being a good programmer means not having good problem solving skills.  The inverse, however, is not necessarily true so I cannot claim that not having good problem solving skills contributes to not being a good programmer.  I'll let you decide on the validity of that statement, but for the purposes of this post, I'll change my argument to, "using a source code translation tool is little more than hiring a programmer, who lacks the traits of a good programmer, simply because he's fast and cheap."&lt;/p&gt;&lt;p&gt;Think of a translation tool and go through the list of traits.  The translation tool does not have problem solving skills at all, can't learn from experience and certainly isn't interested in doing so, has no passion, and doesn't even know what a big picture is let alone the big picture with your application in your environment.  It may be able to recognize patterns technically, but it likely won't recognize patterns that are meaningful to your application.  It's also unlikely that the tool will communicate effectively, create and try new approaches, or be interested in helping others.&lt;/p&gt;  &lt;p&gt;I suppose you could say it's detail oriented (as long as the details are explicit and don't require problem solving) and it seeks help when needed (likely by way of errors).  It's probably faster than a good programmer, though this may be negated if it's lack of the aforementioned traits produces less than useful results; that is to say as long as you don't need a programmer to fix the translation once it's finished the speed may be beneficial.  Another good thing about the tool is that it's possibly cheaper than a good programmer (see previous caveat) and it's arguably well educated, but that doesn't seem to constitute a good programmer according to the survey.&lt;/p&gt;&lt;p&gt;Software development is an art almost as much as it is an engineering field.  You hear many people talk of &lt;a href="http://en.wikipedia.org/wiki/Software_craftsmanship"&gt;software craftsmanship&lt;/a&gt;.  Allowing a craftsman to rewrite your application in the new framework from scratch using the old application as a guide will allow her to provide a unique expertise that translation tools currently don't have.  She'll be able to apply a high level human analysis that the tool cannot.&lt;/p&gt;&lt;p&gt;For example, imagine there's a bug in the old code?  The programmer can spot and fix the bug, but the tool will translate it into a prettier and newer bug.  What if the old code is doing something unique to that language because the language doesn't have the features of the current language.  A programmer can take advantage of these features and the tool cannot.  A programmer will naturally analyze your business process as the application comes together and will make recommendations that may make your new application even better than the old one was in its heyday; the tool will not.&lt;/p&gt;  &lt;p&gt;I can think of dozens of examples like this and I'm sure you can as well.  Ultimately, I believe that allowing a tool to convert your legacy code into newer legacy code is a waste of time, money, and opportunity and if it doesn't cost more in the short run, it will cost more in the long run.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5574013915272685443?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5574013915272685443/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/11/mainframe-migrations-source-code.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5574013915272685443'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5574013915272685443'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/11/mainframe-migrations-source-code.html' title='Mainframe Migrations: Source Code Translation Tools'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2536249720293367037</id><published>2011-11-15T17:43:00.001-08:00</published><updated>2011-11-15T18:20:03.045-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='dotPeek of the Week'/><title type='text'>Switching on Enums: A Style Observation</title><content type='html'>&lt;a href="http://www.jetbrains.com/decompiler/"&gt;&lt;img src="https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s288/logo_dotpeek.gif" alt="JetBrains dotPeek Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;&lt;p&gt;I was digging through the framework today looking for another good &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html"&gt;dotPeek of the Week&lt;/a&gt; topic.  I was perusing the Lazy&amp;lt;T&amp;gt; class and found an interesting snippet.&lt;/p&gt;&lt;p&gt;This post isn't quite like my previous dotPeek of the Weeks insofar as this is more of an example of what not to do.  This is certainly merely my opinion, but one rule I try to follow when writing code is that more expressive is almost always better than less expressive.  When I was looking at the Lazy class, I found a great example of this.&lt;/p&gt;&lt;p&gt;Here's the code (abridged for clarity &amp;hellip; and also because the threading in this class will make for better discussion later):&lt;pre name="code" class="c#"&gt;private T LazyInitValue()&lt;br /&gt;{&lt;br /&gt;  switch (this.Mode)&lt;br /&gt;  {&lt;br /&gt;    case LazyThreadSafetyMode.None:&lt;br /&gt;      // set the value&lt;br /&gt;      break;&lt;br /&gt;&lt;br /&gt;    case LazyThreadSafetyMode.PublicationOnly:&lt;br /&gt;      // CompareExchange the value&lt;br /&gt;      break;&lt;br /&gt;&lt;br /&gt;    default:&lt;br /&gt;      // lock and set values&lt;br /&gt;      break;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;Is there anything you notice about this code?  Perhaps any unanswered questions as you read it and try to figure out what it does?  Specifically, what exactly constitutes the default case?&lt;/p&gt;&lt;p&gt;As I read through this code, learning about some of the interesting thread safety techniques, I found myself pondering, "why would locking be the default behavior?  In fact, what is the default case?  Do the default values have something in common?"&lt;/p&gt;&lt;p&gt;I looked at the enum LazyThreadSafetyMode and found this:&lt;pre name="code" class="c#"&gt;public enum LazyThreadSafetyMode&lt;br /&gt;{&lt;br /&gt;  None,&lt;br /&gt;  PublicationOnly,&lt;br /&gt;  ExecutionAndPublication,&lt;br /&gt;}&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;That's when I decided that, in most cases, when you're switching on a reasonably small set of values, it's best to express these values explicitly so that the people (including you) who have to maintain the code can better understand why the default case is the default case &amp;hellip; even if there are no comments.&lt;/p&gt;&lt;p&gt;For example, the following code is functionally equivalent:&lt;pre name="code" class="c#"&gt;private T LazyInitValue()&lt;br /&gt;{&lt;br /&gt;  switch (this.Mode)&lt;br /&gt;  {&lt;br /&gt;    case LazyThreadSafetyMode.None:&lt;br /&gt;      // set the value&lt;br /&gt;      break;&lt;br /&gt;&lt;br /&gt;    case LazyThreadSafetyMode.PublicationOnly:&lt;br /&gt;      // CompareExchange the value&lt;br /&gt;      break;&lt;br /&gt;&lt;br /&gt;    case LazyThreadSafetyMode.ExecutionAndPublication:&lt;br /&gt;      // lock and set values&lt;br /&gt;      break;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;Personally, I find the latter example much more expressive.  It's obvious to me what the three cases are and I don't have to wonder what possible values can become the default case.  In fact, I may even go so far as having the default case throw an exception in this class.  I'd do this so that, for whatever reason, if someone were to change the LazyThreadSafetyMode enum and not implement that case for the new values in Lazy&amp;lt;T&amp;gt;.LazyInitValue(), they'd get an exception in testing instead of incorrectly using the default functionality.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2536249720293367037?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2536249720293367037/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/11/switching-on-enums-style-observation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2536249720293367037'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2536249720293367037'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/11/switching-on-enums-style-observation.html' title='Switching on Enums: A Style Observation'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s72-c/logo_dotpeek.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2725748891530363585</id><published>2011-11-12T06:23:00.001-08:00</published><updated>2011-11-12T06:50:11.813-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='How to Get a Job'/><category scheme='http://www.blogger.com/atom/ns#' term='Autopilot Consulting'/><title type='text'>Elon University has #1 MBA Program</title><content type='html'>&lt;a href="http://www.elon.edu"&gt;&lt;img src="https://lh5.googleusercontent.com/-q54x49vIQRk/Tr6BoidcqxI/AAAAAAAABdY/ToX0TYombnI/s288/ElonLogo.jpg" alt="Elon University Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;&lt;p&gt;&lt;a href="http://www.elon.edu/e-web/academics/business/mba/"&gt;Elon University's part-time MBA program&lt;/a&gt; deserves a lot of credit for the success of Autopilot Consulting.  &lt;a href="http://www.businessweek.com/"&gt;Bloomberg Businessweek&lt;/a&gt; recently gave the &lt;a href="http://www.elon.edu/e-web/academics/business/"&gt;Martha and Spencer Love School of Business&lt;/a&gt; (LSB) much deserved recognition.&lt;/p&gt;&lt;p&gt;In &lt;a href="http://www.businessweek.com/magazine/the-best-business-schools-of-2011-11102011.html"&gt;The Best Business Schools of 2011&lt;/a&gt;, Elon's MBA program was ranked first among competitors like UCLA, Carnegie Mellon, UC-Berkley, and Rice.&lt;/p&gt;&lt;p&gt;When I was looking for an MBA program to attend, I compared Elon's program with Duke University's and UNC's programs and was blown away by the comparative quality of the LSB program; the experience, friendliness, and helpfulness of the staff and faculty; and the extraordinarily low price tag. &lt;/p&gt;&lt;p&gt;Congratulations to the Elon LSB Faculty, Staff, Students, and Alum on a job well done.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2725748891530363585?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2725748891530363585/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/11/elon-university-has-1-mba-program.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2725748891530363585'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2725748891530363585'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/11/elon-university-has-1-mba-program.html' title='Elon University has #1 MBA Program'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh5.googleusercontent.com/-q54x49vIQRk/Tr6BoidcqxI/AAAAAAAABdY/ToX0TYombnI/s72-c/ElonLogo.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-1268181157679378463</id><published>2011-09-21T09:22:00.000-07:00</published><updated>2011-11-26T12:44:44.364-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Telephone Game'/><category scheme='http://www.blogger.com/atom/ns#' term='Autopilot Consulting'/><category scheme='http://www.blogger.com/atom/ns#' term='Chuck Norris Facts Widget'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><category scheme='http://www.blogger.com/atom/ns#' term='Mobile Magic Developers'/><title type='text'>Cow Says Moo for Android - Teach Your Kids Farm Animal Sounds</title><content type='html'>&lt;a href="http://mobilemagicdevelopers.com/#cow_says_moo"&gt;&lt;img src="https://lh5.googleusercontent.com/-r-mpmD6zHc8/Tnnkw4q4fCI/AAAAAAAABc8/fIHJlchY4Rw/s288/Cow%252520Says%252520Moo%252520Feature%252520Graphic.png" alt="Cow Says Moo Feature Image" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;&lt;p&gt;James, my partner in &lt;a href="http://mobilemagicdevelopers.com"&gt;Mobile Magic Developers&lt;/a&gt;, and I have a handful of &lt;a href="https://market.android.com/developer?pub=Mobile+Magic+Developers"&gt;Android applications&lt;/a&gt; under our belts now.  We loved working on &lt;a href="http://mobilemagicdevelopers.com/#chinese_whispers"&gt;Chinese Whispers&lt;/a&gt;, an idea my wife gave me for a new twist on The Telephone Game.  James wrote an immensely handy application for getting your &lt;a href="http://mobilemagicdevelopers.com/#wifi_passwords"&gt;WiFi Passwords&lt;/a&gt; from your phone.  &lt;/p&gt;&lt;p&gt;I wrote the &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Chuck%20Norris%20Facts%20Widget"&gt;Chuck Norris Fact of the Day Widget&lt;/a&gt; and the &lt;a href="http://mobilemagicdevelopers.com/#jane_austen_quotes"&gt;Jane Austen Fact of the Day Widget&lt;/a&gt;, and James wrote a really nice widget that shows you your last few installed applications called &lt;a href="http://mobilemagicdevelopers.com/#recently_installed_apps"&gt;Latest Apps&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;But&amp;hellip; Then we Wrote Cow Says Moo&lt;/h2&gt;&lt;p&gt;&lt;a href="http://mobilemagicdevelopers.com/#cow_says_moo"&gt;Cow Says Moo&lt;/a&gt; is definitely my favorite so far.  Many of my friends and family are having babies now and my wife and I have one on the way.  I noticed that kids are growing more and more interested in technology and are remarkably capable with it.  My niece loved playing with my Motorola Xoom and I loved the way her face lights up when she touches the screen and something happens.  That's where we got the idea for Cow Says Moo.&lt;/p&gt;&lt;p&gt;Cow Says Moo is an educational game to teach young kids and toddlers about farm animals and the sounds they make.  Each picture is hand illustrated by Emily Upchurch Robinson and when pressed plays farm animal sounds.  You can use the front facing camera on your device (or the rear camera if you prefer) to record video and sound while your child interacts with your mobile device or while you simply delight him or her with the sounds.  You can use the built in media gallery to share your videos with friends and family.  You'll be surprised how much fun you'll have playing Cow Says Moo with your kids and you'll get a lot of cute video footage like I did &amp;quot;User Acceptance Testing&amp;quot;, i.e., &lt;a href="http://www.youtube.com/watch?v=oPgCZmZ4z70"&gt;playing Cow Says Moo with my godson AJ&lt;/a&gt;.&lt;/p&gt;Check out &lt;a href="http://mobilemagicdevelopers.com/#cow_says_moo"&gt;Cow Says Moo&lt;/a&gt;, and as always, feel free to contact us with any comments or bugs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-1268181157679378463?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/1268181157679378463/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/09/cow-says-moo-for-android-teach-your.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1268181157679378463'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1268181157679378463'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/09/cow-says-moo-for-android-teach-your.html' title='Cow Says Moo for Android - Teach Your Kids Farm Animal Sounds'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh5.googleusercontent.com/-r-mpmD6zHc8/Tnnkw4q4fCI/AAAAAAAABc8/fIHJlchY4Rw/s72-c/Cow%252520Says%252520Moo%252520Feature%252520Graphic.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-4319747023507003903</id><published>2011-09-02T08:18:00.000-07:00</published><updated>2011-11-15T18:11:43.916-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='dotPeek of the Week'/><title type='text'>7-bit Encoding with BinaryWriter in .Net</title><content type='html'>&lt;a href="http://www.jetbrains.com/decompiler/"&gt;&lt;img src="https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s288/logo_dotpeek.gif" alt="JetBrains dotPeek Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;At work this week, I had the need to serialize objects and encrypt them while trying to keep the smallest data footprint I could.  I figured the easiest thing to do would be to binary serialize them with the &lt;a href="http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter.aspx"&gt;BinaryFormatter&lt;/a&gt;.  It was, indeed, the easiest thing to do; however, the BinaryFormatter seems to come with a fair amount of overhead.  By the time the BinaryFormatter was finished listing all of the necessary assembly-qualified type names, the 60 bytes of data I wanted to preserve were more than a kilobyte!&lt;br /&gt;&lt;br /&gt;I needed another way so I extended &lt;a href="http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx"&gt;BinaryWriter&lt;/a&gt; (mostly to get it to serialize the types I needed it to) and now my 60 bytes take 68 bytes to serialize.  In the process of writing this class, I looked through the disassembled BinaryWriter using &lt;a href="http://www.jetbrains.com/decompiler/"&gt;JetBrains's dotPeek&lt;/a&gt; and found this little gem (&lt;a href="http://msdn.microsoft.com/en-us/library/system.io.binarywriter.write7bitencodedint.aspx"&gt;Write7BitEncodedInt(int)&lt;/a&gt;) and decided it'd make a great &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html"&gt;dotPeek of the Week&lt;/a&gt;:&lt;pre name="code" class="c#"&gt;protected void Write7BitEncodedInt(int value)&lt;br /&gt;{&lt;br /&gt;  uint num = (uint) value;&lt;br /&gt;&lt;br /&gt;  while (num &amp;gt;= 128U)&lt;br /&gt;  {&lt;br /&gt;    this.Write((byte) (num | 128U));&lt;br /&gt;    num &amp;gt;&amp;gt;= 7;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  this.Write((byte) num);&lt;br /&gt;}&lt;/pre&gt;This is how the &lt;a href="http://msdn.microsoft.com/en-us/library/yzxa6408.aspx"&gt;BinaryWriter.Write(string)&lt;/a&gt; encodes the length of the string.  Thus, if you have a string with fewer than 128 characters, it only takes one byte to encode the length.  In my implementation, I used this to specify the length of collections too.  In fact, when writing a 32 bit signed integer, you should be able to save space for all positive numbers less than 2 ^ 22 and break even (require four bytes) for 2 ^ 29.  Encoding an Int32 that is greater than or equal to 2 ^29 will require five bytes.  Thus, if your integers tend to be smaller than 536,870,912, you'll probably save space encoding this way.  A similar function could be used for a long where all positive values less than 2 ^ 57 will result in at least breaking even.&lt;br /&gt;&lt;br /&gt;Here's how it works:&lt;ol&gt;  &lt;li&gt;Convert the number to an unsigned int so you can do arithmetic on positive numbers (after all, you're just writing bits)&lt;/li&gt;  &lt;li&gt;    While the converted value is greater than or equal to 128 (i.e., 8 bits)    &lt;ol&gt;      &lt;li&gt;Write low 7 bits and put a 1 in the high bit (to indicate to the decoder more bytes are coming)&lt;/li&gt;      &lt;li&gt;Shift the 7 bits you just wrote off the number&lt;/li&gt;    &lt;/ol&gt;  &lt;/li&gt;  &lt;li&gt;When the loop finishes, there will be 7 or fewer bits to write so write them&lt;/li&gt;&lt;/ol&gt;I think this is really clever and very easy.  Reading the data back is a smidgen more complicated (&lt;a href="http://msdn.microsoft.com/en-us/library/system.io.binaryreader.read7bitencodedint.aspx"&gt;Read7BitEncodedInt()&lt;/a&gt;):&lt;pre name="code" class="c#"&gt;protected internal int Read7BitEncodedInt()&lt;br /&gt;{&lt;br /&gt;  // some names have been changed to protect the readability&lt;br /&gt;  int returnValue = 0;&lt;br /&gt;  int bitIndex = 0;&lt;br /&gt;&lt;br /&gt;  while (bitIndex != 35)&lt;br /&gt;  {&lt;br /&gt;    byte currentByte = this.ReadByte();&lt;br /&gt;    returnValue |= ((int) currentByte &amp;amp; (int) sbyte.MaxValue) &amp;lt;&amp;lt; bitIndex;&lt;br /&gt;    bitIndex += 7;&lt;br /&gt;&lt;br /&gt;    if (((int) currentByte &amp;amp; 128) == 0)&lt;br /&gt;      return returnValue;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  throw new FormatException(Environment.GetResourceString("Format_Bad7BitInt32"));&lt;br /&gt;}&lt;/pre&gt;Here's how this works:&lt;ol&gt;  &lt;li&gt;Set up an int to accumulate your data as you read them&lt;/li&gt;  &lt;li&gt;Set up a place to keep track of which 7-bit block you're reading&lt;/li&gt;  &lt;li&gt;While your bit index is less than 35 (i.e., you've read no more than 5 bytes comprising 1 "more bytes" indicator and 7 data bits)    &lt;ol&gt;      &lt;li&gt;Read a byte&lt;/li&gt;      &lt;li&gt;Take the byte you just read and &lt;a href="http://en.wikipedia.org/wiki/Logical_conjunction"&gt;logical conjuction&lt;/a&gt; (bitwise and) it with sbyte.MaxValue (127 or in binary 0111 1111)&lt;/li&gt;      &lt;li&gt;Left shift those 7 bits to the position in which they belong&lt;/li&gt;      &lt;li&gt;Use a &lt;a href="http://en.wikipedia.org/wiki/Logical_disjunction"&gt;logical disjunction&lt;/a&gt; (bitwise or) to write those seven bits into your accumulator int&lt;/li&gt;      &lt;li&gt;Add 7 to your bit index for the next 7 bits you read&lt;/li&gt;      &lt;li&gt;If the current byte you read does not have a 1 in its &lt;a href="http://en.wikipedia.org/wiki/Most_significant_bit"&gt;most significant bit&lt;/a&gt;  (i.e, byte &amp; 1000 0000 == 0)        &lt;ol&gt;          &lt;li&gt;There are no more bytes to read so just return the current accumulator value&lt;/li&gt;        &lt;/ol&gt;      &lt;/li&gt;    &lt;/ol&gt;  &lt;/li&gt;  &lt;li&gt;If you get to this point, you've read a 6th of 5 bytes and it's time to let the user know there was a formatting problem&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-4319747023507003903?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/4319747023507003903/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/09/7-bit-encoding-with-binarywriter-in-net.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4319747023507003903'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4319747023507003903'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/09/7-bit-encoding-with-binarywriter-in-net.html' title='7-bit Encoding with BinaryWriter in .Net'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s72-c/logo_dotpeek.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3107784337297844415</id><published>2011-08-25T19:09:00.000-07:00</published><updated>2011-09-18T08:17:45.943-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='dotPeek of the Week'/><title type='text'>.Net GetHashcode Functions</title><content type='html'>&lt;a href="http://www.jetbrains.com/decompiler/"&gt;&lt;img src="https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s288/logo_dotpeek.gif" alt="JetBrains dotPeek Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Last week, I wrote a post about a &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/net-method-to-combine-hash-codes.html"&gt;.Net method to combine hashcodes&lt;/a&gt;.  In the process of designing that method, I looked into dozens of .Net's GetHashcode implementations.&lt;br /&gt;&lt;br /&gt;If you'd like to know the principles of hashcodes, take a look at the aforementioned article.  This one is part of the &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html"&gt;dotPeek of the Week&lt;/a&gt; series so I'll just be sharing the insight I got from the framework's implementations here.&lt;br /&gt;&lt;br /&gt;First, some really basic ones:&lt;pre name="code" class="c#"&gt;// from Int32&lt;br /&gt;public override int GetHashCode()&lt;br /&gt;{&lt;br /&gt;  return this;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// from Int16&lt;br /&gt;public override int GetHashCode()&lt;br /&gt;{&lt;br /&gt;  return (int) (ushort) this | (int) this &lt;&lt; 16;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// from Int64&lt;br /&gt;public override int GetHashCode()&lt;br /&gt;{&lt;br /&gt;  return (int) this ^ (int) (this &gt;&gt; 32);&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The Int32 implementation easily meets the hashcode requirements by returning itself.  The Int16 copies itself to the top half of the Int32 and returns that.  The Int64 takes the bottom half of itself and XORs it with the top half.  &lt;br /&gt;&lt;br /&gt;This XOR is the first valuable piece of information.  If you look at the logical functions, the XOR produces the best bit twiddling for hashing.  Basically, the &lt;a href="http://en.wikipedia.org/wiki/Truth_table#Exclusive_disjunction"&gt;XOR&lt;/a&gt; produces a relatively even distribution of bits as opposed to the &lt;a href="http://en.wikipedia.org/wiki/Truth_table#Logical_disjunction"&gt;OR&lt;/a&gt; and the &lt;a href="http://en.wikipedia.org/wiki/Truth_table#Logical_conjunction"&gt;AND&lt;/a&gt; operations which will be biased 3 to 1 in one direction or the other.  &lt;br /&gt;&lt;br /&gt;Some more complicated implementations:&lt;pre name="code" class="c#"&gt;// from String&lt;br /&gt;public override unsafe int GetHashCode()&lt;br /&gt;{&lt;br /&gt;  fixed (char* chPtr = this)&lt;br /&gt;  {&lt;br /&gt;    int num1 = 352654597;&lt;br /&gt;    int num2 = num1;&lt;br /&gt;    int* numPtr = (int*) chPtr;&lt;br /&gt;    int length = this.Length;&lt;br /&gt;    while (length &gt; 0)&lt;br /&gt;    {&lt;br /&gt;      num1 = (num1 &amp;lt;&amp;lt; 5) + num1 + (num1 &amp;gt;&amp;gt; 27) ^ *numPtr;&lt;br /&gt;      if (length &amp;gt; 2)&lt;br /&gt;      {&lt;br /&gt;        num2 = (num2 &amp;lt;&amp;lt; 5) + num2 + (num2 &amp;gt;&amp;gt; 27) ^ numPtr[1];&lt;br /&gt;        numPtr += 2;&lt;br /&gt;        length -= 4;&lt;br /&gt;      }&lt;br /&gt;      else&lt;br /&gt;        break;&lt;br /&gt;    }&lt;br /&gt;    return num1 + num2 * 1566083941;&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// from Tuple&amp;lt;&amp;gt;&lt;br /&gt;internal static int CombineHashCodes(int h1, int h2)&lt;br /&gt;{&lt;br /&gt;  return (h1 &amp;lt;&amp;lt; 5) + h1 ^ h2;&lt;br /&gt;}&lt;/pre&gt;These two methods have some really good information in them.  The String.GetHashcode implementation has what's called a &lt;a href="http://en.wikipedia.org/wiki/Rolling_hash"&gt;rolling hash&lt;/a&gt;.  It loops through the characters, does a &lt;a href="http://en.wikipedia.org/wiki/Barrel_shifter"&gt;barrel shift&lt;/a&gt; by 5, and then XORs with the next character.  &lt;br /&gt;&lt;br /&gt;While I liked this, I preferred the simplicity of the &lt;a href="http://en.wikipedia.org/wiki/List_of_hash_functions#Non-cryptographic_hash_functions"&gt;Bernstein Hash&lt;/a&gt;.  The main component of the Bernstein Hash is the (i &amp;lt;&amp;lt; 5) + i.  i &amp;lt;&amp;lt; 5 == i * 32 (except that bit shifting is much faster than multiplying).  &lt;br /&gt;&lt;br /&gt;Thus, i &amp;lt;&amp;lt; 5 + i == 32i + i == 33i.  The Bernstein Hash just takes the current hash value, multiplies it by 33, and XORs in the new value.&lt;br /&gt;&lt;br /&gt;I didn't use 33 in my hash function because I feel like using prime numbers is healthy.  Thus, instead of adding I subtract so my hashcode method is (hash &amp;lt;&amp;lt; 5) - hash ^ value (or 31 * hash ^ value).  This, by the way, is the way Java tends to do it.&lt;br /&gt;&lt;br /&gt;Here are some interesting ones from System.Drawing:&lt;pre name="code" class="c#"&gt;// from Size&lt;br /&gt;public override int GetHashCode()&lt;br /&gt;{&lt;br /&gt;  return this.width ^ this.height;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// from Rectangle&lt;br /&gt;public override int GetHashCode()&lt;br /&gt;{&lt;br /&gt;  return this.X ^ (this.Y &amp;lt;&amp;lt; 13 | (int) ((uint) this.Y &amp;gt;&amp;gt; 19)) ^ (this.Width &amp;lt;&amp;lt; 26 | (int) ((uint) this.Width &amp;gt;&amp;gt; 6)) ^ (this.Height &amp;lt;&amp;lt; 7 | (int) ((uint) this.Height &amp;gt;&amp;gt; 25));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// from Point&lt;br /&gt;public override int GetHashCode()&lt;br /&gt;{&lt;br /&gt;  return this.x ^ this.y;&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3107784337297844415?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3107784337297844415/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/net-gethashcode-functions.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3107784337297844415'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3107784337297844415'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/net-gethashcode-functions.html' title='.Net GetHashcode Functions'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s72-c/logo_dotpeek.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2042342807228321984</id><published>2011-08-18T15:26:00.000-07:00</published><updated>2012-01-25T08:53:56.116-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>.Net Method to Combine Hash Codes</title><content type='html'>&lt;img src="https://lh5.googleusercontent.com/-2hyqRZ6_9pA/Tk2Pm33U08I/AAAAAAAABaI/qJ1tWeMn2lU/s220/XORVenn.png" alt="XOR Venn Diagram" style="float: left; margin-right: 7px;" class="postimage" /&gt;A while back I developed a helper method that I've been using to aid me in computing good hash values from sets of properties for an object.  I was writing a &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html"&gt;dotPeek of the Week&lt;/a&gt; entry about &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/net-gethashcode-functions.html"&gt;.Net GetHashcode implementations&lt;/a&gt; and I decided to spruce up my implementation and post it on my blog.&lt;br /&gt;&lt;br /&gt;I know It's pretty uncommon that you need to override the Equals method, but when you do, you have to take particular care in considering overriding the GetHashCode method as well.  To make this a little easier, I developed a method I call &lt;a href="#CombineHashCodes"&gt;CombineHashCodes()&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Using CombineHashCodes, I can easily compute a well randomized composite hash code based on several parameters from an object.  This curtails a lot of the work involved when dealing with objects that have complicated .Equals overrides.&lt;br /&gt;&lt;br /&gt;Microsoft's &lt;a href="http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx"&gt;documentation on Object.GetHashCode()&lt;/a&gt; lists the following rules:&lt;blockquote&gt;A hash function must have the following properties:&lt;br /&gt;&lt;br /&gt;If two objects compare as equal, the GetHashCode method for each object must return the same value. However, if two objects do not compare as equal, the GetHashCode methods for the two object do not have to return different values.&lt;br /&gt;&lt;br /&gt;The GetHashCode method for an object must consistently return the same hash code as long as there is no modification to the object state that determines the return value of the object's Equals method. Note that this is true only for the current execution of an application, and that a different hash code can be returned if the application is run again.&lt;br /&gt;&lt;br /&gt;For the best performance, a hash function must generate a random distribution for all input.&lt;/blockquote&gt;&lt;br /&gt;Thus, if you override the .Equals method such that it's no longer doing the default reference equality, you probably need to change your GetHashCode implementation.  I've found this is usually a very simple task.  I whipped up a quick example in &lt;a href="http://www.linqpad.net/"&gt;LINQPad&lt;/a&gt;:&lt;pre name="code" class="c#"&gt;void Main()&lt;br /&gt;{&lt;br /&gt; var lauren1 = new Person { FirstName = "Lauren" };&lt;br /&gt; var lauren2 = new Person { FirstName = "Lauren" };&lt;br /&gt; &lt;br /&gt; Console.WriteLine(lauren1.Equals(lauren2));&lt;br /&gt; Console.WriteLine("{0}.GetHashCode() = {1}", "lauren1", lauren1.GetHashCode());&lt;br /&gt; Console.WriteLine("{0}.GetHashCode() = {1}", "lauren2", lauren2.GetHashCode());&lt;br /&gt; &lt;br /&gt; if (lauren1.Equals(lauren2) &amp;amp;&amp;amp; lauren1.GetHashCode() != lauren2.GetHashCode())&lt;br /&gt;  Console.WriteLine("This is bad.  This is very bad.");&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public class Person&lt;br /&gt;{&lt;br /&gt;   public string FirstName { get; set; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;False&lt;br /&gt;lauren1.GetHashCode() = 47858386&lt;br /&gt;lauren2.GetHashCode() = 20006478&lt;br /&gt;*/&lt;/pre&gt;&lt;br /&gt;As one would expect, lauren1 does not equal lauren2 and neither do the hashcodes; however, if we were to override the .Equals method on the Person object to compare just the first name, we'll find that lauren1 and lauren2 will be equal but will have different hashcodes!  This violates the first rule of GetHashCode Club.  Here's an example:&lt;pre name="code" class="c#"&gt;public class Person&lt;br /&gt;{&lt;br /&gt;   public string FirstName { get; set; }&lt;br /&gt;  &lt;br /&gt;   public override bool Equals (object obj)&lt;br /&gt;   {&lt;br /&gt;  var otherPerson = obj as Person;&lt;br /&gt;  &lt;br /&gt;  if (otherPerson == null)&lt;br /&gt;   return false;&lt;br /&gt;   &lt;br /&gt;  return String.Compare(this.FirstName, otherPerson.FirstName, StringComparison.OrdinalIgnoreCase) == 0;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;True&lt;br /&gt;lauren1.GetHashCode() = 33193253&lt;br /&gt;lauren2.GetHashCode() = 37386806&lt;br /&gt;This is bad.  This is very bad.&lt;br /&gt;*/&lt;/pre&gt;&lt;br /&gt;That's why we override GetHashCode as well and something like this will work:&lt;pre name="code" class="c#"&gt;public override int GetHashCode()&lt;br /&gt;{&lt;br /&gt; return FirstName.GetHashCode();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;True&lt;br /&gt;lauren1.GetHashCode() = 2962686&lt;br /&gt;lauren2.GetHashCode() = 2962686&lt;br /&gt;*/&lt;/pre&gt;&lt;br /&gt;That's all well and good (except the .Equals() does a case insensitive compare so "Lauren" and "lauren" will be equal but will result in different hash codes unless you do something like return FirstName.ToLower().GetHashCode().  Why'd I leave that error in the post just to parenthetically correct it . . . because I think it's a good demonstration of how easy it is to screw this up :)).  Consider what happens when you end up with a more complex class that looks like this:&lt;pre name="code" class="c#"&gt;public class Person&lt;br /&gt;{&lt;br /&gt;   public string FirstName { get; set; }&lt;br /&gt; public string LastName { get; set; }&lt;br /&gt; public DateTime BirthDate { get; set; }&lt;br /&gt;  &lt;br /&gt;   public override bool Equals (object obj)&lt;br /&gt;   {&lt;br /&gt;  var otherPerson = obj as Person;&lt;br /&gt;  &lt;br /&gt;  if (otherPerson == null)&lt;br /&gt;   return false;&lt;br /&gt;   &lt;br /&gt;  return String.Compare(FirstName, otherPerson.FirstName, StringComparison.OrdinalIgnoreCase) == 0&lt;br /&gt;   &amp;amp;&amp;amp; String.Compare(LastName, otherPerson.LastName, StringComparison.OrdinalIgnoreCase) == 0&lt;br /&gt;   &amp;amp;&amp;amp; BirthDate.Equals(otherPerson.BirthDate);&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;You could leave the GetHashCode function the way it was previously, but then all Lauren's will be lumped in the same bucket together which violates the third rule about a random distribution.  I've seen people combat this problem by concatenating the string values of the equals fields delimited by some extremely unlikely string and getting the hash code of that.  &lt;br /&gt;&lt;br /&gt;I felt like that was not really the best way to do it in case someone decides a colon is a good character in a first name &lt;a href="http://xkcd.com/327/"&gt;or something worse&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Thus, I wrote something like this (which has recently been modified to what you see now based on my explorations for my &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html"&gt;dotPeek of the Week&lt;/a&gt; series).  This is &lt;a name="CombineHashCodes"&gt;CombineHashCodes&lt;/a&gt;:&lt;pre name="code" class="c#"&gt;public static class HashCodeHelper&lt;br /&gt;{&lt;br /&gt; public static int CombineHashCodes(params object[] args)&lt;br /&gt; {&lt;br /&gt;  return CombineHashCodes(EqualityComparer&amp;lt;object&amp;gt;.Default, args);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public static int CombineHashCodes(IEqualityComparer comparer, params object[] args)&lt;br /&gt; {&lt;br /&gt;  if (args == null) throw new ArgumentNullException("args");&lt;br /&gt;  if (args.Length == 0) throw new ArgumentException("args");&lt;br /&gt;&lt;br /&gt;  int hashcode = 0;&lt;br /&gt;&lt;br /&gt;  unchecked&lt;br /&gt;  {&lt;br /&gt;   foreach (var arg in args)&lt;br /&gt;    hashcode = (hashcode &amp;lt;&amp;lt; 5) - hashcode ^ comparer.GetHashCode(arg);&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  return hashcode;&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;With this method, you can get a nice pseudo-random distribution of hash codes without violating any of the GetHashCode rules as long as you include the values relevant to your Equals method in your call to CombineHashCodes:&lt;pre name="code" class="c#"&gt;public override int GetHashCode()&lt;br /&gt;{&lt;br /&gt; return HashCodeHelper.CombineHashCodes(FirstName.ToLower(), LastName.ToLower(), BirthDate);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;True&lt;br /&gt;lauren1.GetHashCode() = -891990792&lt;br /&gt;lauren2.GetHashCode() = -891990792&lt;br /&gt;*/&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2042342807228321984?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2042342807228321984/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/net-method-to-combine-hash-codes.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2042342807228321984'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2042342807228321984'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/net-method-to-combine-hash-codes.html' title='.Net Method to Combine Hash Codes'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh5.googleusercontent.com/-2hyqRZ6_9pA/Tk2Pm33U08I/AAAAAAAABaI/qJ1tWeMn2lU/s72-c/XORVenn.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-1359872199899749578</id><published>2011-08-15T14:14:00.001-07:00</published><updated>2011-09-18T08:18:21.153-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='dotPeek of the Week'/><title type='text'>.Net Optimization for Int32</title><content type='html'>&lt;a href="http://www.jetbrains.com/decompiler/"&gt;&lt;img src="https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s288/logo_dotpeek.gif" alt="JetBrains dotPeek Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Another entry in the &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html"&gt;dotPeek of the Week&lt;/a&gt; series here.  I was digging through some .GetHashcode() implementations (expect that as the next dotPeek of the week) and noticed some interesting implementations of overridden .Equals() methods.&lt;br /&gt;&lt;br /&gt;I found this in Int16:&lt;pre name="code" class="c#"&gt;public override bool Equals(object obj)&lt;br /&gt;{&lt;br /&gt;  if (!(obj is short))&lt;br /&gt;    return false;&lt;br /&gt;  else&lt;br /&gt;    return (int) this == (int) (short) obj;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public bool Equals(short obj)&lt;br /&gt;{&lt;br /&gt;  return (int) this == (int) obj;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;So, I checked Byte and sure enough:&lt;pre name="code" class="c#"&gt;public override bool Equals(object obj)&lt;br /&gt;{&lt;br /&gt;  if (!(obj is byte))&lt;br /&gt;    return false;&lt;br /&gt;  else&lt;br /&gt;    return (int) this == (int) (byte) obj;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public bool Equals(byte obj)&lt;br /&gt;{&lt;br /&gt;  return (int) this == (int) obj;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Can you guess how Char.Equals() is implemented?&lt;pre name="code" class="c#"&gt;public override bool Equals(object obj)&lt;br /&gt;{&lt;br /&gt;  if (!(obj is char))&lt;br /&gt;    return false;&lt;br /&gt;  else&lt;br /&gt;    return (int) this == (int) (char) obj;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public bool Equals(char obj)&lt;br /&gt;{&lt;br /&gt;  return (int) this == (int) obj;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Even the unsigned int gets cast as a signed int in UInt.Equals().  I was pretty curious, so I looked at some other methods:&lt;pre name="code" class="c#"&gt;public int CompareTo(short value)&lt;br /&gt;{&lt;br /&gt;  return (int) this - (int) value;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public int CompareTo(byte value)&lt;br /&gt;{&lt;br /&gt;  return (int) this - (int) value;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public int CompareTo(char value)&lt;br /&gt;{&lt;br /&gt;  return (int) this - (int) value;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;I took note and discussed it with a few friends.  It seems to make sense that on a 32 bit operating system, it'd be optimal to work with 32 bit integers.  It turns out, even on a 64 bit architecture, .net is optimized for the Int32.  I found this gem online:&lt;blockquote&gt;Best Practices: Optimizing performance with built-in types&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The runtime optimizes the performance of 32-bit integer types (Int32 and UInt32), so use those types for counters and other frequently accessed integral variables.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;For floating-point operations, Double is the most efficient type because those operations are optimized by hardware.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;MCTS Self-Paced Training Kit (Exam 70-536): Microsoft® .NET Framework 2.0—Application Development Foundation&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;So storing integer values, no matter the ceiling of your expected value, is best done using Int32, particularly if they'll be operated on heavily like an index to a collection or as a counter in an iteration control structure.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-1359872199899749578?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/1359872199899749578/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/net-optimization-for-int32.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1359872199899749578'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1359872199899749578'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/net-optimization-for-int32.html' title='.Net Optimization for Int32'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s72-c/logo_dotpeek.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-7722998959078024183</id><published>2011-08-08T15:58:00.000-07:00</published><updated>2011-09-18T08:18:39.629-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='IEnumerable'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><category scheme='http://www.blogger.com/atom/ns#' term='dotPeek of the Week'/><title type='text'>Enumerable.Any vs. Enumerable.Count</title><content type='html'>&lt;a href="http://www.jetbrains.com/decompiler/"&gt;&lt;img src="https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s288/logo_dotpeek.gif" alt="JetBrains dotPeek Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;This is the innagural entry into the section of my blog I'm calling "&lt;a href="http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html"&gt;dotPeek of the Week&lt;/a&gt;."  Workload permitting, it will be a weekly thing where I'll be using &lt;a href="http://www.jetbrains.com/decompiler/"&gt;JetBrain's dotPeek - a &lt;i&gt;free&lt;/i&gt; .NET decompiler&lt;/a&gt; to learn more about the .NET framework and share any interesting things I come up with.&lt;br /&gt;&lt;br /&gt;In this post, I'll be sharing a little tip I picked up from my friend David Govek.  &lt;br /&gt;&lt;br /&gt;I can't .Count() the number of times I've seen a line of code like this one:&lt;pre name="code" class="c#"&gt;if (someEnumerable.Count() &amp;gt; 0) &lt;br /&gt;    doSomething();&lt;/pre&gt;&lt;br /&gt;I know I've done that myself a handful of times.  I think it comes from the pre-.net 3.5 days when you were used to dealing with ICollections that had a .Count property that returned the value of a private field:&lt;pre name="code" class="c#"&gt;public virtual int Count { get { return this._size; } }&lt;/pre&gt;&lt;br /&gt;When 3.5 came out, the &lt;a href="http://msdn.microsoft.com/en-us/library/system.linq.enumerable.aspx"&gt;System.Linq.Enumerable&lt;/a&gt; class brought with it a &lt;a href="http://msdn.microsoft.com/en-us/library/bb338038.aspx"&gt;Count extension method&lt;/a&gt;.  I believe it was at that point that people started using .Count() everywhere, including on IEnumerables, which previously didn't have a method for getting the length of the enumerable.&lt;br /&gt;&lt;br /&gt;Most of the time, there was very little pain because the engineers over at Microsoft were clever enough to help us out.  The first thing they try to do in the .Count extension method is check to see if the IEnumerable is an ICollection.  If it is, they just use the Count property which we already know is plenty fast; however, if it's not an ICollection, they have to iterate the Enumerable and count the elements.&lt;br /&gt;&lt;br /&gt;Here's what that looks like:&lt;pre name="code" class="c#"&gt;public static int Count&amp;lt;TSource&amp;gt;(this IEnumerable&amp;lt;TSource&amp;gt; source)&lt;br /&gt;{&lt;br /&gt;  if (source == null)&lt;br /&gt;    throw Error.ArgumentNull("source");&lt;br /&gt;&lt;br /&gt;  ICollection&amp;lt;TSource&amp;gt; collection1 = source as ICollection&amp;lt;TSource&amp;gt;;&lt;br /&gt;  if (collection1 != null)&lt;br /&gt;    return collection1.Count;&lt;br /&gt;&lt;br /&gt;  ICollection collection2 = source as ICollection;&lt;br /&gt;  if (collection2 != null)&lt;br /&gt;    return collection2.Count;&lt;br /&gt;&lt;br /&gt;  int num = 0;&lt;br /&gt;  using (IEnumerator&lt;tsource&gt; enumerator = source.GetEnumerator())&lt;br /&gt;  {&lt;br /&gt;    while (enumerator.MoveNext())&lt;br /&gt;      checked { ++num; }&lt;br /&gt;  }&lt;br /&gt;  return num;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;If your source is indeed an Enumerable, this is still a fine way to find out how many items there are.  The problem is, in the sample code where we're just simply checking to ensure that the Enumerable isn't empty, using .Count can prove costly.  Checking that the count is greater than 0 has to enumerate the entire collection and count each item despite the fact that we know it's not empty as soon as we spot the first element.&lt;br /&gt;&lt;br /&gt;Fortunately, the clever folks at Microsoft thought of this and also gave us &lt;a href="http://msdn.microsoft.com/en-us/library/bb337697.aspx"&gt;.Any()&lt;/a&gt;.  This extension method simply gets the Enumerator, calls &lt;a href="http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.movenext.aspx"&gt;.MoveNext()&lt;/a&gt;, and disposes the Enumerator.  MoveNext tries to move to the next element in the collection and returns true until it passes the end of the collection.&lt;br /&gt;&lt;br /&gt;Here's what the .Any() method looks like:&lt;pre name="code" class="c#"&gt;public static bool Any&lt;tsource&gt;(this IEnumerable&amp;lt;TSource&amp;gt; source)&lt;br /&gt;{&lt;br /&gt;  if (source == null)&lt;br /&gt;    throw Error.ArgumentNull("source");&lt;br /&gt;&lt;br /&gt;  using (IEnumerator&amp;lt;TSource&amp;gt; enumerator = source.GetEnumerator())&lt;br /&gt;  {&lt;br /&gt;    if (enumerator.MoveNext())&lt;br /&gt;      return true;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  return false;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Thus, there's no need to Enumerate the entire Enumerable if you can use .Any() like this refactored code:&lt;pre name="code" class="c#"&gt;if (someEnumerable.Any()) &lt;br /&gt;    doSomething();&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-7722998959078024183?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/7722998959078024183/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/enumerableany-vs-enumerablecount.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7722998959078024183'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7722998959078024183'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/enumerableany-vs-enumerablecount.html' title='Enumerable.Any vs. Enumerable.Count'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s72-c/logo_dotpeek.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2439509256901469479</id><published>2011-08-07T10:23:00.000-07:00</published><updated>2011-09-18T08:17:23.457-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='dotPeek of the Week'/><title type='text'>About dotPeek of the Week</title><content type='html'>&lt;a href="http://www.jetbrains.com/decompiler/"&gt;&lt;img src="https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s288/logo_dotpeek.gif" alt="JetBrains dotPeek Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Yesterday, I posted the first post of a series I call the &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/dotPeek%20of%20the%20Week"&gt;dotPeek of the Week&lt;/a&gt;.  Why am I using a &lt;a href="http://www.jetbrains.com/"&gt;JetBrains&lt;/a&gt; product name in my blog?  Well, 'cause I like JetBrains.  I love &lt;a href="http://www.jetbrains.com/resharper"&gt;ReSharper&lt;/a&gt; for C#, &lt;a href="http://www.jetbrains.com/idea"&gt;IntelliJ&lt;/a&gt; for Android development, and of course, dotPeek for decompiling .Net code.&lt;br /&gt;&lt;br /&gt;I also want to support JetBrains by encouraging people to use dotPeek so that they'll keep it free, unlike that other one that got bought by that one company and now is a paid app.  Hopefully, I won't have to change the name of this section any time soon :).&lt;br /&gt;&lt;br /&gt;The other (and primary) objective of this exercise is to learn more about the .Net framework and how it functions.  If I find something interesting, I'll share it here on my blog.&lt;br /&gt;&lt;h2&gt;Latest dotPeek of the Weeks&lt;/h2&gt;&lt;ul id="labelPosts"&gt;&lt;li&gt;&lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/dotPeek%20of%20the%20Week"&gt;All dotPeek of the Week posts&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;script type="text/javascript"&gt;  function showFeedOnLoad(json) {    $(function(){       $labelPosts = $("#labelPosts");      $.each(json.feed.entry, function(i, o) {         $labelPosts.append(getFeedListItem(o));      });    });  }  function getFeedListItem(entry) {    var a = $("&lt;a /&gt;").attr("href", findPostLink(entry.link)).append(entry.title.$t);    var li = $("&lt;li /&gt;").append(a);    return li;  }  function findPostLink(links) {    for (var i = 0; i &lt; links.length; i++)      if (links[i].rel == "alternate")        return links[i].href;  }&lt;/script&gt;&lt;script type="text/javascript" src="http://dpatrickcaldwell.blogspot.com/feeds/posts/default/-/dotPeek%20of%20the%20Week?alt=json-in-script&amp;callback=showFeedOnLoad"&gt;&lt;/script&gt; &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2439509256901469479?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2439509256901469479/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2439509256901469479'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2439509256901469479'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/08/dotpeek-of-week.html' title='About dotPeek of the Week'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh4.googleusercontent.com/-z2FPs5-cPmE/TnYJj9EamoI/AAAAAAAABcY/jGrV72r_zvs/s72-c/logo_dotpeek.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5785840948542554011</id><published>2011-07-25T08:00:00.000-07:00</published><updated>2011-07-25T08:00:10.529-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='IEnumerable'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>C# Yield Keyword, IEnumerable&lt;T&gt;, and Infinite Enumerations</title><content type='html'>&lt;img src="https://lh6.googleusercontent.com/-6TFfjENWyAg/TiXT4jWOJnI/AAAAAAAABYs/slFfYkYwrbE/s288/Matryoshka.jpg" alt="Matryoshka Dolls" style="float: left; margin-right: 7px;" class="postimage" /&gt;Visual Studio 2005 came with a slew of .net and .net compiler features.  One of those features that I particularly enjoy is the &lt;a href="http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx#Y300"&gt;yield keyword&lt;/a&gt;.  It was Microsoft's way of building IEnumerable (or IEnumerator) classes and generic classes around your &lt;a href="http://msdn.microsoft.com/en-us/library/dscyy5s0.aspx"&gt;iterator code block&lt;/a&gt;.  I'm not going to discuss the yield keyword much in this post because the feature has been around a long time now and the internet is replete with discussions on the topic.&lt;br /&gt;&lt;br /&gt;Despite the long life of the yield keyword, I still find it conspicuous when I see it in projects I work on.  I suppose it's just rare that I find myself writing my own enumerable.  As a result, when I see &lt;i&gt;yield return&lt;/i&gt;, it tends to stand out.  I started looking around to see how the rest of the programming world uses the yield keyword wondering if I was under-utilizing the flexibility provided.&lt;br /&gt;&lt;br /&gt;Specifically, I wondered if I was missing out on the lazy nature of the iterator and the numerous linq extension methods optimized to take advantage of that aspect of iterators.  What I mean by that is that an iterator doesn't need to store each sequential value in memory the way a collection would and thus you can use each value without necessarily increasing the memory overhead.  Further, you can take advantage of calculations which tend to already be sequential in nature (like the Fibbonacci sequence for example).&lt;br /&gt;&lt;br /&gt;The second thing I thought about was an infinite (well, sort of infinite) enumerable.  I'm not sure if I feel like it's a bad idea or not so I wrote these examples to be unending?  I may eventually find a use for such an iterator and then get burned when someone tries to call Fibbonacci.Min() and the application throws an overflow exception, but I suppose at that point I'll make it a method and take a sanity check variable.&lt;br /&gt;&lt;br /&gt;In the meantime, here are a few examples of some iterators I thought were interesting and fun challenges:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;static IEnumerable&amp;lt;ulong&amp;gt; Fibbonacci&lt;br /&gt;{&lt;br /&gt;    get&lt;br /&gt;    {&lt;br /&gt;        yield return 0;&lt;br /&gt;        yield return 1;&lt;br /&gt;&lt;br /&gt;        ulong previous = 0, current = 1;&lt;br /&gt;        while (true)&lt;br /&gt;        {&lt;br /&gt;            ulong swap = checked(previous + current);&lt;br /&gt;            previous = current;&lt;br /&gt;            current = swap;&lt;br /&gt;            yield return current;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static IEnumerable&amp;lt;long&amp;gt; EnumerateGeometricSeries(long @base)&lt;br /&gt;{&lt;br /&gt;    yield return 1;&lt;br /&gt;&lt;br /&gt;    long accumulator = 1;&lt;br /&gt;    while (true)&lt;br /&gt;        yield return accumulator = checked(accumulator * @base);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static IEnumerable&amp;lt;ulong&amp;gt; PrimeNumbers&lt;br /&gt;{&lt;br /&gt;    get&lt;br /&gt;    {&lt;br /&gt;        var prime = 0UL;&lt;br /&gt;        while (true)&lt;br /&gt;            yield return prime = prime.GetNextPrime();&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static IEnumerable&amp;lt;List&amp;lt;uint&amp;gt;&amp;gt; PascalsTriangle&lt;br /&gt;{&lt;br /&gt;    get&lt;br /&gt;    {&lt;br /&gt;        var row = new List&amp;lt;uint&amp;gt; { 1 };&lt;br /&gt;        yield return row;&lt;br /&gt;&lt;br /&gt;        while (true)&lt;br /&gt;        {&lt;br /&gt;            var last = row[0];&lt;br /&gt;            for (var i = 1; i &amp;lt; row.Count; i++)&lt;br /&gt;            {&lt;br /&gt;                var current = row[i];&lt;br /&gt;                row[i] = current + last;&lt;br /&gt;                last = current;&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            row.Add(1);&lt;br /&gt;            yield return row;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Some of these methods use a few extension methods I adapted from places in the .net framework:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;public static ulong GetNextPrime(this ulong from)&lt;br /&gt;{&lt;br /&gt;    for (var j = from + 1 | 1UL; j &amp;lt; ulong.MaxValue; j += 2)&lt;br /&gt;        if (j.IsPrime())&lt;br /&gt;            return j;&lt;br /&gt;&lt;br /&gt;    return from;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static bool IsPrime(this ulong value)&lt;br /&gt;{&lt;br /&gt;    if ((value &amp;amp; 1) != 0)&lt;br /&gt;    {&lt;br /&gt;        var squareRoot = (ulong)Math.Sqrt((double)value);&lt;br /&gt;        for (ulong i = 3; i &amp;lt;= squareRoot; i += 2)&lt;br /&gt;            if (value % i == 0)&lt;br /&gt;                return false;&lt;br /&gt;&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;    return value == 2;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;To keep the test console app clean, of course, I used my favorite &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/07/extension-method-to-replace-foreach.html"&gt;IEnumerable.Each() extension method&lt;/a&gt;:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;public static void Each&amp;lt;T&amp;gt;(this IEnumerable&amp;lt;T&amp;gt; enumerable, Action&amp;lt;T&amp;gt; action)&lt;br /&gt;{&lt;br /&gt;    foreach (var element in enumerable)&lt;br /&gt;        action(element);&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Here's the sample code:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;static void Main()&lt;br /&gt;{&lt;br /&gt;    Fibbonacci.Skip(10).Take(10).Each(Console.WriteLine);&lt;br /&gt;    Console.WriteLine();&lt;br /&gt;&lt;br /&gt;    EnumerateGeometricSeries(2).Take(10).Each(Console.WriteLine);&lt;br /&gt;    Console.WriteLine();&lt;br /&gt;&lt;br /&gt;    PrimeNumbers.Where(p =&amp;gt; p &amp;gt; 600).Take(10).Each(Console.WriteLine);&lt;br /&gt;    Console.WriteLine();&lt;br /&gt;&lt;br /&gt;    foreach (var row in PascalsTriangle.Take(10))&lt;br /&gt;    {&lt;br /&gt;        row.Each(element =&amp;gt; Console.Write("{0} ", element)); &lt;br /&gt;        Console.WriteLine();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    Console.ReadLine();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// The second set of 10 elements in the Fibbonacci sequence&lt;br /&gt;// 55 89 144 233 377 610 987 1597 2584 4181&lt;br /&gt;&lt;br /&gt;// Base of 2 to the first 10 powers&lt;br /&gt;// 1 2 4 8 16 32 64 128 256 512&lt;br /&gt;&lt;br /&gt;// The first 10 prime numbers greater than 600&lt;br /&gt;// 601 607 613 617 619 631 641 643 647 653&lt;br /&gt;&lt;br /&gt;// The first 10 rows of Pascal's Triangle&lt;br /&gt;// 1&lt;br /&gt;// 1 1&lt;br /&gt;// 1 2 1&lt;br /&gt;// 1 3 3 1&lt;br /&gt;// 1 4 6 4 1&lt;br /&gt;// 1 5 10 10 5 1&lt;br /&gt;// 1 6 15 20 15 6 1&lt;br /&gt;// 1 7 21 35 35 21 7 1&lt;br /&gt;// 1 8 28 56 70 56 28 8 1&lt;br /&gt;// 1 9 36 84 126 126 84 36 9 1&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5785840948542554011?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5785840948542554011/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/07/c-yield-keyword-ienumerable-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5785840948542554011'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5785840948542554011'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/07/c-yield-keyword-ienumerable-and.html' title='C# Yield Keyword, IEnumerable&amp;lt;T&amp;gt;, and Infinite Enumerations'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh6.googleusercontent.com/-6TFfjENWyAg/TiXT4jWOJnI/AAAAAAAABYs/slFfYkYwrbE/s72-c/Matryoshka.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-980587828311137748</id><published>2011-07-20T07:20:00.000-07:00</published><updated>2011-07-20T07:20:11.085-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>.Net ObjectFormatter - Using Tokens in a Format String</title><content type='html'>&lt;i&gt;If you've already read this article and you don't feel like scrolling through my sample formats, you can jump directly to &lt;a href="https://github.com/tncbbthositg/ObjectFormatter"&gt;ObjectFormatter on github&lt;/a&gt; to get the source.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;At some point in almost every business application, it seems you eventually run into the ubiquitous email notifications requirement.  Suddenly, in the middle of what was once a pleasant and enjoyable project come the dozens of email templates with &amp;laquo;guillemets&amp;raquo; marking the myriad fields which will need replacing with data values.  &lt;br /&gt;&lt;br /&gt;You concoct some handy way of storing these templates in a database, on the file system, or in resource files.  You compose your many String.Format() statements with the dozens of variables required to format the email templates and you move on to the greener pastures of application development.&lt;br /&gt;&lt;br /&gt;Now, you've got a dozen email templates like this one:&lt;br /&gt;&lt;blockquote&gt;Dear {0},&lt;br /&gt;&lt;br /&gt;{1} has created a {2} task for your approval.  This task must be reviewed between {3} and {4} to be considered for final approval.&lt;br /&gt;&lt;br /&gt;This is an automagically generated email sent from an unmonitored email address.  Please do not reply to this message.  No, seriously . . . stop that.  Nobody is going to read what you are typing right now.  Don't you dare touch that send button.  Stop right now.  I hate you.  I wish I could hate you to death.&lt;br /&gt;&lt;br /&gt;Thank you,&lt;br /&gt;The Task Approval Team&lt;/blockquote&gt;&lt;br /&gt;No big deal, everything is going swimmingly, and the application goes into beta.  Then, it turns out, the stakeholders don't want an email template that looks like that.  That was more of a draft really.  Besides, you should've already known what they wanted in the template to begin with.  After all, it's like you have ESPN or something.&lt;br /&gt;&lt;br /&gt;It's important to add information about the user for whom this action is taking place, so this is your new template:&lt;br /&gt;&lt;blockquote&gt;Dear {0},&lt;br /&gt;&lt;br /&gt;{1} has created a {2} task for your approval regarding {5}({6}).  This task must be reviewed between {3} and {4} to be considered for final approval.&lt;br /&gt;&lt;br /&gt;If you have questions, please contact your approvals management supervisor {7}.&lt;br /&gt;&lt;br /&gt;This is an automagically generated email sent from an unmonitored email address.  Please do not reply to this message.  No, seriously . . . stop that.  Nobody is going to read what you are typing right now.  Don't you dare touch that send button.  Stop right now.  I hate you.  I wish I could hate you to death.&lt;br /&gt;&lt;br /&gt;Thank you,&lt;br /&gt;The Task Approval Team&lt;/blockquote&gt;&lt;br /&gt;So far so good.  You've updated the template, updated your String.Format() parameters, passed QA and gone into production.  But, now that users are actually hitting the system, it turns out that you need a few more changes.  Specifically, you need to add contact information for the supervisor, remove the originator of the task, and by the way, what kind of sense does it make to put a low end limit on a deadline?  Here's your new template:&lt;br /&gt;&lt;blockquote&gt;Dear {0},&lt;br /&gt;&lt;br /&gt;A {2} task for {5}({6}) is awaiting your approval.  This task must be reviewed by {4} to be considered for final approval.&lt;br /&gt;&lt;br /&gt;If you have questions, please contact your approvals management supervisor {7} at {1}.&lt;br /&gt;&lt;br /&gt;This is an automagically generated email sent from an unmonitored email address.  Please do not reply to this message.  No, seriously . . . stop that.  Nobody is going to read what you are typing right now.  Don't you dare touch that send button.  Stop right now.  I hate you.  I wish I could hate you to death.&lt;br /&gt;&lt;br /&gt;Thank you,&lt;br /&gt;The Task Approval Team&lt;/blockquote&gt;&lt;br /&gt;Now you have an email template format with various numbers all over the place, a String.Format() call with more parameters than there are tokens, and you have to go through the QA - deployment cycle again.  &lt;br /&gt;&lt;br /&gt;I've gone through this process on almost every application throughout my career as a software engineer.  Hence the ObjectFormatter.  Now, my email template looks like this:&lt;br /&gt;&lt;blockquote&gt;Dear &lt;i&gt;{Employee.FullName}&lt;/i&gt;,&lt;br /&gt;&lt;br /&gt;A &lt;i&gt;{Task.Description}&lt;/i&gt; task for &lt;i&gt;{TargetUser.FullName}&lt;/i&gt;(&lt;i&gt;{TargetUser.UserId}&lt;/i&gt;) is awaiting your approval.  This task must be reviewed by &lt;i&gt;{DueDate}&lt;/i&gt; to be considered for final approval.&lt;br /&gt;&lt;br /&gt;If you have questions, please contact your approvals management supervisor &lt;i&gt;{Supervisor.FullName}&lt;/i&gt; at &lt;i&gt;{Supervisor.PhoneNumber}&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;This is an automagically generated email sent from an unmonitored email address.  Please do not reply to this message.  No, seriously . . . stop that.  Nobody is going to read what you are typing right now.  Don't you dare touch that send button.  Stop right now.  I hate you.  I wish I could hate you to death.&lt;br /&gt;&lt;br /&gt;Thank you,&lt;br /&gt;The Task Approval Team&lt;/blockquote&gt;&lt;br /&gt;I find that the ObjectFormatter makes my templating much easier to maintain and much more flexible.  It also usually makes my calling code a lot cleaner.  Here's an example of the approaches you could take to populate the sample templates:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;// plain string formatting&lt;br /&gt;String.Format(template, Employee.FullName, Supervisor.PhoneNumber, Task.Description, String.Empty, DueDate, TargetUser.FullName, TargetUser.UserId);&lt;br /&gt;&lt;br /&gt;// if you have a dto already built&lt;br /&gt;ObjectFormatter.Format(template, myDto);&lt;br /&gt;&lt;br /&gt;// if you don't have a dto built&lt;br /&gt;ObjectFormatter.Format(template, new { Employee, Supervisor, Task, DueDate, TargetUser });&lt;/pre&gt;&lt;br /&gt;I've found that most of the time they ask for template changes, they want me to add some value that is already a property on an object that's already in my object graph because of the current email template.  That way, when they come tell me they want he target user's name formatted differently, I don't even need to recompile (well, sometimes I do . . . I mean, I can't predict everything).  I can implement a lot of changes by using objects I already know I'm passing into the ObjectFormatter.Format() method.  Here's the new template with the changes and I didn't have to change a line of code to make it work:&lt;br /&gt;&lt;blockquote&gt;Dear &lt;i&gt;{Employee.FullName}&lt;/i&gt;,&lt;br /&gt;&lt;br /&gt;A &lt;i&gt;{Task.Description}&lt;/i&gt; task for &lt;i&gt;{TargetUser.LastName}&lt;/i&gt;, &lt;i&gt;{TargetUser.FirstName}&lt;/i&gt;(&lt;i&gt;{TargetUser.UserId}&lt;/i&gt;) is awaiting your approval.  This task must be reviewed by &lt;i&gt;{DueDate}&lt;/i&gt; to be considered for final approval.&lt;br /&gt;&lt;br /&gt;If you have questions, please contact your approvals management supervisor &lt;i&gt;{Supervisor.FullName}&lt;/i&gt; at &lt;i&gt;{Supervisor.PhoneNumber}&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;This is an automagically generated email sent from an unmonitored email address.  Please do not reply to this message.  No, seriously . . . stop that.  Nobody is going to read what you are typing right now.  Don't you dare touch that send button.  Stop right now.  I hate you.  I wish I could hate you to death.&lt;br /&gt;&lt;br /&gt;Thank you,&lt;br /&gt;The Task Approval Team&lt;/blockquote&gt;&lt;br /&gt;If you'd like to check out the source or use the ObjectFormatter in your own projects, look for &lt;a href="https://github.com/tncbbthositg/ObjectFormatter"&gt;ObjectFormatter on github&lt;/a&gt;.  If you make any cool changes, please let me know and I'll try to figure out how to merge them into the repository.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-980587828311137748?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/980587828311137748/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/07/net-objectformatter-using-tokens-in.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/980587828311137748'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/980587828311137748'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/07/net-objectformatter-using-tokens-in.html' title='.Net ObjectFormatter - Using Tokens in a Format String'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-4850305297734162912</id><published>2011-07-19T16:45:00.000-07:00</published><updated>2011-07-19T19:46:53.024-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Extension Method to Replace foreach With Lambda Expression</title><content type='html'>It's pretty often I find myself looping through some enumerable and performing an action on the elements.  Sometimes it's just displaying results with Console.WriteLine.  Other times I need to do something a little more complicated.  In any case, every once in a while, I feel like the foreach statement and the for statement aren't really quite expressive enough.  &lt;br /&gt;&lt;br /&gt;That's why I have this little guy:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;public static void Each&amp;lt;T&amp;gt;(this IEnumerable&amp;lt;T&amp;gt; enumerable, Action&amp;lt;T&amp;gt; action)&lt;br /&gt;{&lt;br /&gt;    foreach (var element in enumerable)&lt;br /&gt;        action(element);&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;It's pretty basic but I like the way it looks and feels.  I used it in my blog post about a &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/11/improved-upto-extension-method.html"&gt;C# UpTo Extension Method&lt;/a&gt; a la &lt;a href="http://www.ruby-doc.org/core/classes/Integer.html#M000185"&gt;Ruby's int.upto method&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Here's a simple demonstration I wrote in &lt;a href="http://www.linqpad.net"&gt;LinqPad&lt;/a&gt;:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;void Main()&lt;br /&gt;{&lt;br /&gt;    Enumerable.Range(1, 5).Each(Console.WriteLine);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static class Extensions&lt;br /&gt;{&lt;br /&gt;    public static void Each&amp;lt;T&amp;gt;(this IEnumerable&amp;lt;T&amp;gt; source, Action&amp;lt;T&amp;gt; action)&lt;br /&gt;    {&lt;br /&gt;        foreach (var element in source)&lt;br /&gt;            action(element);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;In writing this post (and perhaps because I've been spending way too much time with &lt;a href="http://www.jquery.com"&gt;jQuery&lt;/a&gt; lately), it occurred to me that I may want to be able to chain my actions with another Each() or with other extensions from Linq perhaps:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;void Main()&lt;br /&gt;{&lt;br /&gt;    Enumerable.Range(1, 5).Each(Console.WriteLine).Each(Console.WriteLine);&lt;br /&gt;    // 1 2 3 4 5 1 2 3 4 5&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static class Extensions&lt;br /&gt;{&lt;br /&gt;    public static IEnumerable&amp;lt;T&amp;gt; Each&amp;lt;T&amp;gt;&lt;br /&gt;        (this IEnumerable&amp;lt;T&amp;gt; source, Action&amp;lt;T&amp;gt; action)&lt;br /&gt;    {&lt;br /&gt;        foreach (var element in source)&lt;br /&gt;            action(element);&lt;br /&gt;   &lt;br /&gt;        return source;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The problem is though, that generally you don't want your action to enumerate your enumerable until something is to be done with the results.  Instead, you often want it to be executed during the enumeration of your enumerable, so you'd write it like this:&lt;br /&gt;&lt;pre name="code" class="c#"&gt;void Main()&lt;br /&gt;{&lt;br /&gt;    Enumerable.Range(1, 10)&lt;br /&gt;        .Each(Console.WriteLine)&lt;br /&gt;        .Where(i =&amp;gt; i &amp;lt;= 5)&lt;br /&gt;        .ToList();&lt;br /&gt;    // 1 2 3 4 5 6 7 8 9 10&lt;br /&gt;    &lt;br /&gt;    Console.WriteLine();&lt;br /&gt;    &lt;br /&gt;    Enumerable.Range(1, 10)&lt;br /&gt;        .Each(Console.WriteLine)&lt;br /&gt;        .Take(5)&lt;br /&gt;        .ToList();&lt;br /&gt;    // 1 2 3 4 5&lt;br /&gt;&lt;br /&gt;    Console.WriteLine();&lt;br /&gt;&lt;br /&gt;    Enumerable.Range(1, 10)&lt;br /&gt;        .Each(Console.WriteLine)&lt;br /&gt;        .Skip(5)&lt;br /&gt;        .Take(5)&lt;br /&gt;        .ToList();&lt;br /&gt;    // 1 2 3 4 5 6 7 8 9 10&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;static class Extensions&lt;br /&gt;{&lt;br /&gt;    public static IEnumerable&amp;lt;T&amp;gt; Each&amp;lt;T&amp;gt;&lt;br /&gt;        (this IEnumerable&amp;lt;T&amp;gt; source, Action&amp;lt;T&amp;gt; action)&lt;br /&gt;    {&lt;br /&gt;        foreach (var element in source)&lt;br /&gt;        {&lt;br /&gt;            action(element);&lt;br /&gt;            yield return element;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-4850305297734162912?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/4850305297734162912/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/07/extension-method-to-replace-foreach.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4850305297734162912'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4850305297734162912'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/07/extension-method-to-replace-foreach.html' title='Extension Method to Replace foreach With Lambda Expression'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-1015208488311617790</id><published>2011-06-17T15:36:00.000-07:00</published><updated>2011-06-20T12:42:30.042-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaScript'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='jQuery'/><title type='text'>GiantDropdown jQuery Plugin for Styling Large Select Lists</title><content type='html'>A few months ago, I was working on a performance management application for the Centers for Disease Control.  They wanted a select box that contained a list of competencies from which they could compose a template.  The problem was some of the items in the list were hundreds of characters long and, at the time of writing, most browsers would behave very badly with drop down boxes of that size.&lt;br /&gt;&lt;br /&gt;I was sitting at home that night and decided I could probably come up with a &lt;a href="http://www.jquery.org"&gt;jQuery&lt;/a&gt; plugin that would use the select list as the backing field and present a prettier display.  I did and we ended up using it at the CDC.  I decided I'd share in case anyone else runs into the same issue.&lt;br /&gt;&lt;br /&gt;Here's an example of what one of the select boxes would look like:&lt;br /&gt;&lt;select name="single"&gt;&lt;br /&gt;&lt;option value="" selected="selected"&gt;What type of element would you like to see?&lt;/option&gt;&lt;br /&gt;&lt;option value="1"&gt;A short one&lt;/option&gt;&lt;br /&gt;&lt;option value="2"&gt;One that is a little longer&lt;/option&gt;&lt;br /&gt;&lt;option value="3"&gt;One that is a little bit longer than that&lt;/option&gt;&lt;br /&gt;&lt;option value="4"&gt;An element that is really quite long and takes up a lot of display room on the screen&lt;/option&gt;&lt;br /&gt;&lt;option value="5"&gt;An element that is so long and takes up so much room on the screen that you basically have no way of displaying the element without wrapping the text within it&lt;/option&gt;&lt;br /&gt;&lt;option value="6"&gt;Another one that isn't quite as long&lt;/option&gt;&lt;br /&gt;&lt;option value="7"&gt;One that is really very long but doesn't wrap around the screen&lt;/option&gt;&lt;br /&gt;&lt;option value="8"&gt;Short&lt;/option&gt;&lt;br /&gt;&lt;option value="9"&gt;Entheusiastic!&lt;/option&gt;&lt;br /&gt;&lt;option value="10"&gt;The last one&lt;/option&gt;&lt;br /&gt;&lt;/select&gt;&lt;br /&gt;&lt;br /&gt;It's not very pretty and it behaves unpredictably with other DOM elements.  With the GiantDropdown plugin, we can style it any way we choose and still preserve all of the original behavior of the select list (meaning, it even works with multi-selects and option groups).&lt;br /&gt;&lt;br /&gt;Here are some samples of the giant-ified dropdowns:&lt;br /&gt;&lt;iframe src="http://pcdesigns.net/giantdropdown/giantdropdownsample.html" style="width: 650px; height: 825px; border: 0px solid #fff;"&gt;&lt;br /&gt;&lt;div&gt;Sorry, I felt like I had to make it an iframe to get a reliable demo.  If you can't see the samples, visit my blog for the &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/06/giantdropdown-jquery-plugin-for-styling.html"&gt;giant dropdown demos&lt;/a&gt; at my old url.  You can also check them out in git.&lt;/div&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;If you'd like to get the giant dropdown jquery plugin, the can check out the &lt;a href="https://github.com/tncbbthositg/GiantDropdown"&gt;Giant Dropdown jQuery Plugin Git Repository&lt;/a&gt;.  Feel free to make improvements.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-1015208488311617790?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/1015208488311617790/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/06/giantdropdown-jquery-plugin-for-styling.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1015208488311617790'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1015208488311617790'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/06/giantdropdown-jquery-plugin-for-styling.html' title='GiantDropdown jQuery Plugin for Styling Large Select Lists'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3366172966945223779</id><published>2011-06-16T14:36:00.000-07:00</published><updated>2011-06-16T14:39:06.444-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Mainframe Migration'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Autopilot Consulting'/><title type='text'>Autopilot Consulting has a Logo!</title><content type='html'>&lt;div style="text-align: center; width: 100%;"&gt;&lt;a href="http://www.autopilotllc.com"&gt;&lt;img style="width: 95%;" src="https://lh6.googleusercontent.com/-DO0EbXD-EKQ/Tfpy47OsazI/AAAAAAAABXs/nc5TulxKECk/AutopilotLogo.png" alt="Autopilot Consulting Logo" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;At long last, Autopilot Consulting has an official logo!  It's exciting to see this dream of mine coming together and now there's a face to the name.  &lt;br /&gt;&lt;br /&gt;I know it seems slow going.  After all, I posted the &lt;a href="http://dpatrickcaldwell.blogspot.com/2011/05/beginning-of-autopilot-consulting-llc.html"&gt;announcement of Autopilot&lt;/a&gt; at the end of May and here we are in the middle of June and I'm just getting my logo together.&lt;br /&gt;&lt;br /&gt;Well, it's true.  I'm trying to get all of my ducks in a row in my spare time while most of my efforts are still spent trying to do a great job here at the Centers for Disease Control.  We're not doing the same &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Mainframe%20Migration"&gt;mainframe migration&lt;/a&gt; project anymore (but I still intend to finish that series someday).  We've actually started doing some pretty interesting asp.net projects.  I usually prefer mvc.net, but they focus on very rich user experience which is a good experience for a nuts-and-bolts kind of guy like me.&lt;br /&gt;&lt;br /&gt;I noticed that not only are my blog posts becoming fewer and further between, they're also pretty non-technical.  I promise to spend a little more time writing about the things I'm learning out there in the thick of it (and maybe I'll have a little advice from time to time for those who are thinking about going independent).&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3366172966945223779?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3366172966945223779/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/06/autopilot-consulting-has-logo.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3366172966945223779'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3366172966945223779'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/06/autopilot-consulting-has-logo.html' title='Autopilot Consulting has a Logo!'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh6.googleusercontent.com/-DO0EbXD-EKQ/Tfpy47OsazI/AAAAAAAABXs/nc5TulxKECk/s72-c/AutopilotLogo.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6283539115719590458</id><published>2011-05-27T08:39:00.000-07:00</published><updated>2011-08-09T06:32:11.850-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Autopilot Consulting'/><title type='text'>The Beginning of Autopilot Consulting, LLC</title><content type='html'>If you follow my blog, there are a few things I'd imagine you know about me:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;I love writing software!&lt;/li&gt;&lt;li&gt;I love feeling like I'm improving the lives of my customers.&lt;/li&gt;&lt;li&gt;I love feeling like I'm doing my job well.&lt;/li&gt;&lt;li&gt;I love flying airplanes.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h2&gt;I Love Writing Software!&lt;/h2&gt;This is the very reason I got into the software industry in the first place.  I get such a thrill out of a good days work programming.  I love the challenge of solving problems as quickly, creatively, and efficiently as possible.  A software engineer not only thinks about solving problems, but thinks about thinking about solving problems.  Our craft is always changing and growing and it's great to be a part of this community.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Improving the Lives of my Customers&lt;/h2&gt;Whether I'm your employee, I'm a contractor for you, or you purchase my commercially available software, my goal is to find some way to improve your life.  I've worked in paperless onboarding, business process automation, data warehousing, entertainment, e-commerce, insurance, pharmaceutical research, and academia.  &lt;br /&gt;&lt;br /&gt;There's one thing I've found in common no matter what kind of software I've been writing.  That is that I find the most joy in my work when I can change peoples lives for the better.  I like to think that some of the work I have done has bettered the human condition, and I am hopeful that my work will continue to do so.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Doing my Job Well&lt;/h2&gt;If I'm not doing my job well, I shouldn't be doing it.  If I know I'm not doing a good job, I'm not satisfied with my work.  If I'm not satisfied with my work, my customers can't be satisfied with my work.  Sometimes you find yourself in a position where you can't meet your full potential.  Other times, you don't know what your full potential is.&lt;br /&gt;&lt;br /&gt;It is for this reason that I've decided to strike out on my own and form my own software company.  I've been developing software for a long time working for various companies.  I've decided that starting my company will allow me to touch the lives of more people.&lt;br /&gt;&lt;br /&gt;I hope to continue consulting in the software industry while I build my library of commercial products including web based services and mobile applications.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Flying Airplanes&lt;/h2&gt;About the same time I got my first job with the word "programmer" in the title, I started flying airplanes.  Since then I've flown tailwheel, multi-engine, upside down, through the clouds, and I can't get enough.  One thing I've learned is that sometimes an airplane has so much going on, that you just can't do everything by yourself.  That's why some planes require 2 pilots.&lt;br /&gt;&lt;br /&gt;That's also why a lot of airplanes have autopilots.  An autopilot is there to take care of going where you tell it to so you can focus on the important things.  That's why Autopilot Consulting is here.  We want you to focus on what's important &amp;emdash; your business; we will handle the technology.&lt;br /&gt;&lt;br /&gt;Together, we'll grow your business and improve the lives of your customers.&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: center; font-size: 18px; font-family: georgia; color: #039ed7;"&gt;Do more.  Work less.  Put your business on autopilot.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6283539115719590458?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6283539115719590458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/05/beginning-of-autopilot-consulting-llc.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6283539115719590458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6283539115719590458'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/05/beginning-of-autopilot-consulting-llc.html' title='The Beginning of Autopilot Consulting, LLC'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-8052147402875565527</id><published>2011-01-26T18:36:00.000-08:00</published><updated>2011-11-26T15:48:42.338-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Telephone Game'/><category scheme='http://www.blogger.com/atom/ns#' term='Autopilot Consulting'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><category scheme='http://www.blogger.com/atom/ns#' term='Mobile Magic Developers'/><title type='text'>Telephone Game for Android</title><content type='html'>&lt;a href="http://mobilemagicdevelopers.com/#chinese_whispers"&gt;&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/TUDWL9dpUXI/AAAAAAAABVs/vWPzHUxsOX4/s288/Telephone%20Game%20Promotional%20Image%20High%20Res.png" alt="Chinese Whispers Promo Image" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;You may know it as Grapevine, Gossip, or the Telephone Game; we call it Chinese Whispers.  No matter what you call it, this is a new take on the classic game and we hope you enjoy playing it as much as we enjoyed making it for you.&lt;br /&gt;&lt;br /&gt;You start the game with a short story and pass it to a friend.  The next player illustrates the story with our built in finger painting canvas and passes it to the next player who will provide a new caption.  Several rounds later, hilarity ensues!&lt;br /&gt;&lt;br /&gt;We hope you'll check the &lt;a href="https://market.android.com/details?id=com.wtfware.telephonegame"&gt;Android Market&lt;/a&gt; for the free &lt;a href="http://mobilemagicdevelopers.com/#chinese_whispers"&gt;Telephone Game for Android&lt;/a&gt; by &lt;a href="http://mobilemagicdevelopers.com"&gt;Mobile Magic Developers&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-8052147402875565527?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/8052147402875565527/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/01/telephone-game-for-android.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8052147402875565527'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8052147402875565527'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/01/telephone-game-for-android.html' title='Telephone Game for Android'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/TUDWL9dpUXI/AAAAAAAABVs/vWPzHUxsOX4/s72-c/Telephone%20Game%20Promotional%20Image%20High%20Res.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3782749595709640807</id><published>2011-01-26T18:12:00.000-08:00</published><updated>2011-01-26T18:13:22.580-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='WtfWare'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>WTF is WTF Ware?</title><content type='html'>&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/TTzvRVaYorI/AAAAAAAABVc/FtlpGSFbfYc/s288/Wtf%20Square%20Promo%20Image.png" alt="Wtf Ware Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;James and I worked together for about 3 years at &lt;a href="http://www.emeraldsoftwaregroup.com"&gt;Emerald Software Group&lt;/a&gt;.  James went off to &lt;a href="http://www.thoughtworks.com"&gt;ThoughtWorks&lt;/a&gt; and I stuck with Emerald.  We've both spent most of our careers writing business applications.  &lt;br /&gt;&lt;br /&gt;While we love leaving a big project knowing we've made a difference, we got to wondering how we could write software that enriches the lives of more people.  We decided we'd get together and start writing mobile apps that are entertaining, useful, funny, amusing, and enriching.&lt;br /&gt;&lt;br /&gt;We started writing apps on our own and have had a lot of fun doing it.  We decided that we ought to join our efforts and see what we came up with.  Thus, WTF Ware was born.&lt;br /&gt;&lt;br /&gt;I wish I could say we had a good story for how the name came to be or at least something clever WTF Ware could stand for.  Unfortunately, no such story exists.&lt;br /&gt;&lt;br /&gt;We were just about to start writing our first app and we realized we needed some sort of organization name.  James asked, "WTF is our company name?"&lt;br /&gt;&lt;br /&gt;"Beats me dude.  Just make something up for now and we'll come up with something later."&lt;br /&gt;&lt;br /&gt;Well, WTF Ware is what James made up and it stuck.  So, without further ado, I'd like to introduce ourselves.  We're &lt;a href="http://www.wtfware.com"&gt;wtfware.com&lt;/a&gt; and thanks for checking out our applications.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3782749595709640807?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3782749595709640807/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/01/wtf-is-wtf-ware.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3782749595709640807'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3782749595709640807'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/01/wtf-is-wtf-ware.html' title='WTF is WTF Ware?'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/TTzvRVaYorI/AAAAAAAABVc/FtlpGSFbfYc/s72-c/Wtf%20Square%20Promo%20Image.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6288411124405503291</id><published>2011-01-24T10:12:00.000-08:00</published><updated>2011-01-24T10:12:26.748-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Chuck Norris Facts Widget'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><category scheme='http://www.blogger.com/atom/ns#' term='Widget'/><title type='text'>Upgrade Chuck Norris Facts Free</title><content type='html'>&lt;a href="market://details?id=net.pcdesigns.ChuckNorrisFreeUpgrade"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/TSXP-TyIfWI/AAAAAAAABVI/2qOax4LO6X4/s288/Chuck%20Norris%20Square%20Promo.png" alt="Chuck Norris Facts Widget" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Initially, when I released Chuck Norris Facts, I published two versions.  I had a free version which was limited in facts and functionality.  Then I published a paid version that included all the facts and customization.&lt;br /&gt;&lt;br /&gt;This proved difficult to maintain, so I decided to make the free version ad supported and (at the time of writing) fully functional.  Unfortunately, it was still a little difficult to maintain.  I still wanted to have only one code base so that I could release one app all the time.  Another issue was that if you have the free version for a while and switch to the paid version, you lose your fact history and start over.  &lt;br /&gt;&lt;br /&gt;Thus, I switched upgrade paths.  Now, I encourage everyone to try the &lt;a href="market://details?id=net.pcdesigns.ChuckNorrisFree"&gt;Free Chuck Norris Fact Widget&lt;/a&gt; for a while until you decide that you like it.  Then, you can purchase the &lt;a href="market://details?id=net.pcdesigns.ChuckNorrisFreeUpgrade"&gt;Chuck Norris Fact Widget Upgrade&lt;/a&gt; for less than a buck.  This will remove the ads immediately and you won't lose your current fact history.&lt;br /&gt;&lt;br /&gt;If you are a user who has already installed the full Chuck Norris Fact Widget, you can download and install the free version and there will be no ads (as long as you don't delete the paid version).  You'll have to start over with your facts (but they're random anyhow so it shouldn't be too bad).  This way you'll continue getting updates with new facts and seasonal backgrounds.&lt;br /&gt;&lt;br /&gt;If you really want to delete the previous paid version, send an email to me and I'm mail you a buck or something so you can download the paid key.&lt;br /&gt;&lt;br /&gt;I'd also like to thank all of the users of both the free and paid versions for sticking with me through this learning experience.  I hope it's been as enjoyable to use as it has been to write.&lt;br /&gt;&lt;br /&gt;Sincerely,&lt;br /&gt;Patrick Caldwell&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6288411124405503291?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6288411124405503291/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/01/upgrade-chuck-norris-facts-free.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6288411124405503291'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6288411124405503291'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/01/upgrade-chuck-norris-facts-free.html' title='Upgrade Chuck Norris Facts Free'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/TSXP-TyIfWI/AAAAAAAABVI/2qOax4LO6X4/s72-c/Chuck%20Norris%20Square%20Promo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6972199714353177830</id><published>2011-01-06T07:12:00.000-08:00</published><updated>2011-09-17T16:32:24.962-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='WtfWare'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='Chuck Norris Facts Widget'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><category scheme='http://www.blogger.com/atom/ns#' term='Widget'/><title type='text'>Chuck Norris Facts Widget for Android</title><content type='html'>&lt;a href="market://details?id=net.pcdesigns.ChuckNorrisFree"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/TSXP-TyIfWI/AAAAAAAABVI/2qOax4LO6X4/s288/Chuck%20Norris%20Square%20Promo.png" alt="Chuck Norris Facts Widget" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;A few weeks ago, I decided I'd like to try my hand at writing an Android application.  One of my favorite features of Android over iOS (other than the fact that I think Apple lacks any form of ethics or sense of social responsibility) is that Android allows you to add &lt;a href="http://blogs.techrepublic.com.com/hiner/?p=5649"&gt;widgets&lt;/a&gt; to your home screens.  Sure, you can still have the half dozen views full of rows of boring icons, but the widgets give you a great deal of functionality without ever having to launch an app.&lt;br /&gt;&lt;br /&gt;I decided a good first shot at Android development would be to write a Chuck Norris Fact of the Day widget my family and friends could stick on their home screens and enjoy a little daily pick-me-up.  I knew that adding customizability would let me work with preferences and the nature of a daily fact would give me some Android data management experience.&lt;br /&gt;&lt;br /&gt;I released the Chuck Norris Fact Widget on the &lt;a href="http://market.android.com"&gt;Android Market&lt;/a&gt; and several of my family and friends downloaded it the first day.  Over the next few weeks, about 1,500 of their family and friends downloaded it, so I've been working to improve it ever since.&lt;br /&gt;&lt;br /&gt;If you're on your Android device now, you can download the free version called, &lt;a href="http://market.android.com/details?id=net.pcdesigns.ChuckNorrisFree"&gt;Chuck Norris Facts Free&lt;/a&gt; or for just a buck, you can get the paid version called, &lt;a href="http://market.android.com/details?id=net.pcdesigns.ChuckNorrisFreeUpgrade"&gt;Chuck Norris Facts with Widget&lt;/a&gt;.  The icon should look familiar from above and the publisher name is, of course, &lt;a href='https://market.android.com/developer?pub=D.+Patrick+Caldwell'&gt;D. Patrick Caldwell&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I hope you check it out and I hope you enjoy a little bit of my sense of humor.  &lt;br /&gt;&lt;br /&gt;Sincerely,&lt;br /&gt;Patrick Caldwell&lt;br /&gt;&lt;br /&gt;&lt;i&gt;For the programmers who read my blog, I'll be posting some technical information I learned while developing this application.  Specifically, how I managed to change the background image of my widget with remote views.&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6972199714353177830?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6972199714353177830/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/01/chuck-norris-facts-widget-for-android.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6972199714353177830'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6972199714353177830'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2011/01/chuck-norris-facts-widget-for-android.html' title='Chuck Norris Facts Widget for Android'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/TSXP-TyIfWI/AAAAAAAABVI/2qOax4LO6X4/s72-c/Chuck%20Norris%20Square%20Promo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6263849812550247115</id><published>2010-11-16T08:57:00.000-08:00</published><updated>2011-07-19T13:33:38.872-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='IEnumerable'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Improved UpTo Extension Method</title><content type='html'>&lt;a href="http://www.ruby-lang.org/"&gt;&lt;img src="http://www.ruby-lang.org/images/logo.gif" alt="Ruby" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Back in May of '09, I wrote a post about &lt;a href="/2009/05/extension-method-to-imitate-upto-in.html"&gt;an extension method to mimic Ruby's upto&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Today, I was hanging out at Visual Studio Live and had another idea.  In Ruby, the upto method takes an end value and executes a code block.  In C#, interpreted this as an extension method that takes an end value and an action.  They syntax ends up being pretty similar and it works fine.&lt;br /&gt;&lt;br /&gt;Lately, I've been using an Each extension method for IEnumerables.  I know an Each extension method seems like a waste, but I like the fluency of it for basic actions.  So, given that I have the Each method on IEnumerable&amp;lt;T&amp;gt; anyway, I decided UpTo should really return an IEnumerable as well.&lt;br /&gt;&lt;br /&gt;Here's my new UpTo extension method: &lt;pre name="code" class="c#"&gt;public static IEnumerable&amp;lt;int&amp;gt; UpTo(this int start, int end)&lt;br /&gt;{&lt;br /&gt;    while (start &amp;lt;= end)&lt;br /&gt;        yield return start++;&lt;br /&gt;}&lt;/pre&gt;Now, instead of passing your action to the UpTo method like Ruby's upto, you get your enumerable and pass your delegate to your Each extension method.  Here's what it looks like:&lt;pre name="code" class="c#"&gt;static void Main()&lt;br /&gt;{&lt;br /&gt;    //for (int i = 5; i &amp;lt;= 10; i++)&lt;br /&gt;    //{&lt;br /&gt;    //    Console.WriteLine(i);&lt;br /&gt;    //}&lt;br /&gt;&lt;br /&gt;    5.UpTo(10).Each(Console.WriteLine);&lt;br /&gt;    &lt;br /&gt;    Console.ReadLine();&lt;br /&gt;&lt;br /&gt;    // output&lt;br /&gt;    // 5&lt;br /&gt;    // 6&lt;br /&gt;    // 7&lt;br /&gt;    // 8&lt;br /&gt;    // 9&lt;br /&gt;    // 10&lt;br /&gt;}&lt;/pre&gt;If you're using .Net 3.5 or newer, Linq shipped with an Enumerator class which has a Range static method so your code could look more like this:&lt;pre name="code" class="c#"&gt;public static class NumericExtensions&lt;br /&gt;{&lt;br /&gt;    public static IEnumerable&amp;lt;int&amp;gt; UpTo(this int start, int end)&lt;br /&gt;    {&lt;br /&gt;        return Enumerable.Range(start, end - start + 1);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class Program&lt;br /&gt;{&lt;br /&gt;    static void Main()&lt;br /&gt;    {&lt;br /&gt;        //foreach (var i in Enumerable.Range(5, 6))&lt;br /&gt;        //{&lt;br /&gt;        //    Console.WriteLine(i);&lt;br /&gt;        //}&lt;br /&gt;&lt;br /&gt;        5.UpTo(10).Each(Console.WriteLine);&lt;br /&gt;        &lt;br /&gt;        Console.ReadLine();&lt;br /&gt;&lt;br /&gt;        // output&lt;br /&gt;        // 5&lt;br /&gt;        // 6&lt;br /&gt;        // 7&lt;br /&gt;        // 8&lt;br /&gt;        // 9&lt;br /&gt;        // 10&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;Another reason to make UpTo return an IEnumerable instead of making it a void action executer is because sometimes you don't really want to pass a large anonymous method to the Each method.  You can use the same UpTo method like this:&lt;pre name="code" class="c#"&gt;static void Main()&lt;br /&gt;{&lt;br /&gt;    //foreach (var i in Enumerable.Range(5, 6))&lt;br /&gt;    //{&lt;br /&gt;    //    // Do a lot of stuff!!&lt;br /&gt;    //}&lt;br /&gt;&lt;br /&gt;    //5.UpTo(10).Each(i =&gt;&lt;br /&gt;    //    {&lt;br /&gt;    //        // Do a lot of stuff!!&lt;br /&gt;    //    });&lt;br /&gt;&lt;br /&gt;    foreach (var i in 5.UpTo(10))&lt;br /&gt;    {&lt;br /&gt;        // Do a lot of stuff!!&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6263849812550247115?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6263849812550247115/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/11/improved-upto-extension-method.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6263849812550247115'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6263849812550247115'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/11/improved-upto-extension-method.html' title='Improved UpTo Extension Method'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-7522939192829791531</id><published>2010-10-28T20:31:00.000-07:00</published><updated>2010-11-15T10:38:05.616-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><title type='text'>Results of the "What Really Makes a Good Programmer" Survey</title><content type='html'>&lt;a href="https://spreadsheets0.google.com/oimg?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;oid=7&amp;zx=dlqbwjyhaj19"&gt;&lt;img src="https://spreadsheets0.google.com/oimg?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;oid=6&amp;zx=dcxiq0bx39y6" alt="Survey Response Counts" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Back in September I wrote a blog post called &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/09/what-really-makes-good-programmer.html"&gt;What Really Makes a Good Programmer?&lt;/a&gt;  My goal was to ask various members of the development community what traits they thought contributed to the quality of a programmer.  If you haven't taken &lt;a href="https://spreadsheets3.google.com/viewform?formkey=dDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE6MQ&amp;theme=0AX42CRMsmRFbUy01ZDc3NWRkZC1mYWQzLTQxYzItYTQ2Yy05OGFmMDE1NGY4ZjY&amp;ifq"&gt;the survey&lt;/a&gt; yet, I'd recommend you do that before reading the results.  Wouldn't want to bias your opinions, right?&lt;br /&gt;&lt;br /&gt;If you have taken the survey, but haven't told everyone you know to take it too, go ahead and do that; I'll wait.&lt;br /&gt;&lt;br /&gt;Now that we've covered that, let's get on to the analysis.  As you recall from taking the survey, there were 17 traits and you rated them on a scale of &lt;i&gt;Very Important, Important, A little Important, Negligible,&lt;/i&gt; and &lt;i&gt;Completely Unimportant&lt;/i&gt;.  In order to do any analysis, I had to take these ratings and convert them to numbers.&lt;br /&gt;&lt;br /&gt;I decided that &lt;i&gt;A Little Important&lt;/i&gt; was the baseline and basically represented a lack of opinion on the topic.  These traits are the ones most people feel are nice to have but aren't requirements.  Basically, a good programmer often has these traits but not having them doesn't mean you're not a good programmer.  I recoded these values with 0 points.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Very Important&lt;/i&gt; and &lt;i&gt;Completely Unimportant&lt;/i&gt; are the ratings that people used when they felt strongly about the item.  That means that, respectively, the trait is either absolutely necessary to being a good programmer or the trait has no bearing on the quality of programmer you are whatsoever (or perhaps a little bit of a negative indicator).  These values were recoded as 10 and -10 respectively.  &lt;br /&gt;&lt;br /&gt;The central values of &lt;i&gt;Important&lt;/i&gt; and &lt;i&gt;Negligible&lt;/i&gt; are what I call the non-committal values.  You feel that they make a difference but they're not quite important enough to bank on them.  I gave these a 3 and -3 respectively.&lt;br /&gt;&lt;br /&gt;For example, "Has good problem solving skills" was rated &lt;i&gt;Very Important&lt;/i&gt; and "Cheap" was rated &lt;i&gt;Completely Unimportant&lt;/i&gt;.  One could say, "someone with poor problem solving skills would make a poor programmer."  On the other hand, you cannot say, "someone who is not cheap is a poor programmer."  Of course, the contrapositive could also be stated that, "someone who is a good programmer is also a good problem solver."  The contrapositive "someone who is a good programmer is also cheap" is considered by the community to be a largely invalid assertion.&lt;br /&gt;&lt;br /&gt;"Fast" and "Co-located" appear nearest to the baseline.  This may be due to the fact that many of the respondents didn't know what I meant by co-located.  In any case, one might say, "a good programmer is a good programmer whether or not she's in the same building," or that "just because you're fast doesn't mean you're good and just because you're slow doesn't mean you're bad."&lt;br /&gt;&lt;br /&gt;"Communicates effectively" and "Interested in helping teammates" are moderately rated in favor of contributing to being a good programmer while the two "college degree" items are rated moderately against contributing to being a good programmer.  For example, you might know a lot of programmers who are great developers but who lack the social skills or interest to become good communicators or team players.  As a result, you might say that good programmers often communicate effectively and help teammates, but some good programmers cannot.&lt;br /&gt;&lt;br /&gt;You might also say, "Many people without college degrees, let alone a computer science degree, are great programmers.  Thus, while having a degree is helpful, you can learn to be a great programmer without it."&lt;br /&gt;&lt;br /&gt;Now, I feel it is worth noting that "unimportant" does not mean "negative."  Just because most of the development community feels that having a CS degree or other certifications is unimportant to being a good programmer, it doesn't mean that the degree itself is unimportant.  It just means that having the degree won't make you a good programmer; you still have a lot of learning to do.&lt;br /&gt;&lt;br /&gt;With notes on the analysis out of the way, what do you say we take a look at some data?  Currently, the new Google charts won't let me force the display of the categories, so you'll have to hover over the dots you're interested in.  Here's a &lt;a href="https://spreadsheets.google.com/oimg?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;oid=4&amp;zx=t7na7t-4scqjd"&gt;legacy chart&lt;/a&gt; you can look at if the javascript version below is unsatisfactory.  The legacy chart has current data; it's just not as fancy as this one:&lt;script type="text/javascript" src="//ajax.googleapis.com/ajax/static/modules/gviz/1.0/chart.js"&gt; {"chartType":"LineChart","chartName":"What Really Makes a Good Programmer?","dataSourceUrl":"//spreadsheets.google.com/tq?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;range=A31%3AC48&amp;gid=2&amp;transpose=0&amp;headers=1&amp;pub=1","options":{"hAxis":{"showTextEvery":1,slantedText:true,slantedTextAngle:50},"reverseCategories":true,"pointSize":"2","is3D":true,"logScale":false,"wmode":"opaque","title":"What Really Makes a Good Programmer?","height":500,"pointSizeOther":"2","mapType":"hybrid","isStacked":false,"showTip":true,"displayAnnotations":true,"nonGeoMapColors":["#ff9900","#0000ff","#FF9900","#109618","#990099","#0099C6","#DD4477","#66AA00","#B82E2E","#316395"],"dataMode":"markers","titleY":"Importance","maxAlternation":1,"colors":["#ff9900","#073763","#FF9900","#109618","#990099","#0099C6","#DD4477","#66AA00","#B82E2E","#316395"],"smoothLine":true,"width":700,"lineWidth":"2","labelPosition":"right","hasLabelsColumn":true,"legend":"right","allowCollapse":false,"reverseAxis":true},"packages":"corechart","refreshInterval":5} &lt;/script&gt;&lt;br /&gt;I'm sure you noticed there are two series in this graph.  &lt;i&gt;All Responses&lt;/i&gt; represents the average responses for all respondents; however, as you noticed in the respondent histogram that I have a dramatically unbalanced sample with programmers outnumbering all other groups combined by almost 3 to 1.  To get a more accurate representation of the "general feel" of the community, I included the &lt;i&gt;Group Average&lt;/i&gt; measure.  This is the average of the averages.  It's sort of the electoral college of informal research.&lt;br /&gt;&lt;br /&gt;This chart demonstrates the average responses for each group:&lt;script type="text/javascript" src="//ajax.googleapis.com/ajax/static/modules/gviz/1.0/chart.js"&gt; {"chartType":"BarChart","chartName":"Average Responses per Group","dataSourceUrl":"//spreadsheets.google.com/tq?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;range=A31%3AJ48&amp;gid=2&amp;transpose=0&amp;headers=1&amp;pub=1","options":{"displayAnnotations":true,"showTip":true,"reverseCategories":true,"titleY":"","dataMode":"markers","titleX":"Importance","maxAlternation":1,"pointSize":"0","colors":["#3366CC","#DC3912","#FF9900","#109618","#990099","#0099C6","#DD4477","#66AA00","#B82E2E","#316395"],"smoothLine":true,"lineWidth":"2","labelPosition":"right","is3D":false,"logScale":false,"wmode":"opaque","hasLabelsColumn":true,"title":"Average Responses per Group","legend":"right","allowCollapse":true,"cht":"bhs","reverseAxis":true,"mapType":"hybrid","isStacked":true,"width":700,"height":500},"packages":"corechart","refreshInterval":5} &lt;/script&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-7522939192829791531?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/7522939192829791531/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/10/results-of-what-really-makes-good.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7522939192829791531'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7522939192829791531'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/10/results-of-what-really-makes-good.html' title='Results of the &quot;What Really Makes a Good Programmer&quot; Survey'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2776696085486737523</id><published>2010-10-28T12:27:00.000-07:00</published><updated>2010-11-08T13:10:38.228-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Security'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Improving Search Performance when using SQL Server 2008 Encrypted Columns</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Personally_identifiable_information"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/TMh7RR6FBhI/AAAAAAAABSE/EFV6bkmC-zw/s288/confidential_stamp1.jpg" alt="Confidential" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;This is a story of courage, honor, and data encryption.  In addition to being something of a tribute to one of my favorite games of all time, I do feel I need to preface this post with a bit of a disclaimer.&lt;br /&gt;&lt;br /&gt;Generally, when I describe this problem to someone, we go through pretty much the same conversation.&lt;blockquote style="margin-left: 300px;"&gt;Why didn't you try &lt;a href="http://technet.microsoft.com/en-us/library/cc278098(SQL.100).aspx"&gt;Sql Server 2008 Transparent Data Encryption&lt;/a&gt;?  Why don't you just update your &lt;a href="http://en.wikipedia.org/wiki/Access_control_list"&gt;&lt;acronym title="Access Control list"&gt;ACL&lt;/acronym&gt;&lt;/a&gt;?  Why don't you try this?  Or that?&lt;/blockquote&gt;&lt;br /&gt;Well, sometimes the environment, either technologically or politically, precludes some of your better options.  Ultimately, you have to go with your best &lt;i&gt;permissible&lt;/i&gt; solution to solve the problem at hand.  That being said, let's say you've been charged with securing your organizations &lt;a href="http://en.wikipedia.org/wiki/Personally_identifiable_information"&gt;&lt;acronym title="Personally Identifiable Information"&gt;PII&lt;/acronym&gt;&lt;/a&gt;.  Your only option is to encrypt the column with SQL Server's column encryption.&lt;br /&gt;&lt;br /&gt;You start looking at the impact this is going to have on your current and future applications.  If you're like me, one of the first concerns you're going to have is performance.  For example, let's say you have the need to search on the encrypted field.  A good example of an encrypted field that you'll likely have to search is the Social Security Number.&lt;br /&gt;&lt;br /&gt;In my original benchmarks, I found the following results:&lt;script type="text/javascript" src="//ajax.googleapis.com/ajax/static/modules/gviz/1.0/chart.js"&gt; {"chartType":"AreaChart","chartName":"Searching for One Element in an Encrypted List","dataSourceUrl":"//spreadsheets.google.com/tq?key=0At0EPL2ZBHgPdFpKdTZzZEhGWXhMbDhMVnIyMEdyY3c&amp;range=A1%3AB99&amp;gid=0&amp;transpose=0&amp;headers=1&amp;pub=1","options":{"displayAnnotations":true,"showTip":true,"reverseCategories":false,"titleY":"Search Time (ms)","dataMode":"markers","titleX":"Number of Encrypted Elements (N)","maxAlternation":1,"pointSize":"2","colors":["#073763","#ff9900","#3366CC","#DC3912","#FF9900","#109618","#990099","#0099C6","#DD4477","#66AA00","#B82E2E","#316395"],"smoothLine":false,"lineWidth":"2","labelPosition":"right","is3D":false,"logScale":true,"hasLabelsColumn":true,"wmode":"opaque","title":"Searching for One Element in an Encrypted List","legend":"none","allowCollapse":true,"reverseAxis":false,"isStacked":false,"mapType":"hybrid","width":700,"height":500},"packages":"corechart","refreshInterval":5} &lt;/script&gt;Knowing that I was going to have to improve this performance, I started playing with some searching alternatives.  First of all, it's obviously much faster to search an indexed column (especially one that is clustered), so my first goal was to generate an indexed column.&lt;br /&gt;&lt;br /&gt;Ryan McGarty (my best friend and a damn fine programmer) and I discussed and quickly ruled out a basic hash.  Sure, it'd be fast to search an indexed column containing the hash value, but that opens you up to a relatively simple &lt;a href="http://en.wikipedia.org/wiki/Chosen-plaintext_attack"&gt;plain text attack&lt;/a&gt; (especially with a &lt;a href="http://en.wikipedia.org/wiki/Known-plaintext_attack"&gt;known set of possible values&lt;/a&gt;).  I decided that you could reduce the threat and still speed up the search by using hash buckets a la &lt;a href="http://en.wikipedia.org/wiki/Hash_table"&gt;hashtables&lt;/a&gt; instead.&lt;br /&gt;&lt;br /&gt;I concocted a hash function that produced a reasonable distribution within the buckets.  My good friend and esteemed colleague David Govek pointed out that an MD5 hash would produce a pretty effective distribution between buckets (with &lt;a href="http://en.wikipedia.org/wiki/Cardinality_(SQL_statements)"&gt;high cardinality data&lt;/a&gt; like the social security number.  David was right and I ended up with this hash function:&lt;pre name="code" class="sql"&gt;CREATE FUNCTION [dbo].[GroupData] &lt;br /&gt;(&lt;br /&gt; @String VARCHAR(MAX),&lt;br /&gt; @Divisor INT&lt;br /&gt;) &lt;br /&gt;RETURNS INT&lt;br /&gt;AS BEGIN&lt;br /&gt;&lt;br /&gt;  DECLARE @Result INT;&lt;br /&gt;  &lt;br /&gt;  SET @Result = HASHBYTES('MD5', @String);&lt;br /&gt;  &lt;br /&gt;  IF (@Divisor &gt; 0)&lt;br /&gt;    SET @Result =  @Result % @Divisor;&lt;br /&gt;  &lt;br /&gt;  RETURN @Result;&lt;br /&gt;&lt;br /&gt;END&lt;/pre&gt;I created the test data just as I had before, except that this time I also added the hash bucket.  I created a clustered index on the bucket and I changed my select statement a little:&lt;pre name="code" class="sql"&gt;-- original select statement&lt;br /&gt;SELECT *&lt;br /&gt;FROM PersonData&lt;br /&gt;WHERE &lt;br /&gt;  CONVERT(CHAR(9), DECRYPTBYKEY(SocialSecurityNumberEncrypted)) = '457555462'&lt;br /&gt;&lt;br /&gt;-- hash key select statement&lt;br /&gt;-- @Divisor is the divisor for the modulus operation in the hash function&lt;br /&gt;SELECT *&lt;br /&gt;FROM PersonData&lt;br /&gt;WHERE &lt;br /&gt;  SocialSecurityNumberGroup = dbo.GroupData('457555462', @Divisor) AND&lt;br /&gt;  CONVERT(CHAR(9), DECRYPTBYKEY(SocialSecurityNumberEncrypted)) = '457555462'&lt;/pre&gt;&lt;br /&gt;Here are my results:&lt;div style="text-align: center;"&gt;&lt;script type="text/javascript" src="//ajax.googleapis.com/ajax/static/modules/gviz/1.0/chart.js"&gt; {"chartType":"AreaChart","chartName":"Searching for One Element in an Encrypted List","dataSourceUrl":"//spreadsheets.google.com/tq?key=0At0EPL2ZBHgPdFpKdTZzZEhGWXhMbDhMVnIyMEdyY3c&amp;range=A1%3AC9&amp;gid=0&amp;transpose=0&amp;headers=1&amp;pub=1","options":{"displayAnnotations":true,"showTip":true,"reverseCategories":false,"titleY":"Search Time (MS)","dataMode":"markers","titleX":"Number of Encrypted Elements (N)","maxAlternation":1,"pointSize":"2","colors":["#073763","#ff9900","#3366CC","#DC3912","#FF9900","#109618","#990099","#0099C6","#DD4477","#66AA00","#B82E2E","#316395"],"smoothLine":false,"lineWidth":"2","labelPosition":"right","is3D":false,"logScale":true,"wmode":"opaque","hasLabelsColumn":true,"title":"Searching for One Element in an Encrypted List","legend":"right","allowCollapse":true,"reverseAxis":false,"mapType":"hybrid","isStacked":false,"width":700,"height":500},"packages":"corechart","refreshInterval":5} &lt;/script&gt;&lt;/div&gt;&lt;h1&gt;So, what of the known plain text attack then?&lt;/h1&gt;So, I don't want to gloss over the plain text attack issue.  Given the possible set of socials, the hashes would be very unlikely to have a collision.  Thus, for most people, you'd be able to get their socials easily by hashing every possible social and joining to that table.  By &lt;a href="http://en.wikipedia.org/wiki/Modular_arithmetic"&gt;modulo dividing&lt;/a&gt; the hash value, I'm able to evenly distribute social security numbers among a known set of buckets.  That means, I can control the approximate number of socials in each bucket given my set of values.  I generally aim for about 1000 socials per bucket.  &lt;br /&gt;&lt;br /&gt;For example, an MD5 % 100 has a possible set of values from -100 to +100.  That's 201 buckets so if you have 2010 rows of data to hash, you'll have about 10 rows per bucket.  The benefit is that you now only have to decrypt 10 rows to find the exact row you're looking for.  The detriment is that you've narrowed your possible result set.  Within your own data set, you'd have narrowed it to 10 possible plaintext values; however, given that these values are unknown, you then have to look at the set of possible values.&lt;br /&gt;&lt;br /&gt;Social security numbers have a set of possible values less than 1,000,000,000.  It's hard to say exactly how many of them are the possible set of values, so let's say we're using only those socials currently in use by living Americans.  The &lt;a href="http://www.wolframalpha.com/input/?i=population+of+the+united+states."&gt;population of the United States&lt;/a&gt; at the time of writing was about 312,000,000.  As I said, I usually aim for about 1,000 records per bucket.  If I have 1,000,000 rows of data, I would modulo divide by 500 (1,001 buckets).  If you knew my set of values, then you'd only have 1,000 possible values to reduce.  Given that you know when and where I was born, you could probably narrow it to 20 or so possible socials.&lt;br /&gt;&lt;br /&gt;But, you don't know my set of values, so you really have 312,000 values to chose from.  Even if you did know the first 5 digits of my social security number (based on my state of issuance and my date of birth), your set of possible options would be so large, you'd probably be better off just pulling my credit report and getting my social that way.&lt;br /&gt;&lt;br /&gt;Thus, while it seems to introduce a weakness to plain text attacks (and with low cardinality data it would be an issue), in the case of social security numbers, I don't believe it to be a reasonable attack.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2776696085486737523?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2776696085486737523/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/10/sql-server-encrypted-columns.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2776696085486737523'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2776696085486737523'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/10/sql-server-encrypted-columns.html' title='Improving Search Performance when using SQL Server 2008 Encrypted Columns'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/TMh7RR6FBhI/AAAAAAAABSE/EFV6bkmC-zw/s72-c/confidential_stamp1.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3281861969649564550</id><published>2010-10-28T08:40:00.000-07:00</published><updated>2010-10-28T09:06:19.316-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Column Encryption in SQL Server 2008 with Symmetric Keys</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Personally_identifiable_information"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/TMh7RR6FBhI/AAAAAAAABSE/EFV6bkmC-zw/s288/confidential_stamp1.jpg" alt="Confidential" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Most of the time when I write blog posts, I do it to share ideas with my fellow developers.  Sometimes I do it just so I can have a place to reference when I forget the syntax for something.  This is one of those reference posts.&lt;br /&gt;&lt;br /&gt;Recently I've been charged to column level encrypt some personally identifiable information.  The present post is not intended to discuss the merits of column level encryption; rather, as I said it is to put a few code snippets up so that I can reference them later.  If you should find yourself in a column level encryption predicament in a SQL Server 2008 environment, you may find these useful as well.&lt;br /&gt;&lt;br /&gt;First thing's first.  Get the database ready for column level encryption by creating a master key:&lt;pre name="code" class="sql"&gt;--if there is no master key create one&lt;br /&gt;IF NOT EXISTS &lt;br /&gt;(&lt;br /&gt;  SELECT * &lt;br /&gt;  FROM sys.symmetric_keys &lt;br /&gt;  WHERE symmetric_key_id = 101&lt;br /&gt;)&lt;br /&gt;CREATE MASTER KEY ENCRYPTION BY &lt;br /&gt;  PASSWORD = 'This is where you would put a really long key for creating a symmetric key.'&lt;br /&gt;GO&lt;/pre&gt;&lt;br /&gt;Now, you'll need a certificate or a set of certificates with which you will encrypt your symmetric key or keys:&lt;pre name="code" class="sql"&gt;-- if the certificate doesn't, exist create it now&lt;br /&gt;IF NOT EXISTS&lt;br /&gt;(&lt;br /&gt;  SELECT *&lt;br /&gt;  FROM sys.certificates&lt;br /&gt;  WHERE name = 'PrivateDataCertificate'&lt;br /&gt;)&lt;br /&gt;CREATE CERTIFICATE PrivateDataCertificate&lt;br /&gt;   WITH SUBJECT = 'For encrypting private data';&lt;br /&gt;GO&lt;/pre&gt;&lt;br /&gt;Once you have your certificates, you can create your key or keys:&lt;pre name="code" class="sql"&gt;-- if the key doesn't exist, create it too&lt;br /&gt;IF NOT EXISTS&lt;br /&gt;(&lt;br /&gt;  SELECT *&lt;br /&gt;  FROM sys.symmetric_keys&lt;br /&gt;  WHERE name = 'PrivateDataKey'&lt;br /&gt;)&lt;br /&gt;CREATE SYMMETRIC KEY PrivateDataKey&lt;br /&gt;  WITH ALGORITHM = AES_256&lt;br /&gt;  ENCRYPTION BY CERTIFICATE PrivateDataCertificate;&lt;br /&gt;GO&lt;/pre&gt;&lt;br /&gt;Before you can use your symmetric key, you have to open it.  I recommend that you get in the habit of closing it when you're finished with it.  The symmetric key remains open for the life of the session.  Let's say that you have a stored procedure in which you open the symmetric key to decrypt some private data which your stored procedure uses internally.  Someone who has access to the stored procedure can run it and then will have the key opened for use in decrypting private data.  My point, close the key before you leave the procedure.  Here's how you open and close keys. &lt;pre name="code" class="sql"&gt;-- open the symmetric key with which to encrypt the data.&lt;br /&gt;OPEN SYMMETRIC KEY PrivateDataKey&lt;br /&gt;   DECRYPTION BY CERTIFICATE PrivateDataCertificate;&lt;br /&gt;&lt;br /&gt;-- close the symmetric key&lt;br /&gt;CLOSE SYMMETRIC KEY PrivateDataKey;&lt;/pre&gt;&lt;br /&gt;Here's a little test script I wrote to demonstrate a few points.  First, the syntax for encrypting and decrypting.  Second, the fact that the the cipher text changes each time you do the encryption.  This prevents a plain text attack.&lt;pre name="code" class="sql"&gt;-- open the symmetric key with which to encrypt the data.&lt;br /&gt;OPEN SYMMETRIC KEY PrivateDataKey&lt;br /&gt;   DECRYPTION BY CERTIFICATE PrivateDataCertificate;&lt;br /&gt;&lt;br /&gt;-- somewhere to put the data&lt;br /&gt;DECLARE @TestEncryption TABLE&lt;br /&gt;(&lt;br /&gt;  PlainText VARCHAR(100),&lt;br /&gt;  Cipher1 VARBINARY(100),&lt;br /&gt;  Cipher2 VARBINARY(100)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;-- some test data&lt;br /&gt;INSERT INTO @TestEncryption (PlainText)&lt;br /&gt;SELECT 'Boogers'&lt;br /&gt;UNION ALL&lt;br /&gt;SELECT 'Foobar'&lt;br /&gt;UNION ALL&lt;br /&gt;SELECT '457-55-5462'; -- ignoranus&lt;br /&gt;&lt;br /&gt;-- encrypt twice&lt;br /&gt;UPDATE @TestEncryption&lt;br /&gt;SET &lt;br /&gt;  Cipher1 = ENCRYPTBYKEY(KEY_GUID('PrivateDataKey'), PlainText),&lt;br /&gt;  Cipher2 = ENCRYPTBYKEY(KEY_GUID('PrivateDataKey'), PlainText);&lt;br /&gt;&lt;br /&gt;-- decrypt and display results  &lt;br /&gt;SELECT&lt;br /&gt;  *,&lt;br /&gt;  CiphersDiffer = CASE WHEN Cipher1 &lt;&gt; Cipher2 THEN 'TRUE' ELSE 'FALSE' END,&lt;br /&gt;  PlainText1 = CONVERT(VARCHAR, DECRYPTBYKEY(Cipher1)),&lt;br /&gt;  PlainText2 = CONVERT(VARCHAR, DECRYPTBYKEY(Cipher2))&lt;br /&gt;FROM @TestEncryption;&lt;br /&gt;&lt;br /&gt;-- close the symmetric key&lt;br /&gt;CLOSE SYMMETRIC KEY PrivateDataKey;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3281861969649564550?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3281861969649564550/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/10/column-encryption-in-sql-server-2008.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3281861969649564550'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3281861969649564550'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/10/column-encryption-in-sql-server-2008.html' title='Column Encryption in SQL Server 2008 with Symmetric Keys'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/TMh7RR6FBhI/AAAAAAAABSE/EFV6bkmC-zw/s72-c/confidential_stamp1.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-7143972432184058284</id><published>2010-09-21T20:18:00.000-07:00</published><updated>2010-09-21T20:21:33.803-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Research'/><title type='text'>Overdraft Fee Survey</title><content type='html'>&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/TJlx98Yt47I/AAAAAAAABRE/iOQf5XpCRsI/s288/burglar.jpg" alt="Burglar" style="float: left; margin-right: 7px;" class="postimage" /&gt;OK, I know I've been big on the surveys lately, but this one is really important to me.&lt;br /&gt;&lt;br /&gt;As you probably know, there's been a lot of hoopla about the new overdraft fee regulations.  Banks are no longer allowed to automatically enroll customers in what they call, "overdraft protection."  To us common folk, we generally call them "overdraft fees" or "allowing you to spend money you don't have so we can screw you out of more money we know you don't have."&lt;br /&gt;&lt;br /&gt;I started thinking about my history with overdraft fees and some things I learned from &lt;a href="http://www.daveramsey.com/"&gt;Dave Ramsey&lt;/a&gt;.  I wondered, who really pays all of these overdraft fees that account for 38 billion dollars per year in revenue?&lt;br /&gt;&lt;br /&gt;This survey intends to find out.  At the time of writing, I had 84 responses and I need hundreds more.  Please, take my anonymous survey and ask all of your friends to do the same.  Here is the &lt;a href="https://spreadsheets2.google.com/viewform?hl=en&amp;formkey=dGxVS0REX3RNU1BSLUxfRWZzTDVYa2c6MQ#gid=1"&gt;Overdraft Fee Survey&lt;/a&gt;:&lt;iframe src="https://spreadsheets2.google.com/embeddedform?formkey=dGxVS0REX3RNU1BSLUxfRWZzTDVYa2c6MQ" width="860" height="760" frameborder="0" marginheight="0" marginwidth="0"&gt;Loading...&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-7143972432184058284?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/7143972432184058284/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/09/overdraft-fee-survey.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7143972432184058284'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7143972432184058284'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/09/overdraft-fee-survey.html' title='Overdraft Fee Survey'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/TJlx98Yt47I/AAAAAAAABRE/iOQf5XpCRsI/s72-c/burglar.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-8406018731733713367</id><published>2010-09-21T19:22:00.000-07:00</published><updated>2011-06-16T15:41:20.852-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaScript'/><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Google Docs'/><category scheme='http://www.blogger.com/atom/ns#' term='Google Apps Script'/><title type='text'>Array Function to Recode Data in Google Apps Scripts</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/TJlZvYcia7I/AAAAAAAABQ8/9nFbWONimzk/s288/GoogleDocs.jpg" alt="Google Docs Icon" style="float: left; margin-right: 7px;" class="postimage" /&gt;I went on my honeymoon with my beautiful wife last week and the week before.  Having a little time off of work gave me the opportunity to get some work done :).  I've been wanting for a while to develop a survey to find out &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/09/what-really-makes-good-programmer.html"&gt;what makes a good programmer&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I've been working with &lt;a href="http://docs.google.com"&gt;Google Docs&lt;/a&gt; and Google Forms to see what they're capable of.  This spreadsheet posed a few difficulties.  The primary problem was that I had a set of text values which needed to be recoded to numerical values from another range.&lt;br /&gt;&lt;br /&gt;I wrote this array function for Google Apps Scripts in Google Spreadsheets to recode values based on an array of values.&lt;br /&gt;&lt;br /&gt;Here's an example spreadsheet demonstrating the &lt;a href="https://spreadsheets.google.com/ccc?key=0At0EPL2ZBHgPdFRCRVNjM183amRTaDd6UWF1ZmRmc1E&amp;hl=en"&gt;Array Data Block Recode Function&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Here's what the function looks like with a few tests:&lt;pre name="code" class="js"&gt;function recode(data, values, valueColumnIndex)&lt;br /&gt;{&lt;br /&gt;  var  valueHash = {};&lt;br /&gt;  &lt;br /&gt;  // if the values are in an array, make a hash table&lt;br /&gt;  if (values.constructor == Array)&lt;br /&gt;    for (var i = 0; i &lt; values.length; i++)&lt;br /&gt;      valueHash[values[i][0]] = values[i][valueColumnIndex];&lt;br /&gt;&lt;br /&gt;  else&lt;br /&gt;    valueHash = values;&lt;br /&gt;  &lt;br /&gt;  var ret = [];&lt;br /&gt;  &lt;br /&gt;  // if the data are in an array, recursively recode them&lt;br /&gt;  if (data.constructor == Array)&lt;br /&gt;    for (var i = 0; i &lt; data.length; i++)&lt;br /&gt;      ret.push(recode(data[i], valueHash, valueColumnIndex));&lt;br /&gt;          &lt;br /&gt;  else&lt;br /&gt;    ret = valueHash[data] != undefined ? valueHash[data] : data;&lt;br /&gt;&lt;br /&gt;  return ret;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;var values = [['a', '1', 'I'], ['b', '2', 'II']];&lt;br /&gt;&lt;br /&gt;print(recode('a', values, 1));&lt;br /&gt;print(recode(['a', 'b', 'c'], values, 1));&lt;br /&gt;print(recode([['a', 'b'], ['b', 'c']], values, 1));&lt;br /&gt;print(recode(['a', ['a', 'b'], [['a', 'b', 'c']]], values, 2));&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;Results:&lt;br /&gt;1&lt;br /&gt;[1, 2, 'c']&lt;br /&gt;[[1, 2], [2, 'c']]&lt;br /&gt;['I', ['I', 'II'], [['I', 'II', 'c']]]&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;Google Apps Syntax:&lt;br /&gt;=Recode(A1:B3, D1:F2, 1)&lt;br /&gt;=Recode(A1:B3, D1:F2, 2)&lt;br /&gt;*/&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-8406018731733713367?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/8406018731733713367/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/09/array-function-to-recode-data-in-google.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8406018731733713367'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8406018731733713367'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/09/array-function-to-recode-data-in-google.html' title='Array Function to Recode Data in Google Apps Scripts'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/TJlZvYcia7I/AAAAAAAABQ8/9nFbWONimzk/s72-c/GoogleDocs.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-456107650276245002</id><published>2010-09-21T14:14:00.000-07:00</published><updated>2011-06-16T15:41:45.787-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaScript'/><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Bookmarklet'/><title type='text'>Regular Expression Search Bookmarklet</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SnsP169urVI/AAAAAAAAA8Q/Yn8zPrCEkKM/s288/Bookmarklets.gif" alt="iPhone Bookmarklets" style="float: left; margin-right: 7px;" class="postimage" /&gt;I have an old website where I keep most of my bookmarklets.  I'm planning on deprecating that site and just putting up some personal stuff (since I don't do what that site says I do anymore).&lt;br /&gt;&lt;br /&gt;This bookmarklet is probably my most used bookmarklet.  Basically, you enter a regular expression and each match in the page will be highlighted.  It cycles through 16 color schemes to change the highlight color.&lt;br /&gt;&lt;br /&gt;If you just want to install the bookmarklet, grab this link and drag it onto your bookmarklet toolbar in your browser.  &lt;br /&gt;&lt;a href="javascript:if(typeof(searches)=='undefined'){var searches=0;};(function(){var count=0,text,regexp;text=prompt('Search regex:','');if(text==null||text.length==0)return;try{regexp=new RegExp(text,'i');}catch(er){alert('Unable to create regular expression using text \''+text+'\'.\n\n'+er);return;}function searchWithinNode(node,re){var pos,skip,acronym,middlebit,endbit,middleclone;skip=0;if(node.nodeType==3){pos=node.data.search(re);if(pos&amp;gt;=0){acronym=document.createElement('ACRONYM');acronym.title='Search '+(searches+1)+': '+re.toString();acronym.style.backgroundColor=backColor;acronym.style.borderTop='1px solid '+borderColor;acronym.style.borderBottom='1px solid '+borderColor;acronym.style.fontWeight='bold';acronym.style.color=borderColor;middlebit=node.splitText(pos);endbit=middlebit.splitText(RegExp.lastMatch.length);middleclone=middlebit.cloneNode(true);acronym.appendChild(middleclone);middlebit.parentNode.replaceChild(acronym,middlebit);count++;skip=1;}}else if(node.nodeType==1&amp;amp;&amp;amp;node.childNodes&amp;amp;&amp;amp;node.tagName.toUpperCase()!='SCRIPT'&amp;amp;&amp;amp;node.tagName.toUpperCase!='STYLE')for(var child=0;child&amp;lt;node.childNodes.length;++child)child=child+searchWithinNode(node.childNodes[child],re);return skip;}var borderColor='#'+(searches+8).toString(2).substr(-3).replace(/0/g,'3').replace(/1/g,'6');var backColor=borderColor .replace(/3/g,'c').replace(/6/g,'f');if(searches%2516/8&amp;gt;=1){var tempColor=borderColor;borderColor=backColor;backColor=tempColor;}searchWithinNode(document.body,regexp);window.status='Found '+count+' match'+(count==1?'':'es')+' for '+regexp+'.';if(count&amp;gt;0)searches++;})();"&gt;Regex Search&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you want to use it on your mobile device, you can use my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/08/iphone-bookmarklet-to-install-iphone_06.html"&gt;Mobile Bookmarklet Installer Bookmarklet&lt;/a&gt; (which, by the way, will install itself too).&lt;br /&gt;&lt;br /&gt;I'm sure it doesn't work in IE, but I haven't tested it in a really long time.  If you'd like to see what it would do in IE if IE didn't suck so badly, just click it.&lt;br /&gt;&lt;br /&gt;If you're interested in the code, here it is!&lt;pre name="code" class="js"&gt;// check to see if the variable searches has been defined.&lt;br /&gt;// if not, create it.  this variable is to cycle through &lt;br /&gt;// highlight colors.&lt;br /&gt;if (typeof(searches) == 'undefined')&lt;br /&gt;{&lt;br /&gt;  var searches = 0;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;(&lt;br /&gt;  function()&lt;br /&gt;  {&lt;br /&gt;    // just some variables&lt;br /&gt;    var count = 0, text, regexp;&lt;br /&gt;&lt;br /&gt;    // prompt for the regex to search for&lt;br /&gt;    text = prompt('Search regexp:', '');&lt;br /&gt;&lt;br /&gt;    // if no text entered, exit bookmarklet&lt;br /&gt;    if (text == null || text.length == 0)&lt;br /&gt;      return;&lt;br /&gt;&lt;br /&gt;    // try to create the regex object.  if it fails&lt;br /&gt;    // just exit the bookmarklet and explain why.&lt;br /&gt;    try&lt;br /&gt;    {&lt;br /&gt;      regexp = new RegExp(text, 'i');&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    catch (er)&lt;br /&gt;    {&lt;br /&gt;      alert('Unable to create regular expression using text \'' + text + '\'.\n\n' + er);&lt;br /&gt;      return;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // this is the function that does the searching.&lt;br /&gt;    function searchWithinNode(node, re)&lt;br /&gt;    {&lt;br /&gt;      // more variables&lt;br /&gt;      var pos, skip, acronym, middlebit, endbit, middleclone;&lt;br /&gt;      skip = 0;&lt;br /&gt;&lt;br /&gt;      // be sure the target node is a text node&lt;br /&gt;      if (node.nodeType == 3)&lt;br /&gt;      {&lt;br /&gt;        // find the position of the first match&lt;br /&gt;        pos = node.data.search(re);&lt;br /&gt;&lt;br /&gt;        // if there's a match . . . &lt;br /&gt;        if (pos &gt;= 0)&lt;br /&gt;        {&lt;br /&gt;          // create the acronym node.&lt;br /&gt;          acronym = document.createElement('ACRONYM');&lt;br /&gt;          acronym.title = 'Search ' + (searches + 1) + ': ' + re.toString();&lt;br /&gt;          acronym.style.backgroundColor = backColor;&lt;br /&gt;          acronym.style.borderTop = '1px solid ' + borderColor;&lt;br /&gt;          acronym.style.borderBottom = '1px solid ' + borderColor;&lt;br /&gt;          acronym.style.fontWeight = 'bold';&lt;br /&gt;          acronym.style.color = borderColor;&lt;br /&gt;    &lt;br /&gt;    // get the last half of the node and cut the match&lt;br /&gt;    // out.  then, clone the middle part and replace it with&lt;br /&gt;    // the acronym&lt;br /&gt;          middlebit = node.splitText(pos);&lt;br /&gt;          endbit = middlebit.splitText(RegExp.lastMatch.length);&lt;br /&gt;          middleclone = middlebit.cloneNode(true);&lt;br /&gt;          acronym.appendChild(middleclone);&lt;br /&gt;          middlebit.parentNode.replaceChild(acronym, middlebit);&lt;br /&gt;          count++;&lt;br /&gt;          skip = 1;&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;      // if the node is not a text node and is not&lt;br /&gt;      // a script or a style tag then search the children&lt;br /&gt;      else if (&lt;br /&gt;        node.nodeType == 1&lt;br /&gt;        &amp;&amp; node.childNodes&lt;br /&gt;        &amp;&amp; node.tagName.toUpperCase() != 'SCRIPT'&lt;br /&gt;        &amp;&amp; node.tagName.toUpperCase != 'STYLE'&lt;br /&gt;      )&lt;br /&gt;        for (var child = 0; child &lt; node.childNodes.length; ++child)&lt;br /&gt;          child = child + searchWithinNode(node.childNodes[child], re);&lt;br /&gt;&lt;br /&gt;      return skip;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // use the search count to get the colors.&lt;br /&gt;    var borderColor = '#' &lt;br /&gt;      + (searches + 8).toString(2).substr(-3)&lt;br /&gt;      .replace(/0/g, '3')&lt;br /&gt;      .replace(/1/g, '6');&lt;br /&gt;    &lt;br /&gt;    var backColor = borderColor&lt;br /&gt;      .replace(/3/g, 'c')&lt;br /&gt;      .replace(/6/g, 'f');&lt;br /&gt;&lt;br /&gt;    // for the last half of every 16 searhes, invert the&lt;br /&gt;    // colors.  this just adds more variation between&lt;br /&gt;    // searches.&lt;br /&gt;    if (searches % 16 / 8 &gt;= 1)&lt;br /&gt;    {&lt;br /&gt;      var tempColor = borderColor;&lt;br /&gt;      borderColor = backColor;&lt;br /&gt;      backColor = tempColor;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    searchWithinNode(document.body, regexp);&lt;br /&gt;    window.status = 'Found ' + count + ' match'&lt;br /&gt;      + (count == 1 ? '' : 'es')&lt;br /&gt;      + ' for ' + regexp + '.';&lt;br /&gt;&lt;br /&gt;    // if we made any matches, increment the search count&lt;br /&gt;    if (count &gt; 0)&lt;br /&gt;      searches++;&lt;br /&gt;  }&lt;br /&gt;)();&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-456107650276245002?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/456107650276245002/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/09/regular-expression-search-bookmarklet.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/456107650276245002'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/456107650276245002'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/09/regular-expression-search-bookmarklet.html' title='Regular Expression Search Bookmarklet'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SnsP169urVI/AAAAAAAAA8Q/Yn8zPrCEkKM/s72-c/Bookmarklets.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5013057954663663727</id><published>2010-09-03T09:43:00.000-07:00</published><updated>2011-06-16T14:28:34.568-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><title type='text'>What Really Makes a Good Programmer?</title><content type='html'>&lt;a href="https://spreadsheets0.google.com/oimg?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;oid=7&amp;zx=dlqbwjyhaj19"&gt;&lt;img src="https://spreadsheets0.google.com/oimg?key=0At0EPL2ZBHgPdDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE&amp;oid=6&amp;zx=dcxiq0bx39y6" alt="Survey Response Counts" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I've been working on a series of articles about &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-tips-tricks-and.html"&gt;mainframe migrations&lt;/a&gt;.  My next post upcoming will be talking about source code translation tools.  It got me to wondering, what is a good programmer?&lt;br /&gt;&lt;br /&gt;I decided that the best way to figure out what makes a good programmer is to ask those who have a history of working with programmers.&lt;br /&gt;&lt;br /&gt;I've been playing around with &lt;a href="http://docs.google.com"&gt;Google Docs&lt;/a&gt; and this seemed to be another solid opportunity to leverage some new Google Docs features, specifically Google Forms.  I know this particular one is a little annoying but Google Forms didn't really give me many options for entering these data so I apologize for that.&lt;br /&gt;&lt;br /&gt;I really appreciate your taking the time to read my blog and to help me with my research.  So, without further ado, here is the &lt;a href="https://spreadsheets3.google.com/viewform?formkey=dDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE6MQ&amp;theme=0AX42CRMsmRFbUy01ZDc3NWRkZC1mYWQzLTQxYzItYTQ2Yy05OGFmMDE1NGY4ZjY&amp;ifq"&gt;What Makes a Good Programmer survey&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;OK, maybe a little ado.  Be sure to read and follow the instructions.  They're pretty easy and, I think, relatively clever little recursive instructions to get a lot of responses.  Thanks again for your help with my informal study.&lt;br /&gt;&lt;br /&gt;If you've already taken the survey, I've published the results.  Please don't look at the &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/10/results-of-what-really-makes-good.html"&gt;results of the "What Really Makes a Good Programmer" survey&lt;/a&gt; until you've completed it.&lt;br /&gt;&lt;br /&gt;&lt;iframe src="https://spreadsheets.google.com/embeddedform?formkey=dDN3RVJYLVJ2dXpoS3FTdDNXRVc4RVE6MQ" width="680" height="1110" frameborder="0" marginheight="0" marginwidth="0"&gt;Loading the What Makes a Good Programmer survey...&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5013057954663663727?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5013057954663663727/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/09/what-really-makes-good-programmer.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5013057954663663727'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5013057954663663727'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/09/what-really-makes-good-programmer.html' title='What Really Makes a Good Programmer?'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6034907640250102137</id><published>2010-06-25T14:39:00.000-07:00</published><updated>2010-06-25T15:01:30.051-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Mainframe Migration'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Mainframe Migrations: Extracting Business Logic from Source Code</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Mainframe_computer"&gt;&lt;img src="http://upload.wikimedia.org/wikipedia/commons/4/49/Ibm704.gif" alt="Mainframe" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I've been involved with several migration projects (some mainframe migrations and some migrating from an older platform to a newer one).  At the beginning of any migration project, I like to ask, "Why do you want to migrate your application instead of just rewriting it?"&lt;br /&gt;&lt;br /&gt;One of the most common responses I hear (among the ones I mentioned in my &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-tips-tricks-and.html"&gt;mainframe migration introduction post&lt;/a&gt;) is &lt;br /&gt;&lt;blockquote&gt;We don't really have documentation; our source code is the documentation.&lt;/blockquote&gt;&lt;br /&gt;I've been in the software industry long enough to know that when schedules or money get tight (and they always do), documentation is the first thing to go, so I seldom expect adequate documentation and that's fine.  Documentation is nice to have, but I'm pretty good and finding out what you need without it.  After all, I get plenty of practice.  By the way programmers talk, one would think there is almost never enough documentation and if there is, it's still not adequate.&lt;br /&gt;&lt;br /&gt;I know that most projects lack documentation and that most code is legacy code (at least would be according to &lt;a href="http://www.amazon.com/gp/product/0131177052?ie=UTF8&amp;tag=dpatcalon-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0131177052"&gt;Working Effectively with Legacy Code&lt;/a&gt; by Michael Feathers).  This is really no big deal and doesn't differ all that much from most projects.  In fact, most of my projects start with a pretty clean slate.  I get to go in, help the client document processes, recommend improvements, automate everything I can, and by the end of the project the client has a nice sleek system with clean documentation and good unit test coverage.&lt;br /&gt;&lt;br /&gt;For some reason, that process frightens clients who already have a system in place.  As a result, a lot of clients facing a mainframe migration elect to migrate rather than rewrite the application.  &lt;br /&gt;&lt;blockquote&gt;We just don't have the time or money to start from scratch.  All of our documentation is in the source code.&lt;/blockquote&gt;I have two issues with this.  First, the source code is not adequate documentation because you cannot derive business logic from code.  Second, if you use the source code as your documentation, the review process will consume almost the same resources as would the discovery process on a brand new project.&lt;br /&gt;&lt;h1&gt;You Cannot Derive Business Logic from Code&lt;/h1&gt;I know this sounds a little hard to swallow at first.  To be sure, there's a lot of code out there that you can look at and come up with a very reasonable approximation of what was the original intent of the developer.  If you assume the developer sufficiently understood the original intent of the business logic, your result will also be a reasonable approximation to the actual business logic.&lt;br /&gt;&lt;br /&gt;But, no client has ever asked me for an application that is just a reasonable approximation of the requirements they have.  What you'll find is that the best result you get from interpreting source code is a set of reasonable approximations to desired business logic.  Every interpretation, no matter how reasonable, will need to be clarified or you'll risk having a dysfunctional application.&lt;br /&gt;&lt;br /&gt;Here's an easy example.  Go ask a programmer to "write a method that will verify that a person is old enough to drink."  You'll probably get something like this:&lt;pre name="code" class="javascript"&gt;function VerifyIsOfLegalDrinkingAge(age)&lt;br /&gt;{&lt;br /&gt;  return age &gt;= 21;&lt;br /&gt;}&lt;/pre&gt;Now, show that code to another programmer and ask what the original business rule was.&lt;br /&gt;&lt;br /&gt;Chances are, you'll get, "ages 21 and older are of legal drinking age."  While that's a close approximation, that's not the business rule you asked for.  You said, "verify that a person is old enough to drink."  You didn't say, "verify that a person is at least 21."&lt;br /&gt;&lt;br /&gt;This is a really basic example and most problems in software engineering aren't as simple.  In fact, even with simple problems, most code (especially legacy code) will not look like that.  In your mainframe application, the above method would look more like this:&lt;pre class="javascript" name="code"&gt;function CheckAge(age)&lt;br /&gt;{&lt;br /&gt;  // be sure that the person has appropriate identification&lt;br /&gt;  // and that it is a valid picture id&lt;br /&gt;  return age &gt; MINIMUM_AGE;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;You'll dig through thousands of lines of code just to find MINIMUM_AGE = 20 somewhere.  Why is the minimum age 20 instead of 21?  Because the code had a bug in it and it should've been age &gt;= MINIMUM_AGE.  Instead of fixing the method, they just decremented the age.  Then, you'll have to find the code that's calling the method to figure out why you would verify that an age is greater than twenty.  By the time you get this method implemented correctly, you'll have consumed far more time than it would have taken the client to say, "I can't serve beer to anyone under 21."&lt;br /&gt;&lt;br /&gt;Now, consider the fact that developers who know their languages well know what the language will handle for them.  For example, C# defaults integers to 0.  If you were to ask a C# developer to write a method that returns true if someone is too young to drink, you might get something like this:&lt;pre name="code" class="c#"&gt;public static bool IsTooYoungForBooze(Person drinker){&lt;br /&gt;    return drinker.Age &lt; MINIMUM_AGE;&lt;br /&gt;}&lt;/pre&gt;If you then ask someone to convert that to JavaScript, you'll probably get something like this:&lt;pre name="code" class="javascript"&gt;function IsTooYoungForBooze(drinker)&lt;br /&gt;{&lt;br /&gt;  return drinker.Age &lt; MINIMUM_AGE;&lt;br /&gt;}&lt;/pre&gt;Seems like a reasonable approximation?  In C#, if someone fails to set Person.Age, 0 will be less than MINIMUM_AGE; however, in JavaScript, undefined is not less than MINIMUM_AGE.  This kind of bug tends to rear its head late in the game.  Is this the kind of mistake you're willing to make because you had a "reasonable approximation?"&lt;h1&gt;Reviewing Source Code Is Resource Intensive&lt;/h1&gt;I know it's tempting to expect a programmer to look at your source code on one screen and write brand new code on the other screen.  It's just not going to work that way.  If you don't believe me, pick one of your smallest methods and ask a programmer to document the business logic.Chances are, you'll get a dozen questions.  &lt;blockquote&gt;Why does this code never look at index 0 in this array?  Where does this value get set?  Is this always going to be 4 characters?  What happens if this number is negative?  What is this method trying to do?  Who's calling it?&lt;/blockquote&gt;"Who's calling it?" is a very serious question.  If you're prepared to list every place in the source code where that method is being called, be prepared for more questions about static or global variables, protection levels, database constraints, etc.  If you finally get through that, be prepared for even more questions because now you're getting into the meat of the issue: Now that I know what it did then, what do you want it to do now?&lt;br /&gt;&lt;br /&gt;Imagine how much improved your situation would be had you told your developer what the business logic was supposed to be instead of asking to have it inferred from a source that provides a best case scenario of a reasonable approximation.&lt;h1&gt;What's the Solution?&lt;/h1&gt;One of my biggest pet peeves is when someone presents a problem but hasn't considered possible solutions.  So, as not to break my own rules, I do have a solution to this particular problem.  Rather than looking at your source code as the only source of business logic documentation, look at it as a guide and recognize that you simply don't have documentation.&lt;br /&gt;&lt;br /&gt;I know it's rough to admit something like that, but if it makes you feel any better, nobody else has documentation either.  Besides, admitting you have a problem is the first step to recovery, right?  So now that we've acknowledged that we lack adequate documentation we can move on to bigger and better things.  We can use the source code as a guide to get us (and keep us) moving in the right direction.  We can write a new application in short iterations with open communication and good unit test coverage while we document business rules.  &lt;br /&gt;&lt;br /&gt;In my experience, a rewrite saves time and money in the short run.  In the long run, as you consider feature changes and maintenance costs, it's no contest that a rewrite beats a migration every time.  Keep in mind that your application is just an application.  I know it seems like there's a lot going on in there, but it's just another application.  Every developer I've ever talked to about mainframe migrations has said, &lt;blockquote&gt;We shouldn't be migrating this.  It would be so much easier just to rewrite from scratch and then it'd be a good program too.  I mean, really . . . it's just a ____________.&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6034907640250102137?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6034907640250102137/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-extracting.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6034907640250102137'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6034907640250102137'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-extracting.html' title='Mainframe Migrations: Extracting Business Logic from Source Code'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2260045010181438911</id><published>2010-06-22T13:30:00.000-07:00</published><updated>2010-06-22T14:16:09.929-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Mainframe Migration'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Mainframe Migrations: Migrating Data and Data Structures</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Mainframe_computer"&gt;&lt;img src="http://upload.wikimedia.org/wikipedia/commons/4/49/Ibm704.gif" alt="Mainframe" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;It's hard to resist the urge to export data from a mainframe database, import them into the new database engine, and call the repository "finished."  It is easy, on the other hand, to say "This has been working for 25 years; why would we change it now?"&lt;br /&gt;&lt;br /&gt;Well, kind of a lot has changed in the last 25 years.  For example, Adabas is a post-relational inverted list database.  Sql Server on the other hand is a relational database with heaps and b-trees.  I'm not saying that one is better than the other; to be sure, each has its merits.  What I am saying is that they're different.&lt;br /&gt;&lt;br /&gt;They store and retrieve data differently.  They have different rules.  They are optimized for different normal forms (again, with a background in both &lt;a href="http://en.wikipedia.org/wiki/Online_transaction_processing"&gt;OLTP&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Online_analytical_processing"&gt;OLAP&lt;/a&gt;, I'm not asserting that one is better than the other).  With all of the ways that DBMSs differ, especially current vs. mainframe DBMSs, you just can't "lift and shift" and expect the same functionality or the same performance.&lt;br /&gt;&lt;br /&gt;Continuing with the Adabas vs. Sql Server example (because that's what I have experience with), if you just pull the data right out of Adabas, you'll find that it is in need of serious refactoring.  While it makes perfect sense to work with the Adabas data structure in Natural, it doesn't make sense using Sql Server and C#.  The denormalized data will complicate your CRUD operations and clutter your database with empty results.&lt;br /&gt;&lt;br /&gt;Then, you'll either have to filter your results or support CRUD operations that populate the empty rows so you'll get expected results back.  In my experience, you can deal with a lot of these hassles by importing the data into a staging database and running an &lt;a href="http://en.wikipedia.org/wiki/Extract,_transform,_load"&gt;ETL&lt;/a&gt; process to get them into your application database.  During the ETL process, you can eliminate empty rows, convert default values to NULLs, and normalize your data.&lt;br /&gt;&lt;br /&gt;If you take the time to rebuild your database to support your application, it's really easy to keep your ETL process up to date to get the data into your database.  On the other hand, if you build your application around the existing data structure, you'll spend countless hours pulling your hair out thinking to yourself, "who stores data this way?"  You'll probably even find yourself cursing Adabas, Software AG, and mainframes in general.  Meanwhile, it's your own fault!&lt;br /&gt;&lt;br /&gt;You should have rebuilt the database and transformed your data to fit into it (which is easy), rather than leaving the database structure alone and forcing your programmers to compensate for the fact the the framework (and current software standards) won't support the intentional misuse of the new DBMS.&lt;br /&gt;&lt;br /&gt;To put this another way, transforming data is repeatable and easy.  It takes very little effort to extract data from a staging database and make them fit into a database designed to be efficient with your application.  It's also not that hard to design the application database either.  It will certainly take more time to design the database and the ETL process than it will to just dump the data into a database, but I assert that you will make up for that difference when your programmers start trying to fit a very large square peg into a very small round hole.&lt;br /&gt;&lt;br /&gt;Just to make sure I'm not alone, I asked several of my colleagues who have work experience in mainframe migrations in a very informal survey.  I asked them to consider a situation where they were migrating an application from Adabas to Sql Server.  The source data are obviously the same and the result was to also be constant.  That is to say that the entities presented to the BLL from the DAL should be clean, the code should be clean, and the data access should be equally perfomant.  How much effort do you have to spend building the new database, writing the ETL process, and constructing the data access layer if you're "lifting and shifting" vs. rebuilding the database from scratch.&lt;br /&gt;&lt;br /&gt;Here are the results:&lt;br /&gt;&lt;img src="http://chart.apis.google.com/chart?cht=bvs&amp;chs=330x180&amp;chd=t:2,1|4,2|2,8&amp;chxt=x,y&amp;chxl=0:|Rebuild|Lift+and+Shift&amp;chbh=70&amp;chxr=1,0,12&amp;chds=0,12&amp;chco=0000ff,00ff00,ff0000&amp;chf=b0,lg,0,0000aa,0,0000ff,.5,aaaaff,1|b1,lg,0,00aa00,0,00ff00,.5,aaffaa,1|b2,lg,0,aa0000,0,ff0000,.5,ffaaaa,1&amp;chtt=Rebuild+vs.+Lift+and+Shift|In+Units+of+Work&amp;chdl=Build+the+Database|Extract,Transform,+Load|Data+Access+Layer&amp;chma=20,20,20,20" alt="Bar Graph: Rebuild vs. Lift and Shift Approach to Mainframe Database Migration" /&gt;&lt;br /&gt;&lt;br /&gt;To my colleagues, the choice seems clear.  The fact is that you're going to have to transform your data at some point.  You either have to do it in the ETL process and store the data in a cleaner format or you're going to have to do it in your &lt;a href="http://en.wikipedia.org/wiki/Data_access_layer"&gt;DAL&lt;/a&gt;.  If you do it in your DAL, it'll be harder to write the DAL because of the amount of &lt;a href="http://dictionary.reference.com/browse/transmogrify"&gt;transmogrification&lt;/a&gt; you'll have to do between your BLL and your repository.  &lt;br /&gt;&lt;br /&gt;And, let's just hope you never have to change anything.  Storing your data in an unnatural format will make it very difficult to add features down the road, your DAL will be difficult to maintain (at best), and your entire application will most likely be considerably less performant than had you opted to save a little time and money and just refactor your database from the beginning.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2260045010181438911?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2260045010181438911/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-migrating-data-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2260045010181438911'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2260045010181438911'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-migrating-data-and.html' title='Mainframe Migrations: Migrating Data and Data Structures'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-8143965894640725150</id><published>2010-06-22T07:52:00.000-07:00</published><updated>2011-11-18T04:44:13.632-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Mainframe Migration'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Autopilot Consulting'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Mainframe Migrations: Tips, Tricks, and How-To's</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Mainframe_computer"&gt;&lt;img src="https://lh5.googleusercontent.com/-Qz7ARmS4uN8/TsMkaofPMoI/AAAAAAAABd4/RYqoeVigBmQ/s288/mainframe.gif" alt="Mainframe" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Yesterday, I wrote a blog post about mainframe migrations.  The top brass in my company took a look at it and told me that the world is just not ready for my thoughts on mainframe migrations yet.&lt;br /&gt;&lt;br /&gt;I unpublished that post and will republish it at the end of this series.  Leading up to my thesis I will post a series of articles detailing the things I've learned leading my last few mainframe migrations.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-migrating-data-and.html"&gt;Migrating Data and Data Structures&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-extracting.html"&gt;The Documentation is in the Source Code&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://dpatrickcaldwell.blogspot.com/2011/11/mainframe-migrations-source-code.html"&gt;Source Code Translation Tools&lt;/a&gt;&lt;/li&gt;&lt;li&gt;The Original Authors are Unavailable&lt;/li&gt;&lt;li&gt;My Original Mainframe Migration Post&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Be on the lookout for more updates.  As I write these posts, each item will become a link to the article.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-8143965894640725150?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/8143965894640725150/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-tips-tricks-and.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8143965894640725150'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8143965894640725150'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/06/mainframe-migrations-tips-tricks-and.html' title='Mainframe Migrations: Tips, Tricks, and How-To&apos;s'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh5.googleusercontent.com/-Qz7ARmS4uN8/TsMkaofPMoI/AAAAAAAABd4/RYqoeVigBmQ/s72-c/mainframe.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3367614470948471829</id><published>2010-03-11T18:24:00.000-08:00</published><updated>2011-06-16T15:42:03.735-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaScript'/><category scheme='http://www.blogger.com/atom/ns#' term='Code Snippets'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><category scheme='http://www.blogger.com/atom/ns#' term='jQuery'/><title type='text'>Custom jQuery Selector for External and Internal Links</title><content type='html'>&lt;a href="http://www.jquery.com"&gt;&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/S5mfLn8BKJI/AAAAAAAABOc/MIdKiZMowoA/s288/jquerylogo1.png" alt="jQuery Logo" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I was working on a website for &lt;a href="http://www.abbyandoat.com"&gt;my fiancee&lt;/a&gt; and her church group called &lt;a href="http://www.dofaya.org"&gt;The Diocese of Atlanta Young Adults&lt;/a&gt;.  They were hosting an event they call the &lt;a href="http://www.youngadultsummit.org"&gt;Young Adult Summit&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;One of the requirements I had was to provide a warning to users before they left the page by after having clicked an external link.  Using &lt;a href="http://www.jquery.com"&gt;jQuery&lt;/a&gt;, I was able to bind to the click event with easy cross browser compatibility and I used &lt;a href="http://www.jqueryui.com"&gt;jQueryUI&lt;/a&gt; to open a modal dialog box.  Pretty basic stuff.&lt;br /&gt;&lt;br /&gt;The one thing I regretted was that I had to use a class name to determine which links were external and which were internal.  A few days ago, I discovered that jQuery supports custom selectors and I decided to write a pair of custom selectors for identifying internal and external links.&lt;br /&gt;&lt;pre name="code" class="javascript"&gt;jQuery.extend(&lt;br /&gt;  jQuery.expr[ ":" ],&lt;br /&gt;  {&lt;br /&gt;    /*&lt;br /&gt;      /:\/\// is simply looking for a protocol definition.&lt;br /&gt;      technically it would be better to check the domain&lt;br /&gt;      name of the link, but i always use relative links&lt;br /&gt;      for internal links.&lt;br /&gt;    */&lt;br /&gt;&lt;br /&gt;    external: function(obj, index, meta, stack)&lt;br /&gt;    {&lt;br /&gt;      return /:\/\//.test($(obj).attr("href"));&lt;br /&gt;    },&lt;br /&gt;&lt;br /&gt;    internal: function(obj, index, meta, stack)&lt;br /&gt;    {&lt;br /&gt;      return !/:\/\//.test($(obj).attr("href"));&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;);&lt;/pre&gt;I do have a few items of note.  First, you'll notice that I'm not actually looking for the current domain name in the links.  That's because I use relative links for all of my internal links these days.  Thus, I know that if there's not a protocol definition that it's an internal link.  If you need to identify internal links by domain as well, you can just pull that out of top.location.href and check for it too.&lt;br /&gt;&lt;br /&gt;Second, you could technically use this selector on any DOM object.  There are several ways to get around this.  One way would be to verify that the object is an anchor tag.  Another would be to verify that the href attribute exists.  I just plan on using common sense.&lt;br /&gt;&lt;br /&gt;Here's a quick usage example:&lt;pre name="code" class="javascript"&gt;$("a:external").click(verifyNavigateAway);&lt;br /&gt;$("a:internal").css("font-weight", "bold");&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3367614470948471829?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3367614470948471829/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/03/custom-jquery-selector-for-external-and.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3367614470948471829'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3367614470948471829'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/03/custom-jquery-selector-for-external-and.html' title='Custom jQuery Selector for External and Internal Links'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/S5mfLn8BKJI/AAAAAAAABOc/MIdKiZMowoA/s72-c/jquerylogo1.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3996933537785565623</id><published>2010-03-11T16:59:00.000-08:00</published><updated>2010-03-11T17:01:27.304-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Fail'/><title type='text'>The Accidental Programmer</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/S48RvMlpYTI/AAAAAAAABOA/NuQEnuu7bsE/s288/learn-to-fly-here.jpg" alt="Airplane Accident" style="float: left; margin-right: 7px;" class="postimage" /&gt;I've been in the software field for some time now, and over the years I have worn many hats.  I have been the sole developer in a psychopharmacological research lab; I have been a private contractor and security analyst; I have developed human resources software and data warehouses; I've been a programmer, a tech lead, a project manager, a VP, and a partner.  I've hired and fired developers, laid off friends (and a fiancee), and interviewed at least a hundred candidates locally and overseas.&lt;br /&gt;&lt;br /&gt;In all of my experience, I've discovered an unavoidable and intolerable fact: most programmers can't program.  Just so we're clear, by "most programmers," I'm talking roughly 90 - 95 percent.  Now, I know this isn't an original sentiment.  &lt;a href="http://www.codinghorror.com/blog/2007/02/why-cant-programmers-program.html"&gt;Jeff Atwood&lt;/a&gt; talked about this in 2007 in an article which has since been quoted by dozens of people in the development community including folks like &lt;a href="http://haacked.com/archive/2007/02/27/Why_Cant_Programmers._Read.aspx"&gt;Phil Haack&lt;/a&gt;.  So, if it's an already beaten dead horse, why am I writing about it again?&lt;br /&gt;&lt;br /&gt;Well, frankly, I was wondering why there are so many bad programmers out there who seem evidently to be doing so well.  Why are there so many crappy programmers getting work and making bank?  Look at how many large companies are learning hard lessons from off-shoring experiences, yet everybody still wants to send work overseas!  It's like there's a big chronic "WTF barrier" between business people thinking they could save a few bucks and programmers telling them how much it'll cost them in the long run.&lt;br /&gt;&lt;br /&gt;Why doesn't anybody notice?  Why doesn't everybody realize that these people don't know what they're doing?  Well, it's because they produce programs that partially work.  Jeff's right that most programmers can't even write a single line of code, so how is it that they manage to produce work that's functional enough to convince the world that they're capable developers?  Well, I figured it out.  They do it by accident; I call them accidental programmers.&lt;br /&gt;&lt;br /&gt;By combining the forces of the internet, feature rich IDEs, code templating, and auto completion, an accidental programmer has all the tools he or she needs to accidentally write a semi-functional bad program without ever having to write a real line of code.&lt;br /&gt;&lt;br /&gt;Now, I am sure my readers wouldn't let me get away with making such claims without providing empirical evidence, so here are some code snippets I believe you couldn't write on purpose:&lt;pre name="code" class="c#"&gt;tbPassword.Text.Trim().ToString().Trim();&lt;/pre&gt;&lt;pre name="code" class="c#"&gt;protected void btnLogin_Click(object sender, EventArgs e)&lt;br /&gt;{&lt;br /&gt;    SqlClientUtilities sqlData = new SqlClientUtilities();&lt;br /&gt;    SqlDataReader drPassinfo = null;&lt;br /&gt;&lt;br /&gt;    string sLoginSQL = "select a.agentid,u.userName,a.FirstName,a.Lastname from dbo.person a, dbo.users u where  ";&lt;br /&gt;    sLoginSQL +=  "  a.userid = u.userid and u.username='" + txtUserName.Text.Trim() + "'";&lt;br /&gt;    sLoginSQL += " AND personid = '" + txtPassword.Text.Trim() + "'";&lt;br /&gt;&lt;br /&gt;    drPassinfo = sqlData.SqlClientExecuteDataReader(sLoginSQL);&lt;br /&gt;&lt;br /&gt;    if (drPassinfo.HasRows)&lt;br /&gt;    {&lt;br /&gt;        AppSupportUtils.WriteError("Records found");&lt;br /&gt;&lt;br /&gt;        //valid login - redirect&lt;br /&gt;        Session["userlogin"] = txtUserName.Text.Trim();&lt;br /&gt;        Response.Redirect("Manage.aspx",false);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;pre name="code" class="c#"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;/// This class just returns an object which holds a date&lt;br /&gt;/// &amp;lt;/summary&amp;gt;&lt;br /&gt;public class Date&lt;br /&gt;{&lt;br /&gt;    public int Year = 0;&lt;br /&gt;    public int Month = 0;&lt;br /&gt;    public int Day = 0;&lt;br /&gt;&lt;br /&gt;    public Date()&lt;br /&gt;    {&lt;br /&gt;        Year = 1901;&lt;br /&gt;        Month = 1;&lt;br /&gt;        Day = 1;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Date(System.DateTime dt)&lt;br /&gt;        : this()&lt;br /&gt;    {&lt;br /&gt;        Year = dt.Year;&lt;br /&gt;        Month = dt.Month;&lt;br /&gt;        Day = dt.Day;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public Date(string dt)&lt;br /&gt;        : this()&lt;br /&gt;    {&lt;br /&gt;        // en-US     M/d/yyyy&lt;br /&gt;        CultureInfo MyCultureInfo = new CultureInfo("en-US");&lt;br /&gt;        try&lt;br /&gt;        {&lt;br /&gt;            DateTime MyDateTime = DateTime.Parse(dt, MyCultureInfo);&lt;br /&gt;            Year = MyDateTime.Year;&lt;br /&gt;            Month = MyDateTime.Month;&lt;br /&gt;            Day = MyDateTime.Day;&lt;br /&gt;        }&lt;br /&gt;        catch { ;}&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public override string ToString()&lt;br /&gt;    {&lt;br /&gt;        return Month.ToString() + "/" + Day.ToString() + "/" + Year.ToString();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public override int GetHashCode()&lt;br /&gt;    {&lt;br /&gt;        return (int)(Year * 12) + (Month * 30) + Day;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;pre name="code" class="javascript"&gt;function invertBool(bool)&lt;br /&gt;{&lt;br /&gt;  if (bool == false) return true;&lt;br /&gt;  return false;&lt;br /&gt;}&lt;/pre&gt;&lt;pre name="code" class="javascript"&gt;function sendSecureVote(index)&lt;br /&gt;{&lt;br /&gt;  var checksum = Math.round(getFormattedDate() * 57 / 33 - 147 + 2009);&lt;br /&gt;  startAjaxRequest("http://www.theserverhasbeenanonymized.com/voteCounter.php?checksum=" + checksum + "&amp;voteFor=" + index);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function getFormattedDate()&lt;br /&gt;{&lt;br /&gt;  var date = new Date();&lt;br /&gt;  return date.getMonth() + date.getDate() + date.getHours();&lt;br /&gt;}&lt;/pre&gt;&lt;pre name="code" class="javascript"&gt;var ssnValidator = /[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3996933537785565623?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3996933537785565623/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/03/accidental-programmer.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3996933537785565623'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3996933537785565623'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/03/accidental-programmer.html' title='The Accidental Programmer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/S48RvMlpYTI/AAAAAAAABOA/NuQEnuu7bsE/s72-c/learn-to-fly-here.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-7503797447855354308</id><published>2010-02-10T18:42:00.000-08:00</published><updated>2010-06-25T11:43:03.020-07:00</updated><title type='text'>My Mission Work in Central Tanganyika</title><content type='html'>&lt;img src="http://www.state.gov/cms_images/map_tanzania.gif" alt="Football" style="float: left; margin-right: 7px;" class="postimage" /&gt;A few years ago, I started looking for ways that I can use my time and skills to contribute more to the world around me.  One way I found that I could help my community was by volunteering for my local fire department as a volunteer member of the Alpharetta Fire Corps.  I've really enjoyed my time volunteering with the Alpharetta Fire Department, and it has excited something in me that I can't really explain.&lt;br /&gt;&lt;br /&gt;The first time I was called in to help on a structure fire, I realized what it means to give.  It didn't really click at first because everything was so hectic.  There were 4 fire engines, 6 police cars, and 2 ambulances.  There was no time to realize what was really happening as I ran around performing my duties as a Fire Corps team member.  After the house fire was suppressed, I had a moment to stop and reflect on what just happened.&lt;br /&gt;&lt;br /&gt;A family not only lost their home, but everything in it.  I started thinking about the things I own and how devastated I would be if I lost them.  I mean, possessions can be replaced, but it's what they can't replace that is heartbreaking: the memories, the favorite blankets, the family photos.&lt;br /&gt;&lt;br /&gt;That's when one of the firemen walked out the front door with a wet, singed, but intact photo album he found while extinguishing hot spots.  I watched as the fireman handed the photo album to the homeowner.  The homeowner tearfully gave thanks as the neighbors welcomed him and his family into their homes.&lt;br /&gt;&lt;br /&gt;Feeling that selflessness made me want to find a way to do more.  My fiancee works for the Episcopal Diocese of Atlanta, and the Diocese of Atlanta Young Adults are going on a mission trip to Dodoma, Tanzania.  I told her I wanted to go and she eagerly arranged for me to meet with her and Bishop Alexander.&lt;br /&gt;&lt;br /&gt;I couldn't hide my excitement as Bishop Alexander told me about the ways I could help the people in Tanzania.  It turns out that the places we are going have pretty good technology, but have nobody to help them maintain it.  They have many computers which are in disrepair.&lt;br /&gt;&lt;br /&gt;Not only will I have an opportunity to fix their computers, but I'll be able to help teach them some basic computer repair.  I'll also get to teach them how to use their computers and technologies better.  I'm thinking about setting up a VPN endpoint down there so that volunteers here will be able to provide tech support without having to be on location.&lt;br /&gt;&lt;br /&gt;I feel like I'll be able to do some good and I'm really excited about it.  Lauren and I will be leaving for Central Tanganyika with the rest of the group on May 2nd and will be returning May 11th.  The cost will be right around $3000 for each of us.&lt;br /&gt;&lt;br /&gt;Lauren and I are hoping to defray our cost by asking our friends and family to help.  I get about 85 readers on my blog per day so I thought I'd post this here too.  If you have a few bucks to help us, we would really appreciate it.&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: center; border: 2px solid black; width: 200px; padding: 30px; float: right;"&gt;&lt;form action="https://www.paypal.com/cgi-bin/webscr" method="post"&gt;&lt;input type="hidden" name="cmd" value="_s-xclick"&gt;&lt;input type="hidden" name="hosted_button_id" value="ET77HKT2J6V74"&gt;&lt;input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!"&gt;&lt;/form&gt;&lt;/div&gt;&lt;b&gt;Feel free to snail mail us:&lt;/b&gt;&lt;br /&gt;Lauren Woody and Patrick Caldwell&lt;br /&gt;295 Crab Orchard Way&lt;br /&gt;Roswell, GA 30076&lt;br /&gt;&lt;br /&gt;&lt;b&gt;My email address&lt;/b&gt; is dpatrickcaldwell and I use gmail.&lt;br /&gt;&lt;br /&gt;I'd really appreciate it if you included a note with your donation.  I only have a few weeks in Tanzania, and I'd love your input on what I should focus on.  Any ideas for maximizing their technological capability?  Anything in particular you think I should teach them?  Also, please let me know if it's okay for me to blog about you, your company, or your support.  &lt;br /&gt;&lt;br /&gt;I'd like to be able to thank our supporters on my blog, but if you would like to remain anonymous, that's no problem.  Rest assured, we will be eternally grateful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-7503797447855354308?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/7503797447855354308/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/02/my-mission-work-in-central-tanganyika.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7503797447855354308'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7503797447855354308'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/02/my-mission-work-in-central-tanganyika.html' title='My Mission Work in Central Tanganyika'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5228082912632178815</id><published>2010-01-28T20:35:00.000-08:00</published><updated>2010-01-29T07:46:30.757-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><title type='text'>The No Fun at Work Rule</title><content type='html'>&lt;a href="http://picasaweb.google.com/tncbbthositg/BlogPictures#5432000909758646082"&gt;&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/S2JYDCXDR0I/AAAAAAAABMY/8K8w8FycT8g/s288/4167_4176.jpg" alt="Football" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I've got a pretty large government client at the moment.  They feel better when they get to see me hangin' around the office (and can you blame them?).  Problem is, hangin' around their office takes away some of the luxuries of my office.  For example, my team and I like to go out to throw the football around from time to time.&lt;br /&gt;&lt;br /&gt;Sometimes we get programmers block, or we need to talk about something, or we just need some sunshine so we mosey out to the parking lot for some ball.  The other day, I asked one of my partners if it'd be okay if I brought a football to the client office.  He looked at me like I was impaired.&lt;br /&gt;&lt;br /&gt;Later that day, I was hanging out with another partner waiting to start a meeting.  I said, "Hey Chuck, I'm gonna start smoking.  I'm planning on smoking 6 cigarettes a day and I estimate that it'll take 10 minutes to smoke each one.  Is that okay?"&lt;br /&gt;&lt;br /&gt;Chuck said, "Well, Patrick, other than the fact that it's really odd that you are planning to start smoking, I don't really have a problem with it."&lt;br /&gt;&lt;br /&gt;Then, I asked him, "Well, what if instead of smoking for an hour a day, Ryan and I spend 20 minutes a day throwing the football?" &lt;br /&gt;&lt;br /&gt;Chuck leaned back in his chair and sighed.  "Well, I'm going to have to think about that.  I'm not sure that's going to look good to the client."&lt;br /&gt;&lt;br /&gt;My point is, something is ass-backwards.  If I want to spend an hour a day (on the clock) smoking cigarettes, nobody cares; but if I want to take a 20 minute break (off the clock) throwing the football, having a work related discussion, enjoying a brief reprieve from the stress of the work day, everybody thinks I'm going to look like I'm slacking off.&lt;br /&gt;&lt;br /&gt;Don't get me wrong, Chuck knows that we enjoy our outdoor time back at the Emerald office.  He knows there's benefit to it.  Not only does it keep my team happy, but it helps keep the team cohesive and energetic.  He's just concerned about how it will look to the client.&lt;br /&gt;&lt;br /&gt;My point is, why is there this "no fun at work" rule?  I've decided that I will, for the rest of my career, spend a portion of my time focussed on making sure that my employees are happy, comfortable, and healthy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5228082912632178815?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5228082912632178815/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/01/no-fun-at-work-rule.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5228082912632178815'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5228082912632178815'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/01/no-fun-at-work-rule.html' title='The No Fun at Work Rule'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/S2JYDCXDR0I/AAAAAAAABMY/8K8w8FycT8g/s72-c/4167_4176.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6646924400337889103</id><published>2010-01-11T21:40:00.000-08:00</published><updated>2010-01-12T09:32:35.634-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='How to Get a Job'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Your Field -- How to Land a Job as a Software Engineer</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s288/jobinterview.jpg" alt="Job Interview" style="float: left; margin-right: 7px;" class="postimage" /&gt;Well.  This is it.  My last post in the &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/how-to-land-job-as-software-engineer.html"&gt;How to Land a Job as a Software Engineer&lt;/a&gt; series.  I spent a lot of time deciding whether this would be the first post of the series or the last.&lt;br /&gt;&lt;br /&gt;I thought about making it the first post because it's really the first step to becoming a software engineer, but I saved it for last because I wanted to make sure I gave these thoughts plenty of time to ripen.  If you've been following the series, you probably know how this series came about in the first place.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.emeraldsoftwaregroup.com"&gt;Emerald Software Group&lt;/a&gt; has been interviewing programmers in search of new development talent.  I've used a lot of examples (both good and bad) from real life interviews I've conducted in the past few months.  This particular story is one of the most important lessons I've learned in my career, so it's really important to me to tell it correctly.&lt;br /&gt;&lt;br /&gt;I know that my style and sense of humor can be pretty sardonic and I know that many would be readers find that off-putting, but I will do my best to convey my sincerity in this post.&lt;br /&gt;&lt;br /&gt;A few months ago, I interviewed a candidate who had a masters degree in computer science from a relatively prestigious technical university in Georgia.  I expected great things from this developer and was excited about the interview.&lt;br /&gt;&lt;br /&gt;I went through my standard series of programming questions and was disappointed by his lack of understanding of the technologies he had spent the last 6 years studying.  After a while, I started feeling bad for the kid and decided to move on with the interview.  After all, like I've said before, just because you don't know the information doesn't mean I'm going to assume you're incapable of learning it.&lt;br /&gt;&lt;br /&gt;I decided to ask him a little about himself.  I closed my notebook, leaned back in my chair, and asked, "what kinds of things do you program in your spare time?"  He said, "I don't really program in my spare time."&lt;br /&gt;&lt;br /&gt;I was pretty surprised, so I asked, "Do you read programming blogs or books?  How do you learn about new technologies and techniques?"&lt;br /&gt;&lt;br /&gt;"I don't really bother learning it until I need it."&lt;br /&gt;&lt;br /&gt;I was fundamentally confused.  I didn't understand how he had spent 6 years in school studying computer science and was looking to begin a career in the field, but that he neither knew anything about it nor had any interest in learning it.  I thought for a minute and I asked him, "Why are you here?"&lt;br /&gt;&lt;br /&gt;He said, "I'm looking for a job."&lt;br /&gt;&lt;br /&gt;"No, I mean, why do you want to be a programmer?"&lt;br /&gt;&lt;br /&gt;"Because it pays well."&lt;br /&gt;&lt;br /&gt;"Do you like it?"&lt;br /&gt;&lt;br /&gt;"Not really."&lt;br /&gt;&lt;br /&gt;I was dumbfounded.  I asked him a few more questions so that I didn't have to end the interview abruptly, I told him we'd be in touch, and I walked him to the door.  If you want to write software for a living, you need to love it.  It's not just a job; it's a craft.  If you're not doing it for the love of the field, then you're committing an injustice not only against yourself but also against your employer, your teammates, and the customers.&lt;br /&gt;&lt;br /&gt;To be sure, I don't just feel this way about software engineering, but about all careers.  Whatever it is you do, do it for the love of your field.  If you don't like what you do, let alone love it, you'll be miserable for most of your life and there is no amount of money that will make up for a lifetime of misery.&lt;br /&gt;&lt;br /&gt;Now, I know that there are plenty of people out there who would be perfectly happy spending 5 days a week doing something they don't care about so that they can have more fun the other 2 days of the week.  You cannot be great at what you do if you don't care enough to try and I will not tolerate mediocrity, especially intentional mediocrity.&lt;br /&gt;&lt;br /&gt;So chose your field carefully.  Don't get a degree in software engineering if you don't like it.  Obviously, don't get two degrees.  Think about the things that you love to do.  What would you like spending your weekends doing?  Take those things and find a way to make money doing them.  If you make your living doing what you love, you'll always love what you do, you'll always be proud of your work, and you'll be in the top of your field.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6646924400337889103?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6646924400337889103/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/01/your-field-how-to-land-job-as-software.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6646924400337889103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6646924400337889103'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/01/your-field-how-to-land-job-as-software.html' title='Your Field -- How to Land a Job as a Software Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s72-c/jobinterview.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2687849431787118515</id><published>2010-01-04T09:50:00.000-08:00</published><updated>2010-01-04T09:56:43.130-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='How to Get a Job'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Your Communication Skills -- How to Land a Job as a Software Engineer</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s288/jobinterview.jpg" alt="Job Interview" style="float: left; margin-right: 7px;" class="postimage" /&gt;At long last (due to some intense deadlines), I'm getting around to the fourth installment of the &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/how-to-land-job-as-software-engineer.html"&gt;How to Land a Job as a Software Engineer&lt;/a&gt; series.  This concludes the job application sequence starting with &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/your-resume-how-to-land-job-as-software.html"&gt;your resume&lt;/a&gt; and ending with &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/your-interview-how-to-land-job-as.html"&gt;your interview&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This post is about maintaining good communication skills throughout the process.  A very close friend of mine named Ryan LaFevre, who is a Ramblin' Wreck from Georgia Tech and a hell of an engineer, once mused to me, "I don't write good; I are a engineer."  Obviously said in jest, I bring that up because I know a lot of programmers who feel like even a cursory grasp of language arts is completely unnecessary for anybody in a technical field.  My former English teachers would probably be pleased to see me say that communication skills are of fundamental importance.  &lt;br /&gt;&lt;br /&gt;Do you ever watch those court room shows?  If so, you've probably seen scenes where one attorney makes a damning argument or the witness breaks down in an emotional display, at which point the other attorney objects.  The judge says, "sustained" and instructs the jury to ignore the preceding outburst and has the text stricken from the record.  Now, imagine being on that jury.  Could you possibly ignore that?  Even if you don't make your verdict based on said event, could you possibly remain unbiased by it?&lt;br /&gt;&lt;br /&gt;It's the same thing with your communication (both verbal and non-verbal).  While I'd rather claim that I focus primarily on the skills you actually need to do your job, and while I can ignore your communication inefficiencies, I cannot ignore the feelings I had when I communicated with you.&lt;br /&gt;&lt;br /&gt;For example, I'm not going to refuse to hire a candidate just because he's 25 years old and still can't discern the difference between they're, there, and their; however, there's a pretty good chance I will assume she's not all that bright.  Thus, when I sit down with the resumes and my notes from my three favorite candidates to compare them side by side for selection, ceteris paribus, he'll get eliminated first because I won't feel like he has as much potential as the other candidates.&lt;br /&gt;&lt;br /&gt;Here are some actual excerpts I've received from job hopefuls:&lt;br /&gt;&lt;blockquote&gt;Hello Mr. Caldwell, I just wanted to touch base with you to see how your doing.&lt;/blockquote&gt;What about my doing?  You leave my doing out of this and I won't say anything about your dumb.&lt;br /&gt;&lt;blockquote&gt;The sample project I sent you is very simple on the front end, but it's back end is where all the magic happens.&lt;/blockquote&gt;I just read your email and its dumb is showing; it is back end is where you are not getting a job.  Thanks.&lt;br /&gt;&lt;blockquote&gt;I know what you mean about taking your work home with you.  I've spent a lot of time with my computer in my underwear coding until the middle of the night.&lt;/blockquote&gt;First of all, gross.  Second of all, your computer codes by itself?  Third of all, what was your computer doing in your underwear?!?!?&lt;br /&gt;&lt;blockquote&gt;Thank you very much for meeting with me today -- I really enjoyed talking with you -- If there's anything else that you need from me, please feel free to ask for it -- I'm available by phone most of the day and I'll get back to you as soon as possible&lt;/blockquote&gt;WTF?  The period exists for a reason; please use it.  If, by some chance, your period key is broken and you can't find a working keyboard with which to send your email, please hold alt and hit 0046 in lieu of the double hyphen.&lt;br /&gt;&lt;blockquote&gt;i'll be available tuesday afternoon around 1:30 or so.  is there anything i need to bring to the interview other than my resume?  i'm really excited about the opportunity and i think emerald software group will be a great fit for me&lt;/blockquote&gt;i hate you (alt + 0046)&lt;br /&gt;&lt;br /&gt;Also, sometimes it's not what you say, but how you say it (or, how often).  Don't nag me please.  I will get back to you; I promise.  If you email me every day asking about the status of the job, on your third try (or second if I'm in a bad mood), I will simply let you know that you have been selected out of the applicant pool.&lt;br /&gt;&lt;br /&gt;Written communication isn't the only place you can make silly mistakes.  Oral communication is replete with its own difficulties.  Sure, you less often misspell things orally than you do in written communication, but there are some things you're unlikely to get away with.  For example, I really don't want to hear your stance on the adult entertainment industry, I don't want to hear a story about a time you were drunk, and I really don't want to know what crimes you've gotten away with.  Keep it on a professional footing &amp;mdash; please!&lt;br /&gt;&lt;br /&gt;Here are some other hints for oral communication:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; swear a lot; I'll just think you've insufficient vocabulary.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; tell me a dirty joke you heard a few days ago.  Let's get to know each other a little first&lt;/li&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; opine on politics or religion.  Chances are you won't offend me, but on the other hand, chances are I know someone at the office you would offend.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; tell me my school sucks.  For that matter, don't tell me any school sucks.  I don't care if you're just kidding and I don't care if you went to a rival school.  If you say something disparaging about any school, I'm either going to think you weren't smart enough to get in or you're pompous.  Either way, I don't want you on my team because I may later want to hire someone from that school.&lt;/li&gt;&lt;/ul&gt;Finally, it is worth mentioning that not all communication is verbal.  There are plenty of ways you can non-verbally tell me you're not ready for the position in question.  For example, don't be late.  Don't smell bad.  Don't wear your favorite pair of tattered jeans and raggedy flip-flops.  Don't be over-confident.  Don't be under-confident.&lt;br /&gt;&lt;br /&gt;By all means, &lt;b&gt;be yourself&lt;/b&gt; and try to have some fun.  If you have fun, people are more likely to enjoy talking with you.  If you have fun, the worst that can happen is you have a good time, meet some good folks, and learn some new skills.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2687849431787118515?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2687849431787118515/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/01/your-communication-skills-how-to-land.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2687849431787118515'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2687849431787118515'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2010/01/your-communication-skills-how-to-land.html' title='Your Communication Skills -- How to Land a Job as a Software Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s72-c/jobinterview.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3751829299527797215</id><published>2009-11-20T09:28:00.000-08:00</published><updated>2009-11-20T10:46:09.666-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='How to Get a Job'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Your Interview -- How to Land a Job as a Software Engineer</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s288/jobinterview.jpg" alt="Job Interview" style="float: left; margin-right: 7px;" class="postimage" /&gt;This is the third installment of the &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/how-to-land-job-as-software-engineer.html"&gt;How to Land a Job as a Software Engineer&lt;/a&gt; series.  Now that you've submitted &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/your-resume-how-to-land-job-as-software.html"&gt;your resume&lt;/a&gt; and completed &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/your-prescreening-how-to-land-job-as.html"&gt;your prescreening&lt;/a&gt;, you've been called in to interview.&lt;br /&gt;&lt;br /&gt;It's a pretty safe bet that you'll have several interviews to go through including talking to someone in HR, some management folks, and some technical people too.  I do the technical interview around here and there are some things I've noticed interviewees do that consistently irk me.  Here are things you shouldn't do on a job interview (in no particular order):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; tell me you don't need to answer my question.  I've actually had several interviewees tell me that they don't need to answer my question because it's obvious that they know the answer.  If I've asked you the question, it's because I want to know how you're going to answer it.  Especially if it's obvious you have no idea WTF you're talking about.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; bother going if you embellished on your resume or cheated on your prescreener.  If you tell me you're a 7 out of 10 with SQL and you can't tell me the difference between an inner join and a left join, I'm not going to hire you.  If you tell me you're a 3 out of 10, then you have a fighting chance.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; ramble.  I've only got so much time to talk with you.  Most of the time, I enjoy interviewing and I enjoy the conversation, but I really do need to get back to work.  Just answer the question I asked and move on.&lt;br /&gt;&lt;br /&gt;Also, as a side note, when I was in graduate school, we talked a lot about interview techniques.  The business oriented interviewers are likely to ask you questions like, "What's your biggest shortcoming?" or "Tell me about a time you had a conflict with a co-worker."  These are cheesy questions and you're going to have a canned response, so you probably wonder why they even bother asking the question.  Keep in mind that these folks have a lot of experience interviewing . . . much more than you do most likely.  They ask the question because if you have a canned response, they can catch you off guard.&lt;br /&gt;&lt;br /&gt;Here's the technique.  I ask you a question, you give me the canned response, and I just look at you waiting for you to say more.  In only seconds, the silence becomes deafening and you start talking again.  Now, you're out of your comfort zone and you start telling the truth.  The more I nod and keep my mouth shut, the more you confess.  Moral of the story?  Don't ramble.  Just answer the question and then STFU.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; make things up.  If you can't answer the question and you can't make an educated guess, don't just make something up assuming I won't know.  I very seldom ask questions in an interview that I don't already know the answer to.  If you make something up so that you don't have to say, "I don't know," I can almost guarantee that I'm going to know you're full of it.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Don't&lt;/b&gt; say you don't know the answer to a question unless you're absolutely sure you know nothing about the topic.  Most of the time, I'm trying to figure out where you are in your professional journey.  I don't expect you to know everything.  Chances are, if you can give me a starting point, I'll prompt you until you get the answer.  I just want to get a feel for how well you understand programming concepts.  If you simply say, "I don't know" then I have nowhere to start and you don't get partial credit.&lt;br /&gt;&lt;br /&gt;For example, if I ask you the difference between an interface and an abstract class, and you say, "I have no idea" then I have to operate under the assumption that you indeed have no idea.  If you say, "well, I know they both define contracts for classes" then instantly you have partial credit.  Then I'll ask you, "why would you use them?"  You say something remotely intelligent about why you'd want to do this and you have even more partial credit.  I'll keep asking you questions until I'm convinced that I've given you as much credit as you can possibly get.  &lt;br /&gt;&lt;br /&gt;By the way, this doesn't contradict the previous item.  If you make something up and present it like it's fact, I'll assume that you're making things up.  If you look at me and say, "you know, I'm not sure, but it sounds like . . . " and then you make your best inference drown from past experience . . . even if you're way out in left field, you still get partial credit.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3751829299527797215?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3751829299527797215/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/your-interview-how-to-land-job-as.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3751829299527797215'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3751829299527797215'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/your-interview-how-to-land-job-as.html' title='Your Interview -- How to Land a Job as a Software Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s72-c/jobinterview.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-699691046612651280</id><published>2009-11-19T09:04:00.000-08:00</published><updated>2009-11-19T09:09:32.864-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Agile Software Development'/><title type='text'>The ThoughtWorks Agile Southeast Conference</title><content type='html'>&lt;a href="http://www.thoughtworks.com"&gt;&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/SwVmUoCapyI/AAAAAAAABH8/2O_M5gV0Rb0/s288/logo.gif" alt="ThoughtWorks" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I'm taking a quick reprieve from my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/how-to-land-job-as-software-engineer.html"&gt;How to Land a Job as a Software Engineer&lt;/a&gt; series to write about a very informative and enjoyable event I attended this Tuesday.  &lt;a href="http://www.thoughtworks.com"&gt;ThoughtWorks&lt;/a&gt; held its first professional conference in the Southeast and a friend of mine who is a thought worker invited me to go.  It was a well organized event and several noteworthy thought workers presented.&lt;br /&gt;&lt;br /&gt;We kicked off the day by listening to a keynote by &lt;a href="http://www.nealford.com/"&gt;Neal Ford&lt;/a&gt; called, "Why, Not How."  Neal pointed out that there is solid evidence that agile works, but that few people investigate why it works.  That is to say, the talk focused on why agile works rather than how to work agile.  There was a great deal of merit to the things he said about the psychology and biology behind the effectiveness and efficiency of agile methods.  This was one of my favorite talks, perhaps because it appealed to my background in behavioral neuroscience research at Duke, my experience getting my MBA, as well as my interest in becoming a better programmer.  If only he had said something about &lt;a href="http://joysofflight.blogspot.com"&gt;airplanes&lt;/a&gt; it would've been perfect.&lt;br /&gt;&lt;br /&gt;The breakout sessions started after Neal's keynote allowing us to chose between the management track and the technology track.  I generally stuck with the management track.  Just kidding.  But, I wish I could've been in two places at once and I'll explain why shortly.&lt;br /&gt;&lt;br /&gt;The first breakout session I went to discussed code metrics, tools for gathering metrics, and ways to interpret metrics.  In fact, the significant interest in measurement may be one of my favorite aspects to the agile method.  I suppose it appeals to my empirical nature; after all, if you can't observe it, it didn't happen. :)&lt;br /&gt;&lt;br /&gt;Next, Anthony Pitluga and Ali Aghareza talked about cloud computing.  They made an interesting distinction to me regarding Software as a Service (SaaS), Platform as a Services (PaaS), and Infrastructure as a Service (IaaS).  They also pointed out a use for cloud computing that I can really get behind.  Just so you know, I think there are very few instances where cloud computing is better than having your own infrastructure, but I believe these fellas were right on when they said it's perfect for testing.  I mean, you can spin up (on demand mind you) a full test environment that very closely mimics your production environment on &lt;a href="http://aws.amazon.com/ec2/"&gt;Amazon's EC2&lt;/a&gt;.  Imagine that!  Rather than doubling your capex to get two (or god forbid) three duplicate environments, you just build, test, and deploy your solution to EC2.  You bang on it for as long as you need and the whole testing cycle costs a few hundred an hour.  Very clever indeed.&lt;br /&gt;&lt;br /&gt;I switched over to the management track for Saleem Siddiqui's talk about software complexity.  He talked about essential complexity and incidental complexity in software, making a distinction between complexity that &lt;i&gt;has&lt;/i&gt; to be in software and complexity that just sort of happens.  Then, he talked about why it's better to focus on the latter rather than the former because, well, the former &lt;i&gt;has&lt;/i&gt; to be there.  He's a well spoken and interesting speaker to be sure.&lt;br /&gt;&lt;br /&gt;After that, we took a much needed lunch break and had some really good food (although there was a vegimarian behind me in line who seemed to disagree, but since I'm a meatitarian I didn't notice).  During lunch, Scott Conley gave his keynote about Platforms and innovation.  As ThoughtWorks Chief Strategy Officer, Scott knows a lot about innovation and he's damn good at talking about it.  Despite the incessant tinging of forks on plates, I got a lot out of Scott's talk.&lt;br /&gt;&lt;br /&gt;After lunch, I went back to the technical track and enjoyed Graham Brooks's talk about testing web applications.  He showed us a framework he's been working on that allows him to apply test driven development techniques to web applications.&lt;br /&gt;&lt;br /&gt;Next, Dave Vollbracht talked about the perils of allowing everyone to have a unique development environment.  It's important to note, though, that he didn't say anything about being able to try new tools.  Basically, he said that if a team member finds a great tool and starts using it, that everybody should have it as well.  That way, the machine you are working on may not be the same piece of hardware, but the environment will always be identical.  That makes it much easier to image new development machines, to engage in paired programming, or to share team members with other teams.  Dave also talked about several methods to achieve this uniformity.&lt;br /&gt;&lt;br /&gt;Really, the biggest downside is that I couldn't be in both breakout sessions at the same time.  I would really have enjoyed Ross Pettit's talk about Budgeting and the Financial Implications of Agile and I'm sure I would have loved to hear about Gregory Reiser's experience with offshore development.  Can agile make offshore development actually work?  I guess I won't know until next time.&lt;br /&gt;&lt;br /&gt;Speaking of next time, I'll definitely be there and I'll be bringing friends.  It was a good conference, and I enjoyed getting the ThoughtWorks perspective.  I hope the next one will have a lot more attendance because I'd love to get other perspectives too.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-699691046612651280?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/699691046612651280/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/thoughtworks-agile-southeast-conference.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/699691046612651280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/699691046612651280'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/thoughtworks-agile-southeast-conference.html' title='The ThoughtWorks Agile Southeast Conference'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/SwVmUoCapyI/AAAAAAAABH8/2O_M5gV0Rb0/s72-c/logo.gif' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-4802003267669547566</id><published>2009-11-10T12:55:00.000-08:00</published><updated>2009-11-10T12:55:56.116-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='How to Get a Job'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Your Prescreening -- How to Land a Job as a Software Engineer</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s288/jobinterview.jpg" alt="Job Interview" style="float: left; margin-right: 7px;" class="postimage" /&gt;My &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/how-to-land-job-as-software-engineer.html"&gt;How to Land a Job as a Software Engineer&lt;/a&gt; series has me inspired and this is the third post I've written today.  If you've ever applied for a technical position, you've probably met the prescreening questionnaire before.  For software engineers, it generally comprises a set of questions about the language you'll be using and a few puzzles or programming tasks.&lt;br /&gt;&lt;br /&gt;My company has something of a prescreening questionnaire and we're working on a more robust one.  I've done them in the past and I have several friends who have done them for the companies they work for now.  It's a common tool to assess your goodness of fit with the requirements of the position being filled.  &lt;br /&gt;&lt;br /&gt;So, you get the email from the HR department that says, "Thank you for your resume.  You've been selected to move to the next stage of the process.  Please complete this prescreening questionnaire and return it.  You have one week from today."  Sometimes the questionnaire will be online in quiz form and other times it'll just be a list of problems.  Sometimes you're allowed to use the internet to help you and other times they ask you not to.  In any case, my suggestion to you is to follow whatever rules they give you.  Don't cheat!  In my post about &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/your-resume-how-to-land-job-as-software.html"&gt;what hiring managers are looking for in a resume&lt;/a&gt; I kind of harped on the whole honesty thing.  &lt;br /&gt;&lt;br /&gt;Well, I'm about to do it again.  Some people have their friends help them with the test or the coding problems or they Google when they were asked not to.  Sure, this may help you get the job, but you are setting yourself up for failure.  If you cheat on the test, they're going to find out when you go in for the technical interview.  If they don't find out in the technical interview, they're going to find out that you're ill qualified once you start working.  In any case, you're just gonna disappoint them and there's very little good that can come of it.&lt;br /&gt;&lt;br /&gt;Work the problems and do it with integrity.  If you don't get the interview or you don't get the job, it's for the best; that job was not for you and you're now better prepared to move on.  After you submit your results, study the questionnaire.  Learn everything you didn't already know.  Then, learn more about what you know.  Next time you have a prescreening task, you'll actually know the information you need.  When you do get called into that interview, you're going to impress them with your knowledge and capability instead of disappointing them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-4802003267669547566?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/4802003267669547566/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/your-prescreening-how-to-land-job-as.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4802003267669547566'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4802003267669547566'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/your-prescreening-how-to-land-job-as.html' title='Your Prescreening -- How to Land a Job as a Software Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s72-c/jobinterview.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-7391210712148751582</id><published>2009-11-10T11:31:00.000-08:00</published><updated>2009-11-10T11:40:23.808-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='How to Get a Job'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Your Resume -- How to Land a Job as a Software Engineer</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s288/jobinterview.jpg" alt="Job Interview" style="float: left; margin-right: 7px;" class="postimage" /&gt;This is the first post of a series I call &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/how-to-land-job-as-software-engineer.html"&gt;How to Land a Job as a Software Engineer&lt;/a&gt;.  I chose this as the first post because it's really the first step in the process; however, I do not think it's the most important step because I believe that accolade belongs to selecting the right field in the first place.  Also, from your perspective, finding a job to which to apply is the first step; however, I'm writing this series from the perspective of the person who makes the ultimate decision about who is and who is not worth hiring.&lt;br /&gt;&lt;br /&gt;That being said, your resume has to be your foot in the door.  Let's say that for a given job opening, we receive 100 resumes.  At least half of those will be screened out before they even get to me.  I'll eliminate at least half of the remaining resumes before we start inviting people to interview.&lt;br /&gt;&lt;br /&gt;I've worked with resumes from every angle I can think of.  I studied job applications and resumes when I was getting my MBA, I've been a job applicant, and I've reviewed dozens of resumes of job hopefuls.  In graduate school, I read chapter after chapter about how a good resume is structured.  As a job applicant, I perused every online resource I could find for hints and tricks.  As a hiring manager, I can tell you what makes a difference to me.&lt;br /&gt;&lt;br /&gt;But, before I get to my tips and tricks, I think it is worth pointing out what happens when a job opportunity opens up.  First, the organization has identified a need.  Second, the organization publishes that need.  Third, many people apply.  Fourth, one person is selected.&lt;br /&gt;&lt;br /&gt;When you apply for the job, you feel like the organization is trying to hire the best candidate; well, they're not.  Don't worry though because they think they're trying to hire the best candidate too; but they're not.  In reality, they're trying to eliminate the worst candidates from each phase until there's only one left.  I know it's something of a pedantic distinction, but it is important.&lt;br /&gt;&lt;br /&gt;Why?  Well, when I start looking at resumes, I'm not looking for reasons to hire someone.  I assume that if you've sent me your resume, you feel like you'd be right for the job and that the job will be right for you.  Therefore, when I look at your resume, I'm looking for reasons you're wrong.  I'm trying to eliminate you because I don't want to waste your time and mine by having you in for an interview if I can tell it won't work out.&lt;br /&gt;&lt;br /&gt;Sure, there have been times that something really stood out on a resume.  Usually, it's a bad thing, but sometimes it's a really good thing.  Just, keep in mind that things that stand out don't always stand out in a good way.  So, in all forthrightness (and at the risk of seeming petty), here are some of the things that I find to eliminate you from the applicant pool:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I don't want to read your 5 page resume.  In fact, I &lt;i&gt;won't&lt;/i&gt; read your 5 page resume.  I'll look at the first half of the first page and the last half of the last page.  If you cured cancer in the middle of your career, chances are pretty good I'll never even know about it.  If there's something you want me to know, put it in those sections.  Even better, keep the whole thing succinct so that I don't think you're verbose.&lt;/li&gt;&lt;li&gt;Don't have grammatical or spelling errors in your resume.  Ultimately, you're talking about a two page report about a topic you're the only living expert in -- you.  If you're writing a doctoral dissertation about a topic you previously knew very little about and a few grammatical or spelling errors slip into the first draft, it's one thing.  It's another thing altogether to write a two page paper about what you've been doing for the last 10 years and to make grammatical or spelling mistakes in your final copy.  What does that say about your attention to detail?&lt;/li&gt;&lt;li&gt;Be factual; don't be boastful.  I don't care if you think you're a great communicator or if you think you're an excellent problem solver with 12 years of experience leading successful teams.  I just want to know how long you've been at it and what you've done.  I'll figure out on my own if you're a great communicator or an excellent problem solver.  Besides, how do I know that your definition of a "successful team" isn't one where no more than half of the team quit and 10% of your projects were on time and on budget?  In fact, if you put this in your resume, I'm going to tailor your interview to prove you wrong.&lt;/li&gt;&lt;li&gt;Tell the truth!  I mean, seriously . . . in the name of all things holy and good, don't lie on your resume.  If you tell me you have 10 years of programming experience but you really only have 5, I'm probably going to figure it out.  As soon as I figure it out, I'm just going to tell you to leave.  I'm not kidding; end of the interview.  If you tell me you have 5 years of experience and I need a 10 year programmer, you didn't want the job anyway because you're probably not ready yet; however, I'll think, "man, that person was honest and I'm gonna save that resume for the next junior opportunity."  &lt;br /&gt;&lt;br /&gt;Most of the time, I just need a programmer of any sort.  So, if I'm just looking for a programmer and you say, "I have 5 years of programming experience," and I can tell that you're being honest, I may still hire you; I just won't pay you as much.  So, just be honest.  If you're honest, the worst that can happen is you don't get the job and everybody's happy (or at least at worst mildly disappointed).  If you lie, the worst that can happen is you take a new job, everybody suffers, and you get the ax after your 90 day interim.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Now, here are some positive things I look for in a resume:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I actually do care what your hobbies are.  I'm not going to hire you based on your hobbies, but if you're up against someone who is exactly equal to you in every way except that your hobbies are cooler, more relevant to the field, or are similar to my own then I'm more likely to hire you.  In fact, I once invited someone in for an interview because I wanted to know more about his hobby.  Came to find out that he was a very capable engineer, but was just a little too expensive for us.  If I had the money, I'd have hired him on the spot.&lt;br /&gt;&lt;br /&gt;It also makes you a real person.  I look at a lot of resumes and none of them are real people, but if I know you're into underwater basket weaving, then I'm likely to remember you.  In fact, I don't even look at the names on resumes.  I just look at the resumes, chose the ones who seem capable, and hand them to my boss saying, "call these in please."  If I see that you're into underwater basket weaving, I'll be like, "heh, what's this person's name?"&lt;br /&gt;&lt;br /&gt;So, unless your hobby is rolling your poop into little balls, then go ahead and include a hobbies section on your resume. (Not that there's anything wrong with rolling your poop into little balls; I just don't want you touching company equipment.)&lt;/li&gt;&lt;li&gt;I want to see that you stick with jobs for a long time.  While I know that contractors will have a lot of short term projects and few long term employments, I really like it if you consolidate your contract work into one item on your resume.  Then, you can list the contracts you've worked on under that line item rather than listing each contract as though it was one job.  It helps me rapidly review your work history.  If nothing else, at least specify that the position was a contract position or I'm going to assume that you can't keep a job longer than a 9 months.&lt;/li&gt;&lt;li&gt;Apply for a job that's appropriate to your experience.  If you've been programming for 10 years and still consider yourself a junior developer, I'm probably not going to call you back.  I know that in most cases, one of two things will be true.  Either you're not progressing at your craft and thus aren't worth the investment or you're better than the job you're applying for and you're just going to quit as soon as another opportunity comes up and then you're not worth the investment either.  If you should be a senior developer, be a senior developer.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-7391210712148751582?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/7391210712148751582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/your-resume-how-to-land-job-as-software.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7391210712148751582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7391210712148751582'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/your-resume-how-to-land-job-as-software.html' title='Your Resume -- How to Land a Job as a Software Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s72-c/jobinterview.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-4407635445531002330</id><published>2009-11-10T09:43:00.000-08:00</published><updated>2010-01-11T21:42:35.576-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='How to Get a Job'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>How to Land a Job as a Software Engineer</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s288/jobinterview.jpg" alt="Job Interview" style="float: left; margin-right: 7px;" class="postimage" /&gt;My company is hiring again.  It's a bittersweet process for me because I love meeting new people and getting new perspectives (and God knows we need the manpower -- er, person-power); however, it is a very frustrating process to endure parsing all of the resumes, selecting the first round for interviews, interviewing, rejecting, being left empty handed.&lt;br /&gt;&lt;br /&gt;I started working with &lt;a href="http://www.emeraldsoftwaregroup.com"&gt;Emerald Software Group&lt;/a&gt; about three and a half years ago, and have been responsible for evaluating the technical capabilities of our job applicants for the last several years.  Sitting on the other side of the table has given me some insight that I'd like to share with job seekers (especially, my fellow software engineers in need of gainful employment).&lt;br /&gt;&lt;br /&gt;This is actually the first post in a series of posts about landing the perfect job for you.  Nota bene, I didn't say "landing your dream job."  I said, "landing the perfect job for you," because your dream job may not be the perfect job for you.  For example, my dream job is to drive a &lt;a href="http://en.wikipedia.org/wiki/A-10_Thunderbolt_II"&gt;Hog&lt;/a&gt; for the USAF, but alas the perfect job for me is as a software engineer.  I just wanted to make this distinction now because the topic is going to come up again (and again).&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Your Resume&lt;/h1&gt;I'll be starting with a post about resumes.  After all, a majority of our applicants are eliminated before I even see their resumes.  Then, I eliminate a majority of the remaining candidates before we invite anyone to interview.  I know there are thousands of blog posts out there about resumes, but I want to be completely frank about it.  Learn more about &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/your-resume-how-to-land-job-as-software.html"&gt;what a hiring manager looks for in a resume&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Your Prescreening&lt;/h1&gt;Next, I'll discuss the prescreening process.  For those of you who don't work in a technical field, you'll probably find this a little foreign.  A lot of technical industries have developed prescreening processes to establish your technical capability before you ever set foot in the building.  For software engineers, these tend to be basic framework / language questions and some cute and clever programming tasks.  I will give you hints to &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/your-prescreening-how-to-land-job-as.html"&gt;get the most out of your experience with the prescreening questionnaire&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Your Interview&lt;/h1&gt;Here's a great place to make an impression (or a great place to really screw it up).  A good interview is a precipice.  Keep in mind that your interviewer is not looking for a reason to include you in the next round of interviews; your interviewer is looking for reasons to exclude you.  The ultimate goal is to eliminate all but one applicant and you want to make sure that it will be you if it should be.  Here's how you &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/11/your-interview-how-to-land-job-as.html"&gt;make sure your job interview goes well&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Your Communication Skills&lt;/h1&gt;Communication skills are important even in technical fields.  The way that you communicate says a lot about what kind of employee you'll be.  People want to know that they'll be able to understand you, that they'll enjoy working with you, and that you'll bring something to the table.  We can tell a lot about a candidate by the way they communicate with us.  &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/01/your-communication-skills-how-to-land.html"&gt;Learn how to communicate effectively&lt;/a&gt; and land your software engineering job.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Your Field&lt;/h1&gt;I thought long and hard about whether this should be the first post or the last post on the "How to Land a Job as a Software Engineer" topic.  It's really the first step, but I've been saving it for last because I have a lot to say about this.  I love writing software; if you don't, it'll show.  In the post about selecting a field, I'll discuss what we look for when we interview job hopefuls.  &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/01/your-field-how-to-land-job-as-software.html"&gt;Selecting the right field&lt;/a&gt; for you is the only way to ensure that your perfect job is, in fact, your dream job.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-4407635445531002330?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/4407635445531002330/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/how-to-land-job-as-software-engineer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4407635445531002330'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4407635445531002330'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/how-to-land-job-as-software-engineer.html' title='How to Land a Job as a Software Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SvmWr3mkrkI/AAAAAAAABHg/MJDKsKxg4_o/s72-c/jobinterview.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-134532619926279516</id><published>2009-11-03T06:20:00.000-08:00</published><updated>2009-11-03T06:20:07.139-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='It Takes an Engineer'/><title type='text'>Designing a Shampoo Bottle Takes an Engineer</title><content type='html'>&lt;a href="http://picasaweb.google.com/tncbbthositg/BlogPictures#5399877881953380178"&gt;&lt;img src="http://lh4.ggpht.com/_cFBwGCiU3y4/SvA4UuhR51I/AAAAAAAABGs/--Ji6C_Itg0/s288/GarnierFructisShampoo.jpg" alt="Garnier Fructis" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I was taking a shower this morning when I realized that &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/It%20Takes%20an%20Engineer"&gt;it takes an engineer&lt;/a&gt; to design a shampoo bottle.  My fiancee buys this Garnier Fructis stuff.  I don't know if it's any good because I'm not smart enough to tell the difference between two shampoos, but she is so I assume it's not bad.  In any event, it may have the dumbest design I've ever seen on a product container.&lt;br /&gt;&lt;br /&gt;Take a look at the picture of the bottle.  You see that little dark greed ball at the top?  That's the mechanism for opening the bottle.  Now, I obviously don't know in what order you execute your shower routine, but I'd hazard to guess that by the time you get to opening your shampoo bottle, your hands are already wet.  &lt;br /&gt;&lt;br /&gt;Now, let's say, just hypothetically, that over time a thin layer of soap or shampoo builds up on that stupid little ball.  Now, your hands are wet and the bottle is damp and covered in soap and you have to try to pop the top of that bottle by what means?  Grabbing?  Prying?  Squeezing?  Try as you may, you can't get a grip on a semi-spherical wet soapy cap.  &lt;br /&gt;&lt;br /&gt;But, consider what happens over time.  When you squeeze the shampoo out of the bottle and then close the cap, you've undoubtedly confined a small amount of shampoo between the cap and the shampoo dispensing opening.  This shampoo dries while you're at work somehow morphing into the exact chemical composition of super glue!  The next morning, you have to try to un-super glue your shampoo cap by using your wet fingers to apply friction to a soapy sphere.&lt;br /&gt;&lt;br /&gt;WTF Garnier?  Just, WTF?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-134532619926279516?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/134532619926279516/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/designing-shampoo-bottle-takes-engineer.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/134532619926279516'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/134532619926279516'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/11/designing-shampoo-bottle-takes-engineer.html' title='Designing a Shampoo Bottle Takes an Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_cFBwGCiU3y4/SvA4UuhR51I/AAAAAAAABGs/--Ji6C_Itg0/s72-c/GarnierFructisShampoo.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3624772983056901076</id><published>2009-10-21T08:17:00.000-07:00</published><updated>2009-10-21T08:19:02.082-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='IComparer'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>IComparable.Between Extension Method</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/St8kys2HjII/AAAAAAAABFc/1JaRojJNjWc/s288/applesandoranges.jpg" alt="IComparable" style="float: left; margin-right: 7px;" class="postimage" /&gt;T-SQL has a between statement to make it easier to determine if a test object occurs between two other objects.  I needed similar functionality in C# but I didn't know what the type the objects would be; however, I did know they would always be IComparable.  Obviously, there's no real need for an extension method here, but it did make things a bit more convenient.&lt;br /&gt;&lt;br /&gt;Like T-SQL, the between extension method is inclusive and the start parameter doesn't have to be lower than the end parameter.&lt;br /&gt;&lt;br /&gt;Here's what some test cases look like:&lt;pre name="code" class="c#"&gt;5.Between(1, 10) // true&lt;br /&gt;5.Between(10, 1) // true&lt;br /&gt;5.Between(10, 6) // false&lt;br /&gt;5.Between(5, 5)) // true&lt;/pre&gt;&lt;br /&gt;Here's the method:&lt;pre name="code" class="c#"&gt;public static bool Between&amp;lt;T&amp;gt;(this T target, T start, T end)&lt;br /&gt;    where T : IComparable&lt;br /&gt;{&lt;br /&gt;    if (start.CompareTo(end) == 1)&lt;br /&gt;        return (target.CompareTo(end) &amp;gt;= 0) &amp;&amp; (target.CompareTo(start) &amp;lt;= 0);&lt;br /&gt;&lt;br /&gt;    return (target.CompareTo(start) &amp;gt;= 0) &amp;&amp; (target.CompareTo(end) &amp;lt;= 0);&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3624772983056901076?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3624772983056901076/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/10/icomparablebetween-extension-method.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3624772983056901076'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3624772983056901076'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/10/icomparablebetween-extension-method.html' title='IComparable.Between Extension Method'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/St8kys2HjII/AAAAAAAABFc/1JaRojJNjWc/s72-c/applesandoranges.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-8477891224730747711</id><published>2009-10-21T07:24:00.000-07:00</published><updated>2009-10-21T07:43:20.171-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Strongly Typed GetValue Extension Method for SqlDataReader</title><content type='html'>&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/St8ZIchdJEI/AAAAAAAABFY/beYpagR1IxA/DataReaderGetValue.jpg" alt="SqlDataReader GetValue Extension Method" style="float: left; margin-right: 7px;" class="postimage" /&gt;For a very long time, I've been bitching about the fact that the methods on the System.Data.SqlDataReader class which get a strongly typed result don't have overloads that take column names.  I've seen a number of blogs that say, "the reason you can't do this is because you can't overload a method on return type," which is true but irrelevant.  I don't see any reason the GetInt32(int i) method couldn't have a GetInt32(string name) overload that handles the GetOrdinal(string name) on your behalf (other than the fact that nobody wanted to write the two dozen overloads.&lt;br /&gt;&lt;br /&gt;Well, finding this state of affairs unacceptable, I decided to rectify it.  I expected it to be either difficult or annoying depending on the solution I accepted.  First, I decided that it should be an extension method because &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;I love extension methods&lt;/a&gt; and the fluency they provide.  I also decided that it should be generic so you could have one method and specify the desired type.  The tough decision came when I was trying to figure out how to handle all of the conversions between types.&lt;br /&gt;&lt;br /&gt;Should I switch on the generic type and call the associated method on the SqlDataReader?  That is to say, if you call GetValue&lt;int32&gt; should I call SqlDataReader.GetInt32 or should I just get the value as an object and handle all of the converting myself?  Well, I decided that I'd rather deal with the difficulty of the type conversions than have a big ugly case statement in my method.&lt;br /&gt;&lt;br /&gt;Then, I was looking around in the .net classes and found System.Convert.ChangeType and it made the whole process considerably easier.  We also had a need to attempt to return the value as a specified type without throwing an exception if it failed (like int.TryParse) so I included a TryGetValue method as well.&lt;pre name="code" class="c#"&gt;public static T GetValue&amp;lt;T&amp;gt;(this SqlDataReader reader, string name)&lt;br /&gt;{&lt;br /&gt;    return (T)Convert.ChangeType(reader[name], typeof(T));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static bool TryGetValue&amp;lt;T&amp;gt;(this SqlDataReader reader, string name, out T output)&lt;br /&gt;{&lt;br /&gt;    try&lt;br /&gt;    {&lt;br /&gt;        output = reader.GetValue&amp;lt;T&amp;gt;(name);&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    catch (Exception ex)&lt;br /&gt;    {&lt;br /&gt;        if (ex is InvalidCastException || ex is FormatException || ex is OverflowException)&lt;br /&gt;        {&lt;br /&gt;            output = default(T);&lt;br /&gt;            return false;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        else&lt;br /&gt;            throw;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// usage examples&lt;br /&gt;&lt;br /&gt;// string testString;&lt;br /&gt;// bool result = reader.TryGetValue&amp;lt;string&amp;gt;("ColumnName", out testString);&lt;br /&gt;&lt;br /&gt;// int testInt;&lt;br /&gt;// bool result = reader.TryGetValue&amp;lt;int&amp;gt;("ColumnName", out testInt);&lt;br /&gt;&lt;br /&gt;// int i = reader.GetValue&amp;lt;int&amp;gt;("ColumnName");&lt;br /&gt;// string s = reader.GetValue&amp;lt;string&amp;gt;("ColumnName");&lt;br /&gt;// DateTime dt = reader.GetValue&amp;lt;datetime&amp;gt;("ColumnName");&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-8477891224730747711?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/8477891224730747711/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/10/strongly-typed-getvalue-extension.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8477891224730747711'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8477891224730747711'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/10/strongly-typed-getvalue-extension.html' title='Strongly Typed GetValue Extension Method for SqlDataReader'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/St8ZIchdJEI/AAAAAAAABFY/beYpagR1IxA/s72-c/DataReaderGetValue.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-1419654446858056582</id><published>2009-09-14T05:05:00.000-07:00</published><updated>2009-09-14T06:06:37.788-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Agile Software Development'/><category scheme='http://www.blogger.com/atom/ns#' term='Book Reviews'/><title type='text'>Agile Estimating and Planning</title><content type='html'>&lt;a href="http://www.amazon.com/gp/product/0131479415?ie=UTF8&amp;tag=dpatcalon-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0131479415"&gt;&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/Sp00BN3Tx6I/AAAAAAAAA-Y/ncfM4gNvmws/s288/Agile%20Estimating%20and%20Planning.jpg" alt="Agile Estimating and Planning"  style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;A few months ago my company picked up a new project with a fairly major government organization.  The mission is to migrate several applications from a mainframe (Natural / Adabas) to a modern environment (C# / SQL Server).  We were told that the mainframe was going to be disabled in about 9 months . . . period.&lt;br /&gt;&lt;br /&gt;I told the client very early in the process that a typical waterfall approach would be impossible to implement under the conditions of the project and that we should take an agile methodology.  The client agreed; however, the lack of resources corralled a few of our team members back to the waterfall approach.  After a few weeks in waterfall, we realized that there was no way a waterfall project could be successful in this case.&lt;br /&gt;&lt;br /&gt;We officially switched back to an agile approach and the project has been swimming along nicely ever since.  I owe a lot of this success to &lt;a href="http://www.amazon.com/gp/product/0131479415?ie=UTF8&amp;tag=dpatcalon-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0131479415"&gt;Agile Estimating and Planning&lt;/a&gt; by &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fgp%2Fentity%2FMike-Cohn%2FB001H6MN56%3Fie%3DUTF8%26ref%255F%3Dntt%255Fathr%255Fdp%255Fpel%255Fpop%255F1&amp;tag=dpatcalon-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=390957"&gt;Mike Cohn&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Agile Estimating and Planning is part of the &lt;a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=http%3A%2F%2Fwww.amazon.com%2Fs%3Fie%3DUTF8%26x%3D0%26ref%255F%3Dnb%255Fss%26y%3D0%26field-keywords%3DRobert%2520C%2520Martin%26url%3Dsearch-alias%253Dstripbooks&amp;tag=dpatcalon-20&amp;linkCode=ur2&amp;camp=1789&amp;creative=390957"&gt;Robert C. Martin Series&lt;/a&gt; and is listed in every "Top 10 Agile Books" lists I've found.  The book is full of well thought out logical explanations and detailed examples of agile software development practices and principles.  It is a very easy read (I read it in 2 days) and the author did an excellent job of relating each chapter to other chapters in the book.&lt;br /&gt;&lt;br /&gt;The book begins by pointing out the difference between planning an agile project and agile planning.  Next, Mike describes several reasons planning fails in many projects (some of which I discuss in my blog post a few weeks ago called "&lt;a href="http://dpatrickcaldwell.blogspot.com/2009/08/programming-is-gas.html"&gt;Programming is a Gas&lt;/a&gt;").  &lt;br /&gt;&lt;br /&gt;The next several chapters discuss the benefits and techniques of abstracting estimations away from time and monetary units.  This creates a set of relative estimations based on feature complexity that allows the project room to change over time (and promotes more open and honest communication with the client).  Two techniques discussed specifically are story points and ideal days.&lt;br /&gt;&lt;br /&gt;The following section is more planning oriented with a detailed discussion of themes, features, and prioritization.  There is a discussion about what to do with epics (very large stories) and boundaries on which to split those stories.  Conversely, there is also a discussion about combining several small but related user stories into one larger more meaningful one.&lt;br /&gt;&lt;br /&gt;The scheduling section describes release and iteration planning, estimating velocity, and techniques for buffering projects (especially risky projects).  the tracking and communicating section gives you a set of methods for monitoring the release and iteration plans and a tool set you can use to communicate this with the project stakeholders.&lt;br /&gt;&lt;br /&gt;The next section describes how (and why) agile projects work so well in real life.  High visibility into the project, frequent feature based planning, and small story size make agile projects preferred for many software development groups as well as many clients who have had the good fortune to have experienced a well run agile project.&lt;br /&gt;&lt;br /&gt;The final chapter is a hypothetical case study which demonstrates the entire agile software development life cycle from beginning to end.  In this chapter, you get to see how an agile project begins, how stakeholders become involved, how iterations are planned, and how easily problems can be solved.  If you read carefully, you can also pick up a lot of insight Mike has to offer from his experience as an agile coach.&lt;br /&gt;&lt;br /&gt;You can purchase &lt;a href="http://www.amazon.com/gp/product/0131479415?ie=UTF8&amp;tag=dpatcalon-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0131479415"&gt;Agile Estimating and Planning&lt;/a&gt; from Amazon.com at a deeply discounted rate.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-1419654446858056582?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/1419654446858056582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/09/agile-estimating-and-planning.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1419654446858056582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1419654446858056582'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/09/agile-estimating-and-planning.html' title='Agile Estimating and Planning'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/Sp00BN3Tx6I/AAAAAAAAA-Y/ncfM4gNvmws/s72-c/Agile%20Estimating%20and%20Planning.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-4488667042486355328</id><published>2009-09-03T06:16:00.000-07:00</published><updated>2009-09-03T11:02:42.327-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PostSharp'/><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Aspect Oriented Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Book Reviews'/><title type='text'>Head First Design Patterns</title><content type='html'>&lt;a href="http://www.amazon.com/gp/product/0596007124?ie=UTF8&amp;tag=dpatcalon-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0596007124"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/Sp_BdM13yLI/AAAAAAAABAs/PRV-SMn3B5U/s288/head_first_design_patterns_cover.jpg" alt="Head First Design Patterns"  style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I read &lt;a href="http://www.amazon.com/gp/product/0596007124?ie=UTF8&amp;tag=dpatcalon-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0596007124"&gt;Head First Design Patterns&lt;/a&gt; about a year ago and it changed my life.  Now, I know that a lot of non-programmers read this blog and I'm sure you must be thinking, "how can a book, especially one like that, possibly change your life?"&lt;br /&gt;&lt;br /&gt;Well, it made my code much cleaner, much more flexible, and much easier to write.  That's why I decided to make this the first book review on this blog.  First of all, I'm a pretty big fan of the Head First style.  I like most of the jokes and intentional thought provoking.  It's a clever way to help people understand and remember what they're learning.  Admittedly, sometimes it goes a little overboard, but I'd rather it go a little overboard than be a dry read.  I read almost the whole book on a road trip from Atlanta to Orlando.&lt;br /&gt;&lt;br /&gt;The book sets out to give the reader an understanding of what a design pattern is and what it's good for.  A design pattern is a template for solving commonly occurring problems in software design.  Head First Design Patterns discusses the details of about 15.  You'll be surprised how many of these patterns you've seen before (and may have even written before).  One of the big benefits you get out of this book is a common language to describe the patterns in your code.  Next time you're doing a code review, you'll find yourself saying things like, "this is just a singleton" and your reviewer will understand what you're trying to accomplish without discussing the implementation details.&lt;br /&gt;&lt;br /&gt;After reading this book, I've noticed more and more patterns:  &lt;br /&gt;&lt;br /&gt;The &lt;b&gt;decorator&lt;/b&gt; pattern that you see throughout the .net framework.  There are a lot of classes that take a stream and return a stream.  Some read, some write, some encrypt &amp;mdash; all of them are decorators.&lt;br /&gt;&lt;br /&gt;The &lt;b&gt;proxy&lt;/b&gt; pattern controls access to other objects.  I wrote a blog post back in December of 2008 where I used the &lt;a href="http://dpatrickcaldwell.blogspot.com/2008/12/using-proxy-pattern-to-write-to.html"&gt;proxy pattern to write to multiple TextWriters&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The &lt;b&gt;singleton&lt;/b&gt; pattern ensures that a class has only one instance and has a global point of access.  It bears noting that there are a lot of programmers who have criticized the singleton to which the singleton replied in a public statement last year, "you can kiss my ass."  It is true that the singleton can come back to bite you if you use it incorrectly; the singleton is the right tool for the job it has.  Head First Design Patterns helps you understand what exactly that job is.&lt;br /&gt;&lt;br /&gt;The &lt;b&gt;template method&lt;/b&gt; pattern popped up the other day when I was looking at some &lt;a href="http://www.postsharp.org/"&gt;PostSharp&lt;/a&gt; aspects.  If you use the OnMethodInvocationAspect like I did with my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/02/memoizer-attribute-using-postsharp.html"&gt;memoizer&lt;/a&gt;, PostSharp will produce something very similar to a template method.  Obviously, PostSharp isn't creating a base class and then overriding the template method, but it is allowing you to control the flow of the method without having knowledge of the method implementation.&lt;br /&gt;&lt;br /&gt;The &lt;b&gt;factory method&lt;/b&gt; and &lt;b&gt;abstract factory&lt;/b&gt; patterns are also quite common and are well described in Head First Design Patterns.  People use these patterns all the time.  I've seen many data access layer implementations where a factory method loads a particular data access provider based on configuration options.  You can achieve the same results with inversion of control containers like &lt;a href="http://www.ninject.org"&gt;Ninject&lt;/a&gt; and &lt;a href="http://code.google.com/p/autofac/"&gt;autofac&lt;/a&gt;, but sometimes you just need a quick factory.&lt;br /&gt;&lt;br /&gt;Almost every review I've seen of Head First Design Patterns has been positive; however, every once in a while you find a detractor like Jeff Atwood in his &lt;a href="http://www.codinghorror.com/blog/archives/000380.html"&gt;blog post from 2005&lt;/a&gt;.  I think Jeff is probably a good guy and a good coder; he's just contrary and cynical.  I know that being contrary and cynical doesn't negate your point, but it is a bit like crying wolf.  I'm not going to address his argument in this post (because I could dedicate an entire post to the argument if I thought it'd do any good), but I think you should read it because he does have one some valid points.&lt;br /&gt;&lt;br /&gt;Unneeded complexity is bad.  I wouldn't say that design patterns should be eschewed altogether, but you should use the right pattern for the right problem.  Reading this book should teach you to identify patterns yourself.  What are some common problems you run into in your field?  Can you think of a pattern for addressing these problems?&lt;br /&gt;&lt;br /&gt;You have to learn to abstract your knowledge and apply it practically.  Don't just decide that patterns are bad and start knocking books because the graphic on the cover has been reused.  A pattern doesn't have to be necessary to be valuable.  When the pattern provides maintainability, readability, testability, or simplicity then use the pattern; that's what it's there for.  The pattern is just another tool in your tool belt.  &lt;br /&gt;&lt;br /&gt;I very seldom recommend you throw a specialized tool away just because you don't need it all the time.  Buy (or borrow) Head First Design Patterns and focus on learning how to recognize and solve common problems.  You won't regret it.&lt;br /&gt;&lt;br /&gt;You can get &lt;a href="http://www.amazon.com/gp/product/0596007124?ie=UTF8&amp;tag=dpatcalon-20&amp;linkCode=as2&amp;camp=1789&amp;creative=390957&amp;creativeASIN=0596007124"&gt;Head First Design Patterns from Amazon.com&lt;/a&gt; for a great price.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-4488667042486355328?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/4488667042486355328/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/09/head-first-design-patterns.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4488667042486355328'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4488667042486355328'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/09/head-first-design-patterns.html' title='Head First Design Patterns'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/Sp_BdM13yLI/AAAAAAAABAs/PRV-SMn3B5U/s72-c/head_first_design_patterns_cover.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-956088236951856789</id><published>2009-08-30T20:33:00.000-07:00</published><updated>2009-08-30T21:41:20.947-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='It Takes an Engineer'/><title type='text'>Understanding Tax Brackets Takes an Engineer</title><content type='html'>&lt;a href="http://picasaweb.google.com/tncbbthositg/BlogPictures#5375979541562751026"&gt;&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SptQ6O6deDI/AAAAAAAAA-M/gcxU5ZsqWoA/s288/IMG_0283.PNG" alt="Tax Bracket Graph" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I've talked to dozens of people who have a dramatic misunderstanding of tax brackets (or tax rate schedules).  In fact, I think I've run into more people who misunderstand how taxes work than who understand.  So, I thought I'd demystify one small part of the American income tax system &amp;mdash; brackets.&lt;br /&gt;&lt;br /&gt;Many people believe that shifting from one tax bracket to another yields a major leap in income tax.  This is incorrect.  When you move from the 15% tax bracket to the 25% bracket for example, you're not suddenly paying an additional 10% in taxes.  That is to say that if you make $33,950.00 per year (the highest salary for the 15% bracket) and you get a 1 cent raise bumping you up to the next tax bracket, that your change in income tax will actually be zero dollars.&lt;br /&gt;&lt;br /&gt;Here's how that works.  If you're in the 15% bracket, you pay 15% on income above $8,350.00 and you add $835 to that.  The next bracket up is for people who earn more than $33,950.00 and less than $82,250.00.  They pay 25% on income above $33,950.00 plus $4,675.00.  Now, here's an interesting observation.  If you take 15% of $33,950.00 - $8,350.00 (the maximum for the 15% bracket) and add $850, you get $4,675.00.  That means that the maximum for the 15% bracket is the same as the minimum for the 25% bracket.&lt;br /&gt;&lt;br /&gt;Thus, there's not a major leap between brackets.  So, what are brackets for?  Well, basically, the tax bracket just defines the rate of change between the minimum and the maximum incomes in the bracket.  If you remember taking algebra back in the old days, you're just talking about slope.  Basically, your tax bracket defines the slope of the line on which your tax is determined from your income.&lt;br /&gt;&lt;br /&gt;Consider the slope-intercept form for a line: &lt;i&gt;y = mx + b&lt;/i&gt; where &lt;i&gt;b&lt;/i&gt; is the y intercept and &lt;i&gt;m&lt;/i&gt; is the slope.  If you look at the graph at the top of this blog (created with a free iPhone app called grafunc by &lt;a href="http://fabio.policarpo.nom.br/Welcome.html"&gt;Fabio Policarp&lt;/a&gt;), you'll see a blue and an orange line.  The blue line represents the 15% bracket using the equation &lt;i&gt;y = .15 (x - 8350) + 835&lt;/i&gt; and the orange line represents the 25% bracket with the equation &lt;i&gt;y = .25 (x - 33950) + 4675&lt;/i&gt;.  Where those two lines intersect is where you switch brackets.&lt;br /&gt;&lt;br /&gt;That's about all there is to understanding tax brackets.  There's much more to understand about taxes, but that's another blog post altogether.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-956088236951856789?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/956088236951856789/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/08/understanding-tax-brackets-takes.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/956088236951856789'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/956088236951856789'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/08/understanding-tax-brackets-takes.html' title='Understanding Tax Brackets Takes an Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SptQ6O6deDI/AAAAAAAAA-M/gcxU5ZsqWoA/s72-c/IMG_0283.PNG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5745459196750543071</id><published>2009-08-26T05:36:00.000-07:00</published><updated>2009-08-26T17:27:01.957-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Fail'/><title type='text'>Programming is a Gas</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SpUsE48g6wI/AAAAAAAAA9g/JXONePftmoU/s288/natural%20gas.jpg" alt="gas" style="float: left; margin-right: 7px;" class="postimage" /&gt;Programming is a gas.  I don't mean programming is entertaining and exciting (even though sometimes it really is); I mean more along the lines of the physical state of matter.  Programming is a gas.  You see, a gas having perfect mobility and infinite expansion will take the size and shape of the container you put it in.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Parkinson's_Law"&gt;Parkinson's Law&lt;/a&gt; states that "work expands so as to fill the time available for its completion."  I'm taking this one step forward and saying that "software development expands so as to take the size and shape of the constraints placed upon it."&lt;br /&gt;&lt;br /&gt;I've worked with tens (if not hundreds) of software engineers and I can tell you one thing with certainty: ceteris paribus, the more the engineer charges for services, the better the result.  Now, obviously, some engineers misrepresent their skill level intentionally or unintentionally, but . . . on average . . .&lt;br /&gt;&lt;br /&gt;So, if this is the case, you can put several of these programmers together and derive that the more you pay a software consultancy, the better the result as well.  I know to some, this probably just sounds obtuse.  You might be saying to yourself, "well, some companies just have higher profit margins than others."  'tis true; some programmers have higher profit margins.  &lt;br /&gt;&lt;br /&gt;If you've been at the top of the game for 10 years, you deserve more than someone who just started programming (or someone who has stagnated for 10 years) because you can apply all of your experience to each new project.  Similarly, a software consultancy with premium engineers (no matter how old the organization) can earn a higher margin because its engineers produce such valuable results.&lt;br /&gt;&lt;br /&gt;Understanding this stipulation (whether you agree or not) is pivotal to the rest of this post, so I'll very briefly summarize what I'm saying: you get what you pay for!&lt;br /&gt;&lt;br /&gt;So, why do so many projects turn sour?  If you're a software engineer, think of the last 10 projects you worked on.  If you're not an engineer, think of the last 10 projects your company had someone else work on.  What's the first step of all of these projects?  Almost uniformly, it's an &lt;a href="http://en.wikipedia.org/wiki/Request_for_proposal"&gt;RFP&lt;/a&gt; and if not an RFP per se, it's some kind of competitive bidding process.&lt;br /&gt;&lt;br /&gt;That's where the situation starts going south &amp;mdash; before the project even gets kicked off!  Have you ever had a project where the vendor wasn't selected almost exclusively based on cost?  When was the last time someone said, "whichever vendor comes back with the best feature ideas will get the contract" or "whichever vendor has the lowest turnover rate and the highest employee satisfaction will get the contract?"  In fact, in a perfect world, I believe you could select a vendor based on its mission statement and core values (and perhaps a brief summary of its gestalt experience).&lt;br /&gt;&lt;br /&gt;&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/SpUz1XqTioI/AAAAAAAAA9o/wfbK-1jOYrc/s288/inscribed.png" alt="inscribed circles" style="float: left; margin-right: 7px;" class="postimage" /&gt;Almost every project I've been on has had two constraints . . . money and time.  Before a pen ever touches the signature line on the contract, the project is doomed.  The shape of the project is defined by money and time, not by desired features.  &lt;br /&gt;&lt;br /&gt;Thus, when a developer looks at the project, it is going to take the shape of money and time; however, to the customer, the project is expected to be shaped by the desired features.  More often than not, the money x time shape fits quite neatly &lt;i&gt;inside&lt;/i&gt; the feature shape.&lt;br /&gt;&lt;br /&gt;So, what exactly am I proposing?  I'm proposing that the team (whether it is internal or external) should be chosen based on values and trustworthiness.  A team you can trust is much more valuable than a team which is cheap.  Take the team you can trust and then define the shape of your project based on the features you want to see.  When you ask the team for an estimate, it's not to compete for the project; rather, the estimate is so that you'll know if you have the budget for the project in the first place.&lt;br /&gt;&lt;br /&gt;If you do it this way, you'll get an honest estimate.  Many software companies will low-ball estimates because they know that once you're committed to the project, you'll dedicate whatever additional resources are necessary to complete it.  Ultimately, it'll end up costing more in the long run because of the way each "phase" will build on the "phase" before it compared to a solution that was well architected from the get-go.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5745459196750543071?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5745459196750543071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/08/programming-is-gas.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5745459196750543071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5745459196750543071'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/08/programming-is-gas.html' title='Programming is a Gas'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SpUsE48g6wI/AAAAAAAAA9g/JXONePftmoU/s72-c/natural%20gas.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-8260854841093336925</id><published>2009-08-06T10:24:00.000-07:00</published><updated>2009-08-06T10:47:25.818-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>iPhone Bookmarklet Installer Source</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SnsP169urVI/AAAAAAAAA8Q/Yn8zPrCEkKM/s288/Bookmarklets.gif" alt="iPhone Bookmarklets" style="float: left; margin-right: 7px;" class="postimage" /&gt;A few hours ago, I posted my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/08/iphone-bookmarklet-to-install-iphone_06.html"&gt;iPhone Bookmarklet Installer Bookmarklet&lt;/a&gt; and several people have asked how it works.&lt;br /&gt;&lt;br /&gt;Well, it's pretty basic.  The effort is to loop through all the links on the page, identify the bookmarklets, and modify them so that the bookmarklet is appended to the URL somehow.  The first time I tried, I used the '?' character making the bookmarklet look like a query string.  This proved problematic though as a number of bookmarklets failed to convert cleanly.  I didn't really investigate but I'd imagine it has something to do with the '&amp;' character and/or '=' signs.&lt;br /&gt;&lt;br /&gt;In any event, it's pretty easy to fix that by using the '#' character instead of ? because then it just looks like an anchor.  It also keeps the page from having to reload.  So, all you have to do is run the bookmarklet on your iPhone, click the bookmarklet you want to install, save the bookmark and remove everything up to and including the #.&lt;br /&gt;&lt;br /&gt;Now, the actual bookmarklet code has been scrubbed a little to give it something of a smaller footprint like this: &lt;pre name="code" class="js"&gt;(function(){&lt;br /&gt;  var s,i,l=document.links;&lt;br /&gt;  for(i=0;i&amp;lt;l.length;i++)&lt;br /&gt;  {&lt;br /&gt;    if(l[i].href.indexOf('javascript:')!=0)&lt;br /&gt;      continue;&lt;br /&gt;&lt;br /&gt;    l[i].href='#'+l[i].href;&lt;br /&gt;  }&lt;br /&gt;})();&lt;/pre&gt;&lt;br /&gt;But, that's a little hard to read, so here's the pre-scrubbed version.  There's also an additional section I added later to make it easier to identify which links have been transformed (i.e., which are identified as bookmarklets).  Here's the code:&lt;pre name="code" class="js"&gt;// anonymous function&lt;br /&gt;(function(){&lt;br /&gt;&lt;br /&gt;  // put the link array somewhere&lt;br /&gt;  var links = document.links;&lt;br /&gt;&lt;br /&gt;  // loop through all the links&lt;br /&gt;  for (var i = 0; i &amp;lt; links.length; i++)&lt;br /&gt;  {&lt;br /&gt;&lt;br /&gt;    // if the link does not start with javascript:&lt;br /&gt;    // it isn't a bookmarklet and we can exit this&lt;br /&gt;    // itteration and continue the loop&lt;br /&gt;    if(links[i].href.indexOf('javascript:') != 0)&lt;br /&gt;      continue;&lt;br /&gt;&lt;br /&gt;    // update the href of the link prepending the&lt;br /&gt;    // '#' character to make it look like an anchor&lt;br /&gt;    links[i].href = '#' + links[i].href;&lt;br /&gt;&lt;br /&gt;    // add some styling information to identify the&lt;br /&gt;    // modified links&lt;br /&gt;    var style = links[i].style;&lt;br /&gt;    style.backgroundColor = '#eee';&lt;br /&gt;    style.color = '#333';&lt;br /&gt;    style.border = '1px solid #333';&lt;br /&gt;    style.textDecoration = 'none';&lt;br /&gt;    style.padding = '2px';&lt;br /&gt;    style.fontWeight = 'normal';&lt;br /&gt;  }&lt;br /&gt;})();&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-8260854841093336925?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/8260854841093336925/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/08/iphone-bookmarklet-installer-source.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8260854841093336925'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8260854841093336925'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/08/iphone-bookmarklet-installer-source.html' title='iPhone Bookmarklet Installer Source'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SnsP169urVI/AAAAAAAAA8Q/Yn8zPrCEkKM/s72-c/Bookmarklets.gif' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-7332979901582837426</id><published>2009-08-06T09:23:00.000-07:00</published><updated>2010-09-21T14:25:45.811-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Bookmarklet'/><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>iPhone Bookmarklet to Install iPhone Bookmarklets</title><content type='html'>&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SnsP169urVI/AAAAAAAAA8Q/Yn8zPrCEkKM/s288/Bookmarklets.gif" alt="iPhone Bookmarklets" style="float: left; margin-right: 7px;" class="postimage" /&gt;As a software engineer and a web developer, I have really grown to love the bookmarklet.  I have a &lt;a href="http://dpatrickcaldwell.blogspot.com/2010/09/regular-expression-search-bookmarklet.html"&gt;Regex Search Bookmarklet&lt;/a&gt;, my favorite &lt;a href="javascript:with(window.open(%22%22,%22_blank%22,%22width=%22+screen.width*.6+%22,left=%22+screen.width*.35+%22,height=%22+screen.height*.9+%22,resizable,scrollbars=yes%22)){document.write(%22&amp;lt;!DOCTYPE%20HTML%20PUBLIC%20\%22-//W3C//DTD%20HTML%204.01//EN\%22%20\%22http://www.w3.org/TR/html4/strict.dtd\%22&amp;gt;\n\n&amp;lt;html%20onclick=\%22keepFocusInTextbox(event)\%22&amp;gt;\n&amp;lt;head&amp;gt;\n&amp;lt;meta%20http-equiv=\%22Content-Type\%22%20content=\%22text/html;%20charset=iso-8859-1\%22&amp;gt;\n&amp;lt;title&amp;gt;JavaScript%20Shell%201.4&amp;lt;/title&amp;gt;\n\n&amp;lt;script%20type=\%22text/javascript\%22&amp;gt;\nvar%20\nhistList%20=%20[\%22\%22],%20\nhistPos%20=%200,%20\n_scope%20=%20{},%20\n_win,%20//%20a%20top-level%20context\nquestion,\n_in,\n_out,\ntooManyMatches%20=%20null,\nlastError%20=%20null;\n\nfunction%20refocus()\n{\n%20%20_in.blur();%20//%20Needed%20for%20Mozilla%20to%20scroll%20correctly.\n%20%20_in.focus();\n}\n\nfunction%20init()\n{\n%20%20_in%20=%20document.getElementById(\%22input\%22);\n%20%20_out%20=%20document.getElementById(\%22output\%22);\n\n%20%20_win%20=%20window;\n\n%20%20if%20(opener%20&amp;&amp;%20!opener.closed)\n%20%20{\n%20%20%20%20println(\%22Using%20bookmarklet%20version%20of%20shell:%20commands%20will%20run%20in%20opener's%20context.\%22,%20\%22message\%22);\n%20%20%20%20_win%20=%20opener;\n%20%20}\n\n%20%20initTarget();\n\n%20%20recalculateInputHeight();\n%20%20refocus();\n}\n\nfunction%20initTarget()\n{\n%20%20_win.Shell%20=%20window;\n%20%20_win.print%20=%20shellCommands.print;\n}\n\n\n//%20Unless%20the%20user%20is%20selected%20something,%20refocus%20the%20textbox.\n//%20(requested%20by%20caillon,%20brendan,%20asa)\nfunction%20keepFocusInTextbox(e)%20\n{\n%20%20var%20g%20=%20e.srcElement%20?%20e.srcElement%20:%20e.target;%20//%20IE%20vs.%20standard\n%20%20\n%20%20while%20(!g.tagName)\n%20%20%20%20g%20=%20g.parentNode;\n%20%20var%20t%20=%20g.tagName.toUpperCase();\n%20%20if%20(t==\%22A\%22%20||%20t==\%22INPUT\%22)\n%20%20%20%20return;\n%20%20%20%20\n%20%20if%20(window.getSelection)%20{\n%20%20%20%20//%20Mozilla\n%20%20%20%20if%20(String(window.getSelection()))\n%20%20%20%20%20%20return;\n%20%20}\n%20%20else%20if%20(document.getSelection)%20{\n%20%20%20%20//%20Opera?%20Netscape%204?\n%20%20%20%20if%20(document.getSelection())\n%20%20%20%20%20%20return;\n%20%20}\n%20%20else%20{\n%20%20%20%20//%20IE\n%20%20%20%20if%20(%20document.selection.createRange().text%20)\n%20%20%20%20%20%20return;\n%20%20}\n%20%20\n%20%20refocus();\n}\n\nfunction%20inputKeydown(e)%20{\n%20%20//%20Use%20onkeydown%20because%20IE%20doesn't%20support%20onkeypress%20for%20arrow%20keys\n\n%20%20//alert(e.keyCode%20+%20\%22%20^%20\%22%20+%20e.keycode);\n\n%20%20if%20(e.shiftKey%20&amp;&amp;%20e.keyCode%20==%2013)%20{%20//%20shift-enter\n%20%20%20%20//%20don't%20do%20anything;%20allow%20the%20shift-enter%20to%20insert%20a%20line%20break%20as%20normal\n%20%20}%20else%20if%20(e.keyCode%20==%2013)%20{%20//%20enter\n%20%20%20%20//%20execute%20the%20input%20on%20enter\n%20%20%20%20try%20{%20go();%20}%20catch(er)%20{%20alert(er);%20};\n%20%20%20%20setTimeout(function()%20{%20_in.value%20=%20\%22\%22;%20},%200);%20//%20can't%20preventDefault%20on%20input,%20so%20clear%20it%20later\n%20%20}%20else%20if%20(e.keyCode%20==%2038)%20{%20//%20up\n%20%20%20%20//%20go%20up%20in%20history%20if%20at%20top%20or%20ctrl-up\n%20%20%20%20if%20(e.ctrlKey%20||%20caretInFirstLine(_in))\n%20%20%20%20%20%20hist(true);\n%20%20}%20else%20if%20(e.keyCode%20==%2040)%20{%20//%20down\n%20%20%20%20//%20go%20down%20in%20history%20if%20at%20end%20or%20ctrl-down\n%20%20%20%20if%20(e.ctrlKey%20||%20caretInLastLine(_in))\n%20%20%20%20%20%20hist(false);\n%20%20}%20else%20if%20(e.keyCode%20==%209)%20{%20//%20tab\n%20%20%20%20tabcomplete();\n%20%20%20%20setTimeout(function()%20{%20refocus();%20},%200);%20//%20refocus%20because%20tab%20was%20hit\n%20%20}%20else%20{%20}\n\n%20%20setTimeout(recalculateInputHeight,%200);\n%20%20\n%20%20//return%20true;\n};\n\nfunction%20caretInFirstLine(textbox)\n{\n%20%20//%20IE%20doesn't%20support%20selectionStart/selectionEnd\n%20%20if%20(textbox.selectionStart%20==%20undefined)\n%20%20%20%20return%20true;\n\n%20%20var%20firstLineBreak%20=%20textbox.value.indexOf(\%22\\n\%22);\n%20%20\n%20%20return%20((firstLineBreak%20==%20-1)%20||%20(textbox.selectionStart%20&amp;lt;=%20firstLineBreak));\n}\n\nfunction%20caretInLastLine(textbox)\n{\n%20%20//%20IE%20doesn't%20support%20selectionStart/selectionEnd\n%20%20if%20(textbox.selectionEnd%20==%20undefined)\n%20%20%20%20return%20true;\n\n%20%20var%20lastLineBreak%20=%20textbox.value.lastIndexOf(\%22\\n\%22);\n%20%20\n%20%20return%20(textbox.selectionEnd%20&amp;gt;%20lastLineBreak);\n}\n\nfunction%20recalculateInputHeight()\n{\n%20%20var%20rows%20=%20_in.value.split(/\\n/).length\n%20%20%20%20+%201%20//%20prevent%20scrollbar%20flickering%20in%20Mozilla\n%20%20%20%20+%20(window.opera%20?%201%20:%200);%20//%20leave%20room%20for%20scrollbar%20in%20Opera\n%20%20\n%20%20if%20(_in.rows%20!=%20rows)%20//%20without%20this%20check,%20it%20is%20impossible%20to%20select%20text%20in%20Opera%207.60%20or%20Opera%208.0.\n%20%20%20%20_in.rows%20=%20rows;\n}\n\nfunction%20println(s,%20type)\n{\n%20%20if((s=String(s)))\n%20%20{\n%20%20%20%20var%20newdiv%20=%20document.createElement(\%22div\%22);\n%20%20%20%20newdiv.appendChild(document.createTextNode(s));\n%20%20%20%20newdiv.className%20=%20type;\n%20%20%20%20_out.appendChild(newdiv);\n%20%20%20%20return%20newdiv;\n%20%20}\n}\n\nfunction%20printWithRunin(h,%20s,%20type)\n{\n%20%20var%20div%20=%20println(s,%20type);\n%20%20var%20head%20=%20document.createElement(\%22strong\%22);\n%20%20head.appendChild(document.createTextNode(h%20+%20\%22:%20\%22));\n%20%20div.insertBefore(head,%20div.firstChild);\n}\n\n\nvar%20shellCommands%20=%20\n{\nload%20:%20function%20load(url)\n{\n%20%20var%20s%20=%20_win.document.createElement(\%22script\%22);\n%20%20s.type%20=%20\%22text/javascript\%22;\n%20%20s.src%20=%20url;\n%20%20_win.document.getElementsByTagName(\%22head\%22)[0].appendChild(s);\n%20%20println(\%22Loading%20\%22%20+%20url%20+%20\%22...\%22,%20\%22message\%22);\n},\n\nclear%20:%20function%20clear()\n{\n%20%20var%20CHILDREN_TO_PRESERVE%20=%203;\n%20%20while%20(_out.childNodes[CHILDREN_TO_PRESERVE])%20\n%20%20%20%20_out.removeChild(_out.childNodes[CHILDREN_TO_PRESERVE]);\n},\n\nprint%20:%20function%20print(s)%20{%20println(s,%20\%22print\%22);%20},\n\n//%20the%20normal%20function,%20\%22print\%22,%20shouldn't%20return%20a%20value\n//%20(suggested%20by%20brendan;%20later%20noticed%20it%20was%20a%20problem%20when%20showing%20others)\npr%20:%20function%20pr(s)%20\n{%20\n%20%20shellCommands.print(s);%20//%20need%20to%20specify%20shellCommands%20so%20it%20doesn't%20try%20window.print()!\n%20%20return%20s;\n},\n\nprops%20:%20function%20props(e,%20onePerLine)\n{\n%20%20if%20(e%20===%20null)%20{\n%20%20%20%20println(\%22props%20called%20with%20null%20argument\%22,%20\%22error\%22);\n%20%20%20%20return;\n%20%20}\n\n%20%20if%20(e%20===%20undefined)%20{\n%20%20%20%20println(\%22props%20called%20with%20undefined%20argument\%22,%20\%22error\%22);\n%20%20%20%20return;\n%20%20}\n\n%20%20var%20ns%20=%20[\%22Methods\%22,%20\%22Fields\%22,%20\%22Unreachables\%22];\n%20%20var%20as%20=%20[[],%20[],%20[]];%20//%20array%20of%20(empty)%20arrays%20of%20arrays!\n%20%20var%20p,%20j,%20i;%20//%20loop%20variables,%20several%20used%20multiple%20times\n\n%20%20var%20protoLevels%20=%200;\n\n%20%20for%20(p%20=%20e;%20p;%20p%20=%20p.__proto__)\n%20%20{\n%20%20%20%20for%20(i=0;%20i&amp;lt;ns.length;%20++i)\n%20%20%20%20%20%20as[i][protoLevels]%20=%20[];\n%20%20%20%20++protoLevels;\n%20%20}\n\n%20%20for(var%20a%20in%20e)\n%20%20{\n%20%20%20%20//%20Shortcoming:%20doesn't%20check%20that%20VALUES%20are%20the%20same%20in%20object%20and%20prototype.\n\n%20%20%20%20var%20protoLevel%20=%20-1;\n%20%20%20%20try\n%20%20%20%20{\n%20%20%20%20%20%20for%20(p%20=%20e;%20p%20&amp;&amp;%20(a%20in%20p);%20p%20=%20p.__proto__)\n%20%20%20%20%20%20%20%20++protoLevel;\n%20%20%20%20}\n%20%20%20%20catch(er)%20{%20protoLevel%20=%200;%20}%20//%20\%22in\%22%20operator%20throws%20when%20param%20to%20props()%20is%20a%20string\n\n%20%20%20%20var%20type%20=%201;\n%20%20%20%20try\n%20%20%20%20{\n%20%20%20%20%20%20if%20((typeof%20e[a])%20==%20\%22function\%22)\n%20%20%20%20%20%20%20%20type%20=%200;\n%20%20%20%20}\n%20%20%20%20catch%20(er)%20{%20type%20=%202;%20}\n\n%20%20%20%20as[type][protoLevel].push(a);\n%20%20}\n\n%20%20function%20times(s,%20n)%20{%20return%20n%20?%20s%20+%20times(s,%20n-1)%20:%20\%22\%22;%20}\n\n%20%20for%20(j=0;%20j&amp;lt;protoLevels;%20++j)\n%20%20%20%20for%20(i=0;i&amp;lt;ns.length;++i)\n%20%20%20%20%20%20if%20(as[i][j].length)%20\n%20%20%20%20%20%20%20%20printWithRunin(\n%20%20%20%20%20%20%20%20%20%20ns[i]%20+%20times(\%22%20of%20prototype\%22,%20j),%20\n%20%20%20%20%20%20%20%20%20%20(onePerLine%20?%20\%22\\n\\n\%22%20:%20\%22\%22)%20+%20as[i][j].sort().join(onePerLine%20?%20\%22\\n\%22%20:%20\%22,%20\%22)%20+%20(onePerLine%20?%20\%22\\n\\n\%22%20:%20\%22\%22),%20\n%20%20%20%20%20%20%20%20%20%20\%22propList\%22\n%20%20%20%20%20%20%20%20);\n},\n\nblink%20:%20function%20blink(node)\n{\n%20%20if%20(!node)%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20throw(\%22blink:%20argument%20is%20null%20or%20undefined.\%22);\n%20%20if%20(node.nodeType%20==%20null)%20%20%20%20%20throw(\%22blink:%20argument%20must%20be%20a%20node.\%22);\n%20%20if%20(node.nodeType%20==%203)%20%20%20%20%20%20%20%20throw(\%22blink:%20argument%20must%20not%20be%20a%20text%20node\%22);\n%20%20if%20(node.documentElement)%20%20%20%20%20%20throw(\%22blink:%20argument%20must%20not%20be%20the%20document%20object\%22);\n\n%20%20function%20setOutline(o)%20{%20\n%20%20%20%20return%20function()%20{\n%20%20%20%20%20%20if%20(node.style.outline%20!=%20node.style.bogusProperty)%20{\n%20%20%20%20%20%20%20%20//%20browser%20supports%20outline%20(Firefox%201.1%20and%20newer,%20CSS3,%20Opera%208).\n%20%20%20%20%20%20%20%20node.style.outline%20=%20o;\n%20%20%20%20%20%20}\n%20%20%20%20%20%20else%20if%20(node.style.MozOutline%20!=%20node.style.bogusProperty)%20{\n%20%20%20%20%20%20%20%20//%20browser%20supports%20MozOutline%20(Firefox%201.0.x%20and%20older)\n%20%20%20%20%20%20%20%20node.style.MozOutline%20=%20o;\n%20%20%20%20%20%20}\n%20%20%20%20%20%20else%20{\n%20%20%20%20%20%20%20%20//%20browser%20only%20supports%20border%20(IE).%20border%20is%20a%20fallback%20because%20it%20moves%20things%20around.\n%20%20%20%20%20%20%20%20node.style.border%20=%20o;\n%20%20%20%20%20%20}\n%20%20%20%20}\n%20%20}%20\n%20%20\n%20%20function%20focusIt(a)%20{\n%20%20%20%20return%20function()%20{\n%20%20%20%20%20%20a.focus();%20\n%20%20%20%20}\n%20%20}\n\n%20%20if%20(node.ownerDocument)%20{\n%20%20%20%20var%20windowToFocusNow%20=%20(node.ownerDocument.defaultView%20||%20node.ownerDocument.parentWindow);%20//%20Moz%20vs.%20IE\n%20%20%20%20if%20(windowToFocusNow)\n%20%20%20%20%20%20setTimeout(focusIt(windowToFocusNow.top),%200);\n%20%20}\n\n%20%20for(var%20i=1;i&amp;lt;7;++i)\n%20%20%20%20setTimeout(setOutline((i%252)?'3px%20solid%20red':'none'),%20i*100);\n\n%20%20setTimeout(focusIt(window),%20800);\n%20%20setTimeout(focusIt(_in),%20810);\n},\n\nscope%20:%20function%20scope(sc)\n{\n%20%20if%20(!sc)%20sc%20=%20{};\n%20%20_scope%20=%20sc;\n%20%20println(\%22Scope%20is%20now%20\%22%20+%20sc%20+%20\%22.%20%20If%20a%20variable%20is%20not%20found%20in%20this%20scope,%20window%20will%20also%20be%20searched.%20%20New%20variables%20will%20still%20go%20on%20window.\%22,%20\%22message\%22);\n},\n\nmathHelp%20:%20function%20mathHelp()\n{\n%20%20printWithRunin(\%22Math%20constants\%22,%20\%22E,%20LN2,%20LN10,%20LOG2E,%20LOG10E,%20PI,%20SQRT1_2,%20SQRT2\%22,%20\%22propList\%22);\n%20%20printWithRunin(\%22Math%20methods\%22,%20\%22abs,%20acos,%20asin,%20atan,%20atan2,%20ceil,%20cos,%20exp,%20floor,%20log,%20max,%20min,%20pow,%20random,%20round,%20sin,%20sqrt,%20tan\%22,%20\%22propList\%22);\n},\n\nans%20:%20undefined\n};\n\n\nfunction%20hist(up)\n{\n%20%20//%20histList[0]%20=%20first%20command%20entered,%20[1]%20=%20second,%20etc.\n%20%20//%20type%20something,%20press%20up%20--&amp;gt;%20thing%20typed%20is%20now%20in%20\%22limbo\%22\n%20%20//%20(last%20item%20in%20histList)%20and%20should%20be%20reachable%20by%20pressing%20\n%20%20//%20down%20again.\n\n%20%20var%20L%20=%20histList.length;\n\n%20%20if%20(L%20==%201)\n%20%20%20%20return;\n\n%20%20if%20(up)\n%20%20{\n%20%20%20%20if%20(histPos%20==%20L-1)\n%20%20%20%20{\n%20%20%20%20%20%20//%20Save%20this%20entry%20in%20case%20the%20user%20hits%20the%20down%20key.\n%20%20%20%20%20%20histList[histPos]%20=%20_in.value;\n%20%20%20%20}\n\n%20%20%20%20if%20(histPos%20&amp;gt;%200)\n%20%20%20%20{\n%20%20%20%20%20%20histPos--;\n%20%20%20%20%20%20//%20Use%20a%20timeout%20to%20prevent%20up%20from%20moving%20cursor%20within%20new%20text\n%20%20%20%20%20%20//%20Set%20to%20nothing%20first%20for%20the%20same%20reason\n%20%20%20%20%20%20setTimeout(\n%20%20%20%20%20%20%20%20function()%20{\n%20%20%20%20%20%20%20%20%20%20_in.value%20=%20'';%20\n%20%20%20%20%20%20%20%20%20%20_in.value%20=%20histList[histPos];\n%20%20%20%20%20%20%20%20%20%20var%20caretPos%20=%20_in.value.length;\n%20%20%20%20%20%20%20%20%20%20if%20(_in.setSelectionRange)%20\n%20%20%20%20%20%20%20%20%20%20%20%20_in.setSelectionRange(caretPos,%20caretPos);\n%20%20%20%20%20%20%20%20},\n%20%20%20%20%20%20%20%200\n%20%20%20%20%20%20);\n%20%20%20%20}\n%20%20}%20\n%20%20else%20//%20down\n%20%20{\n%20%20%20%20if%20(histPos%20&amp;lt;%20L-1)\n%20%20%20%20{\n%20%20%20%20%20%20histPos++;\n%20%20%20%20%20%20_in.value%20=%20histList[histPos];\n%20%20%20%20}\n%20%20%20%20else%20if%20(histPos%20==%20L-1)\n%20%20%20%20{\n%20%20%20%20%20%20//%20Already%20on%20the%20current%20entry:%20clear%20but%20save\n%20%20%20%20%20%20if%20(_in.value)\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20histList[histPos]%20=%20_in.value;\n%20%20%20%20%20%20%20%20++histPos;\n%20%20%20%20%20%20%20%20_in.value%20=%20\%22\%22;\n%20%20%20%20%20%20}\n%20%20%20%20}\n%20%20}\n}\n\nfunction%20tabcomplete()\n{\n%20%20/*\n%20%20%20*%20Working%20backwards%20from%20s[from],%20find%20the%20spot\n%20%20%20*%20where%20this%20expression%20starts.%20%20It%20will%20scan\n%20%20%20*%20until%20it%20hits%20a%20mismatched%20(%20or%20a%20space,\n%20%20%20*%20but%20it%20skips%20over%20quoted%20strings.\n%20%20%20*%20If%20stopAtDot%20is%20true,%20stop%20at%20a%20'.'\n%20%20%20*/\n%20%20function%20findbeginning(s,%20from,%20stopAtDot)\n%20%20{\n%20%20%20%20/*\n%20%20%20%20%20*%20%20Complicated%20function.\n%20%20%20%20%20*\n%20%20%20%20%20*%20%20Return%20true%20if%20s[i]%20==%20q%20BUT%20ONLY%20IF\n%20%20%20%20%20*%20%20s[i-1]%20is%20not%20a%20backslash.\n%20%20%20%20%20*/\n%20%20%20%20function%20equalButNotEscaped(s,i,q)\n%20%20%20%20{\n%20%20%20%20%20%20if(s.charAt(i)%20!=%20q)%20//%20not%20equal%20go%20no%20further\n%20%20%20%20%20%20%20%20return%20false;\n\n%20%20%20%20%20%20if(i==0)%20//%20beginning%20of%20string\n%20%20%20%20%20%20%20%20return%20true;\n\n%20%20%20%20%20%20if(s.charAt(i-1)%20==%20'\\\\')%20//%20escaped?\n%20%20%20%20%20%20%20%20return%20false;\n\n%20%20%20%20%20%20return%20true;\n%20%20%20%20}\n\n%20%20%20%20var%20nparens%20=%200;\n%20%20%20%20var%20i;\n%20%20%20%20for(i=from;%20i&amp;gt;=0;%20i--)\n%20%20%20%20{\n%20%20%20%20%20%20if(s.charAt(i)%20==%20'%20')\n%20%20%20%20%20%20%20%20break;\n\n%20%20%20%20%20%20if(stopAtDot%20&amp;&amp;%20s.charAt(i)%20==%20'.')\n%20%20%20%20%20%20%20%20break;\n%20%20%20%20%20%20%20%20\n%20%20%20%20%20%20if(s.charAt(i)%20==%20')')\n%20%20%20%20%20%20%20%20nparens++;\n%20%20%20%20%20%20else%20if(s.charAt(i)%20==%20'(')\n%20%20%20%20%20%20%20%20nparens--;\n\n%20%20%20%20%20%20if(nparens%20&amp;lt;%200)\n%20%20%20%20%20%20%20%20break;\n\n%20%20%20%20%20%20//%20skip%20quoted%20strings\n%20%20%20%20%20%20if(s.charAt(i)%20==%20'\\''%20||%20s.charAt(i)%20==%20'\\\%22')\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20//dump(\%22skipping%20quoted%20chars:%20\%22);\n%20%20%20%20%20%20%20%20var%20quot%20=%20s.charAt(i);\n%20%20%20%20%20%20%20%20i--;\n%20%20%20%20%20%20%20%20while(i%20&amp;gt;=%200%20&amp;&amp;%20!equalButNotEscaped(s,i,quot))%20{\n%20%20%20%20%20%20%20%20%20%20//dump(s.charAt(i));\n%20%20%20%20%20%20%20%20%20%20i--;\n%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20//dump(\%22\\n\%22);\n%20%20%20%20%20%20}\n%20%20%20%20}\n%20%20%20%20return%20i;\n%20%20}\n\n%20%20//%20XXX%20should%20be%20used%20more%20consistently%20(instead%20of%20using%20selectionStart/selectionEnd%20throughout%20code)\n%20%20//%20XXX%20doesn't%20work%20in%20IE,%20even%20though%20it%20contains%20IE-specific%20code\n%20%20function%20getcaretpos(inp)\n%20%20{\n%20%20%20%20if(inp.selectionEnd%20!=%20null)\n%20%20%20%20%20%20return%20inp.selectionEnd;\n%20%20%20%20%20%20\n%20%20%20%20if(inp.createTextRange)\n%20%20%20%20{\n%20%20%20%20%20%20var%20docrange%20=%20_win.Shell.document.selection.createRange();\n%20%20%20%20%20%20var%20inprange%20=%20inp.createTextRange();\n%20%20%20%20%20%20if%20(inprange.setEndPoint)\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20inprange.setEndPoint('EndToStart',%20docrange);\n%20%20%20%20%20%20%20%20return%20inprange.text.length;\n%20%20%20%20%20%20}\n%20%20%20%20}\n\n%20%20%20%20return%20inp.value.length;%20//%20sucks,%20punt\n%20%20}\n\n%20%20function%20setselectionto(inp,pos)\n%20%20{\n%20%20%20%20if(inp.selectionStart)%20{\n%20%20%20%20%20%20inp.selectionStart%20=%20inp.selectionEnd%20=%20pos;\n%20%20%20%20}\n%20%20%20%20else%20if(inp.createTextRange)%20{\n%20%20%20%20%20%20var%20docrange%20=%20_win.Shell.document.selection.createRange();\n%20%20%20%20%20%20var%20inprange%20=%20inp.createTextRange();\n%20%20%20%20%20%20inprange.move('character',pos);\n%20%20%20%20%20%20inprange.select();\n%20%20%20%20}\n%20%20%20%20else%20{%20//%20err...\n%20%20%20%20/*\n%20%20%20%20%20%20inp.select();\n%20%20%20%20%20%20if(_win.Shell.document.getSelection())\n%20%20%20%20%20%20%20%20_win.Shell.document.getSelection()%20=%20\%22\%22;\n%20%20%20%20%20%20%20%20*/\n%20%20%20%20}\n%20%20}\n%20%20%20%20//%20get%20position%20of%20cursor%20within%20the%20input%20box\n%20%20%20%20var%20caret%20=%20getcaretpos(_in);\n\n%20%20%20%20if(caret)%20{\n%20%20%20%20%20%20//dump(\%22----\\n\%22);\n%20%20%20%20%20%20var%20dotpos,%20spacepos,%20complete,%20obj;\n%20%20%20%20%20%20//dump(\%22caret%20pos:%20\%22%20+%20caret%20+%20\%22\\n\%22);\n%20%20%20%20%20%20//%20see%20if%20there's%20a%20dot%20before%20here\n%20%20%20%20%20%20dotpos%20=%20findbeginning(_in.value,%20caret-1,%20true);\n%20%20%20%20%20%20//dump(\%22dot%20pos:%20\%22%20+%20dotpos%20+%20\%22\\n\%22);\n%20%20%20%20%20%20if(dotpos%20==%20-1%20||%20_in.value.charAt(dotpos)%20!=%20'.')%20{\n%20%20%20%20%20%20%20%20dotpos%20=%20caret;\n//dump(\%22changed%20dot%20pos:%20\%22%20+%20dotpos%20+%20\%22\\n\%22);\n%20%20%20%20%20%20}\n\n%20%20%20%20%20%20//%20look%20backwards%20for%20a%20non-variable-name%20character\n%20%20%20%20%20%20spacepos%20=%20findbeginning(_in.value,%20dotpos-1,%20false);\n%20%20%20%20%20%20//dump(\%22space%20pos:%20\%22%20+%20spacepos%20+%20\%22\\n\%22);\n%20%20%20%20%20%20//%20get%20the%20object%20we're%20trying%20to%20complete%20on\n%20%20%20%20%20%20if(spacepos%20==%20dotpos%20||%20spacepos+1%20==%20dotpos%20||%20dotpos%20==%20caret)\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20//%20try%20completing%20function%20args\n%20%20%20%20%20%20%20%20if(_in.value.charAt(dotpos)%20==%20'('%20||\n%20(_in.value.charAt(spacepos)%20==%20'('%20&amp;&amp;%20(spacepos+1)%20==%20dotpos))\n%20%20%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20%20%20var%20fn,fname;\n%20%20var%20from%20=%20(_in.value.charAt(dotpos)%20==%20'(')%20?%20dotpos%20:%20spacepos;\n%20%20%20%20%20%20%20%20%20%20spacepos%20=%20findbeginning(_in.value,%20from-1,%20false);\n\n%20%20%20%20%20%20%20%20%20%20fname%20=%20_in.value.substr(spacepos+1,from-(spacepos+1));\n%20%20//dump(\%22fname:%20\%22%20+%20fname%20+%20\%22\\n\%22);\n%20%20%20%20%20%20%20%20%20%20try%20{\n%20%20%20%20%20%20%20%20%20%20%20%20with(_win.Shell._scope)\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20with(_win)\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20with(Shell.shellCommands)\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fn%20=%20eval(fname);\n%20%20%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20%20%20catch(er)%20{\n%20%20%20%20%20%20%20%20%20%20%20%20//dump('fn%20is%20not%20a%20valid%20object\\n');\n%20%20%20%20%20%20%20%20%20%20%20%20return;\n%20%20%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20%20%20if(fn%20==%20undefined)%20{\n%20%20%20%20%20%20%20%20%20%20%20%20%20//dump('fn%20is%20undefined');\n%20%20%20%20%20%20%20%20%20%20%20%20%20return;\n%20%20%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20%20%20if(fn%20instanceof%20Function)\n%20%20%20%20%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20%20%20%20%20//%20Print%20function%20definition,%20including%20argument%20names,%20but%20not%20function%20body\n%20%20%20%20%20%20%20%20%20%20%20%20if(!fn.toString().match(/function%20.+?\\(\\)%20+\\{\\n%20+\\[native%20code\\]\\n\\}/))\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20println(fn.toString().match(/function%20.+?\\(.*?\\)/),%20\%22tabcomplete\%22);\n%20%20%20%20%20%20%20%20%20%20}\n\n%20%20%20%20%20%20%20%20%20%20return;\n%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20else\n%20%20%20%20%20%20%20%20%20%20obj%20=%20_win;\n%20%20%20%20%20%20}\n%20%20%20%20%20%20else\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20var%20objname%20=%20_in.value.substr(spacepos+1,dotpos-(spacepos+1));\n%20%20%20%20%20%20%20%20//dump(\%22objname:%20|\%22%20+%20objname%20+%20\%22|\\n\%22);\n%20%20%20%20%20%20%20%20try%20{\n%20%20%20%20%20%20%20%20%20%20with(_win.Shell._scope)\n%20%20%20%20%20%20%20%20%20%20%20%20with(_win)\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20obj%20=%20eval(objname);\n%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20catch(er)%20{\n%20%20%20%20%20%20%20%20%20%20printError(er);%20\n%20%20%20%20%20%20%20%20%20%20return;\n%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20if(obj%20==%20undefined)%20{\n%20%20%20%20%20%20%20%20%20%20//%20sometimes%20this%20is%20tabcomplete's%20fault,%20so%20don't%20print%20it%20:(\n%20%20%20%20%20%20%20%20%20%20//%20e.g.%20completing%20from%20\%22print(document.getElements\%22\n%20%20%20%20%20%20%20%20%20%20//%20println(\%22Can't%20complete%20from%20null%20or%20undefined%20expression%20\%22%20+%20objname,%20\%22error\%22);\n%20%20%20%20%20%20%20%20%20%20return;\n%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20}\n%20%20%20%20%20%20//dump(\%22obj:%20\%22%20+%20obj%20+%20\%22\\n\%22);\n%20%20%20%20%20%20//%20get%20the%20thing%20we're%20trying%20to%20complete\n%20%20%20%20%20%20if(dotpos%20==%20caret)\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20if(spacepos+1%20==%20dotpos%20||%20spacepos%20==%20dotpos)\n%20%20%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20%20%20//%20nothing%20to%20complete\n%20%20%20%20%20%20%20%20%20%20//dump(\%22nothing%20to%20complete\\n\%22);\n%20%20%20%20%20%20%20%20%20%20return;\n%20%20%20%20%20%20%20%20}\n\n%20%20%20%20%20%20%20%20complete%20=%20_in.value.substr(spacepos+1,dotpos-(spacepos+1));\n%20%20%20%20%20%20}\n%20%20%20%20%20%20else%20{\n%20%20%20%20%20%20%20%20complete%20=%20_in.value.substr(dotpos+1,caret-(dotpos+1));\n%20%20%20%20%20%20}\n%20%20%20%20%20%20//dump(\%22complete:%20\%22%20+%20complete%20+%20\%22\\n\%22);\n%20%20%20%20%20%20//%20ok,%20now%20look%20at%20all%20the%20props/methods%20of%20this%20obj\n%20%20%20%20%20%20//%20and%20find%20ones%20starting%20with%20'complete'\n%20%20%20%20%20%20var%20matches%20=%20[];\n%20%20%20%20%20%20var%20bestmatch%20=%20null;\n%20%20%20%20%20%20for(var%20a%20in%20obj)\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20//a%20=%20a.toString();\n%20%20%20%20%20%20%20%20//XXX:%20making%20it%20lowercase%20could%20help%20some%20cases,\n%20%20%20%20%20%20%20%20//%20but%20screws%20up%20my%20general%20logic.\n%20%20%20%20%20%20%20%20if(a.substr(0,complete.length)%20==%20complete)%20{\n%20%20%20%20%20%20%20%20%20%20matches.push(a);\n%20%20%20%20%20%20%20%20%20%20////dump(\%22match:%20\%22%20+%20a%20+%20\%22\\n\%22);\n%20%20%20%20%20%20%20%20%20%20//%20if%20no%20best%20match,%20this%20is%20the%20best%20match\n%20%20%20%20%20%20%20%20%20%20if(bestmatch%20==%20null)\n%20%20%20%20%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20%20%20%20%20bestmatch%20=%20a;\n%20%20%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20%20%20else%20{\n%20%20%20%20%20%20%20%20%20%20%20%20//%20the%20best%20match%20is%20the%20longest%20common%20string\n%20%20%20%20%20%20%20%20%20%20%20%20function%20min(a,b){%20return%20((a&amp;lt;b)?a:b);%20}\n%20%20%20%20%20%20%20%20%20%20%20%20var%20i;\n%20%20%20%20%20%20%20%20%20%20%20%20for(i=0;%20i&amp;lt;%20min(bestmatch.length,%20a.length);%20i++)\n%20%20%20%20%20%20%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20if(bestmatch.charAt(i)%20!=%20a.charAt(i))\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break;\n%20%20%20%20%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20%20%20%20%20bestmatch%20=%20bestmatch.substr(0,i);\n%20%20%20%20%20%20%20%20%20%20%20%20////dump(\%22bestmatch%20len:%20\%22%20+%20i%20+%20\%22\\n\%22);\n%20%20%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20%20%20////dump(\%22bestmatch:%20\%22%20+%20bestmatch%20+%20\%22\\n\%22);\n%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20}\n%20%20%20%20%20%20bestmatch%20=%20(bestmatch%20||%20\%22\%22);\n%20%20%20%20%20%20////dump(\%22matches:%20\%22%20+%20matches%20+%20\%22\\n\%22);\n%20%20%20%20%20%20var%20objAndComplete%20=%20(objname%20||%20obj)%20+%20\%22.\%22%20+%20bestmatch;\n%20%20%20%20%20%20//dump(\%22matches.length:%20\%22%20+%20matches.length%20+%20\%22,%20tooManyMatches:%20\%22%20+%20tooManyMatches%20+%20\%22,%20objAndComplete:%20\%22%20+%20objAndComplete%20+%20\%22\\n\%22);\n%20%20%20%20%20%20if(matches.length%20&amp;gt;%201%20&amp;&amp;%20(tooManyMatches%20==%20objAndComplete%20||%20matches.length%20&amp;lt;=%2010))%20{\n\n%20%20%20%20%20%20%20%20printWithRunin(\%22Matches:%20\%22,%20matches.join(',%20'),%20\%22tabcomplete\%22);\n%20%20%20%20%20%20%20%20tooManyMatches%20=%20null;\n%20%20%20%20%20%20}\n%20%20%20%20%20%20else%20if(matches.length%20&amp;gt;%2010)\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20println(matches.length%20+%20\%22%20matches.%20%20Press%20tab%20again%20to%20see%20them%20all\%22,%20\%22tabcomplete\%22);\n%20%20%20%20%20%20%20%20tooManyMatches%20=%20objAndComplete;\n%20%20%20%20%20%20}\n%20%20%20%20%20%20else%20{\n%20%20%20%20%20%20%20%20tooManyMatches%20=%20null;\n%20%20%20%20%20%20}\n%20%20%20%20%20%20if(bestmatch%20!=%20\%22\%22)\n%20%20%20%20%20%20{\n%20%20%20%20%20%20%20%20var%20sstart;\n%20%20%20%20%20%20%20%20if(dotpos%20==%20caret)%20{\n%20%20%20%20%20%20%20%20%20%20sstart%20=%20spacepos+1;\n%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20else%20{\n%20%20%20%20%20%20%20%20%20%20sstart%20=%20dotpos+1;\n%20%20%20%20%20%20%20%20}\n%20%20%20%20%20%20%20%20_in.value%20=%20_in.value.substr(0,%20sstart)\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20+%20bestmatch\n%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20+%20_in.value.substr(caret);\n%20%20%20%20%20%20%20%20setselectionto(_in,caret%20+%20(bestmatch.length%20-%20complete.length));\n%20%20%20%20%20%20}\n%20%20%20%20}\n}\n\nfunction%20printQuestion(q)\n{\n%20%20println(q,%20\%22input\%22);\n}\n\nfunction%20printAnswer(a)\n{\n%20%20if%20(a%20!==%20undefined)%20{\n%20%20%20%20println(a,%20\%22normalOutput\%22);\n%20%20%20%20shellCommands.ans%20=%20a;\n%20%20}\n}\n\nfunction%20printError(er)\n{%20\n%20%20var%20lineNumberString;\n\n%20%20lastError%20=%20er;%20//%20for%20debugging%20the%20shell\n%20%20if%20(er.name)\n%20%20{\n%20%20%20%20//%20lineNumberString%20should%20not%20be%20\%22\%22,%20to%20avoid%20a%20very%20wacky%20bug%20in%20IE%206.\n%20%20%20%20lineNumberString%20=%20(er.lineNumber%20!=%20undefined)%20?%20(\%22%20on%20line%20\%22%20+%20er.lineNumber%20+%20\%22:%20\%22)%20:%20\%22:%20\%22;\n%20%20%20%20println(er.name%20+%20lineNumberString%20+%20er.message,%20\%22error\%22);%20//%20Because%20IE%20doesn't%20have%20error.toString.\n%20%20}\n%20%20else\n%20%20%20%20println(er,%20\%22error\%22);%20//%20Because%20security%20errors%20in%20Moz%20/only/%20have%20toString.\n}\n\nfunction%20go(s)\n{\n%20%20_in.value%20=%20question%20=%20s%20?%20s%20:%20_in.value;\n\n%20%20if%20(question%20==%20\%22\%22)\n%20%20%20%20return;\n\n%20%20histList[histList.length-1]%20=%20question;\n%20%20histList[histList.length]%20=%20\%22\%22;\n%20%20histPos%20=%20histList.length%20-%201;\n%20%20\n%20%20//%20Unfortunately,%20this%20has%20to%20happen%20*before*%20the%20JavaScript%20is%20run,%20so%20that%20\n%20%20//%20print()%20output%20will%20go%20in%20the%20right%20place.\n%20%20_in.value='';\n%20%20recalculateInputHeight();\n%20%20printQuestion(question);\n\n%20%20if%20(_win.closed)%20{\n%20%20%20%20printError(\%22Target%20window%20has%20been%20closed.\%22);\n%20%20%20%20return;\n%20%20}\n%20%20\n%20%20try%20{%20(\%22Shell\%22%20in%20_win)%20}\n%20%20catch(er)%20{\n%20%20%20%20printError(\%22The%20JavaScript%20Shell%20cannot%20access%20variables%20in%20the%20target%20window.%20%20The%20most%20likely%20reason%20is%20that%20the%20target%20window%20now%20has%20a%20different%20page%20loaded%20and%20that%20page%20has%20a%20different%20hostname%20than%20the%20original%20page.\%22);\n%20%20%20%20return;\n%20%20}\n\n%20%20if%20(!(\%22Shell\%22%20in%20_win))\n%20%20%20%20initTarget();%20//%20silent\n\n%20%20//%20Evaluate%20Shell.question%20using%20_win's%20eval%20(this%20is%20why%20eval%20isn't%20in%20the%20|with|,%20IIRC).\n%20%20_win.location.href%20=%20\%22javascript:try{%20Shell.printAnswer(eval('with(Shell._scope)%20with(Shell.shellCommands)%20{'%20+%20Shell.question%20+%20String.fromCharCode(10)%20+%20'}'));%20}%20catch(er)%20{%20Shell.printError(er);%20};%20setTimeout(Shell.refocus,%200);%20void%200\%22;\n}\n\n&amp;lt;/script&amp;gt;\n\n&amp;lt;!--%20for%20http://ted.mielczarek.org/code/mozilla/extensiondev/%20--&amp;gt;\n&amp;lt;script%20type=\%22text/javascript\%22%20src=\%22chrome://extensiondev/content/rdfhistory.js\%22&amp;gt;&amp;lt;/script&amp;gt;\n&amp;lt;script%20type=\%22text/javascript\%22%20src=\%22chrome://extensiondev/content/chromeShellExtras.js\%22&amp;gt;&amp;lt;/script&amp;gt;\n\n&amp;lt;style%20type=\%22text/css\%22&amp;gt;\nbody%20{%20background:%20white;%20color:%20black;%20}\n\n#output%20{%20white-space:%20pre;%20white-space:%20-moz-pre-wrap;%20}%20/*%20Preserve%20line%20breaks,%20but%20wrap%20too%20if%20browser%20supports%20it%20*/\nh3%20{%20margin-top:%200;%20margin-bottom:%200em;%20}\nh3%20+%20div%20{%20margin:%200;%20}\n\nform%20{%20margin:%200;%20padding:%200;%20}\n#input%20{%20width:%20100%25;%20border:%20none;%20padding:%200;%20overflow:%20auto;%20}\n\n.input%20{%20color:%20blue;%20background:%20white;%20font:%20inherit;%20font-weight:%20bold;%20margin-top:%20.5em;%20/*%20background:%20#E6E6FF;%20*/%20}\n.normalOutput%20{%20color:%20black;%20background:%20white;%20}\n.print%20{%20color:%20brown;%20background:%20white;%20}\n.error%20{%20color:%20red;%20background:%20white;%20}\n.propList%20{%20color:%20green;%20background:%20white;%20}\n.message%20{%20color:%20green;%20background:%20white;%20}\n.tabcomplete%20{%20color:%20purple;%20background:%20white;%20}\n&amp;lt;/style&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body%20onload=\%22init()\%22&amp;gt;\n\n&amp;lt;div%20id=\%22output\%22&amp;gt;&amp;lt;h3&amp;gt;JavaScript%20Shell%201.4&amp;lt;/h3&amp;gt;&amp;lt;div&amp;gt;Features:%20autocompletion%20of%20property%20names%20with%20Tab,%20multiline%20input%20with%20Shift+Enter,%20input%20history%20with%20(Ctrl+)%20Up/Down,%20&amp;lt;a%20accesskey=\%22M\%22%20href=\%22javascript:go('scope(Math);%20mathHelp();');\%22%20title=\%22Accesskey:%20M\%22&amp;gt;Math&amp;lt;/a&amp;gt;,%20&amp;lt;a%20accesskey=\%22H\%22%20href=\%22http://www.squarefree.com/shell/?ignoreReferrerFrom=shell1.4\%22%20%20title=\%22Accesskey:%20H\%22&amp;gt;help&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;div&amp;gt;Values%20and%20functions:%20ans,%20print(string),%20&amp;lt;a%20accesskey=\%22P\%22%20href=\%22javascript:go('props(ans)')\%22%20title=\%22Accesskey:%20P\%22&amp;gt;props(object)&amp;lt;/a&amp;gt;,%20&amp;lt;a%20accesskey=\%22B\%22%20href=\%22javascript:go('blink(ans)')\%22%20title=\%22Accesskey:%20B\%22&amp;gt;blink(node)&amp;lt;/a&amp;gt;,%20&amp;lt;a%20accesskey=\%22C\%22%20href=\%22javascript:go('clear()')\%22%20title=\%22Accesskey:%20C\%22&amp;gt;clear()&amp;lt;/a&amp;gt;,%20load(scriptURL),%20scope(object)&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;\n\n&amp;lt;div&amp;gt;&amp;lt;textarea%20id=\%22input\%22%20class=\%22input\%22%20wrap=\%22off\%22%20onkeydown=\%22inputKeydown(event)\%22%20rows=\%221\%22&amp;gt;&amp;lt;/textarea&amp;gt;&amp;lt;/div&amp;gt;\n\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;%22);document.close();}void%200"&gt;javascript shell&lt;/a&gt; bookmarklet from &lt;a href="https://www.squarefree.com/bookmarklets/"&gt;Jesse's Bookmarklets&lt;/a&gt;, and of course, you've seen my blog post on &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/tabulate.html"&gt;Tabulate&lt;/a&gt; from the clever folks at &lt;a href="http://gadgets.inventivelabs.com.au/tabulate"&gt;Inventive Labs&lt;/a&gt;.  However, installing bookmarklets on the iPhone without help from a computer is a royal PITA.&lt;br /&gt;&lt;br /&gt;That's why I wrote the &lt;a href="javascript:(function(){var s,i,l=document.links;for(i=0;i&amp;lt;l.length;i++){if(l[i].href.indexOf('javascript:')!=0)continue;l[i].href='#'+l[i].href;s=l[i].style;s.backgroundColor='#eee';s.color='#333';s.border='1px solid #333';s.textDecoration='none';s.padding='2px';s.fontWeight='normal';}})();"&gt;iPhone Bookmarklet Installer&lt;/a&gt;.  &lt;br /&gt;&lt;br /&gt;If you're interested in how it works, you can find details on my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/08/iphone-bookmarklet-installer-source.html"&gt;iPhone Installer Bookmarklet&lt;/a&gt; blog post.  But, the short version is that I use Javascript to loop through all of the links on the page and make them look like anchor tags so you can just bookmark them on the iPhone and remove everything before the anchor tag character '#'.&lt;br /&gt;&lt;br /&gt;With that out of the way, here's how you use it:&lt;ol&gt;&lt;li&gt;Run the bookmarklet on this page by clicking the &lt;a href="javascript:(function(){var s,i,l=document.links;for(i=0;i&amp;lt;l.length;i++){if(l[i].href.indexOf('javascript:')!=0)continue;l[i].href='#'+l[i].href;s=l[i].style;s.backgroundColor='#eee';s.color='#333';s.border='1px solid #333';s.textDecoration='none';s.padding='2px';s.fontWeight='normal';}})();"&gt;iPhone Bookmarklet Installer&lt;/a&gt;.  You should see all of the bookmarklets on this page change styles (They'll look something like &lt;span style="background: #eee; color: #333; border: 1px solid #333; padding: 2px;"&gt;this&lt;/span&gt;).&lt;/li&gt;&lt;li&gt;Click the bookmarklet you wish to install (n.b., the page won't reload).&lt;/li&gt;&lt;li&gt;Bookmark the page.&lt;/li&gt;&lt;li&gt;Edit the bookmark and remove everything up to and including the # character.&lt;/li&gt;&lt;li&gt;Name the bookmarklet appropriately.&lt;/li&gt;&lt;li&gt;Save and enjoy.&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-7332979901582837426?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/7332979901582837426/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/08/iphone-bookmarklet-to-install-iphone_06.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7332979901582837426'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7332979901582837426'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/08/iphone-bookmarklet-to-install-iphone_06.html' title='iPhone Bookmarklet to Install iPhone Bookmarklets'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SnsP169urVI/AAAAAAAAA8Q/Yn8zPrCEkKM/s72-c/Bookmarklets.gif' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6070476317354167023</id><published>2009-07-10T08:47:00.000-07:00</published><updated>2009-07-10T09:35:33.021-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><title type='text'>Leave the Poor Table Alone</title><content type='html'>&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/SldjfOWiMcI/AAAAAAAAA54/ZjjrljBMNHk/s288/table.jpg" alt="Traffic Sign" style="float: left; margin-right: 7px;" class="postimage" /&gt;So, I realize that with some clever hacking and a butt-load of time, you can do just about anything with divs and CSS, but I think the anti-table thing has gone way too far!  I've even seen people display tabular data without using tables.  I mean, great. Look at you.  You're fancy and all, but what have you accomplished?  Tabular data is what tables were designed for in the first place.&lt;br /&gt;&lt;br /&gt;So, why are people so steadfastly opposed to tables in HTML layout?  Well, I've seen oodles of reasons including things like speed of data transfer, separation of content and layout, ease of redesign, no spacer gifs, lower html complexity, accessibility for persons with disabilities, etc.&lt;br /&gt;&lt;br /&gt;Of course, there are sites out there that demonstrate all of the things that can be accomplished using nothing but divs and CSS and they're really great to look at and enjoy; I've learned a lot about CSS from them.  But, just because you can doesn't mean you should.  I mean, when was the last time you heard about someone doing something with "nothing but" and it wasn't a stupid human trick?  Mac Gyver  could take down a swarm of enemies with nothing but a Milk Bone, some shoe string, a little fertilizer, and duct tape; I, on the other hand, carry a Glock.&lt;br /&gt;&lt;br /&gt;But, here's the real issue: whether you use tables or not, there're right and wrong ways to do anything.  Just like there are wrong ways to use tables for layout, there are wrong ways not to.  My point is that you can very cleanly and appropriately use tables for layout and still use CSS for almost all of your actual presentation.&lt;br /&gt;&lt;br /&gt;As a result, I've yet to come up with a compelling reason to eliminate tables from layout altogether.  For example, using a table for your overall layout can give you cross browser compatibility that is very difficult and effortful to achieve with CSS and divs.  CSS and DIVs are rendered differently among the several browsers; however, CSS and tables are relatively consistent.&lt;br /&gt;&lt;br /&gt;I've found that often times, I can use only one table and achieve exactly the layout I was looking for when a div design would require 3 or 4 levels of nesting.  In this case, it is actually cheaper and less complex to use tables than it is to use divs and it took me a tenth of the time.&lt;br /&gt;&lt;br /&gt;While I agree there should be a separation of concerns within the definition of websites to keep content, presentation, and behavior separated, using tables to position elements only is a very minor violation of this principle.  In fact, using tables in this way is exactly the same as using divs this way.  Afterall, any time you use an HTML tag your intent is to describe something about the way the content is displayed.  Then, you modify that behavior with CSS.  Really, the only thing you can't do with tables and CSS is fundamentally change the position of items.&lt;br /&gt;&lt;br /&gt;I suppose that's where people are coming from when they mention redesign.  Now, that may be the case and I don't really know that much about it.  I'm not a web designer; I'm a software engineer.  I spend a very small percentage of my time concerned with layout as there are usually much bigger fish to fry; however, I've never been on a redesign project that didn't require HTML changes.&lt;br /&gt;&lt;br /&gt;As a result, it never made a difference whether the page was tables + CSS or divs + CSS; we always had to rewrite some HTML.  Furthermore, it's generally in a template somewhere.  In fact, many times, it has been easier for me to reposition elements on a page because I was using tables for layout rather than divs.  The CSS required to move stuff around and make it cross-browser compatible is often a royal pain in the ass, but it's pretty easy for me to move a one line server side include or a user control in .net.&lt;br /&gt;&lt;br /&gt;I never really understood why people used spacer gifs.  I don't use them and never have so I don't know what that has to do with tables.  I also don't know what tables have to do with accessibility.  I've read some things about readers not being able to do anything with graphics because they can only interpret and present text.  Divs, tables, plain text, whatever . . . it's all the same text so I don't really know what this has to do with tables for layout.&lt;br /&gt;&lt;br /&gt;In my years of experience, I've gone through the dogmatic anti-table phase and I've gotten over it.  I simply cannot come up with a good reason to eschew tabular layout.  Most of the time, I can do what I want with divs and CSS and I do.  When something is difficult with a div, I usually throw a table at it.  As a result, I get the job done faster and I save clients money and they ultimately get the same quality software.  I've also not had any complaints about accessibility, maintainability, or load times.  To me, it's a win-all-around kind of thing, so until I can find solid arguments against tables, I say leave the poor table alone.  It's another tool in the toolbox; use it if it makes your life easier.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6070476317354167023?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6070476317354167023/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/07/leave-poor-table-alone.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6070476317354167023'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6070476317354167023'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/07/leave-poor-table-alone.html' title='Leave the Poor Table Alone'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/SldjfOWiMcI/AAAAAAAAA54/ZjjrljBMNHk/s72-c/table.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2684411093481678389</id><published>2009-07-06T09:51:00.001-07:00</published><updated>2009-07-06T11:25:29.467-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='It Takes an Engineer'/><category scheme='http://www.blogger.com/atom/ns#' term='Fail'/><title type='text'>Sign Placement Takes an Engineer</title><content type='html'>&lt;a href="http://maps.google.com/maps/mm?ie=UTF8&amp;hl=en&amp;ll=34.004035,-84.33577&amp;spn=0.003273,0.006968&amp;t=h&amp;z=18"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/SlImkUCDvSI/AAAAAAAAA48/tMUoqVeRmwI/Traffic%20Sign.jpg" alt="Traffic Sign" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I was driving into work this morning and I noticed a sign off to my right.  It was a temporary sign which evidently was warning us drivers of some upcoming event.  It was too small to contain the entire message so they broke it down into pages which displayed for about 3 seconds each.  The first page I saw read:&lt;blockquote style="margin-left: 280px;"&gt;July 12, 2009&lt;br /&gt;From 6:00 AM&lt;br /&gt;To 7:00 AM&lt;/blockquote&gt;Interested, I eagerly awaited the next page.  About 3 seconds later the sign read:&lt;blockquote style="margin-left: 280px;"&gt;Between&lt;br /&gt;Exits 6 and 7&lt;/blockquote&gt;But, before I could find out what was happening during that block of time, I passed the sign.  What in the world did that third page read?  Should I plan on staying home?  Should I leave home?  What's happening?&lt;br /&gt;&lt;br /&gt;Well, I started thinking about it and it occurred to me that it was pretty dumb to put that sign on the side of the road right after a curve.  Why not put it after the curve in the ensuing half mile straight away?&lt;br /&gt;&lt;br /&gt;So, I looked into it.  If you take a look at the image above, you'll see the approximate location of the sign.  I drew a line segment starting at the sign, passing the west most occluder, and ending at the median between the northbound and southbound lanes.  &lt;br /&gt;&lt;br /&gt;Then, I traced the median all the way up to the approximate angle where I could no longer see the sign because the visual angle was too narrow.&lt;br /&gt;&lt;br /&gt;Next, I traced the fast lane (the longest route within the "polygon of visibility").  I measured it out in google maps and it turns out that the sign was visible for 448 feet.&lt;br /&gt;&lt;br /&gt;The speed limit at this point on GA 400 is 65 miles per hour (approx. 95 ft / s).  That means that the sign was visible for just shy of 5 seconds.&lt;br /&gt;&lt;br /&gt;I'm sure by now, you've done the math and come to the same conclusion.  If the sign pages rotate at a minimum of 3 seconds per page and the maximum visible time at or above the speed limit is 5 seconds, then it is only possible to read 2 pages of data.  However, the message must be at least 3 pages in order to be complete.&lt;br /&gt;&lt;br /&gt;There's no way to reasonably expect drivers to read the entire message and therefore most people passing that sign today will have no idea what the full message is.  Evidently, when it comes to sign placement, &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/It%20Takes%20an%20Engineer"&gt;it takes an engineer&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2684411093481678389?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2684411093481678389/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/07/sign-placement-takes-engineer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2684411093481678389'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2684411093481678389'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/07/sign-placement-takes-engineer.html' title='Sign Placement Takes an Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/SlImkUCDvSI/AAAAAAAAA48/tMUoqVeRmwI/s72-c/Traffic%20Sign.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5709183529183745152</id><published>2009-07-06T09:30:00.000-07:00</published><updated>2009-07-06T10:30:08.180-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='It Takes an Engineer'/><title type='text'>It Takes an Engineer</title><content type='html'>&lt;a href="http://www.eng.auburn.edu/"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/SlIqL1fx3BI/AAAAAAAAA5A/ytpoUzooKSU/ginn.jpg" alt="Engineering" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I'm pretty excited about this post.  Not only is it my first blog post in a while (I've been pretty swamped lately and haven't had a chance to write anything), but it's also the first new section I've started in a long time.&lt;br /&gt;&lt;br /&gt;This entry marks the first post in a series of posts I call, "&lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/It%20Takes%20an%20Engineer"&gt;It Takes an Engineer&lt;/a&gt;."  It's about all of those things we see in daily life that would be better if they consulted an engineer first.&lt;br /&gt;&lt;br /&gt;I'm sure, if you're reading this blog, you probably know what I'm talking about.  It's the stuff that makes you stop and say, "what the hell were you thinking when you did this?"  Or, "you really thought this would be intuitive?"  Or, "if someone thought about how this was going to be used, I wouldn't have to fix it all the time."&lt;br /&gt;&lt;br /&gt;Well, this is going to be my venue for venting on this topic so I hope it provides enjoyment to all.  If you have anymore good examples, let me know and I'll blog about them too.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5709183529183745152?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5709183529183745152/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/07/it-takes-engineer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5709183529183745152'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5709183529183745152'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/07/it-takes-engineer.html' title='It Takes an Engineer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/SlIqL1fx3BI/AAAAAAAAA5A/ytpoUzooKSU/s72-c/ginn.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-742458476013793090</id><published>2009-06-16T10:59:00.000-07:00</published><updated>2009-06-16T12:09:35.999-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><category scheme='http://www.blogger.com/atom/ns#' term='Fail'/><title type='text'>iPhone MMS Fail</title><content type='html'>&lt;a href="http://picasaweb.google.com/tncbbthositg/BlogPictures#5348003700886891730"&gt;&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/SjftB_DWbNI/AAAAAAAAA3Q/MUYND2Czfvs/s288/iPhoneMMS.png" alt="iPhone MMS" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;So, I understand that &lt;a href="http://www.computerworld.com/action/article.do?command=viewArticleBasic&amp;articleId=9134154&amp;intsrc=news_ts_head"&gt;Apple purports to have good reasons for not supporting MMS at the moment&lt;/a&gt; and that &lt;a href="http://www.apple.com/iphone/softwareupdate/"&gt;Apple plans to support MMS this summer&lt;/a&gt; for &lt;a href="http://news.softpedia.com/news/No-MMS-for-iPhone-2G-with-OS-3-0-114269.shtml"&gt;3G and 3G S phones&lt;/a&gt; and AT&amp;T seems to be proud of themselves for offering &lt;a href="http://www.theregister.co.uk/2009/06/15/att_free_mms/"&gt;MMS for free on the iphone&lt;/a&gt; despite the fact that MMS is free with basic messaging with almost every other provider, but whatever.  That's all well and good.&lt;br /&gt;&lt;br /&gt;But, here's what pisses me off . . . aside the fact that MMS has been around a really long time and that the iPhone is one of the few phones that doesn't support it, I'm still really annoyed by the way AT&amp;T . . . oh excuse me . . . at&amp;t decided to hack together some form of MMS support.&lt;br /&gt;&lt;br /&gt;If you don't have an iPhone, let me tell you how this goes.  You get a text message.  The text message links you to a &lt;a href="http://www.viewmymessage.com/1"&gt;simple at&amp;t multimedia messaging page&lt;/a&gt; with text boxes for message id and password.  Then, you enter your message id and password which are provided in the text message.&lt;br /&gt;&lt;br /&gt;Now, if you don't have a computer handy and you want to view this message on your iPhone, you had better grab a pen, 'cause this message id looks something like this: esth9389uohh or 1ch309903hsuh or 47&amp;75uck5r3411y3ffingb4d!  How I'm expected to remember this, I don't know.&lt;br /&gt;&lt;br /&gt;Then, the password is a little easier to remember.  It's just two four letter words separated by a two digit number.  So, why do I find this so frustrating?  Well, simply because there is no reason under the sun to do this.  There is absolutely no difference between my entering these codes manually and at&amp;t tacking them onto the query string.  In fact, if they did that, they could use longer keys and could make it harder to brute force.&lt;br /&gt;&lt;br /&gt;Then, when I got an MMS message, I could just click the link and, lo and behold, there would be my message.  I wouldn't have to try to write down the dumb message id and try to figure out if that l is a 1.&lt;br /&gt;&lt;br /&gt;I've got a password for you at&amp;t . . . epic69fail.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-742458476013793090?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/742458476013793090/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/06/iphone-mms-fail.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/742458476013793090'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/742458476013793090'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/06/iphone-mms-fail.html' title='iPhone MMS Fail'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/SjftB_DWbNI/AAAAAAAAA3Q/MUYND2Czfvs/s72-c/iPhoneMMS.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2017469249967381963</id><published>2009-06-12T17:59:00.000-07:00</published><updated>2009-06-12T18:42:30.351-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Soundex Extension Method for C#</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Soundex"&gt;&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/SjL8uEpLIoI/AAAAAAAAA2I/RBEW5_lf4t8/s288/Census_Bureau_Seal.png" alt="British Airways" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I've been pretty swamped at work lately so I've not been blogging much.  Worse than that, I haven't really been able to do much creative programming and virtually nothing academic.&lt;br /&gt;&lt;br /&gt;A few days ago, I was working on some basic name searching and recognized an opportunity to squeeze in some play time.  It occurred to me that the users of our application will be looking people up by name and will often not know how to spell the name correctly.&lt;br /&gt;&lt;br /&gt;I wanted to allow users to search for people by name without regard for different spellings.  For example, if you're looking for Geoff McArther or Jeff MacArther, both names will be returned by the same search term.&lt;br /&gt;&lt;br /&gt;To do this, I used a technology invented back in the 1920s for the &lt;a href="http://en.wikipedia.org/wiki/United_States_Census"&gt;US Census&lt;/a&gt; called &lt;a href="http://en.wikipedia.org/wiki/Soundex"&gt;soundex&lt;/a&gt;.  The following is a set of &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;extension methods&lt;/a&gt; that implement the soundex phonetic algorithm:&lt;pre name="code" class="c#"&gt;public static string Soundex(this string s)&lt;br /&gt;{&lt;br /&gt;    // by default, a soundex is the first letter&lt;br /&gt;    // followed by three numbers&lt;br /&gt;    return Soundex(s, 4);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static string Soundex(this string s, int length)&lt;br /&gt;{&lt;br /&gt;    return FullSoundex(s)&lt;br /&gt;        .PadRight(length, '0')  // soundex is no shorter than &lt;br /&gt;        .Substring(0, length);  // and no longer than length&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static string FullSoundex(this string s)&lt;br /&gt;{&lt;br /&gt;    // the encoding information&lt;br /&gt;    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";&lt;br /&gt;    const string codes = "0123012D02245501262301D202";&lt;br /&gt;&lt;br /&gt;    // some helpful regexes&lt;br /&gt;    Regex hwBeginString = new Regex("^D+");&lt;br /&gt;    Regex simplify = new Regex(@"(\d)\1*D?\1+");&lt;br /&gt;    Regex cleanup = new Regex("[D0]");&lt;br /&gt;    &lt;br /&gt;    // i need a capitalized string&lt;br /&gt;    s = s.ToUpper();&lt;br /&gt;&lt;br /&gt;    // i'm building the coded string using a string builder&lt;br /&gt;    // because i think this is probably the fastest and least&lt;br /&gt;    // intensive way&lt;br /&gt;    StringBuilder coded = new StringBuilder();&lt;br /&gt;&lt;br /&gt;    // do the encoding&lt;br /&gt;    for (int i = 0; i &amp;lt; s.Length; i++)&lt;br /&gt;    {&lt;br /&gt;        int index = chars.IndexOf(s[i]);&lt;br /&gt;        if (index &amp;gt;= 0)&lt;br /&gt;            coded.Append(codes[index]);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // okay, so here's how this goes . . .&lt;br /&gt;    // the first thing I do is assign the coded string&lt;br /&gt;    // so that i can regex replace on it&lt;br /&gt;    string result = coded.ToString();&lt;br /&gt;&lt;br /&gt;    // then i remove repeating characters&lt;br /&gt;    //result = repeating.Replace(result, "$1");&lt;br /&gt;    result = simplify.Replace(result, "$1").Substring(1);&lt;br /&gt;&lt;br /&gt;    // now i need to remove any characters coded as D  from&lt;br /&gt;    // the front of the string because they're not really &lt;br /&gt;    // valid as the first code because they don't have an &lt;br /&gt;    // actual soundex code value&lt;br /&gt;    result = hwBeginString.Replace(result, string.Empty);&lt;br /&gt;    &lt;br /&gt;    // i used the char D to indicate that an h or w existed&lt;br /&gt;    // so that if to similar sounds were separated by an h or&lt;br /&gt;    // a w that I could remove one of them.  if the h or w does&lt;br /&gt;    // not separate two similar sounds, then i need to remove&lt;br /&gt;    // it now&lt;br /&gt;    result = cleanup.Replace(result, string.Empty);&lt;br /&gt;&lt;br /&gt;    // return the first character followed by the coded&lt;br /&gt;    // string&lt;br /&gt;    return string.Format("{0}{1}", s[0], result);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Now, it may not yet be perfect as I had a pretty hard time finding the exact specs for the American soundex, but it should be pretty close.  If you notice something wrong, please leave me a comment and I'll correct it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2017469249967381963?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2017469249967381963/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/06/soundex-extension-method-for-c.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2017469249967381963'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2017469249967381963'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/06/soundex-extension-method-for-c.html' title='Soundex Extension Method for C#'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/SjL8uEpLIoI/AAAAAAAAA2I/RBEW5_lf4t8/s72-c/Census_Bureau_Seal.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-7234961584592810987</id><published>2009-06-12T10:26:00.000-07:00</published><updated>2009-09-01T07:11:43.296-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Fail'/><title type='text'>Communication Clarity Fail</title><content type='html'>&lt;a href="http://pragprog.com/titles/tpp/the-pragmatic-programmer"&gt;&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/SjKQsOPLi5I/AAAAAAAAA2E/rmlcpc5_WWA/s288/tpp.png" alt="British Airways" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;When I'm not writing awesome software (grin), you can usually find me flying airplanes, dreaming about airplanes, thinking about airplanes, or &lt;a href="http://joysofflight.blogspot.com"&gt;blogging about airplanes&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Yesterday, I was getting ready to make my &lt;a href="http://joysofflight.blogspot.com/2009/06/first-solo-in-super-decathlon.html"&gt;first solo in a Super Decathlon&lt;/a&gt;, and I spent an agonizing nerve-racked hour waiting for my instructor to get off of a conference call.  I thought I'd try to work off some nervous energy reading.&lt;br /&gt;&lt;br /&gt;Lately, I've been reading &lt;a href="http://www.amazon.com/gp/product/020161622X?ie=UTF8&amp;tag=dpatcalon-20&amp;linkCode=as2&amp;camp=1789&amp;creative=9325&amp;creativeASIN=020161622X"&gt;The Pragmatic Programmer&lt;/a&gt;.  It's a really great book and will probably be the subject of several forthcoming posts reviewing the book, but that's not the topic here.&lt;br /&gt;&lt;br /&gt;This post is about the coincidence of me sitting there at the airport reading about programming and finding this &lt;a href="http://www.britishairways.com/travel/home/public/en_us?gclid=CLz_oMGfhZsCFRJdxwodY1GWow"&gt;British Airways&lt;/a&gt; memorandum that was published in &lt;a href="http://www.pilotmag.com/"&gt;Pilot Magazine&lt;/a&gt; in 2006:&lt;blockquote&gt;From British Airways Flight Operations Department notice:&lt;br /&gt;&lt;br /&gt;There appears to be some confusion over the new pilot role titles. &lt;br /&gt;This notice will hopefully clear up any misunderstandings.&lt;br /&gt;The titles P1, P2, and Co-Pilot will now cease to have any meaning, &lt;br /&gt;within the British Airways operational manuals. They are to be &lt;br /&gt;replaced by&lt;br /&gt;&lt;br /&gt;-Handling Pilot,&lt;br /&gt;-Non-Handling Pilot,&lt;br /&gt;-Handling Landing Pilot,&lt;br /&gt;-Non-Handling Landing Pilot,&lt;br /&gt;-Handling Non-Landing Pilot,&lt;br /&gt;-Non-Handling Non-Landing Pilot.&lt;br /&gt;&lt;br /&gt;The Landing Pilot is initially the Handling Pilot and will handle the&lt;br /&gt;take-off and landing except in role reversal when he is the &lt;br /&gt;Non-Handling Pilot for taxi until the Handling Non-Landing hands &lt;br /&gt;the handling to the Landing Pilot at 80 knots.&lt;br /&gt;The Non-Landing (Non-Handling, since the Landing Pilot is handling) &lt;br /&gt;Pilot reads the checklist to the Handling Pilot until after Before&lt;br /&gt;Descent Checklist completion, when the Handling Landing Pilot &lt;br /&gt;hands the handling to the Non-Handling Non-Landing Pilot who &lt;br /&gt;then becomes the Handling Non-Landing Pilot.&lt;br /&gt;The Landing Pilot is the Non-Handling Pilot until the 'decision &lt;br /&gt;altitude' call, when the Handling Non-Landing Pilot hands the &lt;br /&gt;handling to the Non-Handling Landing Pilot, unless the latter call&lt;br /&gt;'go-around', in which case the Handling Non-Landing Pilot &lt;br /&gt;continues handling and the Non-Handling Landing Pilot continues&lt;br /&gt;non-handling until the next call of 'land' or 'go-around' as appropriate.&lt;br /&gt;In view of recent confusions over these rules, it was deemed &lt;br /&gt;necessary to restate them clearly&lt;/blockquote&gt;Oh, thanks for clearin' that one up fellas!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-7234961584592810987?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/7234961584592810987/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/06/communication-clarity-fail.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7234961584592810987'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7234961584592810987'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/06/communication-clarity-fail.html' title='Communication Clarity Fail'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/SjKQsOPLi5I/AAAAAAAAA2E/rmlcpc5_WWA/s72-c/tpp.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-348230330867699244</id><published>2009-05-19T07:10:00.001-07:00</published><updated>2009-05-19T09:22:11.409-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><title type='text'>Automatic SVN Updates for Offsite Backup</title><content type='html'>&lt;a href="http://subversion.tigris.org"&gt;&lt;img src="http://lh5.ggpht.com/_cFBwGCiU3y4/ShLB0LFWrGI/AAAAAAAAAz0/Pcmh_5M9wtE/s288/subversion_logo.png" alt="Subversion" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;My company has something of an offsite backup strategy.  We have two external hard drives which are manually swapped out on a daily basis and someone takes the inactive one home.  &lt;br /&gt;&lt;br /&gt;We're a pretty small company and that works fine.  Of course, while said person is in the office, both drives (the current drive and the offsite backup) are onsite.  We also have a data center where we send our really important stuff too.&lt;br /&gt;&lt;br /&gt;I've been doing my part to help in disaster recovery by keeping my own copy of the company &lt;a href="http://subversion.tigris.org/"&gt;&lt;acronym title="Subversion"&gt;SVN&lt;/acronym&gt;&lt;/a&gt; repository on an external drive at my house.  The annoying thing was that I was always attaching the drive to my laptop and copying it over manually.&lt;br /&gt;&lt;br /&gt;That process grew irritating so I installed &lt;a href="http://tortoisesvn.net/"&gt;TortoiseSVN&lt;/a&gt; on my home server and now I can just update the SVN folder and our other offsite backup is up to date.  But then, I got a little tired of doing that too!&lt;br /&gt;&lt;br /&gt;I decided to automate the entire process.  First, I needed to connect to the office VPN (which is an &lt;a href="http://openvpn.net/"&gt;OpenVPN&lt;/a&gt; server).  Then, I needed to update the two repositories.  Finally, I needed to disconnect from the office VPN.&lt;br /&gt;&lt;br /&gt;The OpenVPN was really the hard part because if I opened a connection on the command line, I couldn't close the connection without sending an F4 keypress.  I didn't want to write a windows app for this 'cause I should really be able to do this with a batch file.&lt;br /&gt;&lt;br /&gt;Turns out, OpenVPN has a windows service and I can start those command line, so here's my script:&lt;pre name="code" class="bat"&gt;@echo off&lt;br /&gt;&lt;br /&gt;echo net start "OpenVPN Service"       &gt;&gt; %esg%\tempGetESGSVN.bat&lt;br /&gt;echo ipconfig /flushdns                &gt;&gt; %esg%\tempGetESGSVN.bat&lt;br /&gt;echo svn up %esg%\Contractor           &gt;&gt; %esg%\tempGetESGSVN.bat&lt;br /&gt;echo svn up %esg%\Internal             &gt;&gt; %esg%\tempGetESGSVN.bat&lt;br /&gt;echo net stop "OpenVPN Service"        &gt;&gt; %esg%\tempGetESGSVN.bat&lt;br /&gt;echo del %esg%\tempGetESGSVN.bat /F    &gt;&gt; %esg%\tempGetESGSVN.bat&lt;br /&gt;&lt;br /&gt;%esg%\tempGetESGSVN.bat &gt;&gt; %esg%\GetESGSVN.log&lt;br /&gt;&lt;br /&gt;@echo on&lt;/pre&gt;&lt;br /&gt;This batch file looks a little funny and that's because it is.  Basically, it's creating another batch file which executes all of the steps and then deletes itself.  I did this 'cause it made it a little easier for me to log.  The batch file it creates looks like this:&lt;pre name="code" class="bat"&gt;net start "OpenVPN Service"&lt;br /&gt;ipconfig /flushdns            &lt;br /&gt;svn up %esg%\Contractor       &lt;br /&gt;svn up %esg%\Internal         &lt;br /&gt;net stop "OpenVPN Service"    &lt;br /&gt;del %esg%\tempGetESGSVN.bat /F&lt;/pre&gt;&lt;br /&gt;Nota bene, if you don't have a command line SVN client installed, you can use &lt;a href="http://www.sliksvn.com/"&gt;Slik Subversion&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-348230330867699244?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/348230330867699244/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/automatic-svn-updates-for-offsite.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/348230330867699244'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/348230330867699244'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/automatic-svn-updates-for-offsite.html' title='Automatic SVN Updates for Offsite Backup'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_cFBwGCiU3y4/ShLB0LFWrGI/AAAAAAAAAz0/Pcmh_5M9wtE/s72-c/subversion_logo.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-1470206562609409401</id><published>2009-05-18T14:11:00.000-07:00</published><updated>2009-05-18T14:30:46.004-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Tips and Tricks'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Conditional Check Constraints on SQL Server</title><content type='html'>&lt;a href="http://www.microsoft.com/sqlserver/2005/en/us/default.aspx"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/ShHTVVPE6rI/AAAAAAAAAzw/bvoKWXdGf9s/s288/sqlserver.jpg" alt="SQL Server" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I was on &lt;a href="http://www.stackoverflow.com"&gt;Stack Overflow&lt;/a&gt; the other day (something I'm just getting into . . . not sure I care that much for it yet, but we'll see.  I'm definitely enjoying it more now that I'm not restricted on my behaviors because I lack reputation points).&lt;br /&gt;&lt;br /&gt;In any event, someone asked me a question about &lt;a href="http://stackoverflow.com/questions/866061/conditional-unique-constraint/"&gt;conditional check constraints&lt;/a&gt;.  The consensus seemed to be that you couldn't do it so I figured I'd post the solution on my blog as well.&lt;br /&gt;&lt;br /&gt;Basically, the thought was that you couldn't put the check constraint on a subquery or an aggregate so you had to use a trigger only.  Well, that's not technically true since you can write a function to do the aggregating for you.  I'm not sure I like the idea, but it is in fact possible and I'm looking forward to reading all of your comments about the topic.&lt;br /&gt;&lt;br /&gt;So, here's my script that comprises an entire test including a test table, the function, the constraint, some inserts, the results, and cleaning up:&lt;pre name="code" class="sql"&gt;CREATE TABLE CheckConstraint&lt;br /&gt;(&lt;br /&gt;  Id TINYINT,&lt;br /&gt;  Name VARCHAR(50),&lt;br /&gt;  RecordStatus TINYINT&lt;br /&gt;)&lt;br /&gt;GO&lt;br /&gt;&lt;br /&gt;CREATE FUNCTION CheckActiveCount(&lt;br /&gt;  @Id INT&lt;br /&gt;) RETURNS INT AS BEGIN&lt;br /&gt;  &lt;br /&gt;  DECLARE @ret INT;&lt;br /&gt;  SELECT @ret = COUNT(*) FROM CheckConstraint WHERE Id = @Id AND RecordStatus = 1;&lt;br /&gt;  RETURN @ret;&lt;br /&gt;&lt;br /&gt;END;&lt;br /&gt;GO&lt;br /&gt;&lt;br /&gt;ALTER TABLE CheckConstraint&lt;br /&gt;  ADD CONSTRAINT CheckActiveCountConstraint CHECK (dbo.CheckActiveCount(Id) &lt;= 1 OR RecordStatus &lt;&gt; 1);&lt;br /&gt;&lt;br /&gt;INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);&lt;br /&gt;INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);&lt;br /&gt;INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);&lt;br /&gt;INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);&lt;br /&gt;&lt;br /&gt;INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);&lt;br /&gt;INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);&lt;br /&gt;INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);&lt;br /&gt;-- Msg 547, Level 16, State 0, Line 14&lt;br /&gt;-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".&lt;br /&gt;INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);&lt;br /&gt;&lt;br /&gt;SELECT * FROM CheckConstraint;&lt;br /&gt;-- Id   Name         RecordStatus&lt;br /&gt;-- ---- ------------ ------------&lt;br /&gt;-- 1    No Problems  2&lt;br /&gt;-- 1    No Problems  2&lt;br /&gt;-- 1    No Problems  2&lt;br /&gt;-- 1    No Problems  1&lt;br /&gt;-- 2    Oh no!       1&lt;br /&gt;-- 2    Oh no!       2&lt;br /&gt;&lt;br /&gt;ALTER TABLE CheckConstraint&lt;br /&gt;  DROP CONSTRAINT CheckActiveCountConstraint;&lt;br /&gt;&lt;br /&gt;DROP FUNCTION CheckActiveCount;&lt;br /&gt;DROP TABLE CheckConstraint;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-1470206562609409401?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/1470206562609409401/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/conditional-check-constraints-on-sql.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1470206562609409401'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1470206562609409401'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/conditional-check-constraints-on-sql.html' title='Conditional Check Constraints on SQL Server'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/ShHTVVPE6rI/AAAAAAAAAzw/bvoKWXdGf9s/s72-c/sqlserver.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-7225660931135119062</id><published>2009-05-12T08:23:00.000-07:00</published><updated>2009-05-12T08:41:19.993-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Extension Method to Imitate Upto() in Ruby</title><content type='html'>&lt;a href="http://www.ruby-lang.org/"&gt;&lt;img src="http://www.ruby-lang.org/images/logo.gif" alt="Ruby" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Last month, I wrote a blog post about &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/extension-method-to-imitate-times-in.html"&gt;using an extension method to imitate the ruby method times()&lt;/a&gt;.  I recently found another cool method in ruby that I wanted to avail to the C# public.&lt;br /&gt;&lt;br /&gt;Now, before I show you the implementation, I'll just say that it doesn't do anything the for loop wouldn't do on its own.  I think the method has two benefits.  One, I think it looks clean because it's basically a fluent way to create a for loop.  Two, I like the fact that you can pass an action to the method.  &lt;br /&gt;&lt;br /&gt;In Ruby, the implementation looks like this:&lt;pre name="code" class="ruby"&gt;5.upto(10){ |i| puts i; }&lt;br /&gt;&lt;br /&gt;# output&lt;br /&gt;# 5&lt;br /&gt;# 6&lt;br /&gt;# 7&lt;br /&gt;# 8&lt;br /&gt;# 9&lt;br /&gt;# 10&lt;/pre&gt;I added the following extension method to one of my utility extension method libraries:&lt;pre name="code" class="c#"&gt;public static void UpTo(this int start, int upto, Action&amp;lt;int&amp;gt; action)&lt;br /&gt;{&lt;br /&gt;    for (int i = start; i &amp;lt;= upto; i++) action(i);&lt;br /&gt;}&lt;/pre&gt;Here are the test cases and the results:&lt;pre name="code" class="c#"&gt;[TestCase(4, 12)]&lt;br /&gt;[TestCase(-10, 10)]&lt;br /&gt;[TestCase(10, 5)]&lt;br /&gt;public void test_upto_iterator(int from, int upto)&lt;br /&gt;{&lt;br /&gt;    int test = 0;&lt;br /&gt;    from.UpTo(upto, num =&gt;&lt;br /&gt;                        {&lt;br /&gt;                            Console.WriteLine("Index: {0}", num);&lt;br /&gt;                            test += num;&lt;br /&gt;                        }&lt;br /&gt;        );&lt;br /&gt;&lt;br /&gt;    int expected = 0;&lt;br /&gt;    for (int i = from; i &lt;= upto; i++)&lt;br /&gt;        expected += i;&lt;br /&gt;&lt;br /&gt;    Assert.AreEqual(expected, test);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// ***** ESG.Utilities.System.Tests.IntegerExtensionTests.test_upto_iterator(-3,2)&lt;br /&gt;// Index: -3&lt;br /&gt;// Index: -2&lt;br /&gt;// Index: -1&lt;br /&gt;// Index: 0&lt;br /&gt;// Index: 1&lt;br /&gt;// Index: 2&lt;br /&gt;&lt;br /&gt;// ***** ESG.Utilities.System.Tests.IntegerExtensionTests.test_upto_iterator(4,8)&lt;br /&gt;// Index: 4&lt;br /&gt;// Index: 5&lt;br /&gt;// Index: 6&lt;br /&gt;// Index: 7&lt;br /&gt;// Index: 8&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-7225660931135119062?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/7225660931135119062/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/extension-method-to-imitate-upto-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7225660931135119062'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/7225660931135119062'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/extension-method-to-imitate-upto-in.html' title='Extension Method to Imitate Upto() in Ruby'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3659436068788544071</id><published>2009-05-05T08:39:00.001-07:00</published><updated>2009-05-05T09:16:47.411-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Stored Procedure to Unpivot a Table</title><content type='html'>&lt;a href="http://en.wikipedia.org/wiki/Lever"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/SgBl2SQdLDI/AAAAAAAAAu4/EhjWyOASHFQ/s288/pivot.jpg" alt="Pivot" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;When I first started blogging, I wrote a post about this 3 liner (well, technically) stored procedure to unpivot data.  Well, I was new to blogging and I didn't really have all of the great tools that I have now, so it was a pretty crappy post.&lt;br /&gt;&lt;br /&gt;Well, I was just flipping through my archives looking at the lack of &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/SQL"&gt;SQL Server posts&lt;/a&gt; that I have and thought, "I really should rewrite that one and use a good highlighter and post some sample usage."&lt;br /&gt;&lt;br /&gt;So, I did, but before I get to the stored procedure, why don't I explain a little bit about what unpivoting is?  Sometimes you don't know what kind of data you're going to be collecting so you can't really create a third normal schema to store these data.  Instead, you use something called an &lt;a href="http://en.wikipedia.org/wiki/Attribute-value_pair"&gt;Attribute Value Pair&lt;/a&gt; table (or AVP for short).&lt;br /&gt;&lt;br /&gt;This table ends up being tall instead of wide, but you can store any data you please in there.  Problem is, how do you get to a full and meaningful record?  Well, the process to take tall data and make it wide is called &lt;a href="http://en.wikipedia.org/wiki/Pivot_table"&gt;pivoting&lt;/a&gt;.  If, however, you have wide tables and you need to seed this AVP table, that process is called unpivoting.&lt;br /&gt;&lt;br /&gt;I had to do this several times and I didn't really care to write a view every time I had to unpivot, so I wrote a sproc instead.  Here's that stored procedure and it should work on all SQL Server 2008 and SQL Server 2005:&lt;pre name="code" class="sql"&gt;CREATE PROCEDURE UnpivotTable&lt;br /&gt;(&lt;br /&gt;  @tableName AS VARCHAR(512),&lt;br /&gt;  @whereClause AS VARCHAR(2000) = NULL,&lt;br /&gt;  @commonColumns AS VARCHAR(2000) = NULL&lt;br /&gt;) AS&lt;br /&gt;-- ==========================================================&lt;br /&gt;-- Author:        Patrick Caldwell&lt;br /&gt;-- Create date:   2007/12/19&lt;br /&gt;-- Description:   this procedure unpivots data in a table&lt;br /&gt;--                specified in the parameters with an optional&lt;br /&gt;--                where clause.  every line has been commented.&lt;br /&gt;--                the commonColumns variable stores a list of&lt;br /&gt;--                columns that should be on every row.&lt;br /&gt;-- ==========================================================&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-- a variable in which to store the query&lt;br /&gt;DECLARE @query VARCHAR(MAX);&lt;br /&gt;&lt;br /&gt;-- build the query&lt;br /&gt;SELECT            @query = ISNULL(@query + ' ', '') + QueryLine&lt;br /&gt;FROM&lt;br /&gt;(&lt;br /&gt;    SELECT        QueryLine = 'SELECT ' +&lt;br /&gt;                    ISNULL(@commonColumns + ', ', '') +&lt;br /&gt;                    'ColumnName = ''' + COLUMN_NAME + ''', ' +&lt;br /&gt;                    'ColumnValue = CAST([' + COLUMN_NAME + '] AS NVARCHAR(4000)) ' +&lt;br /&gt;                    'FROM ' + @tableName +&lt;br /&gt;                    ISNULL(' WHERE ' + @whereClause, '') +&lt;br /&gt;                    ' UNION ALL'&lt;br /&gt;    FROM          INFORMATION_SCHEMA.COLUMNS&lt;br /&gt;    WHERE         TABLE_SCHEMA + '.' + TABLE_NAME = @tableName&lt;br /&gt;&lt;br /&gt;    UNION ALL&lt;br /&gt;&lt;br /&gt;    SELECT        'SELECT ' +&lt;br /&gt;                    ISNULL(@commonColumns + ', ', '') +&lt;br /&gt;                    'NULL, NULL ' +&lt;br /&gt;                    'FROM ' + @tableName + ' ' +&lt;br /&gt;                    'WHERE 1 = 0'&lt;br /&gt;) AS lines;&lt;br /&gt;&lt;br /&gt;-- execute the query&lt;br /&gt;EXEC(@query);&lt;/pre&gt;For the sake of example, I'm going to need a test table.  Here's the one I'm using:&lt;pre name="code" class="sql"&gt;CREATE TABLE Pals&lt;br /&gt;(&lt;br /&gt;  Id TINYINT IDENTITY(1, 1), -- so what if I only have 255 pals?&lt;br /&gt;  FirstName VARCHAR(128),&lt;br /&gt;  LastName VARCHAR(128),&lt;br /&gt;  Gender CHAR(1)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;INSERT INTO Pals VALUES ('Lauren', 'Woody', 'F'); -- no, really . . . F&lt;br /&gt;INSERT INTO Pals VALUES ('James', 'Brechtel', '?');&lt;br /&gt;INSERT INTO Pals VALUES ('Ryan', 'McGarty', 'M');&lt;/pre&gt;And finally, here are some example usages:&lt;pre name="code" class="sql"&gt;EXEC UnpivotTable @tableName = 'dbo.pals'&lt;br /&gt;/*&lt;br /&gt;ColumnName ColumnValue&lt;br /&gt;---------- -----------&lt;br /&gt;Id         1&lt;br /&gt;Id         2&lt;br /&gt;Id         3&lt;br /&gt;FirstName  Lauren&lt;br /&gt;FirstName  James&lt;br /&gt;FirstName  Ryan&lt;br /&gt;LastName   Woody&lt;br /&gt;LastName   Brechtel&lt;br /&gt;LastName   McGarty&lt;br /&gt;Gender     F&lt;br /&gt;Gender     ?&lt;br /&gt;Gender     M&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;EXEC UnpivotTable @tableName = 'dbo.pals', @commonColumns = 'Id'&lt;br /&gt;/*&lt;br /&gt;Id   ColumnName ColumnValue&lt;br /&gt;---- ---------- -----------&lt;br /&gt;1    Id         1&lt;br /&gt;2    Id         2&lt;br /&gt;3    Id         3&lt;br /&gt;1    FirstName  Lauren&lt;br /&gt;2    FirstName  James&lt;br /&gt;3    FirstName  Ryan&lt;br /&gt;1    LastName   Woody&lt;br /&gt;2    LastName   Brechtel&lt;br /&gt;3    LastName   McGarty&lt;br /&gt;1    Gender     F&lt;br /&gt;2    Gender     ?&lt;br /&gt;3    Gender     M&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;EXEC UnpivotTable @tableName = 'dbo.pals', &lt;br /&gt;                  @commonColumns = 'Id', &lt;br /&gt;                  @whereClause = 'Gender = ''F'''&lt;br /&gt;/*&lt;br /&gt;Id   ColumnName ColumnValue&lt;br /&gt;---- ---------- -----------&lt;br /&gt;1    Id         1&lt;br /&gt;1    FirstName  Lauren&lt;br /&gt;1    LastName   Woody&lt;br /&gt;1    Gender     F&lt;br /&gt;*/&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3659436068788544071?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3659436068788544071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/stored-procedure-to-unpivot-table.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3659436068788544071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3659436068788544071'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/stored-procedure-to-unpivot-table.html' title='Stored Procedure to Unpivot a Table'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/SgBl2SQdLDI/AAAAAAAAAu4/EhjWyOASHFQ/s72-c/pivot.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3892587501479106607</id><published>2009-05-05T08:10:00.000-07:00</published><updated>2009-05-05T09:15:15.414-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Converting Hexadecimal or Binary to Decimal on SQL Server 2008 and 2005</title><content type='html'>&lt;a href="http://t-shirts.cafepress.com/item/10-types-of-people-long-sleeve-tshirt/41005248"&gt;&lt;img src="http://lh6.ggpht.com/_cFBwGCiU3y4/SgBaPd5oBVI/AAAAAAAAAuw/xnT-oLPJPqk/s288/binary_shirt.jpg" alt="Binary Shirt" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I just finished a blog post about &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/05/converting-decimal-to-hexadecimal-with.html"&gt;Converting Decimal to Hexadecimal with T-SQL on SQL Server 2008 and 2005&lt;/a&gt; and it got me thinking, "What if you have a &lt;a href="http://en.wikipedia.org/wiki/Hexadecimal"&gt;hexadecimal&lt;/a&gt; string or a &lt;a href="http://en.wikipedia.org/wiki/Binary_numeral_system"&gt;binary string&lt;/a&gt; and you want to convert it back to decimal?"&lt;br /&gt;&lt;br /&gt;Well, in my previous article, I provided a &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/SQL"&gt;SQL&lt;/a&gt; custom function that would allow you to convert from &lt;a href="http://en.wikipedia.org/wiki/Decimal"&gt;base 10 (decimal)&lt;/a&gt; to any other &lt;a href="http://en.wikipedia.org/wiki/Radix"&gt;base&lt;/a&gt; you wanted.  That way, you wouldn't need a Dec2Bin and a Dec2Hex function.  Similarly, I don't think you should have both a Bin2Dec and a Hex2Dec to convert from binary or hexadecimal to decimal.&lt;br /&gt;&lt;br /&gt;So, I wrote a custom function that converts from any base to base 10 and it looks a little like this:&lt;pre name="code" class="sql"&gt;CREATE FUNCTION ConvertFromBase&lt;br /&gt;(&lt;br /&gt;    @value AS VARCHAR(MAX),&lt;br /&gt;    @base AS BIGINT&lt;br /&gt;) RETURNS BIGINT AS BEGIN&lt;br /&gt;&lt;br /&gt;    -- just some variables&lt;br /&gt;    DECLARE @characters CHAR(36),&lt;br /&gt;            @result BIGINT,&lt;br /&gt;            @index SMALLINT;&lt;br /&gt;&lt;br /&gt;    -- initialize our charater set, our result, and the index&lt;br /&gt;    SELECT @characters = '0123456789abcdefghijklmnopqrstuvwxyz',&lt;br /&gt;           @result = 0,&lt;br /&gt;           @index = 0;&lt;br /&gt;&lt;br /&gt;    -- make sure we can make the base conversion.  there can't&lt;br /&gt;    -- be a base 1, but you could support greater than base 36&lt;br /&gt;    -- if you add characters to the @charater string&lt;br /&gt;    IF @base &amp;lt; 2 OR @base &amp;gt; 36 RETURN NULL;&lt;br /&gt;&lt;br /&gt;    -- while we have characters to convert, convert them and&lt;br /&gt;    -- prepend them to the result.  we start on the far right&lt;br /&gt;    -- and move to the left until we run out of digits.  the&lt;br /&gt;    -- conversion is the standard (base ^ index) * digit&lt;br /&gt; WHILE @index &amp;lt; LEN(@value)&lt;br /&gt;        SELECT @result = @result + POWER(@base, @index) * &lt;br /&gt;                         (CHARINDEX&lt;br /&gt;                            (SUBSTRING(@value, LEN(@value) - @index, 1)&lt;br /&gt;                            , @characters) - 1&lt;br /&gt;                         ),&lt;br /&gt;               @index = @index + 1;&lt;br /&gt;&lt;br /&gt;    -- return the result&lt;br /&gt;    RETURN @result;&lt;br /&gt;&lt;br /&gt;END&lt;/pre&gt;Here's a test query:&lt;pre name="code" class="sql"&gt;SELECT&lt;br /&gt;   dbo.ConvertFromBase('110010110', 2) -- 406&lt;br /&gt;  ,dbo.ConvertFromBase('120001', 3)    -- 406&lt;br /&gt;  ,dbo.ConvertFromBase('626', 8)       -- 406&lt;br /&gt;  ,dbo.ConvertFromBase('406', 10)      -- 406&lt;br /&gt;  ,dbo.ConvertFromBase('196', 16)      -- 406&lt;br /&gt;  ,dbo.ConvertFromBase('ba', 36);      -- 406&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3892587501479106607?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3892587501479106607/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/converting-hexadecimal-or-binary-to.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3892587501479106607'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3892587501479106607'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/converting-hexadecimal-or-binary-to.html' title='Converting Hexadecimal or Binary to Decimal on SQL Server 2008 and 2005'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_cFBwGCiU3y4/SgBaPd5oBVI/AAAAAAAAAuw/xnT-oLPJPqk/s72-c/binary_shirt.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-9084086088571273260</id><published>2009-05-05T07:33:00.001-07:00</published><updated>2009-05-05T09:15:34.470-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Converting Decimal to Hexadecimal with T-SQL on SQL Server 2008 and 2005</title><content type='html'>&lt;a href="http://t-shirts.cafepress.com/item/hexadecimal-green-tshirt/191063292"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/SgBWSOCGvHI/AAAAAAAAAuo/BuEXOa9gQ6U/s288/hex_shirt.jpg" alt="Hexadecimal Shirt" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Yesterday, I was doing a little maintenance on our internal &lt;a href="http://ifdefined.com/bugtrackernet.html"&gt;bugtracker.net&lt;/a&gt; installation.  Bug tracker uses the first column of a result set to determine the background color of the issue.  I wanted to create a nice fade effect between green and red, but that requires that I convert a &lt;a href="http://en.wikipedia.org/wiki/Decimal"&gt;decimal&lt;/a&gt; number to &lt;a href="http://en.wikipedia.org/wiki/Hexadecimal"&gt;hexadecimal&lt;/a&gt; in &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/SQL"&gt;SQL Server&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I didn't really want to write a Dec2Hex conversion function for SQL Server because I could very well end up needing a Dec2Bin function as well (if I ever wanted to convert decimal to &lt;a href="http://en.wikipedia.org/wiki/Binary_numeral_system"&gt;binary&lt;/a&gt; in SQL), so I just wrote a custom function to convert a decimal number to any &lt;a href="http://en.wikipedia.org/wiki/Radix"&gt;base&lt;/a&gt; between 2 and 36.  The same SQL custom function can convert decimal numbers to hexidecimal or decimal to binary (if you need to go the other way, look here to &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/05/converting-hexadecimal-or-binary-to.html"&gt;convert any base to decimal in SQL&lt;/a&gt;).  Here's what that looks like:&lt;pre name="code" class="sql"&gt;CREATE FUNCTION ConvertToBase&lt;br /&gt;(&lt;br /&gt;    @value AS BIGINT,&lt;br /&gt;    @base AS INT&lt;br /&gt;) RETURNS VARCHAR(MAX) AS BEGIN&lt;br /&gt;&lt;br /&gt;    -- some variables&lt;br /&gt;    DECLARE @characters CHAR(36),&lt;br /&gt;            @result VARCHAR(MAX);&lt;br /&gt;&lt;br /&gt;    -- the encoding string and the default result&lt;br /&gt;    SELECT @characters = '0123456789abcdefghijklmnopqrstuvwxyz',&lt;br /&gt;           @result = '';&lt;br /&gt;&lt;br /&gt;    -- make sure it's something we can encode.  you can't have&lt;br /&gt;    -- base 1, but if we extended the length of our @character&lt;br /&gt;    -- string, we could have greater than base 36&lt;br /&gt;    IF @value &amp;lt; 0 OR @base &amp;lt; 2 OR @base &amp;gt; 36 RETURN NULL;&lt;br /&gt;&lt;br /&gt;    -- until the value is completely converted, get the modulus&lt;br /&gt;    -- of the value and prepend it to the result string.  then&lt;br /&gt;    -- devide the value by the base and truncate the remainder&lt;br /&gt;    WHILE @value &gt; 0&lt;br /&gt;        SELECT @result = SUBSTRING(@characters, @value % @base + 1, 1) + @result,&lt;br /&gt;               @value = @value / @base;&lt;br /&gt;&lt;br /&gt;    -- return our results&lt;br /&gt;    RETURN @result;&lt;br /&gt;&lt;br /&gt;END&lt;/pre&gt;Here is a test query:&lt;pre name="code" class="SQL"&gt;SELECT&lt;br /&gt;   dbo.ConvertToBase(406, 2)   -- 110010110&lt;br /&gt;  ,dbo.ConvertToBase(406, 3)   -- 120001&lt;br /&gt;  ,dbo.ConvertToBase(406, 8)   -- 626&lt;br /&gt;  ,dbo.ConvertToBase(406, 10)  -- 406&lt;br /&gt;  ,dbo.ConvertToBase(406, 16)  -- 196&lt;br /&gt;  ,dbo.ConvertToBase(406, 36); -- ba&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-9084086088571273260?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/9084086088571273260/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/converting-decimal-to-hexadecimal-with.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/9084086088571273260'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/9084086088571273260'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/05/converting-decimal-to-hexadecimal-with.html' title='Converting Decimal to Hexadecimal with T-SQL on SQL Server 2008 and 2005'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/SgBWSOCGvHI/AAAAAAAAAuo/BuEXOa9gQ6U/s72-c/hex_shirt.jpg' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-4773670172222161193</id><published>2009-04-30T10:59:00.001-07:00</published><updated>2009-04-30T11:32:07.064-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Extension Method to Imitate Times() in Ruby</title><content type='html'>&lt;a href="http://www.ruby-lang.org/"&gt;&lt;img src="http://www.ruby-lang.org/images/logo.gif" alt="Ruby" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;My friend and co-worker &lt;a href="http://blog.brechtel.us/"&gt;James&lt;/a&gt; is gettin' pretty darned good with &lt;a href="http://www.ruby-lang.org/"&gt;Ruby&lt;/a&gt;.  My experience with Ruby is severely limited.  So far, the only thing I've done with it was to implement a "business rules engine" to host a &lt;a href="http://en.wikipedia.org/wiki/Domain-specific_programming_language"&gt;DSL&lt;/a&gt; in Iron Ruby.&lt;br /&gt;&lt;br /&gt;Today, he showed me a pretty cool method that's built into Ruby called &lt;a href="http://www.ruby-doc.org/core/classes/Integer.html#M001160"&gt;int.times()&lt;/a&gt; and I was pretty much instantly jealousified!  I decided I had to have one . . . and now I do:&lt;pre name="code" class="c#"&gt;public static void Times(this int times, Action action)&lt;br /&gt;{&lt;br /&gt;    for (int i = 0; i &amp;lt; times; i++) action();&lt;br /&gt;}&lt;/pre&gt;I through together a few test cases to try it out.  I had to make a few determinations though.  If you call .Times on a negative number, it loops indefinitely (seemingly).  I decided that you should never want to say "do something infinity times."  If you do want to do that, you should just have your action call itself at the end of its execution.  So, here's the test and the output:&lt;pre name="code" class="c#"&gt;[TestCase(4)]&lt;br /&gt;[TestCase(-6)]&lt;br /&gt;public void test_times_iterator(int times)&lt;br /&gt;{&lt;br /&gt;    int i = 0;&lt;br /&gt;    times.Times(() =&amp;gt;&lt;br /&gt;    {&lt;br /&gt;        Console.WriteLine("Index: {0}", i);&lt;br /&gt;        i++;&lt;br /&gt;    });&lt;br /&gt;&lt;br /&gt;    Assert.AreEqual(Math.Max(times, 0), i);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// Index: 0&lt;br /&gt;// Index: 1&lt;br /&gt;// Index: 2&lt;br /&gt;// Index: 3&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-4773670172222161193?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/4773670172222161193/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/extension-method-to-imitate-times-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4773670172222161193'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/4773670172222161193'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/extension-method-to-imitate-times-in.html' title='Extension Method to Imitate Times() in Ruby'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-8950869170240888332</id><published>2009-04-29T13:10:00.000-07:00</published><updated>2009-04-30T11:31:43.896-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='IComparer'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>IComparer Extension Methods for Fluent Interface</title><content type='html'>&lt;a href="http://picasaweb.google.com/tncbbthositg/BlogPictures#5330147152093398690"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/Sfh8mDEWRqI/AAAAAAAAAtA/_lu0ZJyGtOg/s288/IComparer.jpg" alt="IComparer" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I've pretty much been a blogging fiend today!  I love it when I find a topic like &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/IComparer"&gt;IComparer&lt;/a&gt; or &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;Extension Methods&lt;/a&gt; that are entertaining enough to write about.&lt;br /&gt;&lt;br /&gt;Today alone, I wrote articles about &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/demystifying-icomparer-in-c.html"&gt;how IComparer works&lt;/a&gt;, &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/icomparer-proxy-to-sort-by-multiple.html"&gt;using the proxy pattern to sort by multiple IComparers&lt;/a&gt;, and &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/converting-comparison-to-icomparer.html"&gt;converting a Comparison delegate to an IComparer&lt;/a&gt;.  Bragging aside though, I've found a fun way to combine my current favorite topics: IComparer and Extension Methods.&lt;br /&gt;&lt;br /&gt;I wrote 3 extension methods to help me do some pretty neat things with IComparers and Comparisons.  You'll see references to two of my prior articles in this post.  The &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/icomparer-proxy-to-sort-by-multiple.html"&gt;ComparerProxy sorts by multiple comparers&lt;/a&gt; and the &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/converting-comparison-to-icomparer.html"&gt;ComparisonComparer converts a Comparison to an IComparer&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I'll show you the extension methods first, then I'll explain the reasoning behind them, and then I'll show you some usage.&lt;pre name="code" class="c#"&gt;public static class IComparerExtensions&lt;br /&gt;{&lt;br /&gt;    public static IComparer&amp;lt;T&amp;gt; Then&amp;lt;T&amp;gt;(this IComparer&amp;lt;T&amp;gt; priority, IComparer&amp;lt;T&amp;gt; then)&lt;br /&gt;    {&lt;br /&gt;        return new ComparerProxy&amp;lt;T&amp;gt;(priority, then);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static IComparer&amp;lt;T&amp;gt; Invert&amp;lt;T&amp;gt;(this IComparer&amp;lt;T&amp;gt; comparer)&lt;br /&gt;    {&lt;br /&gt;        return new ComparisonComparer&amp;lt;T&amp;gt;((x, y) =&amp;gt; comparer.Compare(x, y) * -1);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public static IComparer&amp;lt;T&amp;gt; ToComparer&amp;lt;T&amp;gt;(this Comparison&amp;lt;T&amp;gt; comparison)&lt;br /&gt;    {&lt;br /&gt;        return new ComparisonComparer&amp;lt;T&amp;gt;(comparison);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;So, lately I've been pretty big on fluent interfaces.  I haven't written one yet, but I do use extension methods to make my implementations look a little more fluent.  That's why, when I wrote the ComparerProxy, I thought, "Hey, wouldn't be cool if I could just take an IComparer and tack another comparer onto it? And then another? And another?"  That's why I wrote the IComparer.Then() extension method.&lt;br /&gt;&lt;br /&gt;Next, I realized that if you have a Comparison delegate, it'd be nice to be able to add that to a list of IComparers so I added the ToComparer method.  &lt;br /&gt;&lt;br /&gt;Finally, another thing that I often want to do is write an IComparer once but to be able to use it in reverse.  Id est, I want to invert the logic.  Obviously, I could List.Sort() and List.Reverse(), but that's much less efficient than having a CaseInsensitiveComparer and a ReverseCaseInsensitiveComparer.  &lt;br /&gt;&lt;br /&gt;The problem is, I don't really want to have to write both of those comparers.  Instead, now I can call IComparer.Invert() and I get an IComparer that performs exactly like the original, but with inverted logic.&lt;br /&gt;&lt;br /&gt;So, without further ado, here are some usage examples.  I'm using the same test setup as I did with the &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/icomparer-proxy-to-sort-by-multiple.html"&gt;ComparerProxy&lt;/a&gt;.  I've also created several more IComparers to try out:&lt;pre name="code" class="c#"&gt;Comparison&amp;lt;Person&amp;gt; length = (x, y) =&amp;gt; x.ToString().Length - y.ToString().Length;&lt;br /&gt;IComparer&amp;lt;Person&amp;gt; lengthLast = new ComparerProxy&amp;lt;Person&amp;gt;(length.ToComparer(), last);&lt;br /&gt;&lt;br /&gt;IComparer&amp;lt;Person&amp;gt; lastFirstComposed = last.Then(first);&lt;/pre&gt;I executed the following lines which produced the commented results:&lt;pre name="code" class="c#"&gt;_folks.Sort(length);&lt;br /&gt;PrintList(_folks);&lt;br /&gt;&lt;br /&gt;// Woody, Pete&lt;br /&gt;// Woody, Lauren&lt;br /&gt;// Caldwell, Cory&lt;br /&gt;// Caldwell, Colin&lt;br /&gt;// Brechtel, Jamus&lt;br /&gt;// Woody, Royce Ann&lt;br /&gt;// Brechtel, Ashley&lt;br /&gt;// Caldwell, Jennifer&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;_folks.Sort(lengthLast);&lt;br /&gt;PrintList(_folks);&lt;br /&gt;&lt;br /&gt;// Woody, Pete&lt;br /&gt;// Woody, Lauren&lt;br /&gt;// Caldwell, Cory&lt;br /&gt;// Brechtel, Jamus&lt;br /&gt;// Caldwell, Colin&lt;br /&gt;// Brechtel, Ashley&lt;br /&gt;// Woody, Royce Ann&lt;br /&gt;// Caldwell, Jennifer&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;_folks.Sort(lastFirstComposed);&lt;br /&gt;PrintList(_folks);&lt;br /&gt;&lt;br /&gt;// Brechtel, Ashley&lt;br /&gt;// Brechtel, Jamus&lt;br /&gt;// Caldwell, Colin&lt;br /&gt;// Caldwell, Cory&lt;br /&gt;// Caldwell, Jennifer&lt;br /&gt;// Woody, Lauren&lt;br /&gt;// Woody, Pete&lt;br /&gt;// Woody, Royce Ann&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;_folks.Sort(last.Invert().Then(first));&lt;br /&gt;PrintList(_folks);&lt;br /&gt;&lt;br /&gt;// Woody, Lauren&lt;br /&gt;// Woody, Pete&lt;br /&gt;// Woody, Royce Ann&lt;br /&gt;// Caldwell, Colin&lt;br /&gt;// Caldwell, Cory&lt;br /&gt;// Caldwell, Jennifer&lt;br /&gt;// Brechtel, Ashley&lt;br /&gt;// Brechtel, Jamus&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-8950869170240888332?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/8950869170240888332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/icomparer-extension-methods-for-fluent.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8950869170240888332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8950869170240888332'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/icomparer-extension-methods-for-fluent.html' title='IComparer Extension Methods for Fluent Interface'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/Sfh8mDEWRqI/AAAAAAAAAtA/_lu0ZJyGtOg/s72-c/IComparer.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6777382116746711061</id><published>2009-04-29T11:25:00.000-07:00</published><updated>2009-04-30T11:27:04.593-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='IComparer'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Converting Comparison&lt;T&gt; to IComparer&lt;T&gt;</title><content type='html'>&lt;a href="http://picasaweb.google.com/tncbbthositg/BlogPictures#5330147152093398690"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/Sfh8mDEWRqI/AAAAAAAAAtA/_lu0ZJyGtOg/s288/IComparer.jpg" alt="IComparer" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;Today, I was working with IComparers (mostly for my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/demystifying-icomparer-in-c.html"&gt;Demystifying IComparers&lt;/a&gt; blog post).  I realized that there's a lot of use for IComparers when working with C# and &lt;a href="http://msdn.microsoft.com/en-us/netframework/aa904594.aspx"&gt;Linq&lt;/a&gt;.  I decided that I should dedicate an entire &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/IComparer"&gt;IComparer section&lt;/a&gt; to C# Icomparers.&lt;br /&gt;&lt;br /&gt;I was working on fodder for several articles when I came across a little annoyance.  There's not a Comparer class that takes a Comparison delegate as a constructor argument.  Thus, I can call List.Sort() and pass a Comparison delegate or a lambda expression but I cannot create an IComparer that executes that delegate or lambda.&lt;br /&gt;&lt;br /&gt;Dissatisfied with this state of affairs, I throw together this little class:&lt;pre name="code" class="c#"&gt;public class ComparisonComparer&amp;lt;T&amp;gt; : IComparer&amp;lt;T&amp;gt;&lt;br /&gt;{&lt;br /&gt;    private readonly Comparison&amp;lt;T&amp;gt; _comparison;&lt;br /&gt;&lt;br /&gt;    public ComparisonComparer(Comparison&amp;lt;T&amp;gt; comparison)&lt;br /&gt;    {&lt;br /&gt;        _comparison = comparison;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public int Compare(T x, T y)&lt;br /&gt;    {&lt;br /&gt;        return _comparison(x, y);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6777382116746711061?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6777382116746711061/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/converting-comparison-to-icomparer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6777382116746711061'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6777382116746711061'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/converting-comparison-to-icomparer.html' title='Converting Comparison&amp;lt;T&amp;gt; to IComparer&amp;lt;T&amp;gt;'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/Sfh8mDEWRqI/AAAAAAAAAtA/_lu0ZJyGtOg/s72-c/IComparer.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2937380275917683101</id><published>2009-04-29T11:04:00.000-07:00</published><updated>2009-04-30T11:28:22.376-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='IComparer'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>IComparer Proxy to Sort by Multiple Conditions</title><content type='html'>&lt;a href="http://picasaweb.google.com/tncbbthositg/BlogPictures#5330147152093398690"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/Sfh8mDEWRqI/AAAAAAAAAtA/_lu0ZJyGtOg/s288/IComparer.jpg" alt="IComparer" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I thought of another cool use for implementing IComparer.  &lt;br /&gt;&lt;br /&gt;If you've ever wanted to sort a list of objects using more than one comparer (i.e., you want to sort a list of people by last name and then by first name), then you probably had to compose your own IComparer object to do so.  If you wanted to be able to dynamically change sort orders, then your task was more difficult.&lt;br /&gt;&lt;br /&gt;To address this problem, I wrote a class called ComparerProxy which implements IComparer and proxies comparisons through a list of IComparers.  First, I'll show you the class and then I'll explain a few nuances.&lt;pre name="code" class="c#"&gt;public class ComparerProxy&amp;lt;T&amp;gt; : IComparer&amp;lt;T&amp;gt;&lt;br /&gt;{&lt;br /&gt;    private readonly IComparer&amp;lt;T&amp;gt;[] _comparers;&lt;br /&gt;    &lt;br /&gt;    public ComparerProxy(params IComparer&amp;lt;T&amp;gt;[] comparers)&lt;br /&gt;    {&lt;br /&gt;        _comparers = comparers;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public int Compare(T x, T y)&lt;br /&gt;    {&lt;br /&gt;        int retVal = 0, i = 0;&lt;br /&gt;&lt;br /&gt;        while (retVal == 0 &amp;amp;&amp;amp; i &amp;lt; _comparers.Length)&lt;br /&gt;            retVal = _comparers[i++].Compare(x, y);&lt;br /&gt;&lt;br /&gt;        return retVal;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;So, what's happening here is that when ComparerProxy.Compare() is called, it loops through the collection of comparers until it either runs out of comparers and deems the two objects equal or it identifies a difference and returns the value of that difference.&lt;br /&gt;&lt;br /&gt;That sounds confusing even to me and I wrote the class so I'll break it down.  Basically, imagine you have a Person object (and soon we will).  The person has a FirstName property and a LastName property.  If you want to sort by LastName and then by FirstName, it would go something like this:&lt;ol&gt;&lt;li&gt;Sort the two objects by LastName&lt;/li&gt;&lt;li&gt;If the two objects have the same LastName, then sort by FirstName&lt;/li&gt;&lt;li&gt;If the two objects have  the same FirstName, then the two objects are equal&lt;/li&gt;&lt;/ol&gt;  If that doesn't make sense, please comment and I'll elaborate.&lt;br /&gt;&lt;br /&gt;So, shall we see what it does?  I created a Person class (very similar do the one described above) just to do some testing.  I overrode the ToString method to print "Last, First" and I added two IComparers.  Here's what that class looks like:&lt;pre name="code" class="c#"&gt;private class Person&lt;br /&gt;{&lt;br /&gt;    public string First, Last;&lt;br /&gt;&lt;br /&gt;    public class FirstNameComparer : IComparer&amp;lt;Person&amp;gt;&lt;br /&gt;    {&lt;br /&gt;        public int Compare(Person x, Person y)&lt;br /&gt;        {&lt;br /&gt;            return x.First.CompareTo(y.First);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public class LastNameComparer : IComparer&amp;lt;Person&amp;gt;&lt;br /&gt;    {&lt;br /&gt;        public int Compare(Person x, Person y)&lt;br /&gt;        {&lt;br /&gt;            return x.Last.CompareTo(y.Last);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public override string ToString()&lt;br /&gt;    {&lt;br /&gt;        return string.Format("{0}, {1}", Last, First);&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;I created a test list of Persons and a set of IComparers to test with including two complex IComparers using the ComparerProxy:&lt;pre name="code" class="c#"&gt;List&amp;lt;Person&amp;gt; _folks = new List&amp;lt;Person&amp;gt;();&lt;br /&gt;_folks.Add(new Person { First = "Jennifer", Last = "Caldwell" });&lt;br /&gt;_folks.Add(new Person { First = "Cory", Last = "Caldwell" });&lt;br /&gt;_folks.Add(new Person { First = "Lauren", Last = "Woody" });&lt;br /&gt;_folks.Add(new Person { First = "Colin", Last = "Caldwell" });&lt;br /&gt;_folks.Add(new Person { First = "Jamus", Last = "Brechtel" });&lt;br /&gt;_folks.Add(new Person { First = "Pete", Last = "Woody" });&lt;br /&gt;_folks.Add(new Person { First = "Royce Ann", Last = "Woody" });&lt;br /&gt;_folks.Add(new Person { First = "Ashley", Last = "Brechtel" });&lt;br /&gt;&lt;br /&gt;IComparer&amp;lt;Person&amp;gt; first = new Person.FirstNameComparer();&lt;br /&gt;IComparer&amp;lt;Person&amp;gt; last = new Person.LastNameComparer();&lt;br /&gt;&lt;br /&gt;IComparer&amp;lt;Person&amp;gt; firstLast = new ComparerProxy&amp;lt;Person&amp;gt;(first, last);&lt;br /&gt;IComparer&amp;lt;Person&amp;gt; lastFirst = new ComparerProxy&amp;lt;Person&amp;gt;(last, first);&lt;/pre&gt;I executed the following lines and, lo and behold, achieved the expected results:&lt;pre name="code" class="c#"&gt;_folks.Sort(last);&lt;br /&gt;PrintList(_folks);&lt;br /&gt;&lt;br /&gt;// Brechtel, Jamus&lt;br /&gt;// Brechtel, Ashley&lt;br /&gt;// Caldwell, Jennifer&lt;br /&gt;// Caldwell, Colin&lt;br /&gt;// Caldwell, Cory&lt;br /&gt;// Woody, Lauren&lt;br /&gt;// Woody, Royce Ann&lt;br /&gt;// Woody, Pete&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;_folks.Sort(first);&lt;br /&gt;PrintList(_folks);&lt;br /&gt;&lt;br /&gt;// Brechtel, Ashley&lt;br /&gt;// Caldwell, Colin&lt;br /&gt;// Caldwell, Cory&lt;br /&gt;// Brechtel, Jamus&lt;br /&gt;// Caldwell, Jennifer&lt;br /&gt;// Woody, Lauren&lt;br /&gt;// Woody, Pete&lt;br /&gt;// Woody, Royce Ann&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;_folks.Sort(firstLast);&lt;br /&gt;PrintList(_folks);&lt;br /&gt;&lt;br /&gt;// this one is the same as just sorting by first name&lt;br /&gt;// in retrospect, I should've added some folks with the same first name&lt;br /&gt;// c'est la vie&lt;br /&gt;//&lt;br /&gt;// Brechtel, Ashley&lt;br /&gt;// Caldwell, Colin&lt;br /&gt;// Caldwell, Cory&lt;br /&gt;// Brechtel, Jamus&lt;br /&gt;// Caldwell, Jennifer&lt;br /&gt;// Woody, Lauren&lt;br /&gt;// Woody, Pete&lt;br /&gt;// Woody, Royce Ann&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;_folks.Sort(lastFirst);&lt;br /&gt;PrintList(_folks);&lt;br /&gt;&lt;br /&gt;// Brechtel, Ashley&lt;br /&gt;// Brechtel, Jamus&lt;br /&gt;// Caldwell, Colin&lt;br /&gt;// Caldwell, Cory&lt;br /&gt;// Caldwell, Jennifer&lt;br /&gt;// Woody, Lauren&lt;br /&gt;// Woody, Pete&lt;br /&gt;// Woody, Royce Ann&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2937380275917683101?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2937380275917683101/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/icomparer-proxy-to-sort-by-multiple.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2937380275917683101'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2937380275917683101'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/icomparer-proxy-to-sort-by-multiple.html' title='IComparer Proxy to Sort by Multiple Conditions'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/Sfh8mDEWRqI/AAAAAAAAAtA/_lu0ZJyGtOg/s72-c/IComparer.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-8028932288267442167</id><published>2009-04-29T08:03:00.000-07:00</published><updated>2009-04-30T11:30:05.904-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='IComparer'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Demystifying IComparer in C#</title><content type='html'>&lt;a href="http://picasaweb.google.com/tncbbthositg/BlogPictures#5330147152093398690"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/Sfh8mDEWRqI/AAAAAAAAAtA/_lu0ZJyGtOg/s288/IComparer.jpg" alt="IComparer" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;I was looking through the analytics on my blog and I noticed a trend.  I get a ton of searches for things related to the &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/implementing-icomparer-in-c.html"&gt;IComparer in C#&lt;/a&gt; article I wrote a few months back.  I can only imagine that the reason people are searching for information on the IComparer so frequently is because the IComparer is a little mysterious to most people.  It's pretty uncommon that you need to use one (let alone write it).  &lt;br /&gt;&lt;br /&gt;In truth, the &lt;a href="http://msdn.microsoft.com/en-us/library/system.collections.icomparer.compare.aspx"&gt;IComparer Documentation&lt;/a&gt; isn't really all that helpful.  Most examples of an IComparer that I found when I was working on my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/revised-ilist-extension-methods-for.html"&gt;best fit select&lt;/a&gt; and my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/more-ilist-extension-methods.html"&gt;quickselect&lt;/a&gt; articles simply use x.CompareTo(y) or a built in IComparer.  &lt;br /&gt;&lt;br /&gt;Sure, that works, but I think people are hitting my blog trying to uncover the inner workings of the actual comparison.&lt;br /&gt;&lt;br /&gt;Well, here it is.  IComparer has one member method called Compare(object x, object y) (the generic version is strongly typed) which returns an integer.  If the value of x is less than y, the result is less than 0.  If the value of x is greater than y, the result is greater than zero.  If the two values are equal, the result is 0.&lt;br /&gt;&lt;br /&gt;Now, in most implementations I've seen, the results are nominal data (i.e., one trit valued -1, 0, or 1); however, I don't really see any reason you can't return ordinal, interval, or ratio data as well (although, returning ordinal or interval data would be relatively difficult as it requires knowledge of the rest of the collection).  If that didn't make sense, take a look at &lt;a href="http://en.wikipedia.org/wiki/Level_of_measurement"&gt;levels of measurement&lt;/a&gt;.  If you'd like me to elaborate on levels of measurement, leave a comment and I'll write an entire post about it.&lt;br /&gt;&lt;br /&gt;Using my fictional class Person with the Age property, a common AgeComparer class would look something like this:&lt;pre name="code" class="c#"&gt;public class AgeComparer : IComparer&amp;lt;Person&amp;gt;&lt;br /&gt;{&lt;br /&gt;    public virtual int Compare(Person x, Person y)&lt;br /&gt;    {&lt;br /&gt;        if (x.Age == y.Age) return 0;&lt;br /&gt;        return (x.Age &amp;gt; y.Age) ? 1 : -1;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;While that's useful for sorting, it doesn't really give you any information about the magnitude of the difference between the two objects and may as well just be called WhichOneIsGreater(object x, object y).  If you were using the comparer to graph items in a list, this would be relatively useless.  Also, it's an entire line of code more than you need, so I'm proposing a solution like the following:&lt;pre name="code" class="c#"&gt;public class AgeComparer : IComparer&amp;lt;Person&amp;gt;&lt;br /&gt;{&lt;br /&gt;    public virtual int Compare(Person x, Person y)&lt;br /&gt;    {&lt;br /&gt;        return x.Age - y.Age;&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;One caveat is that some people use IComparers expecting 1, 0, or -1 (i.e., if (IComparer.Compare(x, y) == 1) {...}).  Certainly, using a non-nominal result would break their code, but IMO had they read the documentation on IComparer, they'd have seen nothing about 1, 0, and -1 as the return value.&lt;br /&gt;&lt;br /&gt;I challenge you to think of other comparisons you could make between objects and post them in the comments of this blog post.  In the meantime, I'm going to start an entire blog section on IComparer and we'll see what we can come up with.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-8028932288267442167?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/8028932288267442167/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/demystifying-icomparer-in-c.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8028932288267442167'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8028932288267442167'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/demystifying-icomparer-in-c.html' title='Demystifying IComparer in C#'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/Sfh8mDEWRqI/AAAAAAAAAtA/_lu0ZJyGtOg/s72-c/IComparer.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-3287693336313831676</id><published>2009-04-23T12:03:00.000-07:00</published><updated>2009-04-23T14:17:33.533-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iPhone'/><title type='text'>Required iPhone Knowledge for International Travel</title><content type='html'>&lt;a href="http://www.a-zmaps.co.uk/?nid=404"&gt;&lt;img src="http://lh3.ggpht.com/_cFBwGCiU3y4/SfDSkMYaAzI/AAAAAAAAApY/9MYoE-jq1Mc/s288/lonmini3screens.jpg" alt="iPhone Abroad" style="float: left; margin-right: 7px;" class="postimage" /&gt;&lt;/a&gt;&lt;h2&gt;PDA Mode&lt;/h2&gt;You can disable the cellphone antenna on the iPhone by putting it in airplane mode.  By default, airplane mode also disables the wifi antenna.  You can re-enable the wifi antenna independently of the cellphone antenna by clicking Wi-Fi and flipping the switch to on.  This will allow you to access the internet without incurring roaming charges.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Textual Intercourse&lt;/h2&gt;One thing you'll miss is text messaging.  Do not fret Littlefoot; in the days between snail mail and text messages, people sent emails.  iPhone works great with emails.  Set up your &lt;a href="http://www.gmail.com"&gt;gmail&lt;/a&gt; account on your iPhone and check it whenever you get internet access.  &lt;br /&gt;&lt;br /&gt;If you need more immediate response times, get a multi-protocol iPhone IM client.  There are several great IM clients for the iPhone that you can get for free.  My favorites are &lt;a href="http://www.fring.com"&gt;Fring&lt;/a&gt; and &lt;a href="http://www.palringo.com"&gt;Palringo&lt;/a&gt;.  I like the Palringo interface a lot for IMing, but I like Fring for IM and Skype support.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Phone Home&lt;/h2&gt;Calling home is a lot of fun when you're on an international adventure so you can make all of your friends and family jealous.  I'm a big fan of the &lt;a href="http://www.skype.com"&gt;Skype&lt;/a&gt; unlimited U.S. land line calling plan for $2.95 so that's what I use.  Also, Skype-to-Skype calls are free.  There may be better VoIP services out there, but I haven't found one yet.  &lt;br /&gt;&lt;br /&gt;So, how do you Skype on the iPhone?  Like I said before, Fring supports Skype and multiple IM clients and has a decent interface.  As a note, when you use Fring to make a Skype call, you have to put +1 before the area code and phone number (i.e., +17705551212).  Skype recently came out with its own iPhone application but it's spotty at best for me; you may have better luck.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Social Networking&lt;/h2&gt;&lt;a href="http://www.facebook.com/apps/application.php?id=6628568379"&gt;Facebook&lt;/a&gt; has a great free iPhone application to keep you in touch with your friends and family back home.  If you tweet, I really like &lt;a href="http://twitterfon.net/"&gt;TwitterFon&lt;/a&gt;.  TwitterFon conveniently supports &lt;a href="http://www.twitpic.com"&gt;twitpic&lt;/a&gt; too.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Finances&lt;/h2&gt;When you're traveling internationally, it's nice to be able to check on your bank accounts back home; however, it'd be pretty tough to get to every page you're interested in within the 1 hour of internet access you get for 5 billion euro.  If you use &lt;a href="http://www.mint.com"&gt;Mint.com&lt;/a&gt; (and you should), then you can get the free &lt;a href="http://www.mint.com/features/iphone/"&gt;Mint.com iPhone application&lt;/a&gt; and check every balance at once and get a detailed transaction list.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Literature&lt;/h2&gt;Search the iTunes App Store for literature you may want.  You can find travel guides, maps, and translators.  Also, search the internet for images relevant to your trip.  For example, find a large image of the &lt;a href="http://www.subtraction.com/2007/08/27/a-subway-sys"&gt;subway system&lt;/a&gt; and break it into sections so you can view it on your iPhone.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-3287693336313831676?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/3287693336313831676/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/required-iphone-knowledge-for.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3287693336313831676'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/3287693336313831676'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/required-iphone-knowledge-for.html' title='Required iPhone Knowledge for International Travel'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_cFBwGCiU3y4/SfDSkMYaAzI/AAAAAAAAApY/9MYoE-jq1Mc/s72-c/lonmini3screens.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6567915621140685595</id><published>2009-04-07T14:38:00.000-07:00</published><updated>2009-04-07T14:44:46.686-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Extension Method to Join Strings with a Delimiter</title><content type='html'>&lt;img src="http://www.pcdesigns.net/images/stringarray.gif" alt="String Array" style="float: left; margin-right: 7px;" class="postimage" /&gt;I was talking with my coworker &lt;a href="http://blog.brechtel.us/"&gt;James Brechtel&lt;/a&gt; today about writing an &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;extension method&lt;/a&gt; to join an array of strings together with a delimiter like string.Join() does.  I'd prefer string[].Join() because it seems more fluent.&lt;br /&gt;&lt;br /&gt;After I put the &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/toarray-extension-method-converts-ilist.html"&gt;IList&amp;lt;T&amp;gt;.ToArray&amp;lt;T&amp;gt;() extension method&lt;/a&gt; together, implementing the fluent style join was a simple task.&lt;br /&gt;&lt;br /&gt;Here's what that looks like:&lt;pre name="code" class="c#"&gt;public static string Join(this IList&amp;lt;string&amp;gt; list, string separator)&lt;br /&gt;{&lt;br /&gt;    return string.Join(separator, list.ToArray());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;  That's it! :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6567915621140685595?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6567915621140685595/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/extension-method-to-join-strings-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6567915621140685595'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6567915621140685595'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/extension-method-to-join-strings-with.html' title='Extension Method to Join Strings with a Delimiter'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-8924389054577447097</id><published>2009-04-07T14:17:00.000-07:00</published><updated>2009-04-07T14:45:22.059-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>ToArray Extension Method Converts IList&lt;T&gt; to T[]</title><content type='html'>&lt;img src="http://www.pcdesigns.net/images/stringarray.gif" alt="String Array" style="float: left; margin-right: 7px;" class="postimage" /&gt;I was talking with my coworker &lt;a href="http://blog.brechtel.us/"&gt;James Brechtel&lt;/a&gt; today about writing an &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;extension method&lt;/a&gt; to  &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/extension-method-to-join-strings-with.html"&gt;join an array of strings&lt;/a&gt; together with a delimiter like string.Join() does.  I'd prefer string[].Join() because it seems more fluent.  I was throwing the IList&amp;lt;string&amp;gt;.Join() method together when I ran into a small snag.&lt;br /&gt;&lt;br /&gt;I needed an array of strings for the string.Join() method.  I put together a quick ToArray&amp;lt;T&amp;gt;() extension method which iterated through each item in the array and created an array T[].  The problem is, if I already have an array, that's a lot of extra work and some wasted memory.&lt;br /&gt;&lt;br /&gt;So, I tried IList&amp;lt;T&amp;gt; is Array and it works great.  I just cast the IList&amp;lt;T&amp;gt; as T[] and return it.  Otherwise, I execute the iteration.  Here's what it looks like:&lt;pre name="code" class="c#"&gt;public static T[] ToArray&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list)&lt;br /&gt;{&lt;br /&gt;    if (list is Array) return (T[]) list;&lt;br /&gt;&lt;br /&gt;    T[] retval = new T[list.Count];&lt;br /&gt;    for (int i = 0; i &amp;lt; retval.Length; i++) &lt;br /&gt;        retval[i] = list[i];&lt;br /&gt;&lt;br /&gt;    return retval;&lt;br /&gt;}&lt;/pre&gt;  Pretty basic, but nice n' fast.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-8924389054577447097?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/8924389054577447097/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/toarray-extension-method-converts-ilist.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8924389054577447097'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/8924389054577447097'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/toarray-extension-method-converts-ilist.html' title='ToArray Extension Method Converts IList&amp;lt;T&amp;gt; to T[]'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-1790504331003493583</id><published>2009-04-07T09:26:00.000-07:00</published><updated>2009-04-16T06:50:17.958-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Business'/><title type='text'>Why You'll Always Need a Programmer</title><content type='html'>&lt;img src="http://www.serena.com/images/roles/business-analyst.jpg" alt="Business Analyst Lady" style="float: left; margin-right: 7px;" class="postimage" /&gt;I've been writing software on various platforms for a little longer than a decade now and I have noticed a reoccurring trend.  Customers are asking for and software vendors are trying to develop a way to allow business analysts to implement business logic in their applications.&lt;br /&gt;&lt;br /&gt;The idea is that the business analyst knows the business inside and out.  Therefore, if the business analyst had a way to implement the business logic, then there would be no need to have both the business analyst and the programmer (and the ensuing communication overhead).&lt;br /&gt;&lt;br /&gt;I, however, am not concerned.  In my experience, I have come to the conclusion that not only is the implementation of business a programming task, but it is a programming task that requires a strong development background.&lt;br /&gt;&lt;br /&gt;What I mean is, you can write &lt;a href="http://en.wikipedia.org/wiki/Domain_specific_language"&gt;DSLs&lt;/a&gt;, query builders, and the like until you're blue in the face (presuming that you would eventually turn blue in the face writing DSLs and query builders), and the technically savvy business analyst will be able to learn to use these tools; however, without the foundation of good programming practices, this business analyst will produce vast amounts of &lt;a href="http://en.wikipedia.org/wiki/Technical_debt"&gt;technical debt&lt;/a&gt; which will, in the long run (if not the short run), cost the company much more than the programmer would have in the first place.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The Problem&lt;/h2&gt;Over the last several decades, programmers have developed practices and patterns that allow us to write applications that are easily upgradeable, scalable, maintainable, and even readable.  We have built on this body of knowledge and we have tested it.  Sure, there are some disagreements here and there, but for the most part, as we develop professionally we are forming a special way of looking at the world and solving problems which is relatively unique to programmers.&lt;br /&gt;&lt;br /&gt;Similarly, business analysts have been doing the same thing.  They look at the world and approach problems in their own special way.  I believe they are two sides of the same coin.  Both are responsible for understanding, documenting, and implementing business rules.  One side communicates these rules to humans and the other to machines.&lt;br /&gt;&lt;br /&gt;Enabling a business analyst to communicate directly to machines seems like a money saving flexibility that can't go wrong.  It can go wrong &amp;mdash; terribly wrong, and it can do so before you realize there's a problem.  I believe that the business analyst would take as long to learn about the programming practices required to make solid architectural decisions as it would take the programmer to learn to be a business analyst.&lt;br /&gt;&lt;br /&gt;If the business analyst is able to implement solutions, it is almost certain that there will be errors that a programmer would have caught.  When the needs change, the analyst may even be able to address these changes, but this will lead not only to more errors, but also to an increased complexity that a programmer would have avoided.&lt;br /&gt;&lt;br /&gt;Thus, the more work that goes into the solution over time, the more difficult it will be to maintain.  Dealing with maintenance issues will lead to unintended consequences which will be corrected with more patches resulting in what programmers would liken to spaghetti code.&lt;br /&gt;&lt;br /&gt;Eventually, the design entropy will lead to such a disaster that the vendor will need to be called in.  Vendor engineers for large software companies can cost more than 300 dollars an hour.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Case Study - Business Process Automation&lt;/h2&gt;I have been working with a BPM engine for several years now.  It's really great at what it does.  It provides an asynchronous state engine, a visual workflow designer, and data persistence.  The only real problem with this application is the way it is marketed to customers.  &lt;br /&gt;&lt;br /&gt;The company that produces the application claims that business processes can be automated without the need of a programming staff.  They tout the visual designer as business analyst friendly.  I agree that it is BA friendly . . . to read.  Generating workflows is a vastly different story.&lt;br /&gt;&lt;br /&gt;I have implemented this software for my own clients, for a very large government organization, and indeed for the software vendor itself.  In every case, I have shown that even at a very low complexity, these implementations still required a knowledgeable programmer.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Case Study - Data Validation&lt;/h2&gt;I have a friend who works with a dynamic data collection application which implements data validation on the server side in SQL stored procedures and in javascript code snippets on the client side.  The person who develops most of the data validation for one of his customers is an entry level programmer.&lt;br /&gt;&lt;br /&gt;All of the validation works and everything is well and good until something changes.  Even minor somethings can result in very intense development, debugging, and testing cycles.  The people who developed these applications are very smart and clever people, but their lack of programming background has resulted in code which is almost impossible to maintain.&lt;br /&gt;&lt;br /&gt;A more solid understanding of programming principles would have prevented probably 90% of the code repetition in these business rules.  One small change wouldn't require a code change to every single business rule.  Isolating bugs and errors could be accomplished through tests of small units of code rather than very large blocks of difficult to follow code.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The Solution&lt;/h2&gt;You will always need a programmer.  If you are trying to develop a software based solution to save your company money, an architect with a solid programming foundation will produce a much more robust and more easily maintainable solution.  The solution will last longer and will scale easier than anything your business analyst can come up with alone.&lt;br /&gt;&lt;br /&gt;A good BA / software engineer team will produce benefits to your company that you, your BA, and your engineer could never have invented individually.  The initial cost may be higher, but consider this.&lt;br /&gt;&lt;br /&gt;Most of the time, your company will have to foot a hefty bill to train the BA.  There will be evaluations, proof of concepts, and tests.  More often than not, the determination will be that a BA just doesn't have the experience required.  At that point, you will either have to hire a programmer and go through the same process or you will have to contract with the vendor or one of its partners.&lt;br /&gt;&lt;br /&gt;The worse scenario is that your BA can implement a solution and that it works.  When the application changes, you generate need for more application changes.  These changes generate more changes.  These changes compound and you're faced with a very costly overhaul of the entire system.&lt;br /&gt;&lt;br /&gt;It is far better to start with a BA and a programmer.  Send them both to the appropriate training.  Have them both work together to produce adequate project documentation.  Foster an environment where they are mutually productive.  Not only will the whole be more than the sum of its parts, you'll save a great deal of expense and heartache.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-1790504331003493583?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/1790504331003493583/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/why-youll-always-need-programmer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1790504331003493583'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/1790504331003493583'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/why-youll-always-need-programmer.html' title='Why You&apos;ll Always Need a Programmer'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6477987298244884860</id><published>2009-04-06T06:26:00.001-07:00</published><updated>2009-04-06T07:16:25.398-07:00</updated><title type='text'>More Yahoo Pipes</title><content type='html'>&lt;img src="http://www.pcdesigns.net/FeedAggPipe.jpg" class="postimage" style="float: left; margin-right: 7px;" alt="Pipe Image" /&gt;Last week, I wrote a blog post about &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/04/using-yahoo-pipes.html"&gt;Yahoo Pipes&lt;/a&gt;.  Shortly after I finished that post, I was doing some testing and my little brother Colin sent me an email complaint.  &lt;br /&gt;&lt;br /&gt;See, I am using my pipe to power an RSS feed in feed burner which I use both on my &lt;a href="http://www.emeraldsoftwaregroup.com/PatrickCaldwell"&gt;corporate profile&lt;/a&gt; and in my &lt;a href="http://www.twitterfeed.com"&gt;twitterfeed&lt;/a&gt;.  I have &lt;a href="http://www.facebook.com"&gt;facebook&lt;/a&gt; monitoring &lt;a href="http://twitter.com/tncbbthositg"&gt;my tweets&lt;/a&gt; and updating my status.&lt;br /&gt;&lt;br /&gt;The problem is, it looks pretty silly when I get a spam comment (or any comment really) and it pops up as my facebook status.  So, when Colin sent me a message and said, "Pat, I have no idea what your status means or what that has to do with anything," I decided to update my pipe.&lt;br /&gt;&lt;br /&gt;This is my new &lt;a href="http://pipes.yahoo.com/pipes/pipe.info?_id=79ea9beab52402ec0d64d8a6eff0fccc"&gt;feed aggregation Yahoo pipe&lt;/a&gt; with context information.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6477987298244884860?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6477987298244884860/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/more-yahoo-pipes.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6477987298244884860'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6477987298244884860'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/more-yahoo-pipes.html' title='More Yahoo Pipes'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-9016111066592182287</id><published>2009-04-04T14:55:00.000-07:00</published><updated>2009-04-04T15:11:15.058-07:00</updated><title type='text'>Using Yahoo Pipes</title><content type='html'>&lt;img src="http://l.yimg.com/a/i/us/pps/logo_1.gif" alt="Yahoo Pipes" style="float: left; margin-right: 7px; margin-bottom: 3px;" /&gt;I was looking for a good way to combine a set of RSS feeds into one feed.  I saw several web based services out there, but none was quite good enough for my needs.  In my research, I happened across &lt;a href="http://pipes.yahoo.com"&gt;Yahoo Pipes&lt;/a&gt;.  In about 3 minutes, I logged in, added 6 feeds, sorted them by publication date, and selected the top 25.  Now, I have one &lt;a href="http://pipes.yahoo.com/pipes/pipe.run?_id=4FC33Tgh3hGxZWE3rbQIDg&amp;_render=rss"&gt;RSS feed to aggregate everything&lt;/a&gt; including my two blogs (&lt;a href="http://dpatrickcaldwell.blogspot.com"&gt;computing&lt;/a&gt; and &lt;a href="http://joysofflight.blogspot.com"&gt;aviation&lt;/a&gt;) and the comments from each one as well as &lt;a href="http://picasaweb.google.com/patandlulu"&gt;my picasa albums&lt;/a&gt; and their comments.&lt;br /&gt;&lt;br /&gt;The next thing you know, I have &lt;a href="http://twitterfeed.comm"&gt;twitterfeed&lt;/a&gt; reading from that RSS feed and updating &lt;a href="http://twitter.com/tncbbthositg"&gt;my twitter&lt;/a&gt;.  The twitter facebook application then updates my statuses.  Combining all of these services lets pretty much all of my friends and professional contacts know what I'm up to and what I'm publishing.  Good stuff.&lt;br /&gt;&lt;br /&gt;The only problem so far is that the twitter statuses look a bit confusing when I'm setting my facebook status to someone's comment on one of my blogs.  I'm glad it's published, but I'm going to be looking into some of the transformations available in Yahoo Pipes to see if there's a way I can prefix posts to identify them as blog posts vs. comments.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-9016111066592182287?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/9016111066592182287/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/using-yahoo-pipes.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/9016111066592182287'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/9016111066592182287'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/using-yahoo-pipes.html' title='Using Yahoo Pipes'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5065380992804424762</id><published>2009-04-04T14:14:00.000-07:00</published><updated>2009-04-04T14:53:50.918-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Security'/><title type='text'>PIN Entry Screens Considered Harmful</title><content type='html'>&lt;img style="float: left; margin-right: 7px; margin-bottom: 4px;" alt="Digital PIN Pad" src="http://www.posdirect.com/prodimages/8500large.jpg" /&gt;I never wrote a "&lt;a href="http://en.wikipedia.org/wiki/Considered_harmful"&gt;Considered Harmful&lt;/a&gt;" blog post before so I figured it was high time I did so.  I often find myself at the grocery store, gas station, or fast-food restaurant keying my 4 digit PIN into some electronic entry system.&lt;br /&gt;&lt;br /&gt;I look around and see someone watching me type in my code.  Whether they're doing it intentionally or they're doing it incidentally, it still bothers me.  I don't really want anyone seeing my PIN, particularly if that person &lt;i&gt;wants&lt;/i&gt; to know it.  &lt;br /&gt;&lt;br /&gt;The other day, I was waiting behind some woman at the grocery store.  In my frustration, I watched every move she made.  She pulled her card out of her purse, handed it to the cashier, and started keying her PIN.  From my angle, I couldn't see the actual numbers, but when she finished, I was completely shocked that I knew what her PIN was!&lt;br /&gt;&lt;br /&gt;Evidently, without even noticing that I was doing it, I watched her key her PIN and I figured out what it was by the location of her presses alone.  I had to be sure, so I asked, "ma'am, is your pin 4321?"  Shocked and appalled she said, "why were you paying attention to that?"  I replied, "it was an accident . . . I just happened to see where you were pressing."&lt;br /&gt;&lt;br /&gt;That's when I had an idea.  The digital keypads must be simply displaying the numbers on the screen.  It seems that it would be completely reasonable to scramble the actual numbers so that nobody could decode your PIN simply by watching the location of your key presses.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5065380992804424762?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5065380992804424762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/pin-entry-screens-considered-harmful.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5065380992804424762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5065380992804424762'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/04/pin-entry-screens-considered-harmful.html' title='PIN Entry Screens Considered Harmful'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-5922385007180149123</id><published>2009-03-16T07:38:00.000-07:00</published><updated>2009-04-29T09:20:55.751-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='IComparer'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>More IList Extension Methods: Quickselect-ing from an Unsorted List</title><content type='html'>Continuing my &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;extension method&lt;/a&gt; kick and inspired by my &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/revised-ilist-extension-methods-for.html"&gt;extension method to select an item from an unsorted list&lt;/a&gt; that bets fits comparison criteria, I decided I wanted to try to implement an extension method to select from the list the kth best fit rather than just the best fit.&lt;br /&gt;&lt;br /&gt;I left the best fit methods intact because they achieve worst case O(n) time, but the kth select worst case is O(n log n) because the worst case would quicksort the entire array.  I later discovered that the method I implemented is called a &lt;a href="http://en.wikipedia.org/wiki/Selection_algorithm"&gt;quickselect&lt;/a&gt;.  It's a divide an conquer algorithm based on the partitioning scheme of the &lt;a href="http://en.wikipedia.org/wiki/Quicksort"&gt;quicksort&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Basically, with the quicksort, you pick an item (called the pivot) from the list and then put everything smaller than the pivot on the left and everything larger on the right.  Then, you recursively quicksort each side of the pivot.&lt;br /&gt;&lt;br /&gt;The quickselect uses the same technique, but rather than quicksorting both sides of the pivot, you quicksort only the side which contains the element you're looking for.  Id est, if your pivot ends up at index 5 and you're looking for index 7, then you partition the right side.  If the next pivot is index 8, then you partition the left side.  You keep doing that until your pivot ends up being the index you're looking for.&lt;br /&gt;&lt;br /&gt;Here's what the partition methods look like.  There's really not a great reason to make them public, but there's also not a good reason to leave them private.&lt;pre name="code" class="c#"&gt;public static int Partition&amp;lt;T&amp;gt;&lt;br /&gt;    (this IList&amp;lt;T&amp;gt; list, Comparison&amp;lt;T&amp;gt; comparison, int left, int right)&lt;br /&gt;{&lt;br /&gt;    int i = left;&lt;br /&gt;    int j = right;&lt;br /&gt;&lt;br /&gt;    // pick the pivot point and save it&lt;br /&gt;    T pivot = list[left];&lt;br /&gt;&lt;br /&gt;    // until the indices cross&lt;br /&gt;    while (i &amp;lt; j)&lt;br /&gt;    {&lt;br /&gt;        // move the right pointer left until value &amp;lt; pivot&lt;br /&gt;        while (comparison(list[j], pivot) &amp;gt; 0 &amp;amp;&amp;amp; i &amp;lt; j) j--;&lt;br /&gt;&lt;br /&gt;        // move the right value to the left position&lt;br /&gt;        // increment left pointer&lt;br /&gt;        if (i != j) list[i++] = list[j];&lt;br /&gt;&lt;br /&gt;        // move the left pointer to the right until value &amp;gt; pivot&lt;br /&gt;        while (comparison(list[i], pivot) &amp;lt; 0 &amp;amp;&amp;amp; i &amp;lt; j) i++;&lt;br /&gt;&lt;br /&gt;        // move the left value to the right position&lt;br /&gt;        // decrement right pointer&lt;br /&gt;        if (i != j) list[j--] = list[i];&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // put the pivot holder in the left spot&lt;br /&gt;    list[i] = pivot;&lt;br /&gt;&lt;br /&gt;    // return pivot location&lt;br /&gt;    return i;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static int Partition&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list, Comparison&amp;lt;T&amp;gt; comparison)&lt;br /&gt;{&lt;br /&gt;    return list.Partition(comparison, 0, list.Count - 1);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static int Partition&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list, IComparer&amp;lt;T&amp;gt; comparer)&lt;br /&gt;{&lt;br /&gt;    return list.Partition(new Comparison&amp;lt;T&amp;gt;((x, y) =&amp;gt; comparer.Compare(x, y)));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static int Partition&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list)&lt;br /&gt;    where T : IComparable&amp;lt;T&amp;gt;&lt;br /&gt;{&lt;br /&gt;    return list.Partition(new Comparison&amp;lt;T&amp;gt;((x, y) =&amp;gt; x.CompareTo(y)));&lt;br /&gt;}&lt;/pre&gt;Isolating the partition logic makes it really easy to implement both the quicksort logic and the quickselect logic.  In fact, in case you're interested, here's what the quicksort logic looks like: &lt;pre name="code" class="c#"&gt;public static void QuickSort&amp;lt;T&amp;gt;&lt;br /&gt;    (this IList&amp;lt;T&amp;gt; list, Comparison&amp;lt;T&amp;gt; comparison, int left, int right)&lt;br /&gt;{&lt;br /&gt;    // pivot and get pivot location&lt;br /&gt;    int pivot = list.Partition(comparison, left, right);&lt;br /&gt;&lt;br /&gt;    // if the left index is less than the pivot, sort left side&lt;br /&gt;    if (left &amp;lt; pivot) list.QuickSort(comparison, left, pivot - 1);&lt;br /&gt;&lt;br /&gt;    // if right index is greated than pivot, sort right side&lt;br /&gt;    if (right &amp;gt; pivot) list.QuickSort(comparison, pivot + 1, right);&lt;br /&gt;}&lt;/pre&gt;The quickselect is basically the same thing, except that you don't quicksort both sides of pivot:&lt;pre name="code" class="c#"&gt;public static T QuickSelect&amp;lt;T&amp;gt;&lt;br /&gt;    (this IList&amp;lt;T&amp;gt; list, int k, Comparison&amp;lt;T&amp;gt; comparison, int left, int right)&lt;br /&gt;{&lt;br /&gt;    // get pivot position&lt;br /&gt;    int pivot = list.Partition(comparison, left, right);&lt;br /&gt;&lt;br /&gt;    // if pivot is less that k, select from the right part&lt;br /&gt;    if (pivot &amp;lt; k) return list.QuickSelect(k, comparison, pivot + 1, right);&lt;br /&gt;&lt;br /&gt;    // if pivot is greater than k, select from the left side&lt;br /&gt;    else if (pivot &amp;gt; k) return list.QuickSelect(k, comparison, left, pivot - 1);&lt;br /&gt;&lt;br /&gt;    // if equal, return the value&lt;br /&gt;    else return list[pivot];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static T QuickSelect&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list, int k, Comparison&amp;lt;T&amp;gt; comparison)&lt;br /&gt;{&lt;br /&gt;    return list.QuickSelect(k, comparison, 0, list.Count - 1);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static T QuickSelect&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list, int k, IComparer&amp;lt;T&amp;gt; comparer)&lt;br /&gt;{&lt;br /&gt;    return list.QuickSelect(k, new Comparison&amp;lt;T&amp;gt;((x, y) =&amp;gt; comparer.Compare(x, y)));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static T QuickSelect&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list, int k)&lt;br /&gt;    where T : IComparable&amp;lt;T&amp;gt;&lt;br /&gt;{&lt;br /&gt;    return list.QuickSelect(k, new Comparison&amp;lt;T&amp;gt;((x, y) =&amp;gt; x.CompareTo(y)));&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-5922385007180149123?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/5922385007180149123/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/03/more-ilist-extension-methods.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5922385007180149123'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/5922385007180149123'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/03/more-ilist-extension-methods.html' title='More IList Extension Methods: Quickselect-ing from an Unsorted List'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-2872877710173958413</id><published>2009-03-15T17:33:00.000-07:00</published><updated>2009-04-29T09:20:55.751-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='Extension Methods'/><category scheme='http://www.blogger.com/atom/ns#' term='IComparer'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Revised IList Extension Methods for Selecting Best Fitting Items from an Unsorted List</title><content type='html'>Several days ago, I started writing articles about &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;extension methods&lt;/a&gt;.  One of the articles I wrote was about an &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/extension-methods-for-picking-item-out.html"&gt;IList extension method to select the best fitting item out of an unsorted list&lt;/a&gt; in O(n) (linear time).  I was working on another article about implementing the quicksort and quickselect algorithms as extension methods.&lt;br /&gt;&lt;br /&gt;I got pretty tired of having the same logic implemented in multiple places, but I hadn't yet figured out how to refactor it.  Well, I did, so this is an update to those methods that keeps most of the work in one place.&lt;br /&gt;&lt;br /&gt;I think it's a pretty cool implementation, it still works correctly, and it's still relatively fast.  This implementation is also still side-effect free.  That's one reason to use it over a sort/select method.  If you need to leave your IList unsorted, then you can't sort and select.  Also, if you are selecting from items with a custom IComparer, then even if you don't care about side-effects, you're still better off using this method.&lt;br /&gt;&lt;br /&gt;On the other hand, if you don't care about the order of your list and your list type can be sorted with the native TrySZSort, then you're better off using Array.Sort() and selecting the last item in the list.  However, if it can't be externed to TrySZSort then the .Net implementation of Array.Sort() uses quicksort which is O(n log n) and the selection is O(1).  That's less efficient than the O(n) implementation below.&lt;pre name="code" class="c#"&gt;public static T GetBestFit&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list, Comparison&amp;lt;T&amp;gt; comparison)&lt;br /&gt;{&lt;br /&gt;    if (list == null || list.Count == 0)&lt;br /&gt;        throw new ArgumentException("You cannot get the best fit from an empty list!");&lt;br /&gt;&lt;br /&gt;    T currentFit = list[0];&lt;br /&gt;    for (int i = 1; i &amp;lt; list.Count; i++)&lt;br /&gt;        if (comparison(currentFit, list[i]) &amp;lt; 0)&lt;br /&gt;            currentFit = list[i];&lt;br /&gt;&lt;br /&gt;    return currentFit;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static T GetBestFit&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list, IComparer&amp;lt;T&amp;gt; comparer)&lt;br /&gt;{&lt;br /&gt;    return list.GetBestFit(new Comparison&amp;lt;T&amp;gt;((x, y) =&amp;gt; comparer.Compare(x, y)));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static T GetBestFit&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list)&lt;br /&gt;    where T : IComparable&amp;lt;T&amp;gt;&lt;br /&gt;{&lt;br /&gt;    return list.GetBestFit(new Comparison&amp;lt;T&amp;gt;((x, y) =&amp;gt; x.CompareTo(y)));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public static T GetBestFit&amp;lt;T&amp;gt;(this IList&amp;lt;T&amp;gt; list, Func&amp;lt;T, T, bool&amp;gt; rule)&lt;br /&gt;{&lt;br /&gt;    Comparison&amp;lt;T&amp;gt; c = new Comparison&amp;lt;T&amp;gt;((x, y) =&amp;gt; (rule(x, y)) ? 1 : -1);&lt;br /&gt;    return list.GetBestFit(c);&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-2872877710173958413?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/2872877710173958413/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/03/revised-ilist-extension-methods-for.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2872877710173958413'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/2872877710173958413'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/03/revised-ilist-extension-methods-for.html' title='Revised IList Extension Methods for Selecting Best Fitting Items from an Unsorted List'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-6919007053670219836</id><published>2009-03-15T17:24:00.000-07:00</published><updated>2009-03-15T18:46:17.908-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Proxy Class for Making Any Object IComparable</title><content type='html'>I've been continuing my thinking on the topic of &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;extension methods&lt;/a&gt; and &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/extension-methods-for-picking-item-out.html"&gt;selecting items out of an unsorted list&lt;/a&gt;.  I made some &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/revised-ilist-extension-methods-for.html"&gt;changes to the selection extension methods&lt;/a&gt; in order to make them more manageable.&lt;br /&gt;&lt;br /&gt;It led me to another topic which is pretty much unrelated.  I was thinking about the proxy pattern and trying to find another way to use it.  Right now, I only use it in my &lt;a href="http://dpatrickcaldwell.blogspot.com/2008/12/using-proxy-pattern-to-write-to.html"&gt;TextWriter proxy&lt;/a&gt; but haven't used it anywhere else.  But, as I was working on all of these extension methods, I realized how easy it was to sort and compare objects that were IComparable and it kept my method signatures nice and clean.&lt;br /&gt;&lt;br /&gt;The problem is, not everybody uses IComparable and it's not always better to subclass the object just to get it.  Instead, I wrote a proxy class that takes your object and gives you an IComparable.  I don't really have any uses for it yet and it's not "cooked" enough to be production ready, but it does do the trick and it is an example of the proxy pattern at work.&lt;pre name="code" class="c#"&gt;public class ProxyIComparable&amp;lt;T&amp;gt; : IComparable&amp;lt;T&amp;gt;&lt;br /&gt;{&lt;br /&gt;    private T _object;&lt;br /&gt;&lt;br /&gt;    public IComparer&amp;lt;T&amp;gt; Comparer { get; private set; }&lt;br /&gt;    public Comparison&amp;lt;T&amp;gt; Comparison { get; private set; }&lt;br /&gt;&lt;br /&gt;    public ProxyIComparable(T obj, Comparison&amp;lt;T&amp;gt; comparison)&lt;br /&gt;    {&lt;br /&gt;        _object = obj;&lt;br /&gt;        Comparison = comparison;&lt;br /&gt;&lt;br /&gt;        Comparer = new ProxyComparer(Comparison);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public ProxyIComparable(T obj, IComparer&amp;lt;T&amp;gt; comparer)&lt;br /&gt;    {&lt;br /&gt;        _object = obj;&lt;br /&gt;        Comparer = comparer;&lt;br /&gt;&lt;br /&gt;        Comparison = new Comparison&amp;lt;T&amp;gt;((x, y) =&amp;gt; Comparer.Compare(x, y));&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public int CompareTo(T other)&lt;br /&gt;    {&lt;br /&gt;        return Comparison(_object, other);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private class ProxyComparer : IComparer&amp;lt;T&amp;gt;&lt;br /&gt;    {&lt;br /&gt;        private Comparison&amp;lt;T&amp;gt; _comparison;&lt;br /&gt;&lt;br /&gt;        public ProxyComparer(Comparison&amp;lt;T&amp;gt; comparison)&lt;br /&gt;        {&lt;br /&gt;            _comparison = comparison;&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        public int Compare(T x, T y)&lt;br /&gt;        {&lt;br /&gt;            return _comparison(x, y);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5003807027724289640-6919007053670219836?l=dpatrickcaldwell.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dpatrickcaldwell.blogspot.com/feeds/6919007053670219836/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/03/proxy-class-for-making-any-object.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6919007053670219836'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5003807027724289640/posts/default/6919007053670219836'/><link rel='alternate' type='text/html' href='http://dpatrickcaldwell.blogspot.com/2009/03/proxy-class-for-making-any-object.html' title='Proxy Class for Making Any Object IComparable'/><author><name>D. Patrick Caldwell</name><uri>http://www.blogger.com/profile/09236952464473670820</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://1.bp.blogspot.com/_cFBwGCiU3y4/SZuNvbqm8ZI/AAAAAAAAAhw/9q1WBC3UKOU/S220/IMG_2071.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5003807027724289640.post-232857109819059013</id><published>2009-03-13T16:36:00.000-07:00</published><updated>2009-04-29T09:20:55.751-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Programming'/><category scheme='http://www.blogger.com/atom/ns#' term='IComparer'/><category scheme='http://www.blogger.com/atom/ns#' term='C#'/><title type='text'>Implementing IComparer in C#</title><content type='html'>I was working on some &lt;a href="http://dpatrickcaldwell.blogspot.com/search/label/Extension%20Methods"&gt;extension methods&lt;/a&gt; today allowing me to &lt;a href="http://dpatrickcaldwell.blogspot.com/2009/03/extension-methods-for-picking-item-out.html"&gt;pick an item out of an IList that best meets some criteria&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I started with just a delegate that takes two objects and returns a bool indicating that the first is a better fit than the second.  I expanded the functionality to take an IComparer so that you can use the same comparison for the extension method that you use for sorting.&lt;br /&gt;&lt;br /&gt;It was the first time I've written an IComparer so I thought I'd try a few techniques out.  The first thing I wanted to do was to actually implement an IComparer (as opposed to just using the CompareTo method on the underlying comparison object).  That is to say, I wanted to control returning -1, 0, or 1 myself.  Here's what I came up with:&lt;pre name="code" class="c#"&gt;public class AgeComparer : IComparer&amp;lt;Person&amp;gt;&lt;br /&gt;{&lt;br /&gt;    public int Compare(Person x, Person y)&lt;br /&gt;    {&lt;br /&gt;        if (x.Age == y.Age) r
