<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0"><channel><title>Anne Epstein</title><link>http://devlicio.us/blogs/anne_epstein/default.aspx</link><description /><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP1 (Build: 31106.3070)</generator><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/AnneEpstein" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="anneepstein" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item><title>Why is mercurial hanging during adding file changes?</title><link>http://devlicio.us/blogs/anne_epstein/archive/2011/08/11/why-is-mercurial-hanging-during-adding-file-changes.aspx</link><pubDate>Thu, 11 Aug 2011 21:35:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:68091</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>3</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=68091</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2011/08/11/why-is-mercurial-hanging-during-adding-file-changes.aspx#comments</comments><description>&lt;p&gt;So, I was opening a new project in mercurial that had a bit of a history. &amp;nbsp;I ran the normal:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;hg clone ssh://hg@bitbucket.org/mycompany/somerepository&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Which has worked pretty fast on the smaller repositories that I&amp;#39;ve cloned. &amp;nbsp;THIS time, it got to &amp;quot;adding file changes&amp;quot; .... and stopped. &amp;nbsp;The thing appeared totally hung, for at least the 10 minutes that I waited. &amp;nbsp;I cancelled it, and tried again. No luck. &amp;nbsp;So, after some searching, I came upon the debug command, which looks like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;hg --debug clone ssh://hg@bitbucket.org/Mycompany/Somerepository&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The result? &amp;nbsp;&lt;/p&gt;
&lt;p&gt;files: 891:4346 chunks (1.50%)&lt;br /&gt;adding lib/AutoMapper/Automapper.dll revisions&lt;br /&gt;files: 892:4346 chunks (1.51%)&lt;br /&gt;adding lib/FluentNHibernate/Antlr3.Runtime.dll revisions&amp;nbsp;&lt;br /&gt;(etc... a LOT... 4346 times, actually)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Turns out that the problem was just that there were a lot of revisions of everything, and it was all just taking a while. Oops, I probably should have waited instead of restarting it, I would have been 10 minutes further along. But at least with the debug switch now, I know what it&amp;#39;s doing and where it&amp;#39;s up to if it&amp;#39;s going to take a long time.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=68091" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/mercurial/default.aspx">mercurial</category><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/hg/default.aspx">hg</category></item><item><title>Python’s Yield</title><link>http://devlicio.us/blogs/anne_epstein/archive/2011/01/27/python-s-yield.aspx</link><pubDate>Thu, 27 Jan 2011 06:40:31 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:64880</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>1</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=64880</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2011/01/27/python-s-yield.aspx#comments</comments><description>&lt;p&gt;This week, I’ve been playing with implementing an HTTP client in Python. Why Python? It seemed like a straightforward language for this sort of thing, and the fact that I don’t know it at all is a bonus learning opportunity! In any case, I make no claim to be any kind of authority at all on the language.&amp;#160; I am certain there are better ways to do all of these things, but I don’t know them-yet!&lt;/p&gt;  &lt;p&gt;After a bit of googling and reading and copying and pasting, I ended up with the following methods.    &lt;br /&gt;&lt;/p&gt;  &lt;pre class="python" name="code"&gt;def getLine(s):
    line = &amp;#39;&amp;#39;
    for l in iter(lambda: s.recv(1), &amp;#39;\r&amp;#39;):
        if (l != &amp;#39;\n&amp;#39;):
            line += l
    return line

def getHeader(s)
    for line in iter(lambda:getLine(s), &amp;#39;&amp;#39;):
        yield line

def getContentLength(header):    
    for line in header:
        if (re.match(&amp;quot;Content-Length: \d+$&amp;quot;, line)):
            return int(line[line.find(&amp;quot;: &amp;quot;)+2 : len(line)])
    return -1

def fetchlines(s):
    header = getHeader(s)
    contentLength = getContentLength(header)

# other methods to get message
&lt;/pre&gt;


&lt;p&gt;
  &lt;br /&gt;Assume &lt;strong&gt;fetchlines&lt;/strong&gt; is the entry to the above methods. First, we are getting header lines up to the first empty line, then, we are pulling the content length from those lines.&amp;#160; In later code (not shown), I get the body of the message based on the content length. But I had a strange problem. Here’s the header I was receiving: 

  &lt;br /&gt;&lt;/p&gt;

&lt;pre class="plain" name="code"&gt;HTTP/1.1 200 OK
Date: Wed, 26 Jan 2011 06:36:39 GMT
Server: Apache/2.2.4 (Unix) mod_ssl/2.2.4 OpenSSL/0.9.8a DAV/2 PHP/5.2.1
Last-Modified: Tue, 14 Dec 2010 17:23:19 GMT
ETag: &amp;quot;1104fb7-1ea9-4976213a4cfc0&amp;quot;
Accept-Ranges: bytes
Content-Length: 7849
Vary: Accept-Encoding
Content-Type: text/html

&amp;lt;!DOCTYPE html PUBLIC &amp;quot;-//W3C//DTD XHTML 1.0 Strict//EN&amp;quot;
	&amp;quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&amp;quot;&amp;gt;
...
&lt;/pre&gt;

&lt;p&gt;
  &lt;br /&gt;I was expecting to pop everything down through the Content-Type line into &lt;strong&gt;header&lt;/strong&gt;, then get the content length value in &lt;strong&gt;contentLength&lt;/strong&gt;, then, socket reading being a one-way operation, the message reading code would start right on in at the DOCTYPE line.&amp;#160; Instead, I kept having a strange problem:&amp;#160; every time I started in on reading the message body, I ended up at the Vary line instead!&amp;#160; I wasn’t sure why: I was specifically looking to read down to first blank line, and there was no blank line before Vary.&amp;#160; I looked for hidden \r characters; nothing.&lt;/p&gt;

&lt;p&gt;After some reading, I discovered the problem: yield.&amp;#160; Though looking at a few examples, I’d been under the impression that yield was essentially a nice and terse&amp;#160; loop-returning-collection construct; i.e. it would run through, collect up all the results, return as a collection all by itself.&amp;#160; I did remember hearing something about yield being *weird* in C#, but I hadn’t actually ever used it, and couldn’t remember was the problem was.&amp;#160;&amp;#160; Besides, this was Python, and it seemed to be working, except for this little issue. How magical.&lt;/p&gt;

&lt;p&gt;Uh... no.&lt;/p&gt;

&lt;p&gt;Turns out this a case where it may have sorta looked like a duck, but it was something else entirely...&amp;#160; Though I was treating the function’s return value like the list I believed it to be, &lt;strong&gt;getHeaders&lt;/strong&gt; wasn’t returning a list.&amp;#160; It was returning an iterator to a generator function.&amp;#160; A generator function, when called through an iterator, will run through its body, return a value back&amp;#160; to the accessor of the iterator at the yield, save state, and just hang out until you call for the next iterator item, at which point, it will resume on the line after the yield.&amp;#160; So the loop in &lt;strong&gt;getHeaders&lt;/strong&gt; wasn’t actually run when the method was initially called-instead, each run through that loop is done once for each run through &lt;strong&gt;getContentLength&lt;/strong&gt;’s loop... and since &lt;strong&gt;getContentLenth&lt;/strong&gt; exits once it gets the content length, it never pulls the rest of the items from the iterator, and so, the Vary line never gets pulled in &lt;strong&gt;getHeaders&lt;/strong&gt;... the blank line condition is never even hit, and the Vary line is still waiting to get pulled when it was time to read the message body-not what we want!&lt;/p&gt;

&lt;p&gt;A quick fix? take out the yield and use a regular list. It adds an extra line or two over the yield, but this way, I’m sure the full header gets read before pulling the message body. 
  &lt;br /&gt;&lt;/p&gt;

&lt;pre class="python" name="code"&gt;def getHeader(s):
    header =[]
    for line in iter(lambda:getLine(s), &amp;#39;&amp;#39;):
        header.append(line)
        print line
    return header&lt;/pre&gt;

&lt;p&gt;
  &lt;br /&gt;Lessons to take from this: 1) yield is NOT generating a list, it’s an iterator pointing to a generator, which is entirely&amp;#160; different, and on a broader note, 2) copying and pasting code that you don’t understand can result in behavior you don’t understand-or even worse, behavior that you only &lt;em&gt;think&lt;/em&gt; you understand!&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=64880" width="1" height="1"&gt;</description></item><item><title>R is for Reliability</title><link>http://devlicio.us/blogs/anne_epstein/archive/2011/01/08/r-is-for-reliability.aspx</link><pubDate>Sun, 09 Jan 2011 00:59:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:64490</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>0</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=64490</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2011/01/08/r-is-for-reliability.aspx#comments</comments><description>&lt;p&gt;

&lt;/p&gt;
&lt;div&gt;&lt;span&gt;37 Signals was in the news last year for the wrong reasons: they experienced multiple outages in a short time period. &amp;nbsp;From their own site, &lt;/span&gt;&lt;a href="http://productblog.37signals.com/products/2010/12/explaining-the-campfire-outage-on-november-30th.html"&gt;&lt;span&gt;&amp;ldquo;&lt;/span&gt;&lt;span&gt;The virtual machines had severely degraded network and CPU performance causing them to grind to a halt.&amp;rdquo;&lt;/span&gt;&lt;/a&gt;&lt;span&gt; Sounds unfortunate, but these things just happen, right? &lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Well, there&amp;rsquo;s more to it than that. Though accidents happen, reliability isn&amp;rsquo;t luck. &amp;nbsp;The severe problems they had were really due to a series of choices they&amp;rsquo;ve made over the years, one of which was to take their hosting in-house. &amp;nbsp;Hosting some low-usage internal company document server is one thing-but if the core reason for their business relies on those servers, trying to minimize that hosting cost at the possible cost of reliability might be a cost-saving way to shoot themselves in the foot. &amp;nbsp;But enough picking on 37 signals; they are no different from many of us doing dev work-we all tend to think of the development as the &amp;ldquo;real&amp;rdquo; work and everything else as something that&amp;rsquo;s not a big deal, something we maybe don&amp;rsquo;t need pay attention to, something we don&amp;rsquo;t need to pay so much for.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;R&lt;/span&gt;&lt;span&gt;eliability is not simple. &amp;nbsp;It&amp;rsquo;s easy enough to get something working, but what if people come to rely on the application on that server being up at all times? &amp;nbsp;What if getting people to depend on you always being available is part of your business strategy? &amp;nbsp;People wanting high uptime throw around nines of uptime, as in 3-nines or 5-nines (i.e. 99.999% uptime)Think about that: that means you get only a few minutes a year for your service to be down, and that year includes thanksgiving, Black Friday, 4am on any random day, etc. &amp;nbsp;For really high uptime, by the time you get the alert you&amp;rsquo;re already near the end of your SLA.&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;To get that kind of reliability, your infrastructure has got to be be ready for even the rare cases-what if the power goes out? What if it stays out for 4 days? &amp;nbsp;What if internet is down? What if the internet provider&amp;rsquo;s pipe goes down? What if a server&amp;rsquo;s fan goes bad and that server&amp;rsquo;s fan type isn&amp;rsquo;t sold in the area? What if the area is flooded by a freak storm? What if the server room catches on fire? What if someone trips over a cord? What if all the support people going to lunch together get hit by a car? What if your so-far-been-reliable data supplier with the 99.999% SLA has a big failure of their own? What if there&amp;rsquo;s a bad code deploy...or a bad system update? Maybe these questions seem paranoid, perhaps these things seem silly and too unlikely. &amp;nbsp;And honestly, for many of these possible problems, any one of them is a little rare to much worry about on its own. &amp;nbsp;&amp;nbsp;&amp;nbsp;What you&amp;rsquo;re concerned about isn&amp;rsquo;t single risks on their own, it&amp;rsquo;s your entire system-level risk profile-how likely is &lt;i&gt;anything&lt;/i&gt; of any kind going to happen. &amp;nbsp;Calculating, and then bringing that risk down, ends up looking at the small things in service of understanding that big picture. There&amp;rsquo;s a pretty good chance at least one of these sorts of things will happen this year, or maybe next year-the thing is you don&amp;rsquo;t know what thing it&amp;rsquo;ll be, so for high reliability, you have to be ready for a lot.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;What does &amp;ldquo;ready&amp;rdquo; mean? Well, for the internet, it may mean paying to have multiple internet providers, It may mean replacing servers while they are still running well to ensure parts are available. Generators, along with fuel and staff that know how to work it are a must. &amp;nbsp;Many of these require having several complete systems ready to take over at any sign of trouble, located other places in the country, already spun up, with staff. &amp;nbsp;It may mean having more duplicate systems to bring OS updates and patches online with the ability to fall back at any sign of trouble. It may mean spending much more on software development to build in the ability for the software to handle a multitude of seemingly unlikely scenarios, be it slowdowns, flaky connections, incorrect data, 20x the normal load, or dependencies failing. &amp;nbsp;Certainly, there may be some risks that one might not want to prepare for-either because they are too unlikely, it&amp;#39;s too expensive to prepare, or it&amp;#39;s just a risk that changes the whole picture. &amp;nbsp;Take a space alien invasion of Austin, TX for instance. &amp;nbsp;This could be a severe risk for my company&amp;#39;s &amp;nbsp;projects-however it&amp;#39;s a risk that we are OK not mitigating against as 1) to the best of my knowledge a space alien invasion is pretty unlikely 2) fully-office-ready alienproof bunkers are really expensive, and 3) We all might have other things to worry about that point.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;How to handle each type of potential failure is a choice; a choice you may not realize you are making, but a choice nonetheless, and we&amp;rsquo;ve all made them on all of the projects we&amp;rsquo;ve worked on, though we&amp;rsquo;ve probably not thought about it for most of them. &amp;nbsp;All those lines of protective code you didn&amp;rsquo;t write when playing around on a toy project? You could have written them but you didn&amp;rsquo;t (probably a good idea in that case). &amp;nbsp;&amp;nbsp;&amp;nbsp;On the other hand, for a development shop working on a project that requires high reliability, it makes sense to build reliability into the software itself, and it makes sense for the dev shop to handle that internally as part of their core competency; however, for aspects of reliability that are outside of the dev shop&amp;rsquo;s core skillset, the company faces a choice: 1)make all of the minutia another core competency, devoting resources accordingly(i.e. you are no longer just a dev shop, you have specializations in development, disaster mitigation, recovery and server/hosting), 2)outsource those areas to someone for whom it is a core competency, or 3)accept that reliability just isn&amp;rsquo;t going to be what one might like, and set client expectations accordingly. &amp;nbsp;&amp;nbsp;The right choice here is going to depend on company size, resources, and desire for focus; &amp;nbsp;more important than the actual choice made is to make it purposefully and with fullunderstanding-otherwise, there is a fourth possibility-promising a lot more than you&amp;rsquo;re ready to deliver.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;I should mention that a really excellent book on the subject of reliability is &lt;a href="http://pragprog.com/titles/mnee/release-it"&gt;Release It!&lt;/a&gt; If you&amp;rsquo;ve ever made a promise of uptime and not thought much about what you were promising, this book will give you a lot to think about.&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=64490" width="1" height="1"&gt;</description></item><item><title>Passive voice is killing your design documents</title><link>http://devlicio.us/blogs/anne_epstein/archive/2010/08/11/passive-voice-is-killing-your-design-documents.aspx</link><pubDate>Thu, 12 Aug 2010 01:02:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:61448</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>4</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=61448</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2010/08/11/passive-voice-is-killing-your-design-documents.aspx#comments</comments><description>&lt;p&gt;
&lt;div&gt;&lt;span id="internal-source-marker_0.3196091363206506"&gt;Recently, I was proofreading a technical design document written about a system that was unfamiliar to me. It described the flow of execution through a fairly complex system. I found it more difficult to follow the design than it should have been, but I wasn&amp;rsquo;t sure why. &amp;nbsp;After some thought, I narrowed down on the culprit-like many technical documents, this one was written using a fair amount of passive voice. &lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;ldquo;The &lt;/span&gt;&lt;span&gt;passive voice&lt;/span&gt;&lt;span&gt; is a grammatical construction (a &lt;/span&gt;&lt;span&gt;&amp;quot;voice&amp;quot;&lt;/span&gt;&lt;span&gt;) in which the &lt;/span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span&gt; of a sentence or clause denotes the entity undergoing an action or having its state changed.&amp;rdquo; (&lt;/span&gt;&lt;a href="http://en.wikipedia.org/wiki/English_passive_voice"&gt;Wikipedia&lt;/a&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &amp;nbsp;For example, an active voice sentence would be &amp;ldquo;I dyed the cat green&amp;rdquo;, while examples of passive voice would be &amp;ldquo;The cat was dyed green by me&amp;rdquo;, or even just &amp;ldquo;The cat was dyed green&amp;rdquo;. &amp;nbsp;Notice that in the last of these sentences, you never learn that it was me that did the cat-dying; I&amp;rsquo;ve managed to forgo letting you know how I was involved at all. &amp;nbsp;I think many of us in technical fields have learned to write this way throughout our careers. &amp;nbsp;From being asked for impossibly precise estimates to being asked if certain unknown tasks were possible- then being held to those guesses, I think it&amp;rsquo;s become a defense mechanism for many of us to distance ourselves from the things we are trying to communicate.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;There&amp;rsquo;s a problem with this natural instinct to fall back to the safety of passive voice: it can sabotage our attempts at clarity. &amp;nbsp;Following the flow of an unfamiliar technical topic is often *hard*. &amp;nbsp;The person trying to absorb knowledge is carefully trying to follow the process from step to step, somewhat like a driver trying to make his/her way to an unfamiliar destination using printed-out google map directions. &amp;nbsp;&amp;nbsp;A passive voice sentence in a description of a complex technical algorithm can feel analogous to ripping out the middle steps of those google map driving directions. The result is something like this: &amp;ldquo;X performs an action, sending data to Y. &amp;nbsp;Y then transforms the data. &amp;nbsp;Action Q is then performed&amp;rdquo; This leaves the reader asking, &amp;quot;Why was Action Q performed? Was it X, Y, or something else entirely that performed action Q? How many intervening steps are invisible/implied? Is Q even related to X and Y?&amp;quot;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Another reason people use the passive voice is because they aren&amp;rsquo;t sure about some of the details at the time of writing, so passive voice provides a way to keep the document smooth and professional-sounding, while providing the illusion of continuity. &amp;nbsp;&amp;nbsp;I would argue that if the goal is to communicate clearly, actually stating and explaining the uncertainties is going to be much more helpful to readers. Consider this revised version of the previous phrase: &amp;ldquo;X performs an action, sending data to Y. &amp;nbsp;Y then transforms the data. &amp;nbsp;Then, either Y or X performs Action Q -we&amp;#39;re not sure which, it all depends on what turns out to be less resource-intensive, and we haven&amp;#39;t finished that research yet&amp;rdquo;. &amp;nbsp;This new version may feel more wishy-washy, but it is easier to follow, and it communicates some important uncertainties that the previous version glossed over.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Maybe discussing a grammatical point seems to be a bit off-topic or even pointlessly fussy, but use of the passive voice has a real effect on the readability of explanatory/design documents.&amp;nbsp;&lt;/span&gt;&amp;nbsp;(In comparison, I doubt the lay/lie grammar issue or even use of a word like irregardless is going to cause any real readability problems if they show up in your document)&amp;nbsp;Most programmers have to write this kind of document now and then, and it is in everyone&amp;rsquo;s interest that we write these documents so others can understand what we&amp;rsquo;ve written. A little thought about wording can go a long way towards making documents a means of communication instead of another reason for confusion.&lt;/div&gt;
&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=61448" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/writing/default.aspx">writing</category><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/grammar/default.aspx">grammar</category></item><item><title>Why I'm a programmer</title><link>http://devlicio.us/blogs/anne_epstein/archive/2010/06/19/why-i-m-a-programmer.aspx</link><pubDate>Sat, 19 Jun 2010 05:23:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:60644</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>28</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=60644</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2010/06/19/why-i-m-a-programmer.aspx#comments</comments><description>&lt;p&gt;I don&amp;#39;t know if you can say I&amp;#39;m a programmer solely because of my dad(by
 training a physicist and by profession an engineer), but he was a big 
part of getting me there.&amp;nbsp; I had my first experiences with a computer 
when I was very young, no older than 4 or 5- my dad would write small games 
games for me on the TRS-80: One of them was a spelling game: a word 
would flash for a moment on the screen, and then I&amp;#39;d type in the word, 
and the program would say whether I&amp;#39;d spelled the word right-this was 
obviously cool for both of us.&amp;nbsp; He also showed me how he could tweak the
 game-add new words, adjust timing, etc.&amp;nbsp; When I was just a bit older, 
my dad got me a kids&amp;#39; book on BASIC , and I went through most of the 
programs in there, running them on that TRS-80 by repeatedly listing 
them out to look over the code, and editing by individual line. vi would
 have been full-screen luxury, but the line editor was all I knew, so it
 was great! (Yup, one of those programs was a choose your own adventure 
game).&amp;nbsp; &lt;/p&gt;
&lt;p&gt;Aside from the actual programming, my dad&amp;#39;s interest in computers kind of 
infiltrated the house-there were occasional cassette tapes labeled with 
program names and printed program listings lying around in drawers near 
the computer, and we tied up the tomato plants in the backyard with tape
 from spools of old reel-to-reel that was being thrown out at his office.&amp;nbsp; (We 
also tied up the dog with some aircraft carrier deck emergency barricade
 line, but that&amp;#39;s another story)&amp;nbsp; Later on, when we finally replaced the
 TRS-80 with a Gateway 486 and the new computer&amp;#39;s CPU turned out to be 
defective, I remember him replacing that chip while I hung out nearby. 
(It seemed perfectly normal to crack open and poke at the insides of a 
computer on the coffee table, though I&amp;#39;m now pretty sure Gateway would 
have done it for him, had he wanted to ship the machine back)&amp;nbsp; Not to 
mention tons of hacking that autoexec.bat together once that computer 
was finally working...I of course helped through all these things!&lt;br /&gt;&lt;br /&gt;By
 the time I was in middle school or so, my dad had made his way to management.&amp;nbsp; I have many memories of the walks my dad and I 
would go on together, when he&amp;#39;d tell me about what he was learning for work at the time.&amp;nbsp; He spent many walks telling me about Total Quality 
Management(a blatant generalization might be to call it a second cousin 
of Lean), and how they were trying to use it in the Navy.&amp;nbsp; I remember 
him telling me that when one stepped back and looked at results, TQM looked like
 a better way to do things, but a lot of people were resistant.&amp;nbsp; I 
asked, &amp;quot;but if it&amp;#39;s better, why don&amp;#39;t they want to use it? Why won&amp;#39;t 
they at least give it a try?&amp;quot;&amp;nbsp; (heh, a question I&amp;#39;ve asked myself about 
all sorts of things again many times later on.) He said something like 
&amp;quot;some people get comfortable doing things the way they&amp;#39;ve always done 
them, and they don&amp;#39;t want to try things another way.&amp;quot;&amp;nbsp; This answer frustrated me-it seemed so absurd, it couldn&amp;#39;t possibly be true.&amp;nbsp; I believe 
it now; it &lt;i&gt;still&lt;/i&gt; frustrates me, but I understand it better.&lt;br /&gt;&lt;br /&gt;I 
can&amp;#39;t say my dad ever *tried* to make me a programmer-he just showed me 
things that interested him.&amp;nbsp; My experience growing up is not unlike 
those of many other programmers I&amp;#39;ve met-if anything, it may be notable that 
my dad didn&amp;#39;t differentiate at all in showing me and my brother these 
things up to our individual levels of interest. I never got the idea 
that I should be any less interested in computers, in airplanes, or in 
helping to build furniture, than I was in my ballet lessons or drawing.&amp;nbsp;
 I&amp;#39;m very appreciative of that-I didn&amp;#39;t have any idea that some of my 
interests were perhaps &amp;quot;boy&amp;quot; things, and I don&amp;#39;t think I really 
understood that my perception was maybe not the norm until I was older.&amp;nbsp;
 By that time, well, I liked what I liked, and I&amp;#39;m glad things worked out as they did.&amp;nbsp; Thanks, Dad!&lt;/p&gt;
&lt;h3&gt;FEELING 
INSPIRED?&lt;/h3&gt;
&lt;p&gt;I believe one of the best kinds of introduction you can
 give a kid to something you feel passionate about is to just *show* 
them you love it.&amp;nbsp; Not necessarily by (just) teaching, but by example. 
Showing then that you&lt;i&gt; genuinely&lt;/i&gt; think something is a blast is so much more 
inspiring than saying it would be a useful skill, or even claiming that it&amp;#39;s fun (but not appearing to have fun yourself), regardless of the 
subject matter. Those things aside, if you &lt;i&gt;are&lt;/i&gt; interested in directly teaching your kids 
some programming, here are a few resources I&amp;#39;ve run across, though I 
haven&amp;#39;t personally checked them out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.manning.com/sande/"&gt;Hello World!&lt;/a&gt; - A python-based kids&amp;#39; programming book written by a 
father and his 9-year-old son. Scott Hanselman has&lt;a href="http://www.hanselman.com/blog/HanselminutesPodcast194HelloWorldComputerProgrammerForKidsAndOtherBeginners.aspx"&gt; a great podcast episode discussing 
this book&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.dotnetrocks.com/default.aspx?showNum=562"&gt;.NetRocks!
 podcast episode&lt;/a&gt; on teaching programs to kids with &lt;a href="http://msdn.microsoft.com/en-us/beginner/ff384126.aspx"&gt;SmallBasic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://hacketyhack.heroku.com/"&gt;Hackety Hack&lt;/a&gt; -
 a Ruby-based programming environment aimed at beginners (including 
kids)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://scratch.mit.edu/"&gt;Scratch&lt;/a&gt; - Scratch is a programming language that makes it easy to create your own 
interactive stories, animations, games, music, and art -- and share your
 creations on the web.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Edit: Added Scratch)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=60644" width="1" height="1"&gt;</description></item><item><title>Winforms (and WPF) Testing</title><link>http://devlicio.us/blogs/anne_epstein/archive/2010/03/24/winforms-and-wpf-testing.aspx</link><pubDate>Wed, 24 Mar 2010 19:33:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:56255</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>4</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=56255</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2010/03/24/winforms-and-wpf-testing.aspx#comments</comments><description>&lt;p&gt;Those of us who primarily work on web projects have a fairly good idea of what&amp;#39;s available for automating UI testing of web applications; &lt;a href="http://seleniumhq.org/"&gt;Selenium&lt;/a&gt; and &lt;a href="http://watin.sourceforge.net/"&gt;WatiN&lt;/a&gt;&lt;a&gt;&lt;/a&gt;, for instance. At Headspring, we take this up a level by isolating our UI testing code from the nuts and bolts of the page HTML, as well as the equally troublesome nuts and bolts of these UI testing tools. You can take a look at how we do things by taking a look at &lt;a href="http://www.codeplex.com/MVCContrib"&gt;MvcContrib&lt;/a&gt;, which has a testing framework and &lt;a href="http://code.google.com/p/codecampserver/"&gt;Code Camp Server&lt;/a&gt;, which has some example usage.&lt;/p&gt;  &lt;p&gt;A code snippet from Code Camp Server to illustrate:&lt;/p&gt;  &lt;pre class="c-sharp" name="code"&gt;                   Form&amp;lt;logininputproxy&amp;gt;(&amp;quot;/login/login/index&amp;quot;) // url
                       .Input(m =&amp;gt; m.Username, &amp;quot;admin&amp;quot;)
                       .Input(m =&amp;gt; m.Password, &amp;quot;password&amp;quot;)
                       .Submit();&lt;/pre&gt;

&lt;p&gt;It&amp;#39;s great to have these kind of tools for the web, but what if we end up working on a Webforms or WPF project? It turns out there&amp;#39;s a project on CodePlex called &lt;a href="http://www.codeplex.com/white"&gt;white&lt;/a&gt;, and it does for winforms and WPF what WatiN does for the web. Crank it up, and just like WatiN pops up a browser and starts interacting with your web app for you, white will click around and enter text into your winform app. However, working with white as-is means tightly coupling our testing code to the tool-which we want to avoid just as much with winforms as we did with web testing. Not to mention, if white is wrapped well enough that working with it is pretty much the same as working with WatiN, that&amp;#39;s one less difference to remember-meaning more focus on solving problems, and less on figuring out how to work yet-another-tool. We&amp;#39;re only starting out, but here&amp;#39;s an example of how working with it; with any luck, it should look a bit like the code snippet above:&lt;/p&gt;

&lt;pre class="c-sharp" name="code"&gt;            Form&amp;lt;testform2input&amp;gt;(&amp;quot;White Hello World&amp;quot;) // form name
                .Input(x =&amp;gt; x.PersonName, &amp;quot;Bart Simpson&amp;quot;)
                .Submit();&lt;/pre&gt;

&lt;p&gt;Winforms aren&amp;#39;t exactly like the web, and likely the metaphors age going to have to adapt to avoid strange impedance mismatches- so it&amp;#39;s probably not going to be seamless moving from one to the other. However, if it&amp;#39;s similar, taking into account these differences, then when switching to testing a winforms project, it might seem...about the way you&amp;#39;d expect.&lt;/p&gt;

&lt;p&gt;This is very, very young, but the starter code for this wrap of white is available at &lt;a href="http://ajepst.codeplex.com/"&gt;my CodePlex repository&lt;/a&gt;.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=56255" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/winforms+testing+white/default.aspx">winforms testing white</category></item><item><title>On Learning</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/12/28/on-learning.aspx</link><pubDate>Mon, 28 Dec 2009 15:01:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:54786</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>3</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=54786</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/12/28/on-learning.aspx#comments</comments><description>&lt;p&gt;As programmers, we work in a discipline that is a bit different from that of many other people we know.  Many other disciplines have developed to a point of reasonable maturity and stability, with a broad, long-agreed-upon base of knowledge, educational path, and perhaps even certification of some kind to show mastery.   Programming, on the other hand, is comparatively an infant.   The idea of programming as its own discipline is really only a few years old.  Programming has no agreed-upon education of any kind- though degrees in computer science are widespread, it&amp;#39;s not widely accepted that a computer science degree is necessary, or even particularly helpful for those going into a programming career.  In contrast to a long-agreed upon base of knowledge, programming has gone through revolutionary changes every few years.  Whether it&amp;#39;s change of programming language or the changes in software development methodology, programming-related knowledge has a very short half-life.
&lt;/p&gt;
&lt;p&gt;Surrounded by a constantly shifting landscape of knowledge, we can&amp;#39;t stay satisfied with the knowledge acquired a few years ago, or even a few months ago.  Granted, it is probably a bad idea to constantly switch to the newest and coolest thing like some kid always reaching for the shiniest toy, forgetting the pile of neglected toys at his feet, but the discipline of programming is young enough that new, field-changing ideas emerge all the time.  We&amp;#39;re still finding the ground rules here.  I suppose you could keep on working the way you always have, using the languages and techniques that have worked for you for years-however, if you do that without keeping an eye to the future and embracing key improvements, you will likely find yourself in a backwater of software development after some time. Not to mention, if truly improved ways of working come about, it could be considered unethical to blithely ignore them and deliver inferior products. On a different note,  I feel that it&amp;#39;s intrinsic to programming to explore-if exploration of new ideas doesn&amp;#39;t excite you, you may find a career as a programmer a long trudge.
&lt;/p&gt;
&lt;p&gt;As a programmer, you must take responsibility for your own learning and the progression of your career.  Perhaps you currently feel your company should do this for you; after all, it&amp;#39;s work-related, right?  If you feel this way, either change your mind right now, or think about reconsidering your career choice.  Here&amp;#39;s the thing-it doesn&amp;#39;t *matter* to the company how you personally progress in your career and if you learn the new tools -all an organization needs to care about is if the staff as a whole progresses - and it can do that through new hiring if needed, saving them the work of training you.  Truthfully, though you might find it unpleasant, your company might be perfectly happy if you don&amp;#39;t learn-you can be the guy who babysits the old apps which you already know, and they can hire new, fresh guys for new projects. That&amp;#39;s not so good for you though.  If your project gets canceled, you can find yourself with antique knowledge and a lot of years of catching up to get to get yourself in a hire-able state.&lt;/p&gt;
&lt;p&gt;  
From a company&amp;#39;s perspective, keeping up with the pace of things is incredibly important - you don&amp;#39;t want to be the company producing horse-buggies when Ford comes out with the Model-T. Though it can be &amp;quot;easier&amp;quot; to keep moving through turnover and new hiring, actively encouraging learning can really keep employees caring about your company and actively looking out for ways to get better.  Though you might luck out on hiring the right visionary, creating and keeping a whole staff of loyal visionaries is going to be a lot easier and successful in the long run.  (yes, it&amp;#39;s practically a stereotype now, but think of google-many of their successful products emerged through side employee side projects actively fostered by the company) 
&lt;/p&gt;
&lt;p&gt;On a final, personal note: as of early December, I joined the staff at &lt;a href="http://www.headspring.com/"&gt;Headspring&lt;/a&gt;, in Austin, Texas.  One of the things that makes me happiest to work at this company is its commitment to growth and development of its staff, and its excellent balance between recognizing the things that have made the company a success, and recognizing that continuing that success means not resting on its laurels.  Today, I will be spending the day at an all-day technical staff retreat, and I&amp;#39;m looking forward to it. Learning can be a lot of work, but it&amp;#39;s really fulfilling.  Off to learn now...&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=54786" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/learning/default.aspx">learning</category></item><item><title>NHibernate and Composite Keys</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/11/20/nhibernate-and-composite-keys.aspx</link><pubDate>Sat, 21 Nov 2009 01:29:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:53844</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>17</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=53844</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/11/20/nhibernate-and-composite-keys.aspx#comments</comments><description>&lt;p&gt;Composite IDs are a common pain point a beginning NHibernate user runs into.&amp;nbsp; Here&amp;#39;s everything you need to get them up and running. &lt;/p&gt;
&lt;p&gt;First, a caveat: composite keys are certainly mappable in NHibernate, but it&amp;#39;s a little trickier than a typical single identity key would be.&amp;nbsp; Compared to a normal key, there&amp;#39;s some extra setup work, queries are a bit more painful, and they tend to be less optimized in terms of lazy loading. Because of these things, experienced NHibernate users often avoid composite keys entirely when possible.&amp;nbsp; However,&amp;nbsp; there are many legacy situations where multiple existing apps all hit the same db-a situation in which, if a composite key is already in place, it&amp;rsquo;s probably going to have to stay.&amp;nbsp; As that&amp;#39;s the most common use case for composite keys, I&amp;#39;ll start from the assumption that you&amp;#39;ve got an existing database that you can&amp;#39;t alter. (this is a *bad thing* - see &lt;a href="http://nhforge.org/blogs/nhibernate/archive/2009/06/26/database-the-eliot-ness-of-it.aspx"&gt;THIS POST&lt;/a&gt; for why, but as developers, those kinds of decisions aren&amp;#39;t always under our control)&lt;/p&gt;
&lt;h3&gt;YOUR OBJECTS&lt;/h3&gt;
&lt;p&gt;As I mentioned above, if you&amp;#39;re considering mapping a composite key, you probably already have a database. (if not,&amp;nbsp; I&amp;rsquo;d highly advise an alternative-perhaps sets, perhaps idbags, but that&amp;rsquo;s for another blog post) The NORMAL, PREFERRED direction of model design would be to work up your classes and once they work the way you want, extract the persistence structure from that (i.e. your DB). But if that were an option for you... well, you probably wouldn&amp;#39;t be using a composite key in the first place.&amp;nbsp; Anyway, let&amp;#39;s take a scenario:&lt;/p&gt;
&lt;p&gt;Your existing tables:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://docs.google.com/File?id=dtmkfh7_29ghmckcd7_b" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In our brand new NHibernate app, we want to have an object that corresponds to the CategoryProducts idea.&amp;nbsp; This is a start:&lt;/p&gt;
&lt;pre class="c-sharp" name="code"&gt;namespace SuperShop.Domain
{
    public class CategoryProduct
    {
        public virtual Product Product { get; set; }
        public virtual Category Category { get; set; }
        public virtual string CustomizedProductDescription { get; set; }

        private DateTime _LastModifiedOn;

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;
            var t = obj as CategoryProduct;
            if (t == null)
                return false;
            if (Product == t.Product &amp;amp;&amp;amp; Category == t.Category)
                return true;
            return false;
        }
        public override int GetHashCode()
        {
            return (Product.SKU + &amp;quot;|&amp;quot; + Category.Name).GetHashCode();
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;So, why the Equals and GetHashcode?&amp;nbsp; If you try to map a composite key without them, you&amp;#39;ll get an NHibernate error stating that they are required.&amp;nbsp; Here&amp;#39;s why: With this two part identifier, NHibernate can&amp;#39;t do a simple single id object compare - you need to tell it how to decide equality. Implementing Equals and GetHashcode are always a good idea for anyway so your objects will be have properly in cases like multi-session scenarios where an unsaved object might really be the same as an existing object elsewhere, but in the composite key scenario, not having it is not an option- NHibernate doesn&amp;#39;t even *have* a mostly-works technique to fall back on. (Note, this is almost certainly not the most ideal Equals and GetHashcode implementation-take a look &lt;a href="http://devlicio.us/blogs/billy_mccafferty/archive/2007/04/25/using-equals-gethashcode-effectively.aspx"&gt;here&lt;/a&gt; for more on the topic- but hopefully this gives you the general idea. )&lt;/p&gt;
&lt;h3&gt;MAPPING&lt;/h3&gt;
&lt;p&gt;A mapping:&lt;/p&gt;
&lt;pre class="xml" name="code"&gt;&amp;lt;hibernate-mapping&amp;gt;   
&amp;lt;class table=&amp;quot;OrderItemProductDetails&amp;quot; name=&amp;quot;SuperShop.Domain.ComponentPersonalization, SuperShop.Domain&amp;quot;&amp;gt;
    &amp;lt;composite-id&amp;gt;
        &amp;lt;key-many-to-one class=&amp;quot;SuperShop.Domain.OrderItemComponent,SuperShop.Domain&amp;quot; name=&amp;quot;OrderItemComponent&amp;quot; column=&amp;quot;OrderItemProductID&amp;quot; /&amp;gt;
        &amp;lt;key-property name=&amp;quot;DetailType&amp;quot; column=&amp;quot;DetailTypeID&amp;quot; type=&amp;quot;SuperShop.Domain.DetailTypes,SuperShop.Domain&amp;quot; /&amp;gt;
    &amp;lt;/composite-id&amp;gt;
    &amp;lt;version name=&amp;quot;LastModifiedOn&amp;quot; column=&amp;quot;LastModifiedOn&amp;quot; type=&amp;quot;timestamp&amp;quot; access=&amp;quot;field.pascalcase-underscore&amp;quot; /&amp;gt;
    &amp;lt;property name=&amp;quot;DetailValue&amp;quot; column=&amp;quot;DetailValue&amp;quot; type=&amp;quot;String&amp;quot;&amp;gt;&amp;lt;/property&amp;gt;
    &amp;lt;property name=&amp;quot;DetailCharge&amp;quot; column=&amp;quot;DetailCharge&amp;quot; type=&amp;quot;Decimal&amp;quot;&amp;gt;&amp;lt;/property&amp;gt;

&amp;lt;/class&amp;gt;
&amp;lt;/hibernate-mapping&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Note the &lt;b&gt;&amp;lt;version&amp;gt;&lt;/b&gt; element, as well as the matching _LastModifiedOn in the class above. These two items combined let NHibernate know how to tell if an entity is new or not.&amp;nbsp; In the usual scenario where NHibernate manages the ID, NHibernate monitors whether the id value is the original unsaved value and determines whether to Save or Update for you if you call SaveOrUpdate(), a very handy method.&amp;nbsp; If NHibernate is not managing the ID, as is the case in an Assigned ID (think an SSN that you manage) or in composite (where you create the relationships or values yourself that make the id)&amp;nbsp; then it doesn&amp;#39;t know how to tell if your id is saved or not-its usual technique doesn&amp;#39;t work.&amp;nbsp; So with &lt;b&gt;&amp;lt;version&amp;gt;&lt;/b&gt; NHibernate gets a column it has control over, and can safely monitor for an unsaved value. Without this, you&amp;#39;d be unable to use SaveOrUpdate with this element-you&amp;#39;d have to call Save or Update as appropriate-additionally, since ALL cascading functions on collections are essentially NHibernate calling SaveOrUpdate, you&amp;#39;re not going to be able to use cascading. Alternatively, if you don&amp;#39;t like the &lt;b&gt;&amp;lt;version&amp;gt;&lt;/b&gt; column, you could implement IInterceptor &amp;#39;s IsTransient() method to get similar functionality.&amp;nbsp;&amp;nbsp; (&lt;a href="http://www.nhforge.org/doc/nh/en/index.html#components-compositeid"&gt;see documentation at nhforge&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;So, if you want to take the &lt;b&gt;&amp;lt;version&amp;gt;&lt;/b&gt; approach, you&amp;#39;ll need to add a new DateTime column to your CategoryProducts table:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://docs.google.com/File?id=dtmkfh7_30fmsbthcg_b" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;QUERYING&lt;/h3&gt;
&lt;p&gt;An inconvenient aspect of composite ids is the need to query on all parts of the id.&amp;nbsp; For instance: a GetByID query: &lt;/p&gt;
&lt;p&gt;
  &lt;br /&gt;&lt;b&gt;from CategoryProducts c where c.Products = :p and c.Category = :cat&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;br /&gt;but it&amp;#39;s not just on the GetByID, it&amp;#39;s *whenever* you might need to search using a particular CategoryProduct. For instance,&lt;/p&gt;
&lt;p&gt;
  &lt;br /&gt;&lt;b&gt;select distinct p from ProductImages p join p.CategoryProducts c where c.Products = :p and c.Category = :cat&lt;/b&gt;&lt;/p&gt;
&lt;h3&gt;ID OBJECT&lt;/h3&gt;
&lt;p&gt;Composite IDs can be problematic for lazy loading... When lazy loading, NHibernate will get just the ids of a collection, and hold off on getting the rest of the object until it&amp;#39;s needed. An important fact- NHibernate can&amp;#39;t partially load an object-in terms of discrete &amp;quot;things&amp;quot;. If you&amp;#39;re talking a plain integer ID, it can load up the integer, and then load up the associated object later.&amp;nbsp; With our object as specified above, the smallest single discrete thing that contains the key is... the whole object- so, you&amp;#39;ve effectively killed your lazy loading.&amp;nbsp; What to do if we want lazy loading?&amp;nbsp; Well, make something smaller that contains the key.&amp;nbsp; Let&amp;#39;s make that ID object:&lt;/p&gt;
&lt;pre class="c-sharp" name="code"&gt;[Serializable]
public class CategoryProductIdentifier {
        public virtual int ProductId { get; set; }
        public virtual int CategoryId { get; set; }

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;
            var t = obj as CategoryProductIdentifier;
            if (t == null)
                return false;
            if (ProductId == t.ProductId &amp;amp;&amp;amp; CategoryId == t.CategoryId)
                return true;
            return false;
        }
        public override int GetHashCode()
        {
            return (ProductId + &amp;quot;|&amp;quot; + CategoryId).GetHashCode();
        }
}
&lt;/pre&gt;
&lt;p&gt;
then, CategoryProduct becomes: 
&lt;/p&gt;
&lt;pre class="c-sharp" name="code"&gt;public class CategoryProduct
{
        private CategoryProductIdentifier _categoryProductIdentifier = new CategoryProductIdentifier();
        public virtual CategoryProductIdentifier CategoryProductIdentifier
        {
            get { return _categoryProductIdentifier; }
            set { _categoryProductIdentifier = value; }
        }

        private Product _Product;
        public virtual Product Product
        {
            get { return _Product; }
            set { _Product = value;
                _categoryProductIdentifier.ProductId = _Product.Id; }
        }

        private Category _Category;
        public virtual Category Category
        {
            get { return _Category; }
            set { _Category = value;
                _categoryProductIdentifier.CategoryId = _Category.Id; }
        }
        public virtual string CustomizedProductDescription { get; set; }

}
&lt;/pre&gt;
&lt;p&gt;Mapping Tweaks: &lt;/p&gt;
&lt;pre class="xml" name="code"&gt; 
&amp;lt;hibernate-mapping&amp;gt;
&amp;lt;class name=&amp;quot;CategoryProduct&amp;quot; table=&amp;quot;CategoryProducts&amp;quot;&amp;gt;
    &amp;lt;composite-id name=&amp;quot;CategoryProductIdentifier&amp;quot; class=&amp;quot;CategoryProductIdentifier&amp;quot;&amp;gt;
        &amp;lt;key-property name=&amp;quot;ProductId&amp;quot; column=&amp;quot;ProductID&amp;quot; type=&amp;quot;Int32&amp;quot; /&amp;gt;
        &amp;lt;key-property name=&amp;quot;CategoryId&amp;quot; column=&amp;quot;CategoryID&amp;quot; type=&amp;quot;Int32&amp;quot; /&amp;gt;
        &amp;lt;version name=&amp;quot;LastModifiedOn&amp;quot; type=&amp;quot;timestamp&amp;quot; column=&amp;quot;LastModifiedOn&amp;quot; /&amp;gt;
    &amp;lt;/composite-id&amp;gt;
    &amp;lt;many-to-one name=&amp;quot;Product&amp;quot; column=&amp;quot;ProductID&amp;quot; class=&amp;quot;Product&amp;quot; insert=&amp;quot;false&amp;quot; update=&amp;quot;false&amp;quot; access=&amp;quot;field.pascalcase-underscore&amp;quot; /&amp;gt;
    &amp;lt;many-to-one name=&amp;quot;Category&amp;quot; column=&amp;quot;CategoryID&amp;quot; class=&amp;quot;Category&amp;quot; insert=&amp;quot;false&amp;quot; update=&amp;quot;false&amp;quot; access=&amp;quot;field.pascalcase-underscore&amp;quot; /&amp;gt;
    &amp;lt;property name=&amp;quot;CustomizedProductDescription&amp;quot; column=&amp;quot;CustomizedProductDesc&amp;quot; /&amp;gt;
&amp;lt;/class&amp;gt;
&amp;lt;/hibernate-mapping&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The key thing to note here is that Product and Category are referred to twice in both the class and the mapping.&amp;nbsp; The reason for this is that caching uses primitives like int or string, so we need to feed it something caching-ready.The reason for this is because to index by a custom class in the cache, this composite id class, like an ordinary id, gets serialized.&amp;nbsp; This serializability comes for free if your id is a single int, but your id is your own custom object, as with this composite id class, you&amp;#39;ve got to explicitly both specify that serialization is allowed, and make sure the object is valid for serialization.&amp;nbsp; So we&amp;#39;ve pulled out those ids as ints into the identifier. However, we still want to be able to traverse these relationships, so we still include the class.&amp;nbsp; However, if NHibernate tried to update the same db field twice, you&amp;#39;d get errors.&amp;nbsp; To be able to have both the product relation and the ProductId mapped separately in the same class, we mark the class reference as non-updatable.&amp;nbsp; Also, note that the Equals and GetHashcode moved to the CategoryProductIdentifier class - the CategoryProduct class is for the most part free of the composite burden; the burden of composite-ness is now on the CategoryProductIdentifier class.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;It&amp;rsquo;s entirely possible I&amp;rsquo;ve missed something in regards to NHibernate usage with composite keys-if so, let me know, and I&amp;rsquo;ll add it in.&amp;nbsp; If I got anything factually wrong, let me know about that too so I can make it right!&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;EDIT:&amp;nbsp; Added in serialization info that I&amp;#39;d forgotten. Thanks to bonskijr for letting me know!&lt;/i&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=53844" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/NHibernate/default.aspx">NHibernate</category></item><item><title>NHibernate Caching and Assigned Ids - A Potential Pitfall</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/10/08/nhibernate-caching-and-assigned-ids-a-potential-pitfall.aspx</link><pubDate>Thu, 08 Oct 2009 13:53:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:52457</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>4</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=52457</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/10/08/nhibernate-caching-and-assigned-ids-a-potential-pitfall.aspx#comments</comments><description>&lt;p&gt;Yesterday, I spent some time setting up and optimizing SysCache2 caching.&amp;nbsp; (By the way, if you&amp;#39;re doing this kind of NHibernate tuning, NHProf - a devlicio.us friend - is crucial, it would have definitely been a much tougher slog without it) With caching turned on, one of our previously well-functioning classes was consistently giving the following error EVERY time we tried to modify and save an instance: &lt;/p&gt;
&lt;p&gt;ReadOnlyCache: Can&amp;#39;t write to a readonly object&lt;/p&gt;
&lt;p&gt;Looking this up, I ran across a LOT of information about how this meant the cache line in the mapping was wrong - that it needed to be marked read-write, not read-only.&amp;nbsp; The odd thing was, this is what the cache line in our mapping was like already:&lt;/p&gt;
&lt;p&gt;&amp;lt;cache usage=&amp;quot;read-write&amp;quot; region=&amp;quot;SomeRegion&amp;quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;So, our problem was NOT the obvious issue that our object was improperly marked as readonly.&amp;nbsp; But... after a lot of flailing around, the solution emerged.&amp;nbsp; Check out this definition of the ID on this entity:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;id name=&amp;quot;Id&amp;quot; column=&amp;quot;EntityID&amp;quot; type=&amp;quot;Int32&amp;quot; unsaved-value = &amp;quot;0&amp;quot;&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;generator class=&amp;quot;assigned&amp;quot; /&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;/id&amp;gt;&lt;/p&gt;
&lt;p&gt;Note the use of &amp;quot;assigned&amp;quot; for the id generator.&amp;nbsp; It means NHibernate doesn&amp;#39;t control the id-it&amp;#39;s manually managed. Now if you&amp;#39;re familiar with the assigned generator, you know that it&amp;#39;s a somewhat troublesome thing to work with-specifically, if you call SaveOrUpdate on an entity using it, NHibernate won&amp;#39;t know whether it&amp;#39;s a new entity or not on its own, because NHibernate tracks the state of the entiy using the id.&amp;nbsp; This affects cascade saves as well, so assigned ids aren&amp;#39;t fun. If you take over managing that id yourself, NHibernate doesn&amp;#39;t have anything to hang its tracking off of.&amp;nbsp; Assigned ids are something you should probably avoid for new applications, but&amp;nbsp; sometimes that&amp;#39;s not an option&amp;nbsp; for legacy apps.... If you NEED to use an assigned id there&amp;#39;s an easy way to get this entity acting like a normal NHibernate entity-you can give NHibernate something to use like this: &lt;/p&gt;
&lt;p&gt;&amp;lt;version name=&amp;quot;LastModifiedOn&amp;quot; type=&amp;quot;timestamp&amp;quot; column=&amp;quot;LastModifiedOn&amp;quot;&amp;nbsp;&amp;nbsp;&amp;nbsp; /&amp;gt;&lt;br /&gt;-with the matching column, and the matching property in the class - or maybe even just using the access=&amp;quot;field..&amp;quot; option in the mapping so it&amp;#39;s not mistakenly taken to be something to be part of the class&amp;#39;s regular api by another programmer-this field is NOT FOR YOU, it&amp;#39;s for NHibernate. Look at it if you want, but do not touch! Note that if you add a column like this to an existing table, you&amp;#39;re going to have to prepopulate the existing rows for your your new column with an initial datetime or you&amp;#39;ll get errors-any date at all, so long as it&amp;#39;s in the past. 1/1/2009 12:00:00 is as good a value as any.... &lt;/p&gt;
&lt;p&gt;What does this have to do with my caching problem?&amp;nbsp; The particular entity I was getting the erroneous readonly caching&amp;nbsp; problems on didn&amp;#39;t have the &amp;lt;version&amp;gt; property-a bad state of things for an assigned id entity.&amp;nbsp; Oddly, it *was* in fact saving properly using SaveOrUpdate - perhaps it was the  unsaved-value = &amp;quot;0&amp;quot; on the id allowing it to work, maybe not.&amp;nbsp; Anyway, the solution ended up being adding a &amp;lt;version&amp;gt; property and column, and then it worked like a charm. I probably should have caught that this entity was missing &amp;lt;version&amp;gt; earlier, but it seemed to work ok, and the potential problem stayed hidden until the entity got thrown into the cache.&lt;/p&gt;
&lt;p&gt;By the way, if you don&amp;#39;t like&amp;nbsp; &amp;lt;version&amp;gt;, there are alternatives, like &lt;tt class="literal"&gt;&lt;a href="http://www.nhforge.org/doc/nh/en/index.html#example-parentchild-update"&gt;IInterceptor.IsTransient() &lt;/a&gt;&lt;br /&gt;&lt;/tt&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=52457" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/NHibernate/default.aspx">NHibernate</category><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/Caching/default.aspx">Caching</category></item><item><title>You are your Institutional Knowledge</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/08/27/you-are-your-institutional-knowledge.aspx</link><pubDate>Fri, 28 Aug 2009 03:38:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:50790</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>5</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=50790</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/08/27/you-are-your-institutional-knowledge.aspx#comments</comments><description>&lt;p&gt;In the midst of recession, many companies are closing locations and
laying off staff to cut costs.&amp;nbsp; The thinking from many of these
companies is that the remaining staff can pick up any projects the
laid-off staff previously did, resulting in all the important work
being accomplished.&amp;nbsp; Secondary tasks may end up being done a bit less
frequently, and any extra can get picked up by contractors, who don&amp;#39;t
entail the same hire/fire burden.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;There are some flaws in
this thinking for anything with real complexity, and these flaws all
relate to institutional knowledge.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;First, it assumes that the
remaining staff knows what is important.&amp;nbsp; In reality, today&amp;#39;s workers
are often so siloed they don&amp;#39;t have any idea what the person two cubes
over is doing.&amp;nbsp; Lay off a few people, and entire critical
infrastructure applications may be forgotten-until they stop working,
to the confusion of the remaining staff.&amp;nbsp; Also likely- remaining staff
will know of the existence of such applications, but most likely won&amp;#39;t
know where such apps sit, or how they work.&amp;nbsp; It&amp;#39;s hard to prioritize
importance when you don&amp;#39;t have the basic facts.&lt;/p&gt;
&lt;p&gt;Second, it
assumes unfamiliar applications are not overly burdensome to learn,
even when the previous maintainers are no longer present.&amp;nbsp; The truth is
that many existing applications were built over many years using
fragile programming techniques, and then went through a maintenance
period that resulted in counterintuitive, self-contradicting,
hard-to-follow, even misleading code.&amp;nbsp; These issues may not have been&amp;nbsp;
issues for the previous maintainers because they were so familiar with
it that they knew all the common problems, and how to handle them. I
think of the old Dodge Caravan I drove around in college-not fuel
injected, so I knew to press the gas when I started. The hydraulics in
the rear door were broken too, so I knew to keep my head down, and I
kept a hockey stick in the back to prop it up. I also knew to use
premium fuel or it would start knocking like crazy. but I knew about
these issues, had workarounds, and was largely unaffected. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://devlicio.us/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/anne_5F00_epstein/250px_2D00_87_2D00_90_5F00_Dodge_5F00_Grand_5F00_Caravan.jpg"&gt;&lt;img src="http://devlicio.us/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/anne_5F00_epstein/250px_2D00_87_2D00_90_5F00_Dodge_5F00_Grand_5F00_Caravan.jpg" border="0" alt="" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;(This one was not mine, but mine was very similar save for being white with fake wood) However,&amp;nbsp;
had I lent the car out to a friend without this info, my friend may not
have even been able to start it, and later probably would have ended up
getting smacked in the head with the rear door, and then wondered if
they broke the thing after filling up with regular gas and hearing
extra knocking.&amp;nbsp; These problems take only a few minutes to understand;
unfortunately, learning all the subtleties of a large, complex codebase
can take months or even longer, even with the guidance of someone with
experience.&lt;br /&gt;
&lt;br /&gt;
Sure, with time, the important applications will be discovered and
eventually learned, but what happens in the meantime?&amp;nbsp; What if the
important app is customer-facing?&amp;nbsp; What if it runs your business&amp;#39;s
billing?&amp;nbsp; Can you live without this app for days while your staff&amp;nbsp;
takes baby steps learning the most basic elements of an app, trying to
figure out why it&amp;#39;s non-functional?&amp;nbsp; Can you live with new and
different critical errors happening repeatedly, as staff learns the
idiosyncracies of the app the hard way?&amp;nbsp; Or, can you live without any
work of substance being done on a critical app for an extended period
of time for fear of encountering the issues mentioned above?&amp;nbsp; This is
what happens with the loss of institutional knowledge.&amp;nbsp; If you can&amp;#39;t
afford these kinds of problems, you might reconsider the nature of your
business... regardless the stated goal of the company or any products
it might sell, reliance on knowledge means your business, IS, to some
level, the institutional knowledge of the people that make it up, and
losing that knowledge could be tantamount to losing the business. Tread
carefully.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;EDIT&lt;/b&gt;: A related concept I really wanted to mention is the idea of the &lt;a href="http://www.agileadvice.com/archives/2005/05/truck_factor.html"&gt;Truck Factor&lt;/a&gt;, i.e. &amp;quot;The number of people on your team who have to be hit with a truck before the project is in serious trouble&amp;quot;&amp;nbsp; Anytime you lay off people who have specialized knowledge, you&amp;#39;re lowering your truck factor... if you lower it down to 1, particularly on a stressful project, you&amp;#39;re potentially in real trouble.. even if that person isn&amp;#39;t hit by a truck, there&amp;#39;s a good chance he/she will leave when the economy recovers.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=50790" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/truck+factor/default.aspx">truck factor</category><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/management/default.aspx">management</category></item><item><title>Bottlenecks</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/07/22/bottlenecks.aspx</link><pubDate>Wed, 22 Jul 2009 05:46:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:49513</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>1</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=49513</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/07/22/bottlenecks.aspx#comments</comments><description>&lt;p&gt;I&amp;rsquo;ve been reading though &lt;a href="http://www.amazon.com/Implementing-Lean-Software-Development-Addison-Wesley/dp/0321437381/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1248239308&amp;amp;sr=8-1"&gt;Implementing Lean Software Development&lt;/a&gt; as a member of the &lt;a href="http://groups.google.com/group/san-antonio-tech-book-club"&gt;San Antonio Tech Book Club&lt;/a&gt;. Bottlenecks are discussed in Chapter 7, and I had some notes/reflections on the subject&amp;hellip; &lt;/p&gt;
&lt;p&gt;Lean suggests that it&amp;rsquo;s important to figure out, and focus on the main problem.&amp;nbsp; This is an idea we know from optimizing our programs, for instance, if a query is 55 out of a 60 second operation, the thing to optimize is the query-optimizing anything else isn&amp;rsquo;t going to make much difference.&amp;nbsp; Similarly, if an app spends a week in testing, and 6 months in approval, making even huge improvements to finish testing earlier can make at most 1 week&amp;rsquo;s difference in the final delivery date-but even a minor improvement in the approval process can save several weeks.&amp;nbsp; Maybe the biggest bottleneck is a tough task-but it&amp;rsquo;s a task that has the potential to make the biggest real difference.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;Addressing bottlenecks isn&amp;rsquo;t easy.&amp;nbsp; To correct a problem, you&amp;rsquo;ve got to accept it as true, and take ownership.&amp;nbsp; Think about that-ownership doesn&amp;rsquo;t mean denying the problem exists, making excuses or the problem, or blaming the people who set up the current system initially, it means saying this problem is &lt;b&gt;my&lt;/b&gt; problem.&amp;nbsp; It doesn&amp;rsquo;t mean taking the blame, or even denying blame. Blaming doesn&amp;rsquo;t solve problems, removing the root cause solves problems.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;When working on removing a bottleneck, sometimes the first approach doesn&amp;rsquo;t work.&amp;nbsp; Continuously and honestly evaluating the success of new approaches is crucial-it&amp;rsquo;s equally important that though we take ownership of a problem, we don&amp;rsquo;t take the results of a particular new approach personally.&amp;nbsp; If we become overly attached to a failing approach, or even a successful approach, we won&amp;rsquo;t be open to a newer, more successful approach, and worse, we might even see the newer approaches as a threat.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;Perhaps the prerequisite to all of this is organization-wide commitment to eliminating bottlenecks.&amp;nbsp; The biggest hindrance can be anywhere between initial request and final delivery, and without an org-wide commitment, it might be impossible to make changes needed to solve problems, and it might even be difficult to identify what the true problems are.&amp;nbsp; Your team may be optimizing the heck out of itself, but if there&amp;rsquo;s a bottleneck elsewhere, those efforts might not be having a noticeable effect, and depending on the situation of the real bottleneck, those small-scale efforts might even be making things worse. Why waste that effort?&amp;nbsp; Make things better, but make sure you&amp;rsquo;re really making them better.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=49513" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/Lean/default.aspx">Lean</category></item><item><title>Austin Code Camp 2009</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/05/17/austin-code-camp-2009.aspx</link><pubDate>Sun, 17 May 2009 23:57:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:46874</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>1</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=46874</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/05/17/austin-code-camp-2009.aspx#comments</comments><description>&lt;p&gt;I just wanted to give a heads-up to people in the central Texas area that a great event is coming up at the end of the month: &lt;a href="http://www.adnug.org/AustinCodeCamp09/"&gt;Austin Code Camp 2009&lt;/a&gt;. It&amp;#39;s taking place May 30th, and &lt;a href="http://www.lostechies.com/blogs/hex/archive/2009/05/16/austin-code-camp-sessions-voting-results.aspx"&gt;the lineup of likely sessions&lt;/a&gt; is looking fantastic.&amp;nbsp; Our friends at Los Techies are going to have a substantial presence there, and I&amp;#39;ll be there too.&amp;nbsp; A lot of people have worked hard on setting up this &lt;i&gt;free&lt;/i&gt; event, and I&amp;#39;m really looking forward to it.&amp;nbsp; If you&amp;#39;re in the area, you should definitely attend-I hope to see you there!&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=46874" width="1" height="1"&gt;</description></item><item><title>VB.Net and the Case of the Iffy Ifs</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/05/12/vb-net-and-the-case-of-the-iffy-ifs.aspx</link><pubDate>Tue, 12 May 2009 05:49:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:46615</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>2</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=46615</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/05/12/vb-net-and-the-case-of-the-iffy-ifs.aspx#comments</comments><description>&lt;p&gt;Previous Items in Series:&lt;a href="http://devlicio.us/blogs/anne_epstein/archive/2009/01/12/intro-to-series-on-quality-vb-net.aspx"&gt;&lt;br /&gt;Intro to series on quality VB.Net&lt;br /&gt;&lt;/a&gt;&lt;a href="http://devlicio.us/blogs/anne_epstein/archive/2009/01/20/vb-net-and-the-spaghetti-ball-of-doom.aspx"&gt;VB.Net and the Spaghetti Code Of Doom&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;a href="http://devlicio.us/blogs/anne_epstein/archive/2009/01/20/vb-net-and-the-spaghetti-ball-of-doom.aspx"&gt;previous item in this series&lt;/a&gt;, we looked at a pretty
typical class, discussed some of its problems, and started in on fixing
those problems.&amp;nbsp; We finished with smaller, easier-to-understand pieces
of code, but it still exhibits some notable problems.&amp;nbsp; Most visible for
me are all those branching if/else statements, some of them nested.&amp;nbsp;
This is a potential problem for several reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each time we add/remove/change the customer levels, we&amp;#39;re going
to need to change both the OrderTotalService and the ShippingService.&amp;nbsp;&amp;nbsp;
That&amp;#39;s one core change we have to make, and now we&amp;#39;re going to have to
update two different pieces of code.&amp;nbsp; Hopefully, we remember to do that!&lt;/li&gt;
&lt;li&gt;Each time we go in and mess with any customer level, we have to
change these classes.&amp;nbsp; Hopefully, we change the classes safely and
don&amp;#39;t accidentally destabilize other customer levels when we&amp;#39;re in
there!&amp;nbsp; This is starting to feel a little risky...&lt;/li&gt;
&lt;li&gt;Are all these tangled ifs really readable?&amp;nbsp; they take some mental walking through to see what&amp;#39;s going on.&lt;/li&gt;
&lt;li&gt;It looks like constructing test data that matches the pattern
required to drill through all these conditions could be tricky.&amp;nbsp;
Testing is important, but it&amp;#39;s a lot harder to find the motivation when
it seems like it&amp;#39;s going to be an overwhelming task.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, one way to handle the mess is to try some &lt;a href="http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming"&gt;polymorphism&lt;/a&gt;.&amp;nbsp;
The basic idea we&amp;#39;re going to use is that instead of switching on a property of a class,
create a set of classes based on that property that share an interface,
and move the switched behavior over to the classes. Let&amp;#39;s give that a
try.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;The Customer class has been subclassed into three different classes
to reflect the Gold, Silver, and Basic customer levels. Then, the
customer level-specific product price and shipping calculations have
been moved into the subclasses: &lt;/p&gt;
&lt;pre class="vb.net" name="code"&gt;Public Interface ICustomer
    Property CompanyName() As String
    Property ContactName() As String
    Property Orders() As IList(Of Order)
    Sub AddOrder(ByVal order As Order)
    Sub RemoveOrder(ByVal order As Order)
    Function CalculateProductPrice(ByVal prod As Product) As Decimal
    Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal
End Interface

Public Class GoldCustomer
    Inherits Customer
    Implements ICustomer

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then
            Return 11.5D
        Else
            Return prod.Price * 0.8
        End If
    End Function
    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        Return 0D
    End Function
End Class

Public Class SilverCustomer
    Inherits Customer
    Implements ICustomer

    Public Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then
            Return prod.Price * 0.7
        Else
            Return prod.Price * 0.9
        End If
    End Function

    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        Return 3.5D
    End Function
End Class

Public Class BasicCustomer
    Inherits Customer
    Implements ICustomer

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then
            Return prod.Price * 0.9
        Else
            Dim ddService = New DealDayService()
            If ddService.IsDealDays() Then
                Return (prod.Price - 3)
            End If
        End If
    End Function
    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        If orderTotal &amp;lt; 20 Then
            Return 1.4D
        ElseIf orderTotal &amp;lt; 40 Then
            Return 2.5D
        ElseIf orderTotal &amp;lt; 70 Then
            Return 5D
        Else
            Return 8D
        End If
    End Function
End Class

Public MustInherit Class Customer
    Implements ICustomer

    ... (original methods) ...


    Public MustOverride Function CalculateProductPrice(ByVal prod As Product) As Decimal _
        Implements ICustomer.CalculateProductPrice
    Public MustOverride Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal _
        Implements ICustomer.CalculateShipping
End Class
&lt;/pre&gt;
&lt;p&gt;&lt;i&gt;(A note: we&amp;#39;re seeing the effects of a VB.Net &amp;quot;feature&amp;quot; here: in VB.Net-methods implementing an interface *must* state what it&amp;#39;s implementing from that interface.  Customer shouldn&amp;#39;t really need to implement ICustomer-and thus wouldn&amp;#39;t need the two MustOverride methods. The problem is that if Customer didn&amp;#39;t implement ICustomer, then when GoldCustomer, etc used Customer&amp;#39;s methods to implement ICustomer, we&amp;#39;d get a compile error because those method/properties in Customer wouldn&amp;#39;t have the &amp;quot;implements&amp;quot; mapping.  We can avoid this issue by making GoldCustomer contain a Customer instead of inheriting from it, but that&amp;#39;s a discussion for a different post)&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;This shrinks the OrderTotalService to just a few lines, and shrinks
the ShippingService to nothing-it would be reasonable to completely
remove the ShippingService class in the next refactoring pass. 
&lt;/p&gt;
&lt;pre class="vb.net" name="code"&gt;Public Class OrderTotalService
    Public Function CalculateOrderTotal(ByVal ord As Order) As Decimal
        &amp;#39; this first part adds up the cost of all the products
        Dim total As Decimal = 0D
        For Each prod In ord.Products
            total += ord.OrderedBy.CalculateProductPrice(prod)
        Next

        Dim shipService As New ShippingService
        Return shipService.CalculateShipping(ord.OrderedBy, total) + total
    End Function
End Class

Public Class DealDayService
    Public Function IsDealDays() As Boolean
        Return DateTime.Now &amp;gt; New DateTime(2009, 1, 18) AndAlso DateTime.Now &amp;lt; New DateTime(2009, 2, 18)
    End Function
End Class

Public Class ShippingService
    Public Function CalculateShipping(ByVal cust As ICustomer, ByVal total As Decimal) As Decimal
        &amp;#39; this next section adds in the shipping
        Return cust.CalculateShipping(total)
    End Function
End Class
&lt;/pre&gt;
&lt;p&gt;The last change here is in the Product class-I made a decision that
though there were only two hardcoded MegaDeal products, this felt like
a group that could change, and I wanted to get those hardcoded values
out of there. Instead of being hardcoded, those values are now going to
be stored as a flag in Product. (It very well may be that MegaDeals
would be better implemented as some way other than as a property the
first priority was getting out the hardcoded values-that can be
revisited) &lt;/p&gt;
&lt;pre class="vb.net" name="code"&gt;Private _isMegaDeal As Boolean
Public Property IsMegaDeal() As Boolean
     Get
        Return _isMegaDeal
     End Get
     Set(ByVal value As Boolean)
         _isMegaDeal = value
     End Set
End Property
&lt;/pre&gt;
&lt;p&gt;Next, I&amp;#39;m going to do a simple change on those inner ifs just to make them a bit easier to visually scan:&lt;/p&gt;
&lt;pre class="vb.net" name="code"&gt;Public Class GoldCustomer
    Inherits Customer
    Implements ICustomer 

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then Return 11.5D
        Return prod.Price * 0.8
    End Function 

    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        Return 0
    End Function
End Class

 
Public Class SilverCustomer
    Inherits Customer
    Implements ICustomer

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then Return prod.Price * 0.7
        Return prod.Price * 0.9
    End Function

    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        Return 3.5D
    End Function
End Class

 
Public Class BasicCustomer
    Inherits Customer
    Implements ICustomer

    Public Overrides Function CalculateProductPrice(ByVal prod As Product) As Decimal Implements ICustomer.CalculateProductPrice
        If prod.IsMegaDeal Then Return prod.Price * 0.9
        Dim ddService = New DealDayService()
        If ddService.IsDealDays() Then Return (prod.Price - 3)
    End Function

    Public Overrides Function CalculateShipping(ByVal orderTotal As Decimal) As Decimal Implements ICustomer.CalculateShipping
        If orderTotal &amp;lt; 20 Then Return 1.4D
        If orderTotal &amp;lt; 40 Then Return 2.5D
        If orderTotal &amp;lt; 70 Then Return 5D
        Return 8D
    End Function
End Class
&lt;/pre&gt;
&lt;p&gt;Conclusion:&lt;/p&gt;
&lt;p&gt;Getting rid of those ifs made a big difference.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Now,
if we need to modify a customer level, we can do it in one place, in
that level&amp;#39;s class.&amp;nbsp; No more hoping we got all the places we branch
over customer level types!&amp;nbsp; In addition, if we need to add a new level,
*none* of this code needs to change-we just add a new class for that
new level that implements ICustomer, and everything that&amp;#39;s here works
as-is.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Related to the item #1, when we change a customer level we
don&amp;#39;t have to change the places that level is used.&amp;nbsp; We can test the
heck out of what actually *had* to change, the modified or new
level, and since nothing else was touched, we won&amp;#39;t have to consider
any other exiting code destabilized.&lt;/li&gt;
&lt;li&gt;Unfortunately, there are still ifs, but we&amp;#39;ve split them out
some, so they&amp;#39;re not so nested.&amp;nbsp; OrderTotalService and ShippingService
are a lot cleaner and easier to look at.&lt;/li&gt;
&lt;li&gt;Testing these classes is a little easier now.&amp;nbsp; we don&amp;#39;t have to
feed as many combinations CalculateOrderTotal now-it treats all
customer types the same.&amp;nbsp; We still have to test that logic in the
inheritors of ICustomer, but without that extra layer of ifs, it should
be a little clearer what conditions need to be tested.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;This code can definitely still be improved!&amp;nbsp; For one thing, we&amp;#39;ll
have to keep an eye on the logic inside the customer level subtypes.&amp;nbsp;
Though the differing computation logic is now split off and close to
what it differs on, I&amp;#39;ve got a feeling that code could get complex and
overwhelm those classes over time.&amp;nbsp; The DealDays logic and dates seem
to be fixed to a single datespan, but that&amp;#39;s the sort of thing clients
want changed regularly, and we also don&amp;#39;t have a way to calculate the
order total without the shipping right now. We also still haven&amp;#39;t
addressed the upcoming desire to be able to handle pricing for
different shipping vendors either.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=46615" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/VB.Net/default.aspx">VB.Net</category></item><item><title>The Case for Multiple DBs in Multi Tenancy situations</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/04/24/the-case-for-multiple-dbs-in-multi-tenancy-situations.aspx</link><pubDate>Fri, 24 Apr 2009 05:49:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:46166</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>4</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=46166</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/04/24/the-case-for-multiple-dbs-in-multi-tenancy-situations.aspx#comments</comments><description>&lt;p&gt;&lt;a title="Multitenancy" href="http://en.wikipedia.org/wiki/Multitenancy" id="gkfb"&gt;Multitenancy&lt;/a&gt;
isn&amp;#39;t easy, and one of the particularly challenging aspects is what to
do with the database. In Ayende&amp;#39;s excellent posts on multi tenancy,
he&amp;#39;s pretty clear that &lt;a title="he feels a database per tenant is the way to go" href="http://ayende.com/Blog/archive/2008/08/07/Multi-Tenancy--The-Physical-Data-Model.aspx" id="mhtw"&gt;he feels a database per tenant is the way to go&lt;/a&gt; . There are two major, reasonable approaches that I know of:&amp;nbsp; First, to have a database per tenant. (This is the approach discussed
by Ayende in his multi tenancy series) A second common alternative is to have
a tenant id column in all tables that require differentiation by
client.&amp;nbsp; My team was in a situation requiring multitenancy, and needed
to select an approach. &amp;nbsp; Wary of the idea of multiple databases, the
team immediately decided to go with the tenant column approach.&lt;br /&gt;&lt;br /&gt;Looking
back, I think Ayende was right.&amp;nbsp; &lt;i&gt;The one advantage of the tenantid
column system is not having to deal with multiple databases.&lt;/i&gt;&amp;nbsp; There are
many disadvantages.&amp;nbsp; I&amp;#39;ll discuss the advantages, and then give some thoughts on how to deal wwith the complexity of working with multiple databases.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;Advantages of multiple databases:&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;In
my mind the most important developer-centric advantage of having
multiple databases is the ability to refactor the database.&amp;nbsp; Many
times, developers will design a system a system with certain
assumptions, then realize those assumptions are wrong.&amp;nbsp; Other times, a
major new feature would be much easier and cleaner with certain system
changes.&amp;nbsp; If the database is only talking to one system, then when the
developer comes upon these situations, s/he can also refactor the
database to reflect that understanding, and keep the mapping between
database simple, clean and easy to understand.&amp;nbsp; When the code is pushed
live, db update scripts also get pushed live.&amp;nbsp; However, if multiple
systems (in this case tenants) talk to the same database, this kind of
db structure change just can&amp;#39;t happen-pushing a db change would alter
everyone&amp;#39;s db, meaning everyone&amp;#39;s code would also have to be updated at
once.&amp;nbsp; In many situations, that kind of simultaneous mass upgrade would
be totally unacceptable, meaning that once this kind of multitenancy
system is put in place, the single db can never again be altered.&lt;br /&gt;&lt;br /&gt;Another
advantage of multiple databases is the ability to easily load-balance
by moving individual tenants to different db servers as necessary.&amp;nbsp; If
the databases are architected to be isolated, this isn&amp;#39;t a
problem-change a few connection strings, and you&amp;#39;re done.&amp;nbsp; In a shared
system, all tenants suffer if one tenant is overloading the server.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;On
a related note, though nobody likes to think about it, clients
sometimes leave, and when they do, they will want to be able to take
their own data with them.&amp;nbsp; If they have their own database, it&amp;#39;s as
easy as zipping it up and getting it to them.&amp;nbsp; If all tenants are on
the same db, it could be a much more difficult exercise involving
picking through every table and selecting out the tenant-specific
data.&amp;nbsp; Hopefully you dont give them anything that isn&amp;#39;t theirs.&amp;nbsp; Which
cleads to the next point.&lt;br /&gt;&lt;br /&gt;From a business perspective, data
separation is probably the most important reason to go with separate
dbs.&amp;nbsp; With a multitenant system, it&amp;#39;s impossible for one tenant to end
up with anyone else&amp;#39;s data.&amp;nbsp; Other tenants&amp;#39; data is in a completely
different system, so there&amp;#39;s no worry about messing up the tenantid and
passing a client someone else&amp;#39;s data.&amp;nbsp; Depending on the situation, this
could just be embarrassing; in other situations, you might be looking
at a lawsuit.&lt;br /&gt;&lt;br /&gt;Lastly, having separated databases makes many
things in the application more complicated, for the life of the
application.&amp;nbsp; Every join, every query, every new table, every mapping
has to deal with the tenant id.&amp;nbsp; If the strategy is to use the tenant
id to make a composite key on the tables, then you&amp;#39;ve got that much
additional complexity-and if you&amp;#39;re using a tool like NHibernate,
you&amp;#39;ll soon find out that working with composite keys is a LOT less
seamless than a single autogenerated key.&amp;nbsp; Eliminate the tenantid, and
a whole lot of things become forever simpler, clearer and have a lot
less potential for error.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;How to deal with multiple databases?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The
trouble with multiple databases is that now you have 20 or even 100
databases to manage instead of one.&amp;nbsp; Managing this manually would be a
difficult, error-prone task that could become overwhelming.&amp;nbsp; In
addition, any shared data can become out of sync.&amp;nbsp; The solution?&amp;nbsp; Don&amp;#39;t
do *anything* manually with these databases.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;For versioning:
each time you create a new version of your application, create a db
script to update the db from the prior version from the last version to
the newest one, and create a script to roll the database back from the
newest version to the previous version.&amp;nbsp; There are tools to help
produce this kind of script... If you&amp;#39;re programming in .Net, Visual
Studio Database edition has a tool to help do this for SQL Server, and
Red Gate has one as well called SQL Compare.&amp;nbsp; Bundle this script in
with the deploy of the code, and push it all out together, to each
tenant as they get upgraded.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;For shared data: 99% of the
time, shared data is of the sort where it needs to be updated soon, but
not necessarily real-time.&amp;nbsp; An example of this is cross-client data for
statistical computation.&amp;nbsp; To allow this kind of computation in a
multiclient situation, create a process that goes to each system and
collects the appropriate data, possibly in varying ways based on the
tenant&amp;#39;s installed version, then pulls that data into a single system.&amp;nbsp;
The combined reporting system can be optimized and tweaked for
reporting needs without affecting the designs of the individual
tenants.&amp;nbsp; Conversely, if there&amp;#39;s any reference data they all need to
share (dropdown data, etc) this information can be coordinated with a
similar process that does a data push to the different systems, based
on application version.&lt;br /&gt;&lt;br /&gt;This kind of automation&lt;i&gt; is&lt;/i&gt; some up-front
work, but once it&amp;#39;s done, it&amp;#39;s done, and it allows tremendous
application flexibility... and once it&amp;#39;s set up, it&amp;#39;s easy-there&amp;#39;s no
reason it couldn&amp;#39;t become fairly invisible from a management
perspective..&amp;nbsp; In addition, it&amp;#39;s really a very separate task from the
business application itself-it&amp;#39;s actually so separate that the syncing
application can be written by a completely different team that knows
little to nothing about the main application.&amp;nbsp; For that matter, I&amp;#39;m not
sure if there&amp;#39;s a tool out there that does all of these things
already(I bet there is, come to think of it), but there&amp;#39;s no reason
such a tool couldn&amp;#39;t exist.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=46166" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/multitenancy/default.aspx">multitenancy</category></item><item><title>Scrum-It's not about completing the sprint</title><link>http://devlicio.us/blogs/anne_epstein/archive/2009/03/26/scrum-it-s-not-about-completing-the-sprint.aspx</link><pubDate>Fri, 27 Mar 2009 04:18:00 GMT</pubDate><guid isPermaLink="false">40756a8b-6212-4073-9d98-6c26781577de:45155</guid><dc:creator>Anne Epstein</dc:creator><slash:comments>8</slash:comments><wfw:commentRss>http://devlicio.us/blogs/anne_epstein/rsscomments.aspx?PostID=45155</wfw:commentRss><comments>http://devlicio.us/blogs/anne_epstein/archive/2009/03/26/scrum-it-s-not-about-completing-the-sprint.aspx#comments</comments><description>&lt;p&gt;
 
 
 
This week, I was reading &amp;quot;&lt;span id="btAsinTitle"&gt;Agile Principles, Patterns, and Practices in C#&lt;/span&gt;&amp;quot; by Robert Martin.&amp;nbsp; In one of the early chapters, he talks a bit
about Scrum, and mentions that working overtime (excepting the week of the release sprint)&amp;nbsp; is a big antipattern.&amp;nbsp;
&lt;/p&gt;
&lt;p&gt;
Now, I&amp;#39;ve been on a team that practiced Scrum, and we worked at least
some overtime fairly regularly-our single goal was to complete our
chosen tasks for the sprint.&amp;nbsp; We felt that we&amp;#39;d made a solid commitment
that because we&amp;#39;d said we&amp;#39;d do those things, we&amp;#39;d do them.&amp;nbsp; On my team,
overtime was a good thing if it allowed us to keep our word and finish
the sprint.&amp;nbsp; In fact, finishing sprints was the whole key to our
version of Scrum... the company expected us to finish our sprints, and
in return, we got to have sprints.&lt;/p&gt;
&lt;p&gt;How does the team&amp;#39;s regular use of overtime mesh with Scrum? It
doesn&amp;#39;t.&amp;nbsp; We had it wrong.&amp;nbsp; Scrum&amp;#39;s not about completing sprints, it&amp;#39;s
about learning how your own team works.&amp;nbsp; When I read about overtime was
pretty much forbidden in Scrum, I finally got it-if overtime is
forbidden and you&amp;#39;re behind, sometimes you just don&amp;#39;t get everything
done.&amp;nbsp; If you couldn&amp;#39;t get everything done in the time allotted,
something is *wrong*-and whatever it is, you&amp;#39;ve gotta bring that out
into the open so you know about it, as soon as possible.&amp;nbsp; If people
work overtime every sprint, you may not even know that the team is
consistently unable to finish in the time allotted, and the root
problem may compound over time.&amp;nbsp; Whatever it is, be it an increasingly
degraded/unreliable codebase, team disagreements,&amp;nbsp; an underperforming
team member, frequent interruptions, or tasks that just ended up being
a whole lot more difficult than expected, if you don&amp;#39;t know about it,
you can&amp;#39;t fix it. Forbidding overtime and allowing the sprint to finish
uncompleted when things don&amp;#39;t work out keeps the need for introspection
on and resolution of the underlying issues at the forefront. How you
fix the problems is up to you and isn&amp;#39;t specifically addressed by
Scrum-it may involve team building, examining distribuion of work,
taking aside some people to find out why their velocity is down, or
focused efforts to increase team velocity such as training or bringing
in some XP(Extreme Programming) techniques.&amp;nbsp; Whatever you try, Scrum
can be a companion in making sure you know how you&amp;#39;re improving. &lt;br /&gt;&lt;br /&gt;In
the end, consistently completing sprints is really kind of a side
affect, a wonderful reward that comes from the hard work of building
and understanding of the truth about your team, and then making the
tough changes based on that knowledge.&amp;nbsp; Sure, completed sprints is what
you hope for, but overtime isn&amp;#39;t the answer-it&amp;#39;s just a short term
solution when what you&amp;#39;re dealing with is a long-term problem.&amp;nbsp; If
finishing the sprint tasks becomes more important than understanding
the team&amp;#39;s weaknesses, then Scrum becomes simply a way to organize tasks-you&amp;#39;ve lost
all the introspection and growth, leaving just an alternate form of
Microsoft Project.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://devlicio.us/aggbug.aspx?PostID=45155" width="1" height="1"&gt;</description><category domain="http://devlicio.us/blogs/anne_epstein/archive/tags/scrum/default.aspx">scrum</category></item></channel></rss>
