tag:blogger.com,1999:blog-17129528352411670082024-03-13T23:24:56.119-07:00SquaremasherTyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.comBlogger28125tag:blogger.com,1999:blog-1712952835241167008.post-21880669157918940032010-11-01T19:47:00.000-07:002010-11-03T04:35:52.545-07:00Sometimes you need to make a messEdit: This is a follow up on another blog post by my colleague Andy Maleh that you can read <a href="http://www.obtiva.com/blog/1-when-messes-are-fine">here</a>. I wanted to expand on his interpretation and add my own thoughts.<br /><br />What do we mean when we say some code is a mess? We're usually referring to its structure or lake thereof. Interactions between components are awkward and poorly named. There's often logic leaking between the layers causing odd couplings and generally making our lives a pain. Software like that is certainly a mess. That sort of mess is usually traceable to poor discipline on the part of the programmers or poor management of the team. We know these messes well.<br /><br />However, that's not the kind of mess I want to talk about. I want to talk about the kind of mess that happens while you're learning about a problem space by programming in it. These are flaws that you intend to clean up once you've wrapped your head fully around the problem, but you're quite incapable of doing so until you *do* understand what's going on. <br /><br />Would you say code in that state is messy? I would, but I think these messes are actually natural and good. Trying to avoid all kinds of messes early can be a mistake. <br /><br /><span style="font-weight:bold;">It is premature optimization of the design to force structure into the system when your understanding of the problem is too weak to support it. <br /></span><br />Here's an example. I wanted to build a pure-ruby png spiriting library to use in our web applications. It would take multiple png images, smash them together into one "sprite" png, and generate the css you need to access each of the images contained within. Now, I had never done any of this before. I didn't know how to parse a png or splice them together. I had a lot to learn to solve this problem.<br /><br />What did I do? First, I went and found the official png documentation, then I found a png library in ruby I could refer to. Finally, I made a huge gigantic mess trying to get it to work. This mess was a little bit like laying all the puzzle pieces out on the floor before you start to assemble it. I had some functional tests, I had some code that could parse a png - but it was far from an ideal form. Once I had something basic working, even though the code was a disaster, I could start to refactor. More importantly, I had allowed myself to explore the problem space by not getting hung up on the optimal design. I could worry about that later. Once I had enough understanding of the solution that I felt good about introducing new concepts I would do so and refactor the code into its new home. <br /><br />The end result was a nice little spiriting library you can check out <a href="http://github.com/aberant/css-spriter">here</a>. How does this technique work? How can you make a big mess without tossing all the code in the end? I've certainly done that before. Well, most importantly, you need to write the right kinds of tests. Whenever I'm working in a problem space that I don't feel comfortable with I always start my testing at the highest possible level, most of the time this means integration tests. I do this because integration tests give me more freedom to refactor.<br /><br />A system that is easy to refactor doesn't punish me as much for making mistakes and I certainly do make a lot of them. A good suite of integration tests shouldn't break unless you've truly broken the application, that is very useful. In the case of the spiriting library, I started by writing a test that expressed how I wanted to open a png and inspect its data. Once I had that working I would refactor what I thought I understood well enough and move on to the next test. When I got stuck, I tried to think up an easier test that doesn't require me to take on so much of the problem. So foremost, when I have a really awesome set of integration tests I'm never afraid to attack a mess. I know the test suite has got my back. <br /><br />Second, I need to be really good at refactoring. As I understand more of the problem I need to be able to take all these mistakes and turn them into solid code. Another important aspect of refactoring is knowing when the mess is getting too big. I need to understand how to refactor as much as I need to understand when a refactoring is necessary. I try to do this by always keeping the current state of the solution in my brain in sync with the solution in the code. As I learn and as code starts making sense I change it to match that new knowledge. That way when I come back to a problem I can pretty clearly tell where I need to focus my learning - wherever the code is the messiest. <br /><br /><span style="font-weight:bold;">Therefore I endeavor to only allow a mess to live as long as my ignorance of the problem space.</span>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com2tag:blogger.com,1999:blog-1712952835241167008.post-85913357822482103762010-08-06T07:28:00.000-07:002010-08-06T07:31:18.032-07:00Groupon Launches Personalized Deals<span class="Apple-style-span" style=" border-collapse: collapse; font-family:arial, sans-serif;font-size:13px;"><div class="im" style="color: rgb(80, 0, 80); "><p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font: normal normal normal 12px/normal Helvetica; "><span class="Apple-style-span" style="color:#000000;">Last week Groupon's founder </span><a href="http://twitter.com/andrewmason"><span class="Apple-style-span" style="color:#000000;">Andrew Mason</span></a><span class="Apple-style-span" style="color:#000000;"> announced, as he put it, "the biggest thing we've done since launching Groupon". That announcement was </span><a href="http://groublogpon.com/cities/personalized-deals/"><span class="Apple-style-span" style="color:#000000;">personalized deals</span></a><span class="Apple-style-span" style="color:#000000;"> and I'm very proud to say that </span><a href="http://www.obtiva.com/"><span class="Apple-style-span" style="color:#000000;">Obtiva</span></a><span class="Apple-style-span" style="color:#000000;"> was intimately involved in the development this new initiative. </span></p><p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font: normal normal normal 12px/normal Helvetica; min-height: 14px; "><br /></p></div><p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font: normal normal normal 12px/normal Helvetica; ">Collaborating as a team comprised of Obtivians, Groupons, and other consultants we were able to take this project from inception to prototype to launch in just a few months. I believe Groupon and Obtiva have shown once again that taking the time to do things right, to be craftsmen, is the most efficient way to build software.</p><p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font: normal normal normal 12px/normal Helvetica; min-height: 14px; "><br /></p><p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; font: normal normal normal 12px/normal Helvetica; ">I'm confident that the growing Obtiva team at Groupon will continue contributing to the great software that has helped make Groupon one of the fastest growing internet companies of all time.</p></span>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-31190750106329873142010-03-09T21:59:00.001-08:002010-03-09T22:03:40.164-08:00craftsman Swap – Day 5 at Relevance<span class="Apple-style-span" style=" ;font-family:Verdana, sans-serif;font-size:13px;"><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">It was the last day of an amazing week and I wanted to end it with a bang. I poked around after standup to see what people were doing and it was apparent that it was going to be a light day in the Relevance office. I think it was a coincidence, but this Friday found most of the Relevance staff traveling. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">Fridays are special at Relevance as it gives everyone the opportunity to work on his or her own R & D projects with the stipulation that these projects are open source. Not only is this an awesome perk for their staff it is also great for their customers too. A lot of the tools they build on Fridays help them deliver better software faster and more reliably for their clients. If you’ve used any of the projects on <a href="http://thinkrelevance.com/open-source" id="ziql" title="this page">this page</a>, then you owe a nod to Open Source Friday at Relevance. Personally, I find their commitment to the community inspiring. I wish more companies found a way to give back like this (nudge, nudge, <a href="http://www.obtiva.com/" id="jlob" title="Obtiva">Obtiva</a>).</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">This Friday began as every other day this week had, with the company stand-up. Nothing new to report there. After standup everyone broke to do their own thing, either as a pair or solo. I think it was split evenly between those working alone and those working as pairs.</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">I was attempting to sell <a href="http://twitter.com/karnowski"><span class="Apple-style-span" style="color: rgb(0, 32, 227); "><span class="Apple-style-span" style="font-size:85%;">Larry</span></span></a> on my <a href="http://clojure.org/" id="bbyv" title="Clojure">Clojure</a> <a href="http://www.haskell.org/haskellwiki/Introduction_to_QuickCheck" id="r_lx" title="QuickCheck">QuickCheck</a> port when <a href="http://www.exampler.com/" id="oj6x" title="Brian Marick">Brian Marick</a> found his way into the conversation. For those who are unfamiliar, QuickCheck is a Haskell testing library where test data is automatically generated and assertions made against “properties” of a function under test. What I wanted to attempt was a <a href="http://rspec.info/" id="gjdp" title="BDD">BDD</a> / QuickCheck Hybrid that supported both styles of testing.</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">Eventually it was just Brian and I working to bootstrap the framework. We’re both new-ish to Clojure and we ended up bumping our head on a rough edge or two. For instance, using a binding to override the behavior of the "=" function in Clojure is a really bad idea. And it is bad in a way that isn’t immediately obvious. I think we killed a good hour figuring that out. We didn’t make a ton of progress, but it was really great to finally work with Brian. He’s an exceptional guy. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">My Open Source Friday ended too soon, as did my time at Relevance. The people are top notch, the culture is wonderful and the software they write is exceptional. Without a doubt, a most wonderful place to spend a week or a career. So, a huge Thank You to all the Relevance folks for having me out. Let’s do another one soon!</p><div><br /></div></span>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com3tag:blogger.com,1999:blog-1712952835241167008.post-3824653411109657422010-03-09T21:49:00.000-08:002010-03-09T21:58:59.920-08:00craftsman Swap – Day 4 at Relevance<span class="Apple-style-span" style="font-family: Verdana, sans-serif; font-size: 13px; "><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">The fair weather abandoned <a href="http://maps.google.com/maps/place?client=safari&rls=en&oe=UTF-8&um=1&ie=UTF-8&q=Durham+NC&fb=1&gl=us&ftid=0x89ace471120f66f1:0xe17ada898a46f27e&ei=6l5qS6trhco1kvKFzgQ&sa=X&oi=geocode_result&ct=title&resnum=1&ved=0CAwQ8gEwAA" id="bdbi" title="Durham">Durham</a> sometime last night. I awoke to a brisk, rainy day. I had treated myself to an early bedtime and I was feeling especially good even though the weather wasn't spectacular. I made it in early thanks to <a href="http://spicycode.com/" id="s9-m" title="Chad">Chad</a> and spent about 40 minutes working on the blog post from the day before.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">I’m still behind from Tuesday. </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">Standup went with a now expected cadence. I filled everyone in with the delightfully busy day I had yesterday. Today I would continue working with <a href="http://twitter.com/karnowski"><span class="Apple-style-span" style="color: rgb(0, 32, 227); "><span class="Apple-style-span" style="font-size:85%;"><span class="Apple-style-span" style="font-size:85%;">Larry</span></span></span></a> on the <a href="http://github.com/weavejester/compojure" id="h0tl" title="Compojure">Compojure</a> web application. Excellent. </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><a href="http://twitter.com/karnowski"><span class="Apple-style-span" style="color: rgb(0, 32, 227); "><span class="Apple-style-span" style="font-size:85%;">Larry Karnowski</span></span></a><span class="Apple-style-span" style="font-size:85%;"> and I began our pairing session by reviewing the card. Our review turned out a missing behavior. We had yet to allow the caller to provide the recipient's identifier in the request. Our next step was to </span><span class="Apple-style-span" style="font-size:85%;">look to see what was currently supported in the model. Someone had already wired up the model so that a message could have multiple recipients, but it wasn't entirely finished. Multiple recipients is also clearly beyond the scope of our card. I thought we might as well go through with it but Larry was adamant that we rip it out. It was not necessary for the delivery of this card - or any of the cards in this iteration. </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">This intrigued me. We spoke about it a bit and it became clear to me that this is part of what makes Relevance guys different. If the code is dead or if the implementation isn't ideal it gets ripped out right away. </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">I realized that this could be why I found it so easy to integrate myself with their projects. I didn't waste any cycles trying to sift out what was relevant from a year's worth of dust and debris. Great code doesn't say that way long if you're not disciplined about taking out the garbage. Relevance employs some tidy fellows. Larry had to run to a meeting and left me to extract the unnecessary bits.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">I managed to muddle through and finished the job just before lunch.</span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">After lunch Larry and I ran into another little issue. This story we were working on wasn't exactly user facing. We were building a programmatic API for creating messages. In order for the customer to sign off that the story was complete we needed a way for them to create messages through the API. It didn't need to be perfect, just functional. Given this particular API is for a web application we were able to construct a simple form that the customers could use to see the system work on their own. </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">Our afternoon ended early when we broke to do an estimation session. This estimation was to be done in preparation for the next iteration of the project Larry and I were working on. This project was also different. The outcome was to be a proof of concept that would be used to demonstrate some of the application's core features, not an application they'd be putting into production.</span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">Before we talk about what happened let me roughly explain what I learned about how <a href="http://thinkrelevance.com/" id="o-l_" title="Relevance">Relevance</a> estimates cards.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">There are two estimates taken, one at a high level and another at a low level.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">The high level estimate is used to size stories into an iteration.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">An iteration is “full” when the sum of the high level estimates meets the expected velocity of the team.</span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-size:85%;"><br /></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">These estimates are taken using a relative scale derived from completed stories in the project.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">Before each estimation session begins they baseline themselves for this project by looking at completed stories from the previous iteration.</span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-size:85%;"><br /></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">The low level estimate is done by the pair picking up the card for development.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">This low level estimate is the one that the development pair is expected to perform to. It makes a lot of sense then that they get to chose what this number is.</span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-size:85%;"><br /></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">Now, back to this estimation session.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="color: rgb(51, 51, 51); "><span class="Apple-style-span" style="font-size:85%;"><a href="http://thinkrelevance.com/team/muness-alrubaie" id="w4tz" title="Muness">Muness</a></span></span><span class="Apple-style-span" style="font-size:85%;">, </span><a href="http://spicycode.com/" id="q.s9" title="Chad">Chad</a><span class="Apple-style-span" style="font-size:85%;">, <a href="http://twitter.com/karnowski"><span class="Apple-style-span" style="color: rgb(0, 32, 227); "><span class="Apple-style-span" style="font-size:85%;"><span class="Apple-style-span" style="font-size:85%;">Larry</span></span></span></a> and I began by reviewing the available cards in Mingle. We were debating priority from the customer's perspective as well as what sequence made the most sense for development. This gave us a subset of the available cards to plan for the upcoming iteration as well as a rough sense of priority.</span><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-size:85%;"><br /></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">At this point we began estimating cards.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">I abstained as I was unfamiliar with the project and I didn’t want my poor estimates to cause someone trouble down the road.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">Each estimate for a card was collected by counting down 3-2-1, then everyone showing a point value simultaneously. </span><span class="Apple-style-span" style="font-size:85%;">If the variance was small the higher estimate is taken, if the variance is large then the card is debated again and another estimation is taken.</span></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-size:85%;"><br /></span></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><span class="Apple-style-span" style="font-family: Verdana; "><span class="Apple-style-span" style="font-size:85%;">We continued this process until the iteration was full.</span><span class="Apple-style-span" style="font-size:85%;"> </span><span class="Apple-style-span" style="font-size:85%;">And that marked the end of my 4</span><sup><span class="Apple-style-span" style="font-size:85%;">th</span></sup><span class="Apple-style-span" style="font-size:85%;"> day at Relevance.</span><span class="Apple-style-span" style="font-size:85%;"> </span></span></p><div><span class="Apple-style-span" style="font-size: small;"><br /></span></div></span>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-81080320941723341312010-03-09T21:37:00.000-08:002010-03-09T21:49:00.171-08:00Craftsman Swap – Day 3 at Relevance<span class="Apple-style-span" style="font-family: Verdana, sans-serif; font-size: 13px; "><div style="margin-top: 0px; margin-bottom: 0px; ">Morning standup went as expected, <a href="http://twitter.com/rsanheim" id="wvof" title="Rob Sanheim">Rob</a> and I were back to work again on the story we'd began the week with. The module we had extracted for allocating addresses was more or less done. We spent a few minutes discussing our strategy for integrating back into the model that would use it. All of the 'old' behavior was still in place through various lifecycle hooks. There were two before_save hooks and one validation hook we'd need to change. Given there was a lot of code already that depended on this existing behavior Rob and I were hesitant to just start ripping it out. It made more sense to write the new code in parallel with the old keeping everything working until the last possible moment. This way we could have some certainty that our new code in the model worked before we were forced to consolidate our changes with the rest of the system. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">Over another great <a href="http://thinkrelevance.com/" id="o-l_" title="Relevance"><span class="Apple-style-span" style="font-size:85%;">Relevance</span></a> provided lunch it was decided that I would be switching projects in the afternoon. I felt bad about leaving Rob midstream, but consoled myself with the fact that I probably wasn't very much help anyway. He picked up <a href="http://thinkrelevance.com/team/jason-rudolph" id="pxf5" title="Jason">Jason</a> as a pair for the afternoon and I went with <a href="http://spicycode.com/" id="g.xh" title="Chad">Chad</a>. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">Chad and I spent a couple hours spiking out client side certificate validation in <a href="http://jruby.org/" id="m08-" title="JRuby">JRuby</a> and <a href="http://jetty.codehaus.org/jetty/" id="q.x7" title="Jetty">Jetty</a>. We generated a little <a href="http://rubyonrails.org/" id="w85g" title="Rails">Rails</a> project then pulled down <a href="http://github.com/nicksieger/warbler" id="mjux" title="warbler">warbler</a> and <a href="http://github.com/nicksieger/jetty-rails" id="w4m2" title="jetty-rails">jetty-rails</a>. Getting Rails up and running on Jetty with jetty-rails was mindlessly easy. We hit our first bump trying to get an SSL listener up and running in Jetty. We spent a few minutes reviewing the jetty-rails documentation and found no mention of how we could achieve this. In the end we just copied the jetty-rails launcher script and modified it to suit our purposes. This was just a spike after all. We launched our new script and surprisingly the thing worked! Chad and I reported our success back to <a href="http://twitter.com/stuarthalloway" id="cbvm" title="Stuart">Stuart</a> and <a href="http://twitter.com/karnowski"><span class="Apple-style-span" style="color: rgb(0, 32, 227); "><span class="Apple-style-span" style="font-size:85%;">Larry</span></span></a>, then moved on. We knew we could make JRuby work, if we needed to. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">Chad and I had a few free moments and I decided to take him up on his offer of a 10 minute tour of <a href="http://spicycode.com/" id="fqhf" title="RSpec2">RSpec2</a>. He's quite enthusiastic about the new metadata model and spent most of our time telling me about that. My very basic understanding is that everything you declare about a specification is captured in metadata which is accessible from pretty much anywhere you'd care to get to it. This makes RSpec2 extensions a breeze to write. Oh, and its really fast too, he'd want you to know that.</div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">With my RSpec2 introduction complete I moved on to pair with Larry on a <a href="http://github.com/weavejester/compojure" id="r:4h" title="Compojure">Compojure</a> web application. When I sat down Larry was finishing up a card for a part of a messaging API. Specifically, message creation. You'd post to the controller and eventually your message would end up in the database. Compojure looks quite a bit like <a href="http://www.sinatrarb.com/" id="n8:-" title="Sinatra">Sinatra</a>. There was a simple block of functions that mapped routes and http methods to controller functions. These controller functions served the same purpose as actions do in <a href="http://rubyonrails.org/" id="m2dx" title="Rails">Rails</a> controllers. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">Larry mentioned that the create method was getting a little out of hand. There were two conditionals that were essentially doing validation. If we got past the two conditionals a transform operation would execute and turn the form parameters into a <a href="http://clojure.org/" id="zl60" title="Clojure">Clojure</a> data structure. We'd validate the record and if everything was fine it'd be persisted to the database. If validation failed or if either of the conditional guards failed the method would return a 422 response and a message. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">I noted that the conditional guards were serving as as second type of validation. It was easy to move them into the <a href="http://github.com/duelinmarkers/clj-record" id="ak28" title="clj-record">clj-record</a> validations and so we did. I also suggested to test that a system failure would return an appropriate response for an API - not some HTML 500 error page. We wanted to test this by throwing an exception out of the create action. We monkeyed around trying to get dynamic re-binding working through Clojure's binding form as a cheap form of mocking, but we couldn't figure it out. The day was over, so we tabled the test until tomorrow. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">A note on my first day of Clojure experience. Personally, I find the language itself immensely pleasing to work with. As I've grown as a developer I keep finding myself writing smaller and smaller functions. A lot of languages have syntactic overhead that punish you for writing really small functions. Clojure does not, Ruby does not. I really like that.</div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">However, Clojure is a young language and there are definitely rough edges. Naturally, the tools available in a young language are also necessarily young. There's a lot of missing pieces in Compojure that Rails has had baked in for years. Does that mean you shouldn't use Clojure or Compojure for your project? Absolutely not. But you should know what you're getting in to.</div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">Now's the time to act if you want to make a name for yourself in the Open Source Clojure community. There's a ton of work to be done and not nearly enough people doing it. </div></span>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-13876850183778208042010-03-02T06:50:00.000-08:002010-03-02T06:51:44.359-08:00Craftsman Swap – Day 2 at Relevance<span class="Apple-style-span" style="font-family: Verdana, sans-serif; font-size: 13px; "><div style="margin-top: 0px; margin-bottom: 0px; ">Day 2 begin as I expected. We gathered at 8:45 in <a href="http://thinkrelevance.com/" id="o-l_" title="Relevance"><span class="Apple-style-span" style="font-size:85%;">Relevance</span></a><span class="Apple-style-span" style="font-size:85%;">'s</span> large conference room to discuss yesterday's happenings across the company. I said that I had been working on <a href="http://twitter.com/rsanheim" id="wvof" title="Rob Sanheim">Rob</a>'s project and expected more of the same today. We weren't giving story-level progress in this meeting so it wasn't drawn out with the minutia of everyone's day. Ten minutes later I was back at my desk with Rob continuing work on our story. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">Yesterday we had worked through a few model changes that were necessary to support the behavioral work we'd be tackling today. The old model had a one-to-one association that we changed to a one-to-many. We needed to alter the algorithm that populated this relationship.</div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">The existing algorithm was embedded as part of the model's lifecycle hooks. These hooks fired on save and would populate the association. This algorithm was already fairly complex and it was going to get worse. Rob suggested we extract it into its own module. Rob wanted to TDD this new module from scratch where I thought it would be best to refactor into the module we wanted with the support of the existing tests. After some more discussion and thought on my part Rob convinced me the greenfield TDD approach was best. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">Our next debate was: How would the module communicate with the model we were extracting it from? Rob wanted something we would mix in. You'd execute it and it'd do whatever it needed to the model to populate the association. I was worried this approach would create coupling between the model and the module that wasn't immediately obvious. It seemed a bit magical. I suggested that we could reduce the coupling between two and make their interaction obvious by calling out to the module explicitly from the model, then letting the model handle hooking up the associations. Rob agreed to my plan. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">After breaking for another excellent lunch provided by Relevance, Rob and I began <a href="http://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530" id="lokc" title="TDDing">TDDing</a> our new address allocation module. Rob ran out for a phone call, so in the interim I put together a set of pending specs for all the scenarios that I thought we'd want to test. I knew I'd missed some things, but it was a good place to start the conversation when Rob came back. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">The rest of the afternoon was spent building our module, test-first. Many times we would write a failing spec, then realize the behavior we wanted should live in another model. We'd leave that test failing, hop over to the other model's spec and write a second failing test there for the new method we wanted. If everything went well we'd go from two failing tests to a green bar in one fell swoop. Nice. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">This is different than my usual method of inlining the new behavior to get the outer test passing, then refactoring into a new method after I've seen the green bar. Rob's approach has the advantage of making you write that unit test for the new function first. It is easy to rationalize not writing it after you've seen the green bar. I know I've done it. We used the 'fit' (f means focused) feature in <a href="http://blog.davidchelimsky.net/2010/01/12/rspec-2-and-rails-3/" id="kfn9" title="RSpec2">RSpec2</a> a lot for this style of work. It really came together well.</div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">The evening had four of us geeking out with a <a href="http://www.wizards.com/magic/" id="phip" title="Magic The Gathering">Magic The Gathering</a> draft + tournament, which is the reason I am so late in delivering this post. Although I didn't fare well (1-6) it was a lot of fun. </div><div style="margin-top: 0px; margin-bottom: 0px; "><br /></div><div style="margin-top: 0px; margin-bottom: 0px; ">Two days down, three to go. I'm having a lot of fun. There's rumblings that I may move on to a <a href="http://clojure.org/" id="ihuy" title="Clojure">Clojure</a> project tomorrow. That'd be fantastic. </div></span>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com2tag:blogger.com,1999:blog-1712952835241167008.post-23831392201920246532010-01-19T22:09:00.000-08:002010-01-19T22:17:08.575-08:00Craftsman Swap – Day 1 at Relevance<span class="Apple-style-span" style="font-family: Times; font-size: medium; "><div style="margin-top: 6px; margin-right: 6px; margin-bottom: 6px; margin-left: 6px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Verdana; font-size: 10pt; background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); min-height: 1100px; counter-reset: __goog_page__ 0; line-height: normal; "><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">My first day at Relevance actually began Sunday night when <a id="q8:3" href="http://spicycode.com/" title="Chad Humpries">Chad Humpries</a> and Kris Singleton were kind enough to entertain me for the evening. I had dinner with Chad at <a id="bep3" href="http://www.urbanspoon.com/r/25/291754/restaurant/Downtown-Durham/Piedmont-Durham" title="Piedmont">Piedmont</a>, an excellent local Italian place. The food was great and the conversation interesting. Chad talked to me about his inspiration for <a id="i0ch" href="http://github.com/spicycode/micronaut/" title="Micronaut">Micronaut</a>, which is now <a href="http://github.com/dchelimsky/rspec">Rspec2</a>, and went over some of its features. He’s on the hook for giving me a 10 minute overview of the framework and some of its innovative features. Pretty excited about that. He also offered to meet me for breakfast the following morning while we walked to the Relevance office for a late night tour.</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">Kris met Chad and I at the office as he’d kindly offered to shuttle me to and from his place for a night of gaming. We played two rounds of <a href="http://www.boardgamegeek.com/boardgame/36218/dominion">Dominion</a> (the seaside expansion) and a train game I cannot remember the name of, but that was after a glass of bourbon. We played to about ten. It was a great way to finish out the evening. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">I met Chad the next morning at <a id="j5v4" href="http://dosperrosrestaurant.com/" title="Dos Perros">Dos Perros</a> a little Mexican place next door to Relevance. About half way through the meal I look up to see a familiar face outside the window. Holy crap! Its <a id="i9ow" href="http://www.exampler.com/" title="Brian Marick">Brian Marick</a>!</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">He’s in town doing his own craftsman swap – so to speak. I don’t know if he’d call it that, but his plan seems similar to mine – do some work with the top-notch Relevance crew. It’s always a good day when Brian walks through your door. How exciting. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">We all made it up stairs and hung around a bit before the 8:45 all-hands standup. As stand ups go it was pretty much what I expected from an Agile shop. What did you do yesterday? what’s the plan for today? what’s blocking you? Brain and I were set up with our pairing partners as well, which looked to have been discussed before hand. No problem. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><a id="wvof" href="http://twitter.com/rsanheim" title="Rob Sanheim">Rob Sanheim</a> and I (my pair for the day) started off with an architectural overview of the project I was joining. The project itself is really interesting, but I can’t say much about it. NDAs and all. We had the usual sort of arrowed diagrams describing the major components of the system and their interactions. I was interested in how their testing strategy played out across the tiers of the application, so we drew some lines to show the boundaries between the test suites and where they overlapped. It was a useful experiment. It turned out that in a past life I had worked quite a lot in the domain, so most of it made sense to me at a high level. Good enough to get started. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">The work environment at Relevance is really nice. Everyone sits together in one large room. The desks are huge with ample room for whatever you think you’ll need for the day. Each pairing station has two monitors, two keyboards, and two mice. Most of the shops I’ve visited do it that way and it works. The office itself had a really warm, relaxing atmosphere. Exposed wood beams, pine plank flooring and lots of windows.</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">We settled in and began our pairing session by reviewing the story in Mingle. It was larger than I usually like a story to be and Rob agreed that it was a bigger card than they usually played. We discussed breaking the story down. A significant refactoring was necessary, so I pitched doing that first as an “internal” dev-facing card. Rob seemed to like the idea initially, but thought it would be a hard sell for the customer – no value. The second pitch was to break the story by its two major functional components – configuration and execution. That didn’t make sense from a value standpoint either – the configuration part had no value to the customer without being able to execute it. All that discussion took about 5 minutes and in the end we decided to take on the whole card.</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">The card itself broke down like this. It had the usual title and a paragraph or so describing the story. That was followed up by four cucumber features that loosely described the acceptance criteria. These were written by a developer beforehand. The bottom of the card contained a Q/A section that listed responses to developer questions and anything outstanding. The whole card was sent to the customer for approval before we began work. I liked that a lot.</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">The part of the system we’d be changing is a Rails webapp. First things first, we made sure the test suite passed. I noticed Rob was using something like <a href="http://www.nateclark.com/articles/2008/09/17/_autotest_-is-now-_autospec_-how-to-set-up-autospec-for-rspec-and-rails-with-zentest">Autospec</a>, but it wasn’t. It is called <a id="hdou" href="http://github.com/mynyml/watchr" title="Watchr">Watchr</a> and it looks pretty awesome. My first big knowledge win for the week. It works like Autospec in that it automatically re-runs the test suite whenever something changes, but it gives you a lot more control over what's getting executed. Going to be a big win for me on my client project back home.</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">We had spent about an hour on the card when we were pulled into the (I think daily) project standup meeting with the customer. It was efficient and well run. We discussed all of the outstanding issues from last week and how they were being addressed. Ran through the story work for the day. It was pretty much your standard stad-up meeting. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">15 minutes later we’re back to work. The communal stereo offered an eclectic mix of tunes to set the mood – pretty much any genre you can imagine. By lunch we’d managed to translate most of the first acceptance test from the story into something we could execute. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">Relevance brings in lunch every day for its employees. Well that’s nice, but what’s the value? Is catering lunch every day just a perk or does it add something to Relevance’s culture? I think it does. First, everyone eats together every day. A lot of interesting discussions went on over lunch and everyone was there to participate. Second, we all got back to work at the same time. That is a fantastic feature if you’re pairing a lot – and Relevance certainly is. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">The rest of the afternoon was standard Rails fare – a few migrations, some Refactoring, fixing specs. We did get a little hung up in our acceptance test where we wanted to assert the presence of a few fields in the view. We got something working with rspec’s include matcher, but the failure output was terrible. It dumped the entire response body. We blew a good 20 minutes trying to clean it up to no avail. We're using Micronaut (RSpec2) so have_tag wasn't available and webrat's selector doesn't work with assertions. We benched the cleanup when it became apparent it was going to be harder than we thought. It wasn’t that important.</p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">Our day ended with an 1.5 hour company retrospective. I was told these happen every two weeks here at Relevance. We don’t do these at Obtiva and I really liked the idea. We do a lot more on-site work than Relevance does, at least it seems that way. An all-hands company retrospective is a great idea for us and I’m definitely going to take back to Obtiva with me. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "> </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">Overall, it was a pretty awesome first day. The way they put together their projects really resonates with my own personal style. All the code we worked with is exactly what I expected to see. Very high quality, easy to comprehend and simple to test. I had enough confidence to take the driver’s seat after a few hours and I’m happy to say I was able to contribute a couple useful ideas on day 1. That speaks not to my own ability but to the quality of my pairing partner and the expressiveness of the system he was helping me to learn. Well done. </p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><br /></p><p class="MsoNormal" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">Looking forward to day 2.</p><div><br /></div></div></span>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com1tag:blogger.com,1999:blog-1712952835241167008.post-64298378198044640262009-12-08T06:36:00.000-08:002009-12-08T07:21:46.143-08:00Managing dotfiles with gitI've never managed dotfiles before, but now that I'm a serious vim user I thought it was time to get on with it. Naturally, I googled around to see what other fellows had done. I found a few different schemes. They all seemed more complicated than they should be. Some had scripts to run and symlinks everywhere. Others appeared to require a lot of manual bit pushing to maintain.<br /><br />all of this because dotfiles are in your home directory along with a lot of other stuff you want to ignore. There is a very simple way to take care of this, let's get started.<br /><br />First, you'll need a git repository. So cd into your home directory and type<br /><blockquote>git init</blockquote><br />Now run the following command<br /><blockquote>git status</blockquote><br />you should see a gigantic list of files. Some we want to version with git, most we do not.<br /><br />Now here's the first trick. We want git to ignore everything by default, so we type:<br /><blockquote>echo "*" > .gitignore</blockquote><br />Now run 'git status' again and git will tell you there's nothing to add to the repository. Excellent!<br /><br />At this point you have a choice. You can manually add the dotfiles you want to version by prefixing their name with a '!' like this:<br /><blockquote>echo "!.vim" >> .gitignore<br /></blockquote><br />This makes for a short .gitignore file, but git won't tell you when new dotfiles are added - they'll be globally ignored along with everything else. That may be fine for some, but I like to know when new dot files are created.<br /><br />To do this, I told git not to ignore anything that was prefixed with a '.'. My .gitignore file now looks like this:<br /><blockquote>/*<br />!.*</blockquote><br />Now when you run 'git status' you get a big list of all the dotfiles that have not been versioned, most of these we want to ignore. So we capture the output in a file:<br /><blockquote>git status > unversioned</blockquote><br />Open 'unversioned' and '.gitignore' in your favorite editor, copy the file list from 'unversioned' to the bottom of '.gitignore'. Now remove everything that you *DO* want to version. Be careful not to ignore your '.gitignore' file! That'd be silly.<br /><br />Finally, add and commit your changes normally:<br /><blockquote>git commit -a -m "hey dotfiles! woohoo!"</blockquote><br />That's it! If you want to store your dotfiles on github create a repo and follow their instructions for setting up github with an existing git repository.<br /><br />You can see my dotfiles repo <a href="http://github.com/tjennings/dotfiles">here</a><br /><br />Cheers!Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com8tag:blogger.com,1999:blog-1712952835241167008.post-70836360706527634912009-10-29T12:09:00.000-07:002009-10-29T20:58:36.054-07:00A response to "Failures in Isolation"Following <a href="http://www.coreyhaines.com/">Corey Haines</a> on twitter is such a good idea. Today he dropped <a href="http://blog.extracheese.org/2009/10/my-personal-failures-in-test-isolation.html">this gem</a>: <br /><br />Please read, I'm not going to paraphrase it. Nevermind, I will. But it will be really short. Poorly designed code that is tested with a high level of isolation causes a problem. You can't refactor it with confidence because the tests, or the mocks, are blocking your progress. So let's talk about how they impede refactoring first.<br /><br />Tests prevent behavior from flowing 'up'. For instance, if you mistakenly coupled a model class to a view tier class you could not 'refactor' this problem without breaking the tests for your model. Most likely, the test will fail because the API of the class has changed, you no longer pass the request. <br /><br />For Mocks it is the opposite. You cannot move behavior 'down'. Let's say I have leaked some model behavior into my controller and I wish to move it to the appropriate model. If my test uses the real model, not a mock, this is not a problem. I can move the behavior and my controller test will still pass. Now I can safely write model tests for the new behavior and delete the controller test that now duplicates it. <br /><br />If I have mocked the model, however, my controller test will fail when I attempt to move the behavior 'down' into the model. One offense is not an issue. I was involved in one system where an entire test suite was thrown away because of improperly used mocks. Death by a thousand paper cuts. <br /><br />I have no universal solution for this problem - as it is a problem of my skill as a designer and the skill of my peers. However, I have come up with two helpful guidelines.<br /><br /><span style="font-weight:bold;">Only directly test code you like</span><br /><br />I dislike code when I'm unsure of its design. I also dislike code that appears volatile. As my confidence has grown I find myself more comfortable with my early design decisions and more willing to glorify my creations with their own test sooner. When I am not confident I keep my tests at a high enough level where I can refactor freely. As my confidence grows in a system I move towards higher levels of isolation.<br /><br /><span style="font-weight:bold;">Only mock code you like</span><br /><br />For all the same reasons as above. Mocks crystalize an API for a class and can cause a lot of problems if the class is used often in a system and the class it is poorly designed. As the ugliness and volatility of code increase, mock it less.<br /><br /><span style="font-weight:bold;">In Conclusion</span><br /><br />As with everything, the value of isolation in testing is not an absolute. For younger developers I suggest beginning with a state-based testing approach over using mocks pervasively. A <a href="http://martinfowler.com/articles/mocksArentStubs.html#ClassicalAndMockistTesting">state-based</a> style will afford you maximum flexibility to refactor your design. As your experience grows begin to use mocks to solve some of the problems that state-based testing will invariably bring about. <br /><br />That is the path I travelled and it worked out alright for me.Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-67490641357755821462009-08-07T08:19:00.001-07:002009-08-07T08:33:31.922-07:00A log4j adapter for RailsThis current client application I'm working on is a mixed bag of Ruby and Java parts. All our existing logging for the older Java parts uses log4j and we wanted to unify our Rails logging with that. I spent a couple of hours putting together this adapter we now use. If you need something similar it should give you a good place to start.<div class="overflow"><table class="CodeRay"><tr><br /> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt><br /></tt>2<tt><br /></tt>3<tt><br /></tt>4<tt><br /></tt>5<tt><br /></tt>6<tt><br /></tt>7<tt><br /></tt>8<tt><br /></tt>9<tt><br /></tt><strong>10</strong><tt><br /></tt>11<tt><br /></tt>12<tt><br /></tt>13<tt><br /></tt>14<tt><br /></tt>15<tt><br /></tt>16<tt><br /></tt>17<tt><br /></tt>18<tt><br /></tt>19<tt><br /></tt><strong>20</strong><tt><br /></tt>21<tt><br /></tt>22<tt><br /></tt>23<tt><br /></tt>24<tt><br /></tt>25<tt><br /></tt>26<tt><br /></tt>27<tt><br /></tt>28<tt><br /></tt>29<tt><br /></tt><strong>30</strong><tt><br /></tt>31<tt><br /></tt>32<tt><br /></tt>33<tt><br /></tt>34<tt><br /></tt>35<tt><br /></tt>36<tt><br /></tt>37<tt><br /></tt>38<tt><br /></tt>39<tt><br /></tt><strong>40</strong><tt><br /></tt>41<tt><br /></tt>42<tt><br /></tt>43<tt><br /></tt>44<tt><br /></tt>45<tt><br /></tt>46<tt><br /></tt>47<tt><br /></tt>48<tt><br /></tt>49<tt><br /></tt><strong>50</strong><tt><br /></tt>51<tt><br /></tt>52<tt><br /></tt>53<tt><br /></tt></pre></td><br /> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">class</span> <span class="cl">Log4jAdapter</span><tt><br /></tt> include <span class="co">ActiveSupport</span>::<span class="co">BufferedLogger</span>::<span class="co">Severity</span><tt><br /></tt> <span class="co">L4JLevel</span> = org.apache.log4j.<span class="co">Level</span><tt><br /></tt><tt><br /></tt> <span class="co">SEVERETIES</span> = {<tt><br /></tt> <span class="co">DEBUG</span> => <span class="co">L4JLevel</span>::<span class="co">DEBUG</span>,<tt><br /></tt> <span class="co">INFO</span> => <span class="co">L4JLevel</span>::<span class="co">INFO</span>,<tt><br /></tt> <span class="co">WARN</span> => <span class="co">L4JLevel</span>::<span class="co">WARN</span>,<tt><br /></tt> <span class="co">ERROR</span> => <span class="co">L4JLevel</span>::<span class="co">ERROR</span>,<tt><br /></tt> <span class="co">FATAL</span> => <span class="co">L4JLevel</span>::<span class="co">FATAL</span>,<tt><br /></tt> }<tt><br /></tt> <span class="co">INVERSE</span> = <span class="co">SEVERETIES</span>.invert<tt><br /></tt><tt><br /></tt> <span class="r">def</span> <span class="fu">initialize</span><tt><br /></tt> <span class="iv">@logger</span> = org.apache.log4j.<span class="co">Logger</span>.getLogger(<span class="s"><span class="dl">'</span><span class="k">com.shc.mmh.Rails</span><span class="dl">'</span></span>)<tt><br /></tt> <span class="iv">@root</span> = org.apache.log4j.<span class="co">Logger</span>.getRootLogger<tt><br /></tt> <span class="r">end</span><tt><br /></tt><tt><br /></tt> <span class="r">def</span> <span class="fu">add</span>(severity, message = <span class="pc">nil</span>, progname = <span class="pc">nil</span>, &block)<tt><br /></tt> message = (message || (block && block.call) || progname).to_s<tt><br /></tt> <span class="iv">@logger</span>.log(<span class="co">SEVERETIES</span>[severity], message)<tt><br /></tt> <span class="r">end</span><tt><br /></tt><tt><br /></tt> <span class="r">def</span> <span class="fu">level</span><tt><br /></tt> <span class="co">INVERSE</span>[<span class="iv">@logger</span>.getEffectiveLevel]<tt><br /></tt> <span class="r">end</span><tt><br /></tt><tt><br /></tt> <span class="r">def</span> <span class="fu">level=</span>(level)<tt><br /></tt> raise <span class="s"><span class="dl">"</span><span class="k">Invalid log level</span><span class="dl">"</span></span> <span class="r">unless</span> <span class="co">SEVERETIES</span>[level.to_i]<tt><br /></tt> <span class="iv">@root</span>.setLevel(<span class="co">SEVERETIES</span>[level.to_i])<tt><br /></tt> <span class="r">end</span><tt><br /></tt><tt><br /></tt> <span class="r">def</span> <span class="fu">enabled_for?</span>(severity)<tt><br /></tt> <span class="iv">@logger</span>.isEnabledFor(<span class="co">SEVERETIES</span>[severity])<tt><br /></tt> <span class="r">end</span><tt><br /></tt><tt><br /></tt> <span class="c">#Lifted from BufferedLogger</span><tt><br /></tt> <span class="r">for</span> severity <span class="r">in</span> <span class="co">ActiveSupport</span>::<span class="co">BufferedLogger</span>::<span class="co">Severity</span>.constants<tt><br /></tt> class_eval <span class="s"><span class="dl"><<-EOT</span></span>, <span class="pc">__FILE__</span>, <span class="pc">__LINE__</span><span class="s"><span class="k"><tt><br /></tt> def </span><span class="il"><span class="idl">#{</span>severity.downcase<span class="idl">}</span></span><span class="k">(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)<tt><br /></tt> add(</span><span class="il"><span class="idl">#{</span>severity<span class="idl">}</span></span><span class="k">, message, progname, &block) # add(DEBUG, message, progname, &block)<tt><br /></tt> end # end<tt><br /></tt> #<tt><br /></tt> def </span><span class="il"><span class="idl">#{</span>severity.downcase<span class="idl">}</span></span><span class="k">? # def debug?<tt><br /></tt> enabled_for?(</span><span class="il"><span class="idl">#{</span>severity<span class="idl">}</span></span><span class="k">) # DEBUG >= @level<tt><br /></tt> end # end</span><span class="dl"><tt><br /></tt> EOT</span></span><tt><br /></tt> <span class="r">end</span><tt><br /></tt><tt><br /></tt> <span class="r">def</span> <span class="fu">method_missing</span>(meth, *args)<tt><br /></tt> puts <span class="s"><span class="dl">"</span><span class="k">UNSUPPORTED METHOD CALLED: </span><span class="il"><span class="idl">#{</span>meth<span class="idl">}</span></span><span class="dl">"</span></span><tt><br /></tt> <span class="r">end</span><tt><br /></tt><span class="r">end</span></pre></td><br /></tr></table></div>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-75492648053002446712009-08-05T14:42:00.000-07:002009-08-10T08:09:52.566-07:00A Scala version of Unle Bob's lazy PI sequence in ClojureI follow Uncle Bob on twitter and I'm also currently learning Scala. When I saw his tweet about a lazily evaluated infinite PI sequence in Clojure I couldn't resist giving it a shot in Scala. Here's my result (it works!). Feedback is absolutely welcome.<div class="overflow"><table class="CodeRay"><tr><br /> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt><br /></tt>2<tt><br /></tt>3<tt><br /></tt>4<tt><br /></tt>5<tt><br /></tt>6<tt><br /></tt>7<tt><br /></tt>8<tt><br /></tt>9<tt><br /></tt><strong>10</strong><tt><br /></tt>11<tt><br /></tt>12<tt><br /></tt>13<tt><br /></tt>14<tt><br /></tt>15<tt><br /></tt>16<tt><br /></tt>17<tt><br /></tt>18<tt><br /></tt>19<tt><br /></tt><strong>20</strong><tt><br /></tt>21<tt><br /></tt>22<tt><br /></tt>23<tt><br /></tt>24<tt><br /></tt>25<tt><br /></tt>26<tt><br /></tt>27<tt><br /></tt>28<tt><br /></tt>29<tt><br /></tt><strong>30</strong><tt><br /></tt>31<tt><br /></tt>32<tt><br /></tt>33<tt><br /></tt>34<tt><br /></tt>35<tt><br /></tt>36<tt><br /></tt>37<tt><br /></tt>38<tt><br /></tt>39<tt><br /></tt><strong>40</strong><tt><br /></tt>41<tt><br /></tt>42<tt><br /></tt>43<tt><br /></tt>44<tt><br /></tt>45<tt><br /></tt>46<tt><br /></tt>47<tt><br /></tt>48<tt><br /></tt>49<tt><br /></tt><strong>50</strong><tt><br /></tt>51<tt><br /></tt>52<tt><br /></tt>53<tt><br /></tt>54<tt><br /></tt>55<tt><br /></tt>56<tt><br /></tt></pre></td><br /> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">//Gives us the :: syntax for streams. From http://www.codecommit.com/blog/scala/infinite-lists-for-the-finitely-patient.<tt><br /></tt>class RichStream[A](str: =>Stream[A]) {<tt><br /></tt> def ::(hd: A) = Stream.cons(hd, str)<tt><br /></tt>}<tt><br /></tt>implicit def streamToRichStream[A](str: =>Stream[A]) = new RichStream(str)<tt><br /></tt><tt><br /></tt>object M { //Gives us a shorthand for declaring BigDecimals that Scala lacks<tt><br /></tt> def apply(i:Int) = BigDecimal(i)<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>//Hack to work around poor bigdecimal support<tt><br /></tt>val mc = new java.math.MathContext(300,java.math.RoundingMode.HALF_UP)<tt><br /></tt>def divide(a:BigDecimal, b:BigDecimal) = {<tt><br /></tt> new BigDecimal(a.bigDecimal.divide(b.bigDecimal, mc))<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>def piSummands(n: BigDecimal): Stream[BigDecimal] = {<tt><br /></tt> divide(M(1), n) :: piSummands(n + M(2)).map(_ * -1)<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>def scaleStream(stream:Stream[BigDecimal], factor:Int) = {<tt><br /></tt> stream map {_ * factor}<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>def addStreams(s1:Stream[BigDecimal], s2:Stream[BigDecimal]) = {<tt><br /></tt> s1 zip(s2) map {z => z._1 + z._2}<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>def partialSums(s:Stream[BigDecimal]):Stream[BigDecimal] = {<tt><br /></tt> s.head :: addStreams(s.tail, partialSums(s))<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>def piStream = scaleStream(partialSums(piSummands(1)), 4)<tt><br /></tt><tt><br /></tt>def square(s:BigDecimal) = s * s<tt><br /></tt><tt><br /></tt>def eulerTransform(s:Stream[BigDecimal]):Stream[BigDecimal] = {<tt><br /></tt> val s0 = s(0)<tt><br /></tt> val s1 = s(1)<tt><br /></tt> val s2 = s(2)<tt><br /></tt> val head = s2 - divide((square(s2 - s1)), ((s0 + (M(-2) * s1) + s2)))<tt><br /></tt> head :: eulerTransform(s.tail)<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>type Transform = (Stream[BigDecimal]) => Stream[BigDecimal]<tt><br /></tt><tt><br /></tt>def makeTableau(transform: Transform, s:Stream[BigDecimal]):Stream[Stream[BigDecimal]] = {<tt><br /></tt> s :: makeTableau(transform, transform(s))<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>def acceleratedSequence(transform:Transform, s:Stream[BigDecimal]) = {<tt><br /></tt> makeTableau(transform, s) map {_.head}<tt><br /></tt>}<tt><br /></tt><tt><br /></tt>val set = acceleratedSequence(eulerTransform, piStream)<tt><br /></tt>println(set.take(150).last)</pre></td><br /></tr></table></div>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-44725824520640074392009-08-03T07:35:00.001-07:002009-08-07T08:24:36.722-07:00Rails threadsafe! and EnginesIf you're planning to use the combination of engines and threadsafe! you'll need to do some extra work. Due to a bug in Rails the engine load paths are not added to the eager_load_paths at startup time. This makes engines unavailable in threadsafe! mode. <br /><br />Fortunately, working around this problem is simple. Just subclass the plugin loader like this: <div class="overflow"><table class="CodeRay"><tr><br /> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt><br /></tt>2<tt><br /></tt>3<tt><br /></tt>4<tt><br /></tt>5<tt><br /></tt>6<tt><br /></tt>7<tt><br /></tt>8<tt><br /></tt></pre></td><br /> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">class</span> <span class="cl">EagerLoader</span> < <span class="co">Rails</span>::<span class="co">Plugin</span>::<span class="co">Loader</span><tt><br /></tt> <span class="r">def</span> <span class="fu">add_plugin_load_paths</span><tt><br /></tt> <span class="r">super</span><tt><br /></tt> engines.each <span class="r">do</span> |engine|<tt><br /></tt> configuration.eager_load_paths += engine.load_paths<tt><br /></tt> <span class="r">end</span><tt><br /></tt> <span class="r">end</span><tt><br /></tt><span class="r">end</span></pre></td><br /></tr></table></div><br />And wire up your new loader in your environment.rb:<div class="overflow"><table class="CodeRay"><tr><br /> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt><br /></tt>2<tt><br /></tt>3<tt><br /></tt>4<tt><br /></tt>5<tt><br /></tt>6<tt><br /></tt>7<tt><br /></tt>8<tt><br /></tt>9<tt><br /></tt></pre></td><br /> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c">#require the plugin loader</span><tt><br /></tt>require <span class="co">File</span>.join(<span class="co">File</span>.dirname(<span class="pc">__FILE__</span>), <span class="s"><span class="dl">'</span><span class="k">..</span><span class="dl">'</span></span>, <span class="s"><span class="dl">'</span><span class="k">lib</span><span class="dl">'</span></span>, <span class="s"><span class="dl">'</span><span class="k">eager_loader</span><span class="dl">'</span></span>)<tt><br /></tt><tt><br /></tt><span class="co">Rails</span>::<span class="co">Initializer</span>.run <span class="r">do</span> |config|<tt><br /></tt> <span class="c">#override the default loader</span><tt><br /></tt> config.plugin_loader = <span class="co">EagerLoader</span><tt><br /></tt><tt><br /></tt> <span class="c"># ...</span><tt><br /></tt><span class="r">end</span></pre></td><br /></tr></table></div>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com2tag:blogger.com,1999:blog-1712952835241167008.post-22917430369437276812009-06-25T07:20:00.000-07:002009-06-25T07:21:25.348-07:00Big O for all you fellows who slept through the algorithms course<a href="http://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/">http://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/</a>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-68482301195777296892009-05-05T09:56:00.000-07:002009-05-05T18:57:46.694-07:00Mathematicians should never code alone.Case in point:<div class="overflow"><table class="CodeRay"><tr><br /> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt><br /></tt>2<tt><br /></tt>3<tt><br /></tt>4<tt><br /></tt>5<tt><br /></tt>6<tt><br /></tt>7<tt><br /></tt>8<tt><br /></tt>9<tt><br /></tt><strong>10</strong><tt><br /></tt>11<tt><br /></tt>12<tt><br /></tt>13<tt><br /></tt>14<tt><br /></tt>15<tt><br /></tt>16<tt><br /></tt>17<tt><br /></tt>18<tt><br /></tt>19<tt><br /></tt></pre></td><br /> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">if</span> (xGrad * yGrad <= (<span class="pt">float</span>) <span class="i">0</span> <span class="c">/*(1)*/</span><tt><br /></tt> ? Math.abs(xGrad) >= Math.abs(yGrad) <span class="c">/*(2)*/</span><tt><br /></tt> ? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * neMag - (xGrad + yGrad) * eMag) <span class="c">/*(3)*/</span><tt><br /></tt> && tmp > Math.abs(yGrad * swMag - (xGrad + yGrad) * wMag) <span class="c">/*(4)*/</span><tt><br /></tt> : (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * neMag - (yGrad + xGrad) * nMag) <span class="c">/*(3)*/</span><tt><br /></tt> && tmp > Math.abs(xGrad * swMag - (yGrad + xGrad) * sMag) <span class="c">/*(4)*/</span><tt><br /></tt> : Math.abs(xGrad) >= Math.abs(yGrad) <span class="c">/*(2)*/</span><tt><br /></tt> ? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * seMag + (xGrad - yGrad) * eMag) <span class="c">/*(3)*/</span><tt><br /></tt> && tmp > Math.abs(yGrad * nwMag + (xGrad - yGrad) * wMag) <span class="c">/*(4)*/</span><tt><br /></tt> : (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * seMag + (yGrad - xGrad) * sMag) <span class="c">/*(3)*/</span><tt><br /></tt> && tmp > Math.abs(xGrad * nwMag + (yGrad - xGrad) * nMag) <span class="c">/*(4)*/</span><tt><br /></tt> ) {<tt><br /></tt> magnitude[index] = gradMag >= MAGNITUDE_LIMIT ? MAGNITUDE_MAX : (<span class="pt">int</span>) (MAGNITUDE_SCALE * gradMag);<tt><br /></tt> <span class="c">//NOTE: The orientation of the edge is not employed by this</span><tt><br /></tt> <span class="c">//implementation. It is a simple matter to compute it at</span><tt><br /></tt> <span class="c">//this point as: Math.atan2(yGrad, xGrad);</span><tt><br /></tt>} <span class="r">else</span> {<tt><br /></tt> magnitude[index] = <span class="i">0</span>;<tt><br /></tt>}<tt><br /></tt></pre></td><br /></tr></table><br /></div>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-64450461696133352092009-04-30T20:01:00.001-07:002009-04-30T21:47:29.782-07:00Ruby's object.methods reflection in ScalaUpdate: Just found out the Scala REPL in trunk has tab completion, which does pretty much exactly this with a lot less fuss. Cool. <br /><br />If you're coming from the Ruby world to learn Scala you'll learn quickly how much you miss Ruby's elegant reflection. On day 5 of my Scala adventure I took a crack at using Java's reflection APIs to do the job. I think it worked out pretty well. I'd love to hear what you think about it.<br /><br />get the code <a href="http://github.com/tjennings/Tyler-s-Scala-Extensions/tree/master">here </a><br /><table class="CodeRay"><tr><br /> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt><br /></tt>2<tt><br /></tt>3<tt><br /></tt>4<tt><br /></tt>5<tt><br /></tt></pre></td><br /> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c">//names of all methods, including super classes</span><tt><br /></tt>namesOf(methods(<span class="s"><span class="dl">"</span><span class="k">Foo</span><span class="dl">"</span></span>))<tt><br /></tt><tt><br /></tt><span class="c">//signatures of all methods, excluding super classes</span><tt><br /></tt>signaturesOf(declaredMethods(<span class="s"><span class="dl">"</span><span class="k">Foo</span><span class="dl">"</span></span>))</pre></td><br /></tr></table>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-16962602869528047062009-04-29T20:58:00.000-07:002009-04-29T21:01:23.564-07:00Hacking tail call optimization into rubyFound this via Twitter. Really interesting hack.<br /><br /><a href="http://judofyr.net/posts/tailin-ruby.html">http://judofyr.net/posts/tailin-ruby.html</a>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-81302253256045596882009-04-21T07:54:00.000-07:002009-04-21T07:55:49.316-07:00.:format feature breaks URL compliance (rfc1738) in RailsI put in a lighthouse ticket in Rails which you can read <a href="https://rails.lighthouseapp.com/projects/8994/tickets/2536-format-feature-breaks-url-compliance-rfc1738-should-be-optional#ticket-2536-2">here</a>. +1 me if you agree.Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-14371026078161846692009-03-31T19:07:00.000-07:002009-03-31T20:45:41.531-07:00Software Craftsmanship > CertificationThere's been a lot of discussion about <a href="http://jamesshore.com/Blog/Why-I-Dont-Provide-Agile-Certification.html">Agile Certification</a>, a <a href="http://blog.obiefernandez.com/content/2009/02/rails-maturity-model.html">Rails Maturity Model</a>, and <a href="http://www.infoq.com/news/2007/02/agile-certification-debate;jsessionid=700AA3D9DBAE80D429A6F9DB34ECB398">some</a> <a href="http://weblog.jamisbuck.org/2006/11/10/just-say-no-to-certification">such</a>. Are we always doomed to repeat the mistakes of the past? Remember the MS/Java/Sun/Linux certification craze of a few years ago? Yeah, same thing.<br /><br />I did some hiring back then. I'd float right over any resume that prominently listed a big set of certifications because I thought people that misguided couldn't have a clue as to how real software systems are built. Mostly I think I was right. The first resume I see sporting a Rails or Agile certification is going to get framed and put on The Wall of Shame. I don't have a Wall of Shame, but I'll create one just for it. I promise.<br /><br />For those of you coming into this industry, coming into Agile or Rails. Or for those somewhat experienced fellows looking for what to do next. Certification is not the answer. Let me now bestow upon you the few truths I know about this:<br /><span style="font-weight: bold;"><br />First. </span>I can tell you with much certainty that there is only one way to learn how to build great software: from a master. I read this Chinese proverb once and it stuck with me:<br /><br /><span style="font-style: italic;">A single conversation with a wise man is better than ten years of study</span>.<br /><br />A bit of an exaggeration, I know, but truth none the less. Nothing compares to working day in and day out with a master of the craft. Literature, conferences, training. They're all good places to start, but they are not enough. You need a mentor, the best one you can find.<br /><br /><span style="font-weight: bold;">Second</span>. It will take you years of hard work to become generally competent. I've been at it for about seven years now and I feel as if I've climbed to stand upon one great peak of knowledge only to look down on the vastness of my ignorance. Yes that was as bit dramatic. I apologize.<br /><br /><span style="font-weight: bold;">Third</span>. The only way to gauge the true skill of a craftsman (that's you) or organization is by their work and the opinions of their colleges. Not where you got your graduate degree, especially not the number of certifications pinned to your wall.<br /><br />Now that I have you convinced. What can you do? How can you apprentice yourself to a master?<br /><br />Most are not terribly hard to find. They write <a href="http://softwarecraftsmanship.oreilly.com/wiki">lots</a> <a href="http://martinfowler.com/books.html">of</a> <a href="http://www.objectmentor.com/resources/books.html">books</a>, they have <a href="http://michaelfeathers.typepad.com/">blogs</a>, they speak at <a href="http://agile2009.agilealliance.org/">conferences</a>. Find one who's style resonates with you, then figure out how to get your foot in the door wherever their opinions hold sway. If it does not work out, find someone else and try again. And again.<br /><br />If you wanted to get your foot in the door with us, for instance, you can undertake an <a href="http://www.obtiva.com/careers/software-apprentice/">apprenticeship</a>. Otherwise, There are a lot of great places to learn this craft beyond our doors. I know this list is short, but here's the few companies I can recommend (mostly in the Chicago area): <a href="http://www.obtiva.com/">Obtiva</a>, <a href="http://www.8thlight.com/">8th light</a>, <a href="http://www.atomicobject.com/">Atomic Object</a>, <a href="http://www.objectmentor.com/">Object Mentor</a>, <a href="http://www.thoughtworks.com/">ThoughtWorks.</a>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com1tag:blogger.com,1999:blog-1712952835241167008.post-90582280923280417072009-03-06T12:34:00.001-08:002009-03-06T12:36:27.625-08:00Join the Software Craftsmanship community!Interested in Software Craftsmanship but haven't found a good peer group to share your thoughts with? Then this google group is for you. <br /><br /><a href="http://groups.google.com/group/software_craftsmanship/">http://groups.google.com/group/software_craftsmanship/</a>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-23530799656072107882009-02-26T10:46:00.000-08:002009-02-26T10:50:20.669-08:00Watch Paul Graham write an essayNeat technology. It is fascinating to see how often phrases are re-worded and paragraphs completely restructured. The essay isn't half bad either. <br /><br /><a href="http://etherpad.com/ep/pad/slider/13sentences">http://etherpad.com/ep/pad/slider/13sentences</a>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-35524491404823532182009-01-28T15:00:00.000-08:002009-01-28T15:01:27.322-08:00Expand your mind into the Tenth Dimension!http://www.break.com/index/how-to-imagine-the-tenth-dimension.htmlTyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-56886683146821194842008-08-04T12:00:00.001-07:002008-08-05T10:35:28.290-07:00Spotlight updateI've deployed some new code for the spotlight app, which you can find at <a href="http://spotlight.heroku.com/">http://spotlight.heroku.com</a>. I'd like to point out two updates in particular: There is a new Javascript parser thanks to <a href="http://www.blogger.com/profile/11939486603262164846">Fred</a> and I've changed the html rendering scheme to allow you to highlight the code without highlighting the line numbers as well.Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0tag:blogger.com,1999:blog-1712952835241167008.post-1173746570082114292008-05-15T07:07:00.000-07:002008-05-15T10:42:26.072-07:00What makes a pattern?Every Tuesday the <a href="http://www.obtiva.com">Obtivians</a> have a lunchtime gathering at our Chicago office. These gatherings are pretty informal and usually start off with a few folks presenting technical topics followed by the general sort of heckling and mischief that you'd expect a room full of guys to get into. <br /><br />This last Tuesday found us with no one prepared to present a topic, so we broke off into small groups and quiet conversation. I sat down with my lunch among a few of my colleagues and engaged in the conversation they were having. About exactly what I can't remember now, but trust me - it was geeky.<br /><br />I finished my lunch as the conversation wandered toward design patterns and Ruby on Rails. I made the casual comment that I thought it was a bad idea to name a framework after a design pattern (<a href="http://api.rubyonrails.org/classes/ActiveRecord/Base.html">Rails ActiveRecord</a>, I'm looking at you) and that sparked a furious debate. <br /><br />My argument was that somewhere along its evolution ActiveRecord the framework ceased to incorporate <a href="http://martinfowler.com/eaaCatalog/activeRecord.html">Active Record</a> the pattern as it had evolved into a <a href="http://martinfowler.com/eaaCatalog/dataMapper.html">Data Mapper</a> which eventually became a full <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">ORM</a> framework, having heritage in the Data Mapper pattern . Thus rendering the name "ActiveRecord" quite misleading if you're familiar with the <a href="http://www.amazon.com/exec/obidos/ASIN/0321127420">PoEAA</a> definition. A few of my colleagues believed otherwise. <br /><br />However, the core of this debate and the topic of this blog post is not about the implementation of ActiveRecord. It is about what defines a design pattern in software and how you identify code that implements a particular pattern in the field. So let's get to that, we'll finish the debate later.<br /><br />I'm fairly young as programmers go, but I've read most all of the current literature on patterns as well as having the opportunity to work with some truly exceptional individuals. So while I may not be prepared to give a three day lecture on the topic I do believe I've formed some worthwhile opinions.<br /><br />My belief is that a design pattern in software is defined by two things: The particular problem the pattern addresses and the manner in which that problem is addressed. In my mind the interface, the UML diagram, and the implementation in code that often accompany the patterns in a book have very little to do with the definition of the pattern itself. All of these things are the artifacts of one specific implementation of said pattern. If you read the <a href="http://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional/dp/0201633612/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1210860820&sr=1-1">GoF book</a> carefully you'll find that they point this out directly. They warn us against misinterpreting the examples as the literal pattern. A lot of us didn't listen very well.<br /><br />Patterns are conceptual entities and that can make them especially difficult to grasp for the newcomer. Examples help with this, I get that. However, the unfortunate side effect being that these early students often latch on to the well-meaning examples and miss the underlying concept of the pattern itself, the one thing that really matters. Even worse, it can take a long time to get out of this rut.<br /><br />What I try to do is identify what makes a pattern's solution conceptually unique among its peers. Let's take PoEAA Active Record versus Data Mapper, which was the topic of our debate. An Active Record is a simple row-level representation of a table in a relational database. What makes it unique is that the data to be persisted and the logic that handles that persistence all reside inside the same object - the Active Record. <br /><br />In contrast, the Data Mapper creates a separation between the data and the logic that persists it. How that separation is achieved, to me, is unimportant. How I interact with the persisted object is unimportant. The UML definition is unimportant. What is important and what makes Data Mapper different from Active Record is the separation of the data to be persisted and the logic that handles that persistence (yes, I said that twice). That is the essence of Data Mapper. <br /><br />Therefore, Rails' Active Record is more of a PoEAA Data Mapper than it is a PoEAA Active Record.<br /><br />Now let's entertain the counter argument for a minute. What if a pattern is defined by the way in which you interact with it (its interface). And what if we apply that to patterns we see in another field, say architecture?<br /><br />In your lifetime, how many thousands of doors have you interfaced with? How many of those doors had the same interface? Not many. The doors in my house have a knob to engage the latching mechanism and a hinge at one side, but the door to my garage curls up over the cars on rails when I push a button. The door at the grocery store moves out of my way automatically. <br /><br />Are we wrong to say that all of these things are Doors? That there is One True Door and all other impostors must be called something else? Of course not, that's silly. Why? Because the concept - the pattern - of a door has nothing to do with its interface, or the materials it is made of, or its picture in a book. A door is a conceptual thing. If I was to define Door I would say it is "An easily removable barrier between two spaces".<br /><br />What I really want to know is how each of you feel about this? What defines a pattern in your mind? Please, leave a comment.Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com1tag:blogger.com,1999:blog-1712952835241167008.post-75058934682555746492008-03-19T07:00:00.001-07:002008-03-20T07:03:59.557-07:00Pretty code snippets for the massesIn my last blog entry I pointed out a tool called Coderay for generating syntax-highlighted html for blog posts. Two train rides later and I've launched an application that should make life a whole lot easier for folks who want pretty code on their blog.<br /><br />I give you Spotlight:<br /><a href="http://spotlight.heroku.com/"><br />http://spotlight.heroku.com/</a><br /><br />In other news, heroku is <censored/> awesome.Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com5tag:blogger.com,1999:blog-1712952835241167008.post-69840946415340292452008-03-17T19:18:00.001-07:002008-03-17T19:24:34.055-07:00I can stop whining about syntax highlighting for my blogI've been googling off and on in what turned out to be a rather futile attempt at figuring out how to get the cute syntax highlighting you see on a lot of the major rails blogs working. Via <a href="http://www.therailsway.com/">The Rails Way</a> I discovered the feature was apparently related to <a href="http://mephistoblog.com/">Mephisto</a>, for which I promptly downloaded the source. <br /><br />A few minutes later I found what I was looking for:<br /><br /><a href="http://rubyforge.org/projects/coderay/">http://rubyforge.org/projects/coderay/</a>Tyler Jenningshttp://www.blogger.com/profile/10115803805724277158noreply@blogger.com0