<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6501871454363042841</id><updated>2012-01-19T11:06:52.321-08:00</updated><category term='ruby'/><category term='lean'/><category term='visualization'/><category term='jQuery'/><category term='javascript'/><category term='java'/><category term='sass'/><category term='mock objects'/><category term='csster'/><category term='apple'/><category term='powerbook'/><category term='experience'/><category term='scriptaculous'/><category term='selenium'/><category term='battery'/><category term='cloud'/><category term='demo'/><category term='Date class'/><category term='software development'/><category term='style'/><category term='sessions'/><category term='expectations'/><category term='resumes'/><category term='scrum'/><category term='prototyping'/><category term='entities'/><category term='css'/><category term='agile'/><category term='git'/><category term='mini-project'/><category term='browser'/><category term='consulting'/><category term='rails'/><category term='color'/><category term='html'/><category term='coding'/><category term='unit testing'/><category term='design'/><category term='tdd'/><category term='cheatsheet'/><category term='C5'/><category term='xp'/><category term='hardware'/><category term='exploration'/><title type='text'>NDP Software</title><subtitle type='html'>Thoughts on day-to-day software craftsmanship.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>29</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-4969951774159921275</id><published>2012-01-16T21:36:00.000-08:00</published><updated>2012-01-19T11:06:52.389-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='coding'/><title type='text'>Getting paid by LOCs?</title><content type='html'>&lt;div class="p1"&gt;I don't get paid by the lines of code I write. But I've certainly joked about it: there's a rumor that Trevor, who writes way too many comments, gets paid by the line (including comments).&amp;nbsp;Über-programmer Dave things his manhood is measured by the size of his commits.&amp;nbsp;On a recent project the codebase was so large that I joked that every commit should be rejected if it didn't reduce the line count of the project. Haha.&lt;/div&gt;&lt;div class="p2"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="p1"&gt;This brings me to the boy scout rule: always leave the code nicer than when you started. This can be a little hard to quantify with a number: &lt;a href="http://blog.james-carr.org/2009/10/01/beware-code-coverage-metrics/"&gt;test coverage&lt;/a&gt;, &lt;a href="http://www.ibm.com/developerworks/java/library/j-cq03316/"&gt;cyclomatic complexity&lt;/a&gt; and &lt;a href="http://clarkware.com/software/JDepend.html"&gt;coupling&lt;/a&gt; metrics are all often used. But with a bloated codebase, a simple line count reduction may suffice to improve the codebase, although is not the &lt;a href="http://www.thoughtclusters.com/2011/04/refactoring-is-not-about-reducing-code/"&gt;main&lt;/a&gt; &lt;a href="http://www.refactoringredmine.com/refactoring-to-reduce-code/"&gt;goal&lt;/a&gt;. In my casual joking about this with my teammates, they all liked it. But how?&lt;/div&gt;&lt;div class="p2"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="p2"&gt;&lt;b&gt;How to Count&lt;/b&gt;&lt;/div&gt;&lt;div class="p2"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="p1"&gt;If you use&amp;nbsp;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;git log --stat&lt;/span&gt;, you can see&amp;nbsp;the number of lines associated with each commit (after all the details), such as:&lt;/div&gt;&lt;div class="p2"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;331 files changed, 0 insertions(+), 25866 deletions(-)&lt;/span&gt;&lt;/div&gt;&lt;div class="p2"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="p1"&gt;It's pretty easy to write a script that parses this information out, and &lt;a href="https://github.com/ndp/gitlc"&gt;that's what I've done in gitlc tool&lt;/a&gt;. Beyond the obvious grep, I added support for some necessities:&lt;/div&gt;&lt;div class="p1"&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;"pairs" set up using the git-pair tool. Commits checked in by multiple people will be credited to all committers. This is necessary with any project where pairing is used.&lt;/li&gt;&lt;li&gt;"aliases", so that if the same person commits with different email addresses (or it changes throughout the lifecycle of the project), the commit counts can be aggregated. This optional feature can be used by providing an optional yaml file.&lt;/li&gt;&lt;li&gt;commits can be aggregated by person, month, or simply by commit&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div class="p1"&gt;Usage is pretty simple. Just download it&amp;nbsp;and point it at a local directory with a .git folder. Here's gitlc source itself and ask for a summary by person:&lt;/div&gt;&lt;div class="p1"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;$ ./gitlc.rb -r . -p&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;[["ndp", {:net=&amp;gt;657, :adds=&amp;gt;26579, :deletes=&amp;gt;25922}]]&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="p1"&gt;As you can see, I had a 25K of code, which I abandoned quickly (thanks node).&amp;nbsp;For projects with more people, it's more interesting:&lt;/div&gt;&lt;div class="p1"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;./gitlc.rb -p -r ../ruby-build/&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;[["sam", {:net=&amp;gt;499, :adds=&amp;gt;639, :deletes=&amp;gt;140}],&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;["jeremy", {:net=&amp;gt;33, :adds=&amp;gt;50, :deletes=&amp;gt;17}],&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;["josh", {:net=&amp;gt;21, :adds=&amp;gt;41, :deletes=&amp;gt;20}],&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;["jesse", {:net=&amp;gt;16, :adds=&amp;gt;21, :deletes=&amp;gt;5}],&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;["guilleig", {:net=&amp;gt;11, :adds=&amp;gt;53, :deletes=&amp;gt;42}],&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;["bensie", {:net=&amp;gt;6, :adds=&amp;gt;6, :deletes=&amp;gt;0}],&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;["chris", {:net=&amp;gt;6, :adds=&amp;gt;29, :deletes=&amp;gt;23}],&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;["sstephenson", {:net=&amp;gt;5, :adds=&amp;gt;5, :deletes=&amp;gt;0}],&lt;/span&gt;&lt;/div&gt;&lt;div class="p1"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The tool sorts people by their "net" contribution to the project. For ruby-build, there's a long-tail of people with 1 line changes omitted. &amp;nbsp;Please &lt;a href="https://github.com/ndp/gitlc"&gt;check out gitlc&lt;/a&gt; and see what you learn about your project.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'd love to have contributes to this tool. Feel free to fork and contribute back. It's easy to imagine better visualizations and a tool that could be incorporated in a workflow.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Practicalities&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When you've got these statistics, and people start to care about their line count number. People ask me&amp;nbsp;what to do if you need to write new functionality?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I recommend:&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="p1"&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Look first to share code. In a relatively large codebase, most features should already be there, but perhaps in a slightly different form. Most codebases bloat because we aren't able to figure out the common patterns. We can blame this on a hazy product definition, but at some point it's our responsibility to organize and structure the code ourselves.&lt;/li&gt;&lt;li&gt;Look for dead code. What features and code are no longer used? &amp;nbsp;Get code coverage tools running, which help you identify branches of code that you can remove. Use web analytics to discover pages that are seldom visited and advocate to cut them out.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div class="p1"&gt;Riffing on the last point, a corollary to this blog post is the one that says each story needs to be accompanied by an "unstory". If we add a link to this page, what link do you want to take away? If the user can now spend 20 minutes cropping their profile picture, what aren't they going to be doing? But that's left for another day...&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-4969951774159921275?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/4969951774159921275/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=4969951774159921275' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/4969951774159921275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/4969951774159921275'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2012/01/getting-paid-by-locs.html' title='Getting paid by LOCs?'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1313076485069180272</id><published>2011-05-05T21:54:00.000-07:00</published><updated>2012-01-16T22:06:45.753-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='lean'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='prototyping'/><category scheme='http://www.blogger.com/atom/ns#' term='consulting'/><category scheme='http://www.blogger.com/atom/ns#' term='coding'/><title type='text'>Do I really have to write tests?</title><content type='html'>Over at the Lean Startup Circle mailing list, they're discussing&amp;nbsp; &lt;a href="http://groups.google.com/group/lean-startup-circle/browse_thread/thread/def1deba5f97caf9"&gt;what kind of tests to expect from developers&lt;/a&gt;.&amp;nbsp; I enjoy this conversation. People are often looking for a clear guideline ("&lt;a href="http://www.quora.com/Test-Driven-Development/Is-Test-Driven-Development-practical-for-a-startup"&gt;startups don't need tests&lt;/a&gt;") or "code coverage" figure-- or have one in mind.&amp;nbsp;Idealistic agilists insist you &lt;i&gt;always write tests&lt;/i&gt;. They equate not writing tests with abandoning quality-- the beginning of the end. "Such carelessness will lead to bugs upon bugs and eventually, &lt;a href="http://mindprod.com/jgloss/unmain.html"&gt;unmaintainable code&lt;/a&gt;." Reality requires a more nuanced and pragmatic approach suggested below.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Getting Real&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;I learned in Q.A. school (or was it the Q.A. streets?), you can't test everything. Be pragmatic because software is just too complex to allow coverage of all cases. Good test design requires thoughtful prioritization-- minimal money yielding maximum test coverage. Q.A. engineers learn the requirements and the relative importance of each. They let this understanding guide how you design a test suite. They learn to carefully consider the quality requirements, where the critical parts of the software is, and where potential problems might appear, and what the costs of failure are. In this cost-benefit and risk analysis, there isn't just one, simple answer.&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;The main factors to consider to judge how much testing is needed, keeping an eye towards developer-written tests: &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Cost of bugs&lt;/b&gt;. Different software has different quality requirements. One of my projects would cost $1000s of dollars per hour if it crashed-- and cause freeway backups, and bring the L.A. news helicopters, etc. Another one let you comment on your latest book purchase.&amp;nbsp;Some applications really are heart-lung machines.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Anticipated life of the code.&lt;/b&gt; Some code is for prototypes or other "throwaway" purpose. But veterans bristle at the thought, and will recount how quick hacks became the core of mission critical systems, and that code just doesn't go away. Seconding their motion, TDD disciples insist on writing tests for everything. There certainly is risk in creating code too casually, but prototyping is a powerful tool. Prototyping is powerful. If you can figure out how to build out your ideas quickly and explore them with your users,&amp;nbsp; you'll be able to able to out-pace competitors. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Area of the application&lt;/b&gt;. Not all code of an app is the same. At a concert, there are soloists and choir members. The audience will be much more critical of the soloists. Singing coaches don't spend as much time with each choir member as they do with soloists. Therefore, identify the most important part of your app.&lt;br /&gt;&lt;br /&gt;I'm a big advocate of building from the inside out: identify these critical parts of the system. Validate the model with everyone, and test the heck out of them. Make it unbreakable. Then, using these as building blocks, put together the pieces in various ways. Allow yourself to test different combinations with the knowledge that the blocks won't break from underneath you. Many a team has treated speculative admin interfaces with the same vigilance as the core data model. It's quick to build prototypes with solid building blocks.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Likelihood of bugs.&lt;/b&gt; Some areas of an app are like compost piles, and attract bugs.&amp;nbsp; Good developers should be able to give you an  honest appraisal. They should be able to identify difficult problems, as well as error-prone approaches to problems. Some code just evolves to become gnarly to work with. Adjust testing needs appropriately.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Test-driven design requirements&lt;/b&gt;. TDD is an excellent tool for figuring how the code should be written and structured. I find that using TDD, when informing the design, save significant development effort. Don't sacrifice testing when it will benefit the code. If it's faster to write a prototype with TDD, I'll use it.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Team size. &lt;/b&gt;A lone developer focusing on one project will have less of a need for tests than a larger team all sharing a codebase. Yes, with luck, the project will grow into something larger, but there's also an important "just-in-time" theme to agile. This is not an excuse for skipping testing, but is a factor to consider.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Other quality checks.&amp;nbsp; &lt;/b&gt;A team auto-deploying to the cloud needs more checks-- and tests-- than a team with a dedicated Q.A. team watching out for them.&lt;br /&gt;&lt;br /&gt;There are other factors, but those are the important ones. Let's resist talking in absolutes, and do what is best for our project, customers and users.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1313076485069180272?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1313076485069180272/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1313076485069180272' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1313076485069180272'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1313076485069180272'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2011/05/do-i-really-have-to-write-tests.html' title='Do I really have to write tests?'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-5049058761551128593</id><published>2011-05-03T20:09:00.000-07:00</published><updated>2011-05-29T12:36:00.622-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='entities'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='experience'/><category scheme='http://www.blogger.com/atom/ns#' term='exploration'/><category scheme='http://www.blogger.com/atom/ns#' term='cheatsheet'/><category scheme='http://www.blogger.com/atom/ns#' term='mini-project'/><category scheme='http://www.blogger.com/atom/ns#' term='html'/><title type='text'>Interesting Character Entities</title><content type='html'>I spent another couple hours on &lt;a href="http://ndpsoftware.com/&amp;amp;what/"&gt;my character entity finder&lt;/a&gt;, and wanted to share some of the interesting things I've discovered. I made two main improvements:&lt;br /&gt;(1) lookup happens asynchronously, in small batches, so that it suffers less from "locking up". This allows much more flexible exploration.&lt;br /&gt;(2) allow you to bookmark queries, so you can share interesting discoveries.&lt;br /&gt;&lt;br /&gt;To recap, I built this tool to find all the weird quotes:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ndpsoftware.com/&amp;amp;what/#q='"&gt;http://ndpsoftware.com/&amp;amp;what/#q='&lt;/a&gt;&lt;br /&gt;&lt;a href="http://ndpsoftware.com/&amp;amp;what/#q=quot"&gt;http://ndpsoftware.com/&amp;amp;what/#q=quot&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Wow, I discovered there's a nice set of chess pieces:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ndpsoftware.com/&amp;amp;what/#q=chess"&gt;http://ndpsoftware.com/&amp;amp;what/#q=chess&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Just a minute ago, I added the ability to bypass the query builder-- if you enter a full regular expression. The regular expression is matched against entity names (and numbers, and nicknames). For example, there are all sorts of icons available:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ndpsoftware.com/&amp;amp;what/#q=/97[2-6]\d/"&gt;http://ndpsoftware.com/&amp;amp;what/#q=/97[2-6]\d/&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;The currency symbols aren't easily found with a single query, but you can build a page with a selection of them:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ndpsoftware.com/&amp;amp;what/#q=/currency|euro|dollar|pound/"&gt;http://ndpsoftware.com/&amp;amp;what/#q=/currency|euro|dollar|pound/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It's surprisingly fun to play around with. Give it a spin.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-5049058761551128593?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/5049058761551128593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=5049058761551128593' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/5049058761551128593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/5049058761551128593'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2011/05/interesting-character-entities.html' title='Interesting Character Entities'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1760907357804669982</id><published>2011-04-04T23:43:00.000-07:00</published><updated>2011-04-04T23:43:53.840-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='lean'/><category scheme='http://www.blogger.com/atom/ns#' term='demo'/><category scheme='http://www.blogger.com/atom/ns#' term='xp'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='cheatsheet'/><category scheme='http://www.blogger.com/atom/ns#' term='mini-project'/><category scheme='http://www.blogger.com/atom/ns#' term='coding'/><title type='text'>Finding entities, characters, glyphs</title><content type='html'>&lt;a href="http://ndpsoftware.com/&amp;what/"&gt;My latest "mini-project"&lt;/a&gt;, a few hours in, solves the annoying problem of trying to remember entity numbers or names. For example, our last project used the entity &amp;amp;raquo; (&amp;raquo;), and it seemed like it took me two years to remember it. Now, with this tool, I can just type "&gt;&gt;" and the character, symbol and number appear. I tried to make it "mobile friendly", and may experiment with packaging it as an "app".&lt;br /&gt;&lt;br /&gt;I'm intrigued by "mini-projects"... something I can put together in a few minutes or hours and provide value to someone. It's a long-time obsession: in the 90s when I got my first laptop, I tested myself to see what I put together on my 22 minute BART ride from Oakland to 24th Street. It was fun, but I never creating anything of general interest.&lt;br /&gt;&lt;br /&gt;Now, these little tools (and hacks) can be quicker to build out and easier to share. In fact, there are 1000s of "apps" out there, many of which look like they can be done in hours. Anyway, I have a few &lt;a href="http://ndpsoftware.com/"&gt;on my web site&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;What makes things easier is there is so much data out there to build upon. As you'd expect, it takes the entity list of W3C. But I supplemented this with interesting Unicode characters, and a nice set from Remy Sharp.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ndpsoftware.com/&amp;what/"&gt;Check it out&lt;/a&gt;, bookmark it, and please send me feedback.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1760907357804669982?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1760907357804669982/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1760907357804669982' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1760907357804669982'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1760907357804669982'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2011/04/finding-entities-characters-glyphs.html' title='Finding entities, characters, glyphs'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-3286980891773372801</id><published>2011-03-28T21:44:00.000-07:00</published><updated>2011-04-01T07:57:55.074-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='sessions'/><title type='text'>Friendlier Session Timeouts 2.0</title><content type='html'>As we discovered today, coding up friendly session time-outs involves more than meets the eye. As you know, a session time-out logs the user out after a period of inactivity. But interactions with web sites, and "inactivity," have changed over the last 10-15 years.&lt;br /&gt;&lt;br /&gt;We have a fairly plain Rails app, and we're implementing a series of security fixes in anticipation of an audit. In what seemed like a simple "1-pointer", we were asked to remove the "Remember Me" checkbox from our application, and force the user to be logged out after 20 minutes. &lt;br /&gt;&lt;br /&gt;For the first pass, we simply set the session timeout to 20 minutes and removed the checkbox (and the underlying implementation). Easy eh?&lt;br /&gt;&lt;br /&gt;Sadly, this solution leaves much to be desired. When the user (or Q.A. engineer) returns after 1/2 hour, the page is unchanged and ready for action. But any click redirects, confuses, and potentially loses in-progress work.&lt;br /&gt;&lt;br /&gt;Although this might have be acceptable in 1998, it's 2011 and we got Ajax. Clicking a link on a Web 1.0 site takes you to another page; if you happen to be logged out, oh well, sign back in and continue-- you probably weren't doing something that important anyhow. &lt;br /&gt;&lt;br /&gt;But these days, on an Ajax page, if your session expires while you're viewing the page, clicking on any element of the page can reveal a session timeout. This may appear to the user as a server error, or if it's handled correctly (like we did), a page redirect. Clicking on a disclosure triangle redirects to a new page? Now we have a surprised user.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="font-size: 80%; clear: both; background-color: #eee; text-align: left;"&gt;&lt;a href="http://4.bp.blogspot.com/-dAHRdyyCfuM/TZFhGYWnjVI/AAAAAAAAAy0/NWMu5i06-M0/s1600/Picture%2B7.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="246" src="http://4.bp.blogspot.com/-dAHRdyyCfuM/TZFhGYWnjVI/AAAAAAAAAy0/NWMu5i06-M0/s320/Picture%2B7.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I Googled a bit and found lots of bug reports around this behavior. As this Jira snapshot shows, resolving this might be trickier than anticipated.&lt;br /&gt;&lt;br /&gt;I checked out my bank's solution. It looked like they implemented a whole timeout scheme in Javascript. Were they wacko? (No... but we'll get there.)&lt;br /&gt;&lt;br /&gt;Of the hand full of ideas out there on the web, here's one solution (&lt;a href="http://www.sidesofmarch.com/index.php/archive/2005/01/30/friendly-session-timeouts-the-javascript-way/"&gt;of a hand full&lt;/a&gt;) with the idea of timing the session in Javascript:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;script language="javascript"&amp;gt;&lt;br /&gt;&lt;br /&gt;function confirmLogoff() {&lt;br /&gt;&amp;nbsp; if (confirm("Your session will end in one minute.\n\nPress OK to continue for another ten minutes.")){&lt;br /&gt;&amp;nbsp; location.reload();&lt;br /&gt;&amp;nbsp; }&lt;br /&gt;}&lt;br /&gt;setTimeout("confirmLogoff()", &lt;%= sessionTimeout - 1000*60 %&gt;);&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;At first blush this looks like they might be on to something. Alas no. The confirmation will only work the brief period between the client and the server timeout. I'd argue it's even worse than no solution, since the messages promises something it can't deliver on, and loses the user's work-in-progress.&lt;br /&gt;&lt;br /&gt;Did I mention we have &lt;b&gt;quite a few pages with AJAX requests&lt;/b&gt; on them? These conveniently extend the session timeout on the server when the user interacts with them. But they break any sort of client-side timer that is set up at page load. Any sort of "meta refresh" schemes doing something similar to the above were quickly dismissed.&lt;br /&gt;&lt;br /&gt;And why should just AJAX backed behaviors restart this timer? Opening a hidden panel may or may not go to the server (depending on an developer's whim), but should this whim this really affect the user's timeout? So we started considering implementations that ping the server as the user interacts. This was also dismissed as being complicated to implement efficiently (and potentially introducing some sort of security issue).&lt;br /&gt;&lt;br /&gt;Finally where we "settled" (as in prom date), is implementing a timeout within Javascript, like my bank. It's a little more sophisticated: it's reset not only by the initial page load, but all sorts of user interactions. The code finally reduced down to:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;var clientSessionTimeout = function(timeoutMS, logoutFn) {&lt;br /&gt;&amp;nbsp; var lastTimeout;&lt;br /&gt;&lt;br /&gt;&amp;nbsp; var startSessionTimeout = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (lastTimeout) clearTimeout(lastTimeout);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; lastTimeout = setTimeout(function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; logoutFn();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }, timeoutMS);&lt;br /&gt;&amp;nbsp; };&lt;br /&gt;&lt;br /&gt;&amp;nbsp; // Watch for activity&lt;br /&gt;&amp;nbsp; $('body').click(startSessionTimeout).keydown(startSessionTimeout);&lt;br /&gt;&amp;nbsp; startSessionTimeout();&lt;br /&gt;};&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This is called with the 20-minute timeout, and implements it beautifully:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; clientSessionTimeout(20 * 60 * 1000, function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; document.location = '/timeout?return_to=' + document.location.href;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; });&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The server side timeout is for redundancy. Our pages are all pretty focused, so I doubt any user will spend more than a couple minutes on any one of them. We picked a server-side session timeout of 40 minutes. The only way the Javascript timeout won't kick in first is if the user interacts with the page for more than this time with no server side interaction... possible, but not likely.&lt;br /&gt;&lt;br /&gt;After completing this compromise solution, I'm ready to spell out some ideal requirements:&lt;br /&gt;* session timeout with no interaction after 20 minutes&lt;br /&gt;* any interaction on the page should reset the timeout&lt;br /&gt;* warn the user (if possible) when the deadline approaches&lt;br /&gt;* this shouldn't open additional security vulnerabilities or server traffic&lt;br /&gt;&lt;br /&gt;With some additional work, I'm sure an "ideal" solution can be developed. This compromise should get us most of the way there. Thanks to the rest of my team, and an interview candidate who provided some clear thinking on the matter.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-3286980891773372801?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/3286980891773372801/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=3286980891773372801' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/3286980891773372801'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/3286980891773372801'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2011/03/friendlier-session-timeouts-20.html' title='Friendlier Session Timeouts 2.0'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-dAHRdyyCfuM/TZFhGYWnjVI/AAAAAAAAAy0/NWMu5i06-M0/s72-c/Picture%2B7.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1703388981162129277</id><published>2011-03-18T19:14:00.000-07:00</published><updated>2011-03-18T19:16:06.163-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>Curing Frequent Selenium File Upload Failures</title><content type='html'>The symptom was quite simple: do an upload, and on the next request the server reports an "IOError". As our Ruby on Rails app is pretty much thin workflow around lots of file uploads,  this was a problem. We tended not to see in on production, but us frequent users were seeing it enough to know we had to do something about it.&lt;br /&gt;&lt;br /&gt;But the real complainer was Selenium. About half the time the tests failed and needed to be coaxed into running again.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/jwinky"&gt;JWinky&lt;/a&gt; traced the root cause down to a known bug in the temp file class. With a little work (and encouragement by yours truly), he put together a patch that has eliminated the problem. We've been running with it for a couple months and haven't seen the bug once-- or heard a peep from Selenium.&lt;br /&gt;&lt;br /&gt;It's found here: &lt;a href="http://github.com/jwinky/ruby_tempfile_ioerror"&gt;http://github.com/jwinky/ruby_tempfile_ioerror&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1703388981162129277?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1703388981162129277/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1703388981162129277' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1703388981162129277'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1703388981162129277'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2011/03/curing-frequent-selenium-file-upload.html' title='Curing Frequent Selenium File Upload Failures'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1832117059234451177</id><published>2011-03-01T21:28:00.000-08:00</published><updated>2011-03-01T21:30:39.897-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='cheatsheet'/><title type='text'>Updated Cheat Sheets</title><content type='html'>Five years ago, I was working at &lt;a href="http://greatschools.org"&gt;Great Schools&lt;/a&gt;, and got interested in SEO. I started running all sorts of experiments on &lt;a href="http://ndpsoftware.com"&gt;my own site&lt;/a&gt;, to understand how I could affect things. I reorganized the URLs, added keywords, and followed all the standard recommendations. I quickly realized little tweaks to URLs, meta tags, and optimizing keyword density wasn't going to help much. These types of changes really are "optimizations"-- they'll give you a small percentage increase, but they are not game changers. If you've got millions of visitors, a 1% may mean real money, but if you're me, it doesn't matter. &lt;br /&gt;&lt;br /&gt;So, after working on SEO, I pursued another idea. Why not create something of real value to drive people to my site? I had an idea and created some "cheat sheets" to help me with my own development. I created them, and then posted them where I could to get some inbound links.  Shortly thereafter, someone at O'Reilly found my page and linked to it, and all of a sudden I was getting hundreds of page views per day. So that was my lesson: &lt;i&gt;If I provide something of value, people will come.&lt;/i&gt; That was five years ago. Even though technology changes fast, I still have a bit of tail from those original cheat sheets. &lt;br /&gt;&lt;br /&gt;Last night, I decided that since of 80% of the people hitting my site are seeing those pages, I should take a look at them and see what impression they might be making. I really don't have a "goal" of driving traffic anywhere else, but I might as well make them look as good as I can. So I cleaned up the visual design and fixed some of the editing. &lt;br /&gt;&lt;br /&gt;Check out the spruced up pages here: &lt;a href="http://ndpsoftware.com/HibernateMappingCheatSheet.html"&gt;Hibernate Mapping&lt;/a&gt; and &lt;a href="http://ndpsoftware.com/JSPXMLCheatSheet.html"&gt;JSPx Cheatsheet&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1832117059234451177?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1832117059234451177/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1832117059234451177' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1832117059234451177'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1832117059234451177'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2011/03/updated-cheat-sheets.html' title='Updated Cheat Sheets'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1183798718089039743</id><published>2011-01-21T08:09:00.000-08:00</published><updated>2011-02-19T21:17:05.273-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='color'/><title type='text'>Color Scheming with Javascript</title><content type='html'>In this post, I'll share some of my Javascript code for manipuating colors.&lt;br /&gt;&lt;br /&gt;I started a few months back with &lt;b&gt;basic color manipulation routes&lt;/b&gt;. Other libraries take a strictly object-oriented approach. This can be a little heavyweight, as it requires explicit conversions throughout the calling code. But in the HTML DOM, colors are generally expressed as hex strings, and if we have routines were built around these, they would be simpler to use. Plus, Javascript is dynamic language, so a String could have color manipulation methods. That's exactly what I did:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;'#fff'.toHexColor() =&amp;gt; '#ffffff'&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;'black'.toHexColor() =&amp;gt; '#000000'&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;'#123456'.toHexColor() =&amp;gt; '#123456' (no op)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;colorString.toRGB() =&amp;gt; array of numbers [0..255]&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;colorString.toHSL() =&amp;gt; array of numbers [[0..360],[0-100],[0-100]]&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;These building blocks aren't that exciting, but are very helpful to build color manipulation functions on:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;"#ab342c".darken(%)&lt;/code&gt;&amp;nbsp;-- make color darker by given percent&lt;/li&gt;&lt;li&gt;&lt;code&gt;"#ab342c".lighten(%)&lt;/code&gt;&amp;nbsp;-- make color lighter by given percent&lt;/li&gt;&lt;li&gt;&lt;code&gt;"#ab342c".saturate(%)&lt;/code&gt;&amp;nbsp;-- make color more saturated by given percent. To&amp;nbsp;&lt;i style="line-height: 1.4em; margin: 0px; padding: 0px;"&gt;desaturate&lt;/i&gt;, use negative values for the percent. Note that&lt;code&gt;"#ab342c".saturate(-100)&lt;/code&gt;&amp;nbsp;renders in grayscale.&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;Generating new colors&lt;/b&gt;&lt;br /&gt;Sometimes, you just need a color to get you started:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;ColorFactory.random() // a random color, somewhat evenly distributed.&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;ColorFactory.randomGray() // a random gray scale value.&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;ColorFactory.randomHue()  // given a saturation and lightness returns a random color.&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;or &amp;nbsp;primary and secondary color schemes:&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxf4G1QYI/AAAAAAAAAx8/5ounQ4VuJJQ/s1600/primary.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxf4G1QYI/AAAAAAAAAx8/5ounQ4VuJJQ/s1600/primary.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Basic color theory&lt;/b&gt; includes the concepts of complementary and analogous colors, so they are provided:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxfP3scmI/AAAAAAAAAx0/QhUt5soKwqA/s1600/complementary.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxfP3scmI/AAAAAAAAAx0/QhUt5soKwqA/s1600/complementary.png" /&gt;&lt;span class="Apple-style-span" style="color: black;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxepqDDUI/AAAAAAAAAxs/U_b9CtM9vGQ/s1600/analagous.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: left;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxepqDDUI/AAAAAAAAAxs/U_b9CtM9vGQ/s1600/analagous.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;And there is a generic "interpolation" beween two colors. It works interpolating hues, saturation or lightness:&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxfvhTm3I/AAAAAAAAAx4/GmZu38iAUUY/s1600/interpolate.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="171" src="http://2.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxfvhTm3I/AAAAAAAAAx4/GmZu38iAUUY/s400/interpolate.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Generating Schemes for Visualizations&lt;/b&gt;&lt;br /&gt;Now that we have all the building blocks, we can start building schemes for specific purposes. (&lt;a href="http://www.personal.psu.edu/cab38/ColorSch/Schemes.html"&gt;Schemes get applied in specific ways&lt;/a&gt;). For example, if colors are used to represent a &lt;i&gt;quantitative range of values&lt;/i&gt;, the colors must visually read as such. The most straightforward way to do this is to linearly lay them out in a monochromatic scheme.&amp;nbsp;Your eye can read "that value is more than that value" because of the visual relationship, in either saturation or lightness.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxgq62DEI/AAAAAAAAAyE/PZj0a6GIXDk/s1600/sequential.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="117" src="http://1.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxgq62DEI/AAAAAAAAAyE/PZj0a6GIXDk/s400/sequential.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;This differs from &lt;i&gt;qualitatively distinct values&lt;/i&gt; (such as states on a modern election map), which must be read as distinct, but not qualitatively related. A triadic approach is more appropriate in this case. A viewer should at no point be enticed into imaging a "red" state is more or less of something than a "blue" state-- they are distinct categories.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxgDAt0JI/AAAAAAAAAyA/FUjtttnXYm4/s1600/qualitative.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="112" src="http://2.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxgDAt0JI/AAAAAAAAAyA/FUjtttnXYm4/s400/qualitative.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxe2W4twI/AAAAAAAAAxw/-KdweZuaT8U/s1600/binary.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="112" src="http://2.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxe2W4twI/AAAAAAAAAxw/-KdweZuaT8U/s320/binary.png" width="320" /&gt;&lt;/a&gt;Sometimes visualizations include &lt;i&gt;binary&lt;/i&gt;, yes/no values. And some data visualizations are about how the values diverge, where values need to read from as quantitatively diverging from a central value. These are challenging to construct so that they read correctly, and cartographers are experts at this.&lt;br /&gt;&lt;br /&gt;I've in the process of building &lt;a href="http://github.com/ndp/jsutils"&gt;a set up Javascript functions&lt;/a&gt; that support this type of color scheme generation. It's still a work in progress, but I have found it quite valuable. I'm curious if others find this useful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1183798718089039743?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1183798718089039743/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1183798718089039743' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1183798718089039743'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1183798718089039743'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2011/01/color-scheming.html' title='Color Scheming with Javascript'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_RZ6yXxZ1RUA/TUXxf4G1QYI/AAAAAAAAAx8/5ounQ4VuJJQ/s72-c/primary.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-834015355162809175</id><published>2010-12-29T07:50:00.000-08:00</published><updated>2011-01-30T14:43:17.187-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='color'/><title type='text'>Color Factory: Color scheme generators</title><content type='html'>While working on a spin-off from &lt;a href="http://bedsider.org/"&gt;bedsider&lt;/a&gt;, I created &lt;a href="https://github.com/ndp/csster"&gt;Csster&lt;/a&gt;. Alex @ C5 encouraged me (and coached me a bit) on getting through functions around color math, and as far as I know, the functions in Csster are some of the only Javascript implementations. I find the invaluable as I build out Javascript functionality, and I &lt;a href="https://github.com/ndp/jsutils/blob/master/color_helpers.js"&gt;am working on&lt;/a&gt; separating them out from Csster itself.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;Building my own visualizations, I've needed to bone-up on color theory. I thought I'd share some of what I've learned.&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;I've always been a interested in color-- mixing paints, collecting "color" items as a child, and design class in college. I've delved in deep in the past and have a good understanding from a theory of it. Most recently I caught up on the computational side of it, writing a Javascript library to help manage colors. To be honest, I still lack the practical experience necessary to have a great intuitive feel for colors, but I'm getting there.&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;b&gt;Schemes &amp;amp; Tools&lt;/b&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;There are numerous software tools out on the web, and available for download, for creating colors schemes. Most are general free-for-alls-- paste in some hex codes please-- and offer no assistance, beyond live preview, tagging, named schemes or Google ads. The best of the bunch is probably the flashy&amp;nbsp;&lt;a href="http://kuler.adobe.com/"&gt;Kuler&lt;/a&gt;.&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;The ones I like better (like&amp;nbsp;&lt;a href="http://colorschemedesigner.com/"&gt;Color Scheme Designer&lt;/a&gt;) bring to the forefront&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Color_scheme"&gt;categories stemming from color theory and a color wheel&lt;/a&gt;, selecting a set of colors from a single tangent (monochrome), opposite sides (complementary), analogic (or analogous, for nearby). There are also triadic (3) and a host of variations and combinations of these. It's this type of color theory that designers study and provides the necessary grounding to understand a design and a "scheme" that supports it.&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;As I poke around building &lt;a href="http://uxspoke.com/"&gt;more&lt;/a&gt; &lt;a href="http://www.ndpsoftware.com/git-cheatsheet.html"&gt;visualizations&lt;/a&gt;, I quickly found that I needed "color schemes"-- not so much color manipulation as coherent sets of colors. I started building some of my own tools, and then stopped, and stepped back to understand some of the theory behind color schemes. One of the easiest from Cynthia Brewer &lt;a href="http://www.personal.psu.edu/cab38/ColorSch/Schemes.html"&gt;here&lt;/a&gt;. Ms. Brewer explains a few different types of color schemes specific to data visualization: binary, qualitative, linear, and divergent. &lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Sequential schemes&lt;/b&gt; are suited to ordered data that progress from low to high. Lightness steps dominate the look of these schemes, with light colors for low data values to dark colors for high data values.&lt;/li&gt;&lt;li&gt; &lt;b&gt;Diverging schemes&lt;/b&gt; put equal emphasis on mid-range critical values and extremes at both ends of the data range. The critical class or break in the middle of the legend is emphasized with light colors and low and high extremes are emphasized with dark colors that have contrasting hues.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Qualitative schemes&lt;/b&gt; do not imply magnitude differences between legend classes, and hues are used to create the primary visual differences between classes. Qualitative schemes are best suited to representing nominal or categorical data. &lt;/li&gt;&lt;/ul&gt;Using these schemes could bring sanity to lots of charting libraries out there-- and it's quite sad that they don't support such technology.&lt;br /&gt;&lt;br /&gt;I'm building a "color factory" to provide functions for these. Provide the functions one (or two) colors from your palette and get a cohesive "color scheme" that should work with it-- and even better-- be appropriate to the data. These are all found in what I call the &lt;a href="https://github.com/ndp/jsutils/blob/master/color_factory.js"&gt;color factory&lt;/a&gt;. It's nascent technology, and I'm interested in feedback. &lt;br /&gt;&lt;br /&gt;In the next post, I'll dig into the Javascript functions that facilitate this.&amp;nbsp;(All this is part of a generic &lt;a href="https://github.com/ndp/jsutils"&gt;Javascript sketchbook&lt;/a&gt;.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-834015355162809175?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/834015355162809175/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=834015355162809175' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/834015355162809175'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/834015355162809175'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/12/color-factory-color-scheme-generators.html' title='Color Factory: Color scheme generators'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-6138567051050636552</id><published>2010-12-23T21:57:00.000-08:00</published><updated>2010-12-23T21:57:46.098-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='csster'/><category scheme='http://www.blogger.com/atom/ns#' term='sass'/><category scheme='http://www.blogger.com/atom/ns#' term='style'/><category scheme='http://www.blogger.com/atom/ns#' term='jQuery'/><category scheme='http://www.blogger.com/atom/ns#' term='css'/><category scheme='http://www.blogger.com/atom/ns#' term='browser'/><title type='text'>jQuery support in Csster</title><content type='html'>I finally got around to adding a little jQuery plugin for my &lt;a href="http://github.com/ndp/csster"&gt;Csster tool&lt;/a&gt;, and released it as version 0.9.2.&lt;br /&gt;&lt;blockquote style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;$('.selector').csster({ width: 100 });&lt;/blockquote&gt;This looks a lot like the "css" method:&lt;br /&gt;&lt;blockquote style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;$('.selector').css({ width: 100 });&lt;/blockquote&gt;This difference is that Csster creates a "rule" and inserts it into the head, whereas jQuery will attach styles directly to the nodes. Sometimes you want one, and sometimes another.&lt;br /&gt;&lt;br /&gt;It's convenient to use it in the midst of jQuery work, such as:&lt;br /&gt;&lt;blockquote style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;$('.sidebar').wrapAll('&amp;lt;div&amp;gt;').addClass('ready').csster({backgroundColor: '#ffeedd'});&lt;/blockquote&gt;It's also allows the "nesting" of regular Csster:&lt;br /&gt;&lt;blockquote style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;$('.hd').csster({ ul: { margin: 10, li: { margin: 0, padding: 5 }}});&lt;/blockquote&gt;Although it warrants its own post, I added a little note about how to implement clean browser-compatible patches. In the example, csster&amp;nbsp; supports the "opacity" property name in IE by writing a simple Csster plugin that run only within the IE environment and applies the patch. Much nicer than subtler raw CSS solutions... more to come.&lt;br /&gt;&lt;br /&gt;Check it out: &lt;a href="http://github.com/ndp/csster"&gt;http://github.com/ndp/csster&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-6138567051050636552?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/6138567051050636552/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=6138567051050636552' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/6138567051050636552'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/6138567051050636552'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/12/jquery-support-in-csster.html' title='jQuery support in Csster'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1133529433007466125</id><published>2010-11-26T08:20:00.000-08:00</published><updated>2010-11-27T14:03:12.777-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='cheatsheet'/><title type='text'>Learning Git?</title><content type='html'>I just revised &lt;a href="http://ndpsoftware.com/git-cheatsheet.html"&gt;my previous visualization about git&lt;/a&gt; with an eye towards better visual design and usability.&lt;br /&gt;&lt;br /&gt;Here's a little history: As I dove into learning git, I was initially confused about where my code was. I felt pretty confident that git hadn't lost anything, but less confident I could get it back readily. Sure, it's distributed, so I expect my code will be more places. But there was also this "index" and "stash"-- how do those relate? It's a little complex coming from Subversion or CVS.&lt;br /&gt;&lt;br /&gt;Once I figured out the basic locations that things could be, understanding the commands is a second challenge. The commands tend to work on one or two targets, moving code from one to the other. But they aren't named in any obvious way, except for the "stash" commands. To make sense of these, I mapped them onto the locations. In the visualization, just click on "remote repository" to see all the commands that affect it.&lt;br /&gt;&lt;br /&gt;Out of these two frustrations comes &lt;a href="http://ndpsoftware.com/git-cheatsheet.html"&gt;my visualization&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1133529433007466125?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1133529433007466125/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1133529433007466125' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1133529433007466125'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1133529433007466125'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/11/learning-git.html' title='Learning Git?'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-7513749253603032595</id><published>2010-11-19T20:34:00.000-08:00</published><updated>2010-11-27T14:04:02.448-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>Intermittent Selenium Failures</title><content type='html'>Selenium testing is always a little flakey, but I've* found a good treatment for this on my last two projects. It's pretty simple, really:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;If you are using external Javascript services, turn them off.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;This includes Google Analytics, Kiss Metrics, Share This, etc. The number of these services has exploded in the last couple years, and it's hard to build a site that doesn't use at least a couple. These tools do what they can to not interfere, but in the fast-paced world of Selenium, they don't always survive. Just remove them for these tests and you'll see marked improvement.&lt;br /&gt;&lt;br /&gt;Actually, that reminds me of a good talk I heard the other night. It was by Marcus Westin and Martin Hunt &amp;nbsp;of Meebo, and they talked about developing he "Meebo Bar". They figured out some really cool tricks to load asynchronously and not interfere with the host website-- but even better, supporting security contexts client side (which is pretty nifty if you think about it.) I actually think you could build a pretty clever SSO (single sign on) solution using these patterns, but I haven't tackled that one yet. Check the &lt;a href="http://assets.en.oreilly.com/1/event/44/Building%20Fast%20Webapps,%20Fast%20_Lessons%20From%20Creating%20the%20Meebo%20Bar_%20Presentation.ppt"&gt;slides&lt;/a&gt; and &lt;a href="http://www.youtube.com/watch?v=b7SUFLFu3HI"&gt;presentation&lt;/a&gt;.&amp;nbsp;&amp;nbsp;A must read if you're developing your own widget.&lt;br /&gt;&lt;br /&gt;* Actually, credit where credit's due: it was Justin and Jonah (different companies, different projects, not brothers) who identified this problem, not me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-7513749253603032595?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/7513749253603032595/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=7513749253603032595' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/7513749253603032595'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/7513749253603032595'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/11/intermittent-selenium-failures.html' title='Intermittent Selenium Failures'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-318491625973047829</id><published>2010-09-25T16:52:00.000-07:00</published><updated>2010-09-25T16:56:41.466-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='sass'/><category scheme='http://www.blogger.com/atom/ns#' term='css'/><title type='text'>Introducing Csster</title><content type='html'>So I'm a bit of a CSS nerd. For years, I've been complaining that there's not enough "engineer" cycles given to CSS. I've written endless blog posts about how to organize your CSS. Blank stares when I ask interview candidates "how do you structure your CSS?" &amp;nbsp;Well, now we can &lt;a href="http://github.com/ndp/csster"&gt;write CSS in Javascript&lt;/a&gt;&amp;nbsp;with Csster, and maybe-- just maybe-- the world has been set right.&lt;br /&gt;&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;In 2000, I ventured into teaching at&lt;a href="http://ccsf.edu/"&gt; City College&lt;/a&gt;. To be honest, I'd missed the dot com boom and didn't know anything about the web. I was really nervous,&amp;nbsp;so checked out every book the&amp;nbsp;school library had about web development, and studied (and learned) thoroughly. Since then, I built quite a few web sites for people, non-profits and businesses,&amp;nbsp;and developed a certain amount of dexterity with CSS. CSS has evolved a bit in the last ten years,&amp;nbsp;but this firm foundation of understanding of the cascade and such has served me well.&lt;br /&gt;&lt;br /&gt;Not to go off into a rant, but lots of CSS ends up a mess. I always felt like it, like C++, needed more restrictions and guidelines to turn out maintainable. I tried to enforce various guidelines with my teams. &lt;br /&gt;&lt;br /&gt;In the last year, there's been a renewed interested &amp;nbsp;in CSS and the tooling around it. With&amp;nbsp;the advent of tools like SASS, it's starting to receive the engineering attention it's always&amp;nbsp;deserved.&lt;br /&gt;&lt;br /&gt;I was a little hesitant to endorse Sass (or even try it). CSS already has too many "degrees of freedom"--&amp;nbsp;more flexibility seemed like a bad idea. It would make the problem worse.&amp;nbsp;I poo-pooed it.&lt;br /&gt;&lt;br /&gt;Okay, stop laughing.&lt;br /&gt;&lt;br /&gt;Finally a couple weeks ago with some changes to my team, I was convinced to give it another shot. At the same time I started a new small project, so I just tried it out. This project was design and Javascript heavy, but small enough to back out if it wasn't working. Sass was OK, I decided, but I was still maintaining too much junk, and now there was this additional build step.&lt;br /&gt;&lt;br /&gt;On a Friday afternoon, I mentioned to my coworker Jeremy that what I'd really like is to just stay in Javascript. Javascript has this object literal format (JSON), and the whole hierarchy of CSS might just fit in.&lt;br /&gt;&lt;br /&gt;It was a bit of an idea that I just throw out, but over the weekend I pursued a spike implementation of what it would look like. Using test driven design, it came together much faster than I had anticipated. It was a usable tool. Alex at C5 coached me through fixing some of the color functions, and as near as I can tell it equals or exceeds Sass's feature set.&lt;br /&gt;&lt;br /&gt;In the last couple weeks I've used it extensively in my current project and it's great. Being in a real programming language really does make CSS nice.&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Check out the&amp;nbsp;&lt;a href="http://github.com/ndp/csster"&gt;main project page&lt;/a&gt;&amp;nbsp;on github.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-318491625973047829?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/318491625973047829/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=318491625973047829' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/318491625973047829'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/318491625973047829'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/09/introducing-csster.html' title='Introducing Csster'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-5113836738697338940</id><published>2010-09-19T10:15:00.000-07:00</published><updated>2011-05-03T23:42:43.631-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='resumes'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='css'/><title type='text'></title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_RZ6yXxZ1RUA/TJZCxFEelKI/AAAAAAAAAwo/9PNgUjZXjkI/s1600/Picture+1.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="64" src="http://3.bp.blogspot.com/_RZ6yXxZ1RUA/TJZCxFEelKI/AAAAAAAAAwo/9PNgUjZXjkI/s200/Picture+1.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;Most resumes are pretty boring in their presentation. Textual descriptions of each job, reverse chronological. Every once in a while you find a programmer who "gets creative", rendering their whole resume in C++ syntax or a mindmap. We got one the other day that-- well, I'm not sure what it was, but it managed to communicate you'd never want that person working on a user interface.&lt;br /&gt;&lt;br /&gt;I started to wonder what visualizations would be useful to a reader of a resume. &lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;Although I think &lt;a href="http://www.linkedin.com/in/ndpsoftware"&gt;linkedin&lt;/a&gt; does a pretty fine job, here was one idea I sketched out with my new library &lt;a href="http://github.com/ndp/csster"&gt;Csster&lt;/a&gt;. &amp;nbsp;I took some inspiration with recent work I've been doing with IDEO, as well as a summer of being surrounded by modern art in the Baltics.&lt;br /&gt;&lt;br /&gt;&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; z-index: 10000"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_RZ6yXxZ1RUA/TJZCxFEelKI/AAAAAAAAAwo/9PNgUjZXjkI/s1600/Picture+1.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;span class="Apple-style-span" style="color: black;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_RZ6yXxZ1RUA/TJZCxFEelKI/AAAAAAAAAwo/9PNgUjZXjkI/s1600/Picture+1.png" /&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;Primary Programming Languages&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;/blockquote&gt;What do you think? Is it interesting enough to explore the data?&lt;br /&gt;&lt;br /&gt;If you had one visualization to complement your resume, what would you want to convey?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-5113836738697338940?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/5113836738697338940/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=5113836738697338940' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/5113836738697338940'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/5113836738697338940'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/09/most-resumes-are-pretty-boring.html' title=''/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_RZ6yXxZ1RUA/TJZCxFEelKI/AAAAAAAAAwo/9PNgUjZXjkI/s72-c/Picture+1.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1400609533094987387</id><published>2010-04-19T21:50:00.000-07:00</published><updated>2010-04-19T21:50:47.849-07:00</updated><title type='text'>jQuery Conf SF</title><content type='html'>After traveling around the Bay Area talking about Javascript Unit testing, I scored a shot at the &lt;a href="http://events.jquery.org/2010/sf-bay-area/"&gt;San Francisco jQuery Conference&lt;/a&gt;. &amp;nbsp;I'll be there Sunday afternoon, talking &lt;span class="Apple-style-span" style="font-family: inherit;"&gt;about "&lt;/span&gt;&lt;span class="Apple-style-span" style="line-height: 14px;"&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;Organizing Your Code with Testable jQuery Plugins". &amp;nbsp;Stop by and say "Hi"&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1400609533094987387?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1400609533094987387/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1400609533094987387' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1400609533094987387'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1400609533094987387'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/04/jquery-conf-sf.html' title='jQuery Conf SF'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-8487625433344085696</id><published>2010-03-06T14:06:00.000-08:00</published><updated>2011-05-29T12:30:08.153-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><title type='text'>Rails Fixtures with Integrity &amp; Validity</title><content type='html'>A new developer on the project changed the symbolic name of one fixture record&amp;nbsp; and&amp;nbsp;broke a whole bunch of tests in unexpected ways. Pairing, we discovered a some interesting stuff.&lt;br /&gt;&lt;br /&gt;First, if you've never dug into them, it's critical to understand how symbollically named&amp;nbsp;fixtures work. We rely on them heavily, but only yesterday read the code.&amp;nbsp;If you have a fixture like:&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;bill:&lt;/span&gt;&lt;br /&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; full_name: ...&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px; min-height: 14.0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;And another fixture:&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;socks:&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; owner: bill&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px; min-height: 14.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: Times, Courier, monospace;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-family: Helvetica; font-size: small;"&gt;&lt;span class="Apple-style-span" style="font-size: 12px;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;Rails magically inserts bill's ID into socks' record. (Before this feature, developers had to manually manage their IDs and keeping fixtures working well was less fun.)&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;br /&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;br /&gt;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;Nifty. I assumed (incorrectly), that there was some sort of lookup of records involved. So if I change the name of the "bill" fixture to something else-- let's say "william", I expect Rails to complain. It doesn't. There's no data integrity to the fixture system-- at all.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;br /&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;We traced though the code and now understand why: When Rails comes across the symbol "bill", it creates an integer hash of it and sticks it into the id column. It'll be a big number like 39384022 or something. Well, if you change the name to "william", it generates a different hash. That's it. At no time does it go back to verify that such a hash exists. It's really just a nice name for a number! Unless your database has constraints that enforce this (which will make loading fixtures more difficult and generally isn't done), you won't see a problem until a test fails.&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;br /&gt;&lt;/span&gt; &lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;Once we discovered that, we asked, "wouldn't be nice if there was a test that checked the integrity of the fixtures?" With a little work, we had just such a test, which relies on the validation system:&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;&lt;br /&gt;&lt;/span&gt; &lt;/span&gt;describe "fixture integrity" do&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; ActiveRecord::Base.send(:subclasses).each do |cls|&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; &amp;nbsp; it "each fixture for class #{cls} should be valid" do&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; cls&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;.find_each do |record|&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;record.valid?.should(equal(true),"Invalid fixture for #{cls}:\n &amp;nbsp;#{record.errors.full_messages.join("\n")}\n#{record.inspect}")&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;end&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; &amp;nbsp; end&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; end&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;end&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px; min-height: 14.0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-style-span" style="font-family: Times;"&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;Unfortunately that passed until we added a the appropriate validator&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: 12px;"&gt;class Child &amp;lt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; validates_presence_of :parent&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 12.0px Helvetica; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; ...&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;(Make sure you validate "parent", not "parent_id". "parent_id" will be set-- it just won't point to anything.)&lt;br /&gt;&lt;div&gt;&lt;br /&gt;That's it. A simple test and you won't risk your fixtures floating too far from the data structure you expect. Thanks to Lowell Kirsh and Jonah for pairing on this.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-8487625433344085696?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/8487625433344085696/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=8487625433344085696' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/8487625433344085696'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/8487625433344085696'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/03/rails-fixtures-with-integrity-validity.html' title='Rails Fixtures with Integrity &amp; Validity'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-130048874756693630</id><published>2010-02-06T21:22:00.000-08:00</published><updated>2011-05-29T12:31:22.466-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Implement Most Popular the Easy Way (hint: use Google Analytics, garb and Rails)</title><content type='html'>Over the last few years I've implemented "most popular" posts, questions, lists, companies, users, pages, searches, cities, and who knows what else. It's not difficult. I've always implemented this myself-- using a few columns in an SQL database, but found something didn't smell right. We already have this free tool-- &lt;a href="http://www.google.com/analytics/"&gt;Google Analytics (GA)&lt;/a&gt;-- which is collecting usage data on my site. Why would I want to store this data redundantly?&lt;br /&gt;&lt;br /&gt;In this post, I'll walk you through what we did on my most recent project for the &lt;a href="http://www.thenationalcampaign.org/"&gt;National Campaign&lt;/a&gt;. There are three steps: collecting the raw data, processing it into statistics, and displaying it to the user.&lt;br /&gt;&lt;br /&gt;Using GA you can completely outsource the data collection. For the statistical analysis, you gain flexibility-- more on that later. Finally, I won't talk at all about displaying the results to users-- that's up to you.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Step 1: Collecting the Data&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If you are already using GA, you're done-- you're collecting data. If not, you simply need to create an account and start using it. &lt;br /&gt;&lt;br /&gt;Fortunately, using RESTful conventions, most using actions end up being "page views" of some sort. But there might be other steps you want to take. For example, we had a page that served up content via Ajax, and I hadn't bothered to instrument them with GA yet.&amp;nbsp; I added one line of Javascript to the Ajax callback:&amp;nbsp; &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;pageTracker._trackPageview(questionLink);&lt;/span&gt;&amp;nbsp; And it can get more complicated: if your definition of popularity involves something beyond your pages, you'll have to dive into the event tracking or custom variables of GA (which I haven't done). &lt;br /&gt;&lt;br /&gt;It's worth pointing out that if you don't use GA on a project, you need figure out what data to collect and how to store it. This involves the business owners expressing their requirements, and the developers debating which database table to use and how general a solution to build. You can imagine this can be a small sink-hole if you're not careful.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Step 2: Processing the Statistics&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The hard problem to solve here is to create a function that calls out to GA, collects the data, and saves it into your database. &lt;br /&gt;&lt;br /&gt;&lt;i&gt;Understanding the API&lt;/i&gt;&lt;br /&gt;Although you really don't need a deep understanding of &lt;a href="http://code.google.com/apis/analytics/docs/gdata/gdataDeveloperGuide.html"&gt;the API&lt;/a&gt;, you should at least skim it. The salient points are to understand the differences between accounts, profiles and sites. Run off and read it now.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Install the Rails Gem&lt;/i&gt;&lt;br /&gt;You'll need to get "garb"-- not as in trash, but as in Google Analytics for Ruby. Install it:&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp; gem install garb&lt;/div&gt;or&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp; config.gem 'garb' # in environment.rb&lt;/div&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; rake gems:install&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Profile Access&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp; require 'garb'&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp; def self.create_profile(acct, username, password)&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Garb::Session.login(username, password)&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Garb::Profile.first(acct)&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp; end&lt;/div&gt;&lt;br /&gt;&lt;i&gt;Retrieving the Data&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;To retrieve the data, you generate a report. For this, you'll need:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;dimensions&lt;/b&gt;: I just asked for &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;page_path&lt;/span&gt;. If you ask for just one dimension, you can think of it as the row headers in the table you get back.&lt;/li&gt;&lt;li&gt;&lt;b&gt;metrics:&lt;/b&gt; I just asked for the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;pageviews&lt;/span&gt;-- it's the values that go in the row cells returned. You can also ask for bounces, visits, entrances, exits, etc. You can envision a complex definition of popularity that's the pageviews but you could subtract off bounces and exits (&lt;a href="http://code.google.com/apis/analytics/docs/gdata/gdataReferenceDimensionsMetrics.html"&gt;or any of the fields&lt;/a&gt;).&lt;/li&gt;&lt;li&gt;&lt;b&gt;sort&lt;/b&gt;: one of the metrics&lt;/li&gt;&lt;li&gt;&lt;b&gt;filter&lt;/b&gt;: since I was only looking for the question's show action, I filtered out all other pages. The final code for us looks like:&lt;/li&gt;&lt;/ul&gt;&lt;blockquote style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp; def self.report_results(profile)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; report = Garb::Report.new(profile)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; report.metrics :pageviews&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; report.dimensions :page_path&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; report.sort :pageviews&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; report.filters do&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; contains(:page_path, 'questions')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; does_not_contain(:page_path, 'edit')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; does_not_contain(:page_path, 'new')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; does_not_match(:page_path, '/questions')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; does_not_match(:page_path, '/questions/')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; report.results&lt;br /&gt;&amp;nbsp; end&lt;/blockquote&gt;You can also add date ranges on this... by default it follows the GA conventions of returning the last month's worth, which is what we wanted.&lt;br /&gt;&lt;br /&gt;This function returns an array of Structs, with two properties in each struct: &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;pageviews&lt;/span&gt; and &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;page_path&lt;/span&gt;. &lt;br /&gt;&lt;br /&gt;If you ask for a long report, you'll need to page the results. Refer to the garb documentation to see how to do this.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Saving/Updating the data&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp; def self.update_page_views(report_results)&lt;/div&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; report_results.each do |row|&lt;/span&gt;&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if /\/questions\/(\d+)/.match(row.page_path)&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; q = Question.find_by_id(Regexp.last_match(1).to_i)&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; q.update_attribute(:pageviews,row.pageviews.to_i) unless q.nil?&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&amp;nbsp; end&lt;/div&gt;&lt;br /&gt;This logic can be tested using MockRow described above:&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;class MockRow &amp;lt; Struct.new(:pageviews, :page_path);end&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;...&amp;nbsp;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;Question.update_page_views [MockRow.new('50',"/questions/333")]&lt;/div&gt;&lt;br /&gt;Getting this scheduled and run with the correct credentials is the last piece of the puzzle, which I'm not covering here; see cron, DelayedJob and your host.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Benefits&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As you can see, there's nothing that tricky about this. A pair of us had this up and going in a couple hours (although we did spend time getting the DelayedJob running). There are a few important benefits:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;It's a cleaner architectural with less server load than doing it yourself. You don't need to pollute fundamentally read-only operations with database writes.&lt;/li&gt;&lt;li&gt;Better metrics. GA can give you a more sophisticated metric than you could do easily. For example, it's easy to collect raw page views, but collecting unique page views or sessions is a bit more work.&amp;nbsp; &lt;/li&gt;&lt;li&gt;You can change what metrics you use in an ad-hoc basis. For example, you can decide to only count posts from the last week in the most popular, and it's a simple code change. More interesting, you &lt;/li&gt;&lt;li&gt;Can eliminate or at least reduce developer and testers from metrics discussions. You won't have to be there to answer "can we make the most popular pages the ones that people spend the most time on?" If it's in GA, you can us it.&amp;nbsp; If the product owner understands GA, she can figure out how to define "most popular" to produce the results she wants. &lt;/li&gt;&lt;li&gt;If you have already been running your site for a while, but are adding most popular support, you may already have a rich set of data on hand. This wouldn't be possible if you rolled your own.&lt;/li&gt;&lt;/ul&gt;I think this will be a core part of any new site I develop. It's so convenient to have access to this rich data set without any of the burden of collecting it. I hope it works out as well for you,&lt;br /&gt;&lt;br /&gt;-- Andy&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Other Versions&lt;/b&gt;&lt;br /&gt;There are wordpress (drupal, etc.) plugins that do the same thing, but nothing that would work directly for us. For example, &lt;a href="http://www.myoutsourcedbrain.com/2009/11/blogger-most-popular-posts-widget.html"&gt;http://www.myoutsourcedbrain.com/2009/11/blogger-most-popular-posts-widget.html&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-130048874756693630?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/130048874756693630/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=130048874756693630' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/130048874756693630'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/130048874756693630'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/02/implement-most-popular-easy-way-hint.html' title='Implement Most Popular the Easy Way (hint: use Google Analytics, garb and Rails)'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-7578353218411924973</id><published>2010-01-26T21:21:00.000-08:00</published><updated>2010-02-01T23:21:42.918-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><title type='text'>Assert Changes and Fixture Test Helpers</title><content type='html'>About a year ago I posted some &lt;a href="http://blog.carbonfive.com/2009/04/testing/assert_changes-and-assert_no_changes-in-ruby"&gt;test helpers for checking pre- and post-conditions during a test&lt;/a&gt;. I called them "&lt;b&gt;assert_changes&lt;/b&gt;" and "&lt;b&gt;assert_no_changes&lt;/b&gt;".&amp;nbsp; They took a ruby expression to evaluate, a block, and did what you expected:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;    o.answer = 'yes'&lt;br /&gt;    assert_changes 'o.answer' =&amp;gt; ['yes','no'] do&lt;br /&gt;      o.answer = 'no'&lt;br /&gt;    end&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;Since then, I have discovered similar functionality in shoulda and rspec. But if you're using test unit, and you don't want to take the leap to shoulda (which is painless), I packaged up my test helpers into &lt;a href="http://github.com/ndp/ayudante"&gt;a gem that's easy to install&lt;/a&gt;. Plus, mine are a little easier to read, if you don't mind evaling strings within your tests.&lt;br /&gt;&lt;br /&gt;It's called "ayudante". To install, &lt;br /&gt;&lt;pre&gt;&lt;code&gt;    gem install ndp-ayudante --source=http://gems.github.com&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;Also included in the gem are "&lt;b&gt;fixture helpers&lt;/b&gt;". These make it easy to test for sets and lists of your fixture model objects, without the hassle of building up your own lists and sets. &lt;br /&gt;&lt;br /&gt;On &lt;a href="http://www.gobalto.com"&gt;goBalto&lt;/a&gt;, Ingar had created something like it for testing Sphinx search results. We had sets of fixtures, and we wanted to make sure a search returned certain results-- sometimes contains, sometimes exact. Sometimes we didn't care exactly what the order was so we wanted set comparisons. Or we didn't care about extra items.&lt;br /&gt;&lt;br /&gt;Here's how it works. If you have a model object and fixtures for CandyBars, it adds test helper methods for &lt;b&gt;assert_list_of_candy_bars&lt;/b&gt; (using #method_missing). Like all xUnit asserts, you pass the expected values (a list of symbols identifying the fixture objects), and the value you are testing.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;    result = CandyBar.find(:all, ... )&lt;br /&gt;    assert_list_of_candy_bars [:mars, :eminem], result&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;It also supports &lt;b&gt;assert_set_of_candy_bars&lt;/b&gt;, so you can ignore the order of comparisons; and &lt;b&gt;assert_contains_candy_bars&lt;/b&gt; so you can make sure it results contain a subset. Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-7578353218411924973?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/7578353218411924973/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=7578353218411924973' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/7578353218411924973'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/7578353218411924973'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/01/assert-changes-and-fixture-test-helpers.html' title='Assert Changes and Fixture Test Helpers'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-3406294247682905798</id><published>2010-01-26T20:15:00.000-08:00</published><updated>2010-01-26T20:57:06.412-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='xp'/><category scheme='http://www.blogger.com/atom/ns#' term='consulting'/><title type='text'>Recipe for 5 Whys with an XP Software Team</title><content type='html'>5 Whys is a great way to get at the root of quality problems. On my last three projects, when I felt like code quality was dropping, I ran a "5 Whys" session. I have found it adds variety, solves a very specific problem, and plugs right in as an alternative to an &lt;a href="http://blog.carbonfive.com/2009/12/agile/recipe-for-simple-agile-retrospectives"&gt;agile reflection&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;It's not in every agile software team's bag of tricks. Asking around our fairy savvy office, I discovered it's far from universal. In the &lt;a href="http://www.versionone.com/whitepapers.asp"&gt;"State of Agile" report from Version One&lt;/a&gt;, which includes survey results from 2500 software developers, it wasn't mentioned. Since I haven't seen it show up that much in other agile writings, I thought I'd share my experiences here.&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;What is "5 Whys"?&lt;/strong&gt; I picked up "5 Whys" from the lean software movement, which sprang from Toyota manufacturing.  You can read about its history on &lt;a href="http://en.wikipedia.org/wiki/5_Whys"&gt;wikipedia&lt;/a&gt;, but it's pretty simple: at the end of the assembly line, when a widget comes out with a problem, you stop the line and ask "Why?" Whatever the reason, you ask the "Why?" again. Repeat at least 5 times. The goal is to discover the "root cause" of the defect, and fix the root cause, not just some symptom. Wikipedia has a good example around car repair. Here's a software example:&lt;br /&gt;&lt;blockquote&gt;1. Why did Sheryl say the sign-up flow was broken?&lt;br /&gt;&lt;em&gt;A: Because she was trying to sign up a second time. Duh.&lt;/em&gt;&lt;br /&gt;2. Yeah, but why did the flow break if the user is already signed up?&lt;br /&gt;&lt;em&gt;A: It's a bug. We didn't have a test case for that.&lt;/em&gt;&lt;br /&gt;3. Why didn't we have a test case for it?&lt;br /&gt;&lt;em&gt;A: I just didn't think of it.&lt;/em&gt;&lt;br /&gt;4. Why didn't you think about it?&lt;br /&gt;&lt;em&gt;A: The story seemed easy-- I guess I didn't think it through.&lt;/em&gt;&lt;br /&gt;5. Why do you think you didn't think it through?"&lt;br /&gt;&lt;em&gt;A: I guess I was working alone and was in a hurry.&lt;/em&gt;&lt;br /&gt;...&lt;/blockquote&gt;Obviously you don't have ask "Why?" exactly five times, but that does seem to be a pretty good number. The point is you start articulating the behaviors and procedures that cause the problems.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Taking It for a Spin. &lt;/strong&gt; I've come up with exercise for agile software teams.  Like I mentioned, when quality drops below your comfort level, give it a try:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;(2 minutes) Make a list of the most recent bugs you've uncovered. I've found that the last 10 is usually enough-- if not too much. You can put these on a white board, index cards or a wiki page-- all will work.&lt;/li&gt;&lt;li&gt;(2 minutes) Prioritize them based on the ones you want to talk about. Which caused the most embarrassment or cost? You usually only have energy to talk about 5 or 6 of them.&lt;/li&gt;&lt;li&gt;(15 minutes) For each bug, ask "Why did this happen?" Then, probe deeper until you can't get much further. Write the answers down where everyone can see.&lt;/li&gt;&lt;li&gt;(5 minutes) When you're done with all the bugs (or out of time), circle common root causes.&lt;/li&gt;&lt;li&gt;(10 minutes) Brainstorm ways to mitigate or eliminate the root causes. Come of with one or two SMART goals for the team.&lt;/li&gt;&lt;/ol&gt;Some of the root causes we've gotten to recently:&lt;br /&gt;&lt;ul&gt;&lt;li&gt; we didn't pair on hard features&lt;/li&gt;&lt;li&gt; we didn't ask for clarifications on the requirements&lt;/li&gt;&lt;li&gt; one engineer didn't understand the intent behind the code&lt;/li&gt;&lt;li&gt; nobody looked at the app on Internet Explorer 6&lt;/li&gt;&lt;/ul&gt;Just like in agile reflections, the changes made coming out of these meetings stick with the team. During one session, the developers decided that there were just too many mistakes that someone else would have easily caught-- so they always needed two sets of eyes on every checkin. I'd advocated this before, but it was never applied consistently. To my surprise though, after the reflection, everyone was  disciplined about this policy-- and our quality was much better.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-3406294247682905798?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/3406294247682905798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=3406294247682905798' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/3406294247682905798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/3406294247682905798'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/01/recipe-for-5-whys-with-xp-software-team.html' title='Recipe for 5 Whys with an XP Software Team'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-5375062358615117832</id><published>2010-01-09T19:59:00.000-08:00</published><updated>2011-07-29T14:36:55.774-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='software development'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Pairing with Designers</title><content type='html'>&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;I've worked on software with designers for 15 years, ever since software had a visual design. Usually this involves being "handed off" designs, or providing "feedback", via email. Only occasionally have I worked side-by-side to solve visual design and interaction problems.&lt;br /&gt;&lt;br /&gt;Reflecting back, this seems sad, since working together has all the advantages of pair programming-- it's fun and educational, &amp;nbsp;often much faster, and you can produce a superior result.&amp;nbsp;&lt;/div&gt;&lt;br /&gt;There are many blog posts about the merits of pair programming, but none about pairing between programmers with a designer. &amp;nbsp;Since Carbon Five values pairing and collaboration so heavily, I've been trying to do it on all my projects. On my most recent project for the &lt;a href="http://thenc.org/"&gt;National Campaign&lt;/a&gt;, I've had the pleasure of "pairing" with several designers.&lt;br /&gt;&lt;br /&gt;The first day of the project was refreshing-- I sat side by side with Jef and we broke apart his Illustrator designs, reassembled them in HTML, and fluidly passed ideas and png files back and forth. He was standing next to me, we were sharing a dropbox, and it was very exciting.&lt;br /&gt;&lt;br /&gt;Five months in, a recent experience left Suzanna (our current designer) and me in awe of the merits of working closely together.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span style="font-weight: bold;"&gt;The task was a quick page redesign&lt;/span&gt;-- we have 17 detail pages utilizing the same basic template, each about a &lt;a href="http://bedsider.org/methods/the_pill"&gt;different&lt;/a&gt; &lt;a href="http://bedsider.org/methods/the_ring"&gt;contraception&lt;/a&gt; &lt;a href="http://bedsider.org/methods/male_condom"&gt;method&lt;/a&gt; (it's a site about birth control). From early user testing, we'd learned what wasn't working, and had reached consensus on the set of problems we should try to fix.&amp;nbsp;Suzanna synthesized the feedback and produced a mock-up-- a single PNG screen mock-up representing all 17 pages. Pretty traditional so far. Among other things, the new design called for altering the main photo from its prominent placement in a rectangle taking up half the page, to a smaller photo that is elegantly wrapped by the initial paragraphs of text.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Traditionally, this doesn't go well:&lt;/span&gt; Some of the methods are quite small (the size of a match stick), whereas others, like the female condom, take up 6 inches or so (depending on how you measure).&amp;nbsp;They have different shapes and visual weights, so it's going to be hard to get one design that works for all of them.&lt;br /&gt;&lt;br /&gt;Suzanna specified a reasonable size for the image, taking this into account as much as possible. She develops a compromise based on lots of factors, including a &amp;nbsp;guess about how a browser might wrap text. The developer would follow the spec as close as possible, and either wouldn't notice or say anything if there were visual problems.&lt;br /&gt;&lt;br /&gt;Often the process stops there. You have a few pages that look good, and the rest various degrees of bad. Everybody argues that they did their job, and they did.&lt;br /&gt;&lt;br /&gt;So the team tries to fix it. The designer might spec different dimensions to see if something else will work-- but this is simply another big compromise. Or the designer will embark on the tedious task of creating a mockup for each page-- making them beautiful with Photoshop's font rendering and text wrapping (which the developer can't really duplicate). In the best case, a compromise will be reached that looks just okay for all the pages.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Things went differently,&lt;/span&gt; because the programmer and designer were sitting next to each other:&lt;br /&gt;&lt;br /&gt;We are working with a &lt;a href="http://960.gs/"&gt;960 grid&lt;/a&gt;, and Suzanna made everything fit in our grid. As I started the implementation, she measured and confirmed. But fitting in a grid for a designer can be different than fitting in a grid for a developer.&lt;br /&gt;&lt;br /&gt;I probed, "What about that image-- it's so tall and narrow. You want that to stick up above?"&lt;br /&gt;&lt;br /&gt;"Naw... Well, maybe. Why don't we just build the images with 20px "cushion" that the image can bleed into, in case they need to extend?"&lt;br /&gt;&lt;br /&gt;"Sure," I said. &amp;nbsp;I'll use negative margins to break out of the grid (a little):&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;#photo&amp;nbsp;{&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; float: left;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; height: 180px;&amp;nbsp;width: 180px;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; margin: 0 0 -5px -20px;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;}&lt;/span&gt;&lt;/blockquote&gt;Even if we'd stopped there, our implementation would be an improvement over a traditional outcome without the negative margin bleed. Suzanna could now have shadows extend into the margins, which will be a nice visual effect.&lt;br /&gt;&lt;br /&gt;But that's not the end of the story. I walked back to my desk and implemented the page while Suzanna cranked out the images.&lt;br /&gt;&lt;br /&gt;She interrupted me five minutes later, "This one's taller, is that okay?"&lt;br /&gt;I was in the midst of placing the image and realized I could add one CSS rule that would handle this. "Sure!"&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt; #photo.the_pill { height: 241px; margin-bottom: -20px; }&lt;/span&gt;&lt;/blockquote&gt;Ten minutes later Suzanna came over to my desk, "I finished the images and they're in &lt;a href="https://www.dropbox.com/"&gt;your drop box&lt;/a&gt;. I had to make a couple other ones different sizes."&lt;br /&gt;&lt;br /&gt;She'd named them consistently with the old names, so I just copied the files over. We compared my implementation with Suzanna's PNG mockup. Close, but not close enough.&amp;nbsp;Suzanna sat down next to me and we pushed a few pixels around, getting the margins right with the browser's font metrics-- the line heights were slightly different she needed an adjustment. A quick check on Windows as well, and it was perfect.&lt;br /&gt;&lt;br /&gt;Then we looked at the odd size images she'd discovered, and we tweaked those sizes and margins as well. Some of them needed a little margin adjustment as well. No problem-- we just flipped back and forth between browsers and CSS files making everything perfect. (She didn't actually need to make any measurements as we just used our eyes.)&lt;br /&gt;&lt;br /&gt;While we were at it, we ran through the all 17 pages, just as a quick check. The set of non-exceptional pages should be fine, but Suzanna wanted more adjustments. Each photo is unique. Since it was so easy, why not? In a matter of minutes, half the pages had some custom pixel adjustments, and we were quite happy with all of them.&lt;br /&gt;&lt;blockquote style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;#photo.diaphragm { height: 195px; margin-bottom: -20px; }&lt;br /&gt;#photo.withdrawal { height: 201px; margin-bottom: -40px;}&lt;br /&gt;#photo.depo_provera { height: 189px; margin-bottom: -20px; }&lt;br /&gt;#photo.iud { margin-top: 5px; height: 226px; margin-bottom: -60px; }&lt;br /&gt;#photo.the_ring { margin-bottom: -20px; }  &lt;br /&gt;#photo.cervical_cap { width: 195px; height: 192px; margin-right: -15px; margin-bottom: -20px; }&lt;/blockquote&gt;This probably took less than an hour. This isn't very complicated, but it's hard to imagine how tedious this would have been if we hadn't paired to reach the final result. Plus, each page really looked great-- the image tucked in "just right."&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;I've certainly collaborated with designers before&lt;/span&gt;, poking around in Firebug until things looked good. But this was the first time I saw how clearly working closely produced a superior result. And we did it with no tedious measuring of text or dozens of time-consuming mockups.&lt;br /&gt;&lt;br /&gt;What could have taken hours or days took minutes. We just sat down together, and, well, paired.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-5375062358615117832?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/5375062358615117832/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=5375062358615117832' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/5375062358615117832'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/5375062358615117832'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2010/01/pairing-with-designers.html' title='Pairing with Designers'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-5954803928804448265</id><published>2009-09-24T21:50:00.000-07:00</published><updated>2009-10-07T09:07:37.002-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='lean'/><category scheme='http://www.blogger.com/atom/ns#' term='xp'/><category scheme='http://www.blogger.com/atom/ns#' term='scrum'/><title type='text'>Scrum, XP, Agile, and Visualizing the Difference</title><content type='html'>Visualization of agile practices running here:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://ndpsoftware.com/agile_methods/agile_methods.html"&gt;http://ndpsoftware.com/agile_methods/agile_methods.html&lt;/a&gt; (If it seems to draw garbage all over the screen, refresh the page. More on that later.)&lt;br /&gt;&lt;br /&gt;What it shows is the main concepts of 5 different agile viewpoints: XP, Scrum, the Agile Manifesto, Lean and Getting Real. Important words for a practice are "attracted" to (gravitationally) to the practices that mention them in their canonical definition.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Motivation&lt;/h3&gt;&lt;br /&gt;There are different flavors of agile. I discuss the differences coherently, but I wondered if there were some way to distill the differences down to something that could be represented in one page. A cheat sheet. All these practices share some history and concepts, but can be they  be presented and compared in a fairly simplistic way? Each emphasizes different things, but how do they compare?&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Building the Visualization&lt;/h3&gt;&lt;br /&gt;The first challenge was to identify the top few values and concepts that define each practice. My idea was to summarize each practice by finding the canonical description of each practice and capture the top concepts as succinctly as possible. &lt;br /&gt;&lt;br /&gt;So I identified a primary source for each practice. This is arbitrary but not capricious. I believe I was picking reasonable documents:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;XP: &lt;a href="http://www.extremeprogramming.org/"&gt;http://www.extremeprogramming.org/&lt;/a&gt;&lt;br /&gt;&lt;li&gt;Agile Manifesto: &lt;a href="http://agilemanifesto.org/"&gt;http://agilemanifesto.org/&lt;/a&gt;, &lt;a href="http://agilemanifesto.org/principles.html"&gt;http://agilemanifesto.org/principles.html&lt;/a&gt;&lt;br /&gt;&lt;li&gt;Scrum: Agile Software Development with Scrum, Chapter 9; and &lt;a href="http://en.wikipedia.org/wiki/Scrum_(development)"&gt;http://en.wikipedia.org/wiki/Scrum_(development)&lt;/a&gt;&lt;br /&gt;&lt;li&gt;Lean Software: &lt;a href="http://en.wikipedia.org/wiki/Lean_software_development"&gt;http://en.wikipedia.org/wiki/Lean_software_development&lt;/a&gt;&lt;br /&gt;&lt;li&gt;Getting Real: from page 2 of the book&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;Next, I needed to extract the concepts. I created a spreadsheet and listed concepts down the left side, and practices across the top. I put a number in any box where the practice mentioned the concept as one of its first. The first concept mentioned on the page got a "1", and the 10th one got a "10". I didn't collect concepts much beyond that. I ended up with a big grid with practices as columns and concepts as rows. As the terminologies of each practice differ, I had a sparse spreadsheet with only a few data points. It wasn't helpful in comparing the practices.&lt;br /&gt;&lt;br /&gt;So went through a normalization process. Is a "sprint" the same thing as an "iteration"? Well yes (and no). What's a "project heartbeat?" and is it the same thing? I combined rather aggressively the first time I did it, but what you see is a second, more conservative approach. There are still problems, but I took a pretty good shot.&lt;br /&gt;&lt;br /&gt;Even with the "normalized spreadsheet", though, it wasn't easy to compare the practices. I tried different sorting and coloring to no avail. I then tried different graphing options, starting first with "mind maps". I tried 3-d bar charts, but they were little help. And plotting the words in grids, but couldn't make it work.&lt;br /&gt;&lt;br /&gt;I finally stumbled upon the visual thesaurus metaphor. As a regular thesaurus, I always thought the display a bit gratuitous: a word in the center with its synonyms "floating around", connected by some gravity. The more I thought about it, though, the better fit it seemed.&lt;br /&gt;       &lt;br /&gt;Next I needed a tool in which to implement my visualization. You can license the technology, but I had a $0 budget and just a few hours to devote to it. I looked at using Processing (a Java variant that lots of people are using to build visualizations); there were some implementations, but I wanted something even lighter weight if possible. After another Google or two, and I found Javascript-based experiments with "force directed graphs". I grabbed the code, and threw my data in it.&lt;br /&gt;&lt;br /&gt;Actually, there was a little engineering involved. I knew I would update the spreadsheet (and I wanted to add other practices potentially), so instead of converting the spreadsheet directly to the Javascript, I wrote a Ruby script that takes the spreadsheet and generates JSON data structures. The structures are then fed into a small engine I wrote, and the graphic is created.&lt;br /&gt;&lt;br /&gt;Code available &lt;a href="http://github.com/ndp/agile_methods" target="_blank"&gt;http://github.com/ndp/agile_methods&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/us/"&gt;&lt;img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by-sa/3.0/us/88x31.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span xmlns:dc="http://purl.org/dc/elements/1.1/" href="http://purl.org/dc/dcmitype/InteractiveResource" property="dc:title" rel="dc:type"&gt;Agile Software Methodologies Visualization&lt;/span&gt; by &lt;a xmlns:cc="http://creativecommons.org/ns#" href="http://ndpsoftware.com/agile_methods/agile_methods.html" property="cc:attributionName" rel="cc:attributionURL"&gt;Andrew J. Peterson&lt;/a&gt; is licensed under a &lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/us/"&gt;Creative Commons Attribution-Share Alike 3.0 United States License&lt;/a&gt;.&lt;br /&gt;Based on a work at &lt;a xmlns:dc="http://purl.org/dc/elements/1.1/" href="http://www.kylescholz.com/blog/2006/06/force_directed_graphs_in_javas.html" rel="dc:source"&gt;www.kylescholz.com&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-5954803928804448265?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/5954803928804448265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=5954803928804448265' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/5954803928804448265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/5954803928804448265'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2009/09/scrum-xp-agile-and-visualizing.html' title='Scrum, XP, Agile, and Visualizing the Difference'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1453379340727308236</id><published>2009-08-13T23:39:00.000-07:00</published><updated>2010-09-25T16:58:19.322-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><title type='text'>Tips on Taking Autotest for a spin on OS X</title><content type='html'>It's exciting to get &lt;a href="http://ph7spot.com/articles/getting_started_with_autotest"&gt;autotest&lt;/a&gt; working for my last rails project. In case you don't know, autotest is a tool that continuously runs your ruby/rails tests as you work. It monitors your file system watching for changes, and then has a heuristic to figure out which tests to run. Once a test fails, it keeps re-running it  until it passes.&lt;br /&gt;&lt;br /&gt;Gotta say, the jury's still out, but overall I really like it. Lots less navigating around tests and hitting shift-ctrl-F9 (or whatever it is). I got inspired after listening to a &lt;a href="http://blip.tv/file/1163850"&gt;year-old RailsConf talk by Kent Beck&lt;/a&gt; about the origin of TDD (after discovering we went to school together at &lt;a href="http://uoregon.edu/"&gt;UO&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;There are other tutorials that walk through the installation, so I won't repeat them here. But the additional information you need is distributed in 3 or 4 posts across the net, so here they are collected: &lt;br /&gt;&lt;br /&gt;The first trick is to get a couple additional gems (besides ZenTest, which is what you need for autotest).&lt;br /&gt;&lt;br /&gt;You will also want:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;sudo gem install autotest-rails redgreen autotest-fsevent autotest-growl&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Autotest-rails teaches autotest how where tests are in Rails projects (as if there are any other kind). Without you won't see any tests running. RedGreen adds nice coloring. FSEvent makes it faster on MacOS X, and autotest-growl, well, growls.&lt;br /&gt;&lt;br /&gt;Second, I didn't find a complete listing of what your ~/.autotest should look like on OS X. Mine looks like:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;require 'redgreen/autotest'&lt;br /&gt;require 'autotest/growl'&lt;br /&gt;require 'autotest/fsevent'&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;That's it! Definitely give it a shot. It's most convenient if you have another screen for the autotest (IDE integration would be nice). And an extra CPU wouldn't hurt, because it keeps my MBP CPU pegged much of the time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1453379340727308236?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1453379340727308236/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1453379340727308236' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1453379340727308236'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1453379340727308236'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2009/08/tips-on-taking-autotest-for-spin-on-os.html' title='Tips on Taking Autotest for a spin on OS X'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-1469049598005908204</id><published>2008-02-15T20:37:00.001-08:00</published><updated>2008-02-15T21:05:09.063-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='agile'/><category scheme='http://www.blogger.com/atom/ns#' term='xp'/><category scheme='http://www.blogger.com/atom/ns#' term='expectations'/><category scheme='http://www.blogger.com/atom/ns#' term='C5'/><category scheme='http://www.blogger.com/atom/ns#' term='consulting'/><title type='text'>Working with Agile Developers</title><content type='html'>&lt;p&gt;I recently joined &lt;a href="http://carbonfive.com/"&gt;Carbon Five&lt;/a&gt;, a small agile consulting company specializing in boot-strapping start-ups. I got a chance to work with C5 over the last year (as a client), and I learned that the expectations of working with us are very different than working with other consulting companies.&lt;br /&gt;&lt;/p&gt;&lt;h2&gt; What is agile&lt;/h2&gt;&lt;p&gt;Agile is a software development process emphasizing:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;close collaboration between the programmer team and business experts, or in our case, between the client and developers&lt;br /&gt; &lt;/li&gt;&lt;br /&gt; &lt;li&gt;face-to-face communication (as more efficient than written documentation)&lt;br /&gt; &lt;/li&gt;&lt;br /&gt; &lt;li&gt;frequent delivery of new deployable business value&lt;br /&gt; &lt;/li&gt;&lt;br /&gt; &lt;li&gt;ways to craft the code and the team such that changing requirements is not a crisis.&lt;br /&gt; &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Agile teams strive to collaborate in-person as much as possible. And we deliver software not in just a few key milestones, but continuously: weekly, daily or hourly.&lt;a name="WhyanAgileConsultant-Whattoexpectfromus"&gt;&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2&gt; What to expect from us&lt;br /&gt;&lt;/h2&gt;Working with us is collaborative and iterative. You tell us what you want, we listen, and together we identify the most important features. The goal is to identify the features (or "stories") that provide the most value to you and your customers. Once we agree, we will build it quickly-- not in months or years, but weeks.&lt;br /&gt;&lt;br /&gt;We will deliver this early version so you can try it out. Once you experience it, you will likely have a reaction-- a good one, we hope. But if you don't like what you see, that's fine. We haven't invested much, and we are early in the process. We can easily adjust what we're building. It may inspire new ideas or a change in direction. Whatever the reaction, we're not locked in. Hence the term "agile".&lt;br /&gt;&lt;br /&gt;We meet again, and prepare to repeat the cycle. Again, we'll address the highest-value aspects of the project, deliver them, and we get feedback for the next iteration.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;What not to expect&lt;/span&gt;: we are different than an "outsourcing" experience. In that engagement, you expect to tell the agency what you want, and they deliver it to you in three or six or 12 months. (Easy? Yes. But it seldom works easily.) We expect you to be engaged in the development process, defining requirements, setting priorities, and providing feedback.&lt;br /&gt;&lt;h2&gt; &lt;a name="WhyanAgileConsultant-Whyagile"&gt;&lt;/a&gt;Why agile&lt;br /&gt;&lt;/h2&gt;Agile, and the related methodologies like scrum, extreme programming, lean/agile, is a response to the failures of "traditional" software development. Even though the "traditional" process has been around for nearly 40 years, it has never worked particularly well.&lt;br /&gt; &lt;ul&gt;&lt;li&gt;64% of features included in projects are rarely or never used&lt;br /&gt; &lt;/li&gt;&lt;br /&gt; &lt;li&gt;The average project exceeds its schedule by 100% and nearly two-thirds of projects significantly overrun their cost estimates&lt;br /&gt; &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Agile, with its different emphasis, addresses these:&lt;br /&gt;&lt;/p&gt;The most common reasons for software failure are unrealistic or unarticulated project goals and badly defined requirements. We build working software in collaboration with our customers. By collaborating, we identify missing requirements right away (and resolve them with you.) And our customers see problems right away, and get a chance to respond to them. This tends to reveal right away when our goals aren't aligned.&lt;br /&gt;&lt;br /&gt;The other common problem is communication among customers, developers and users, and poor reporting of the project status. Agile addresses these head-on. At it's core, it's about communication. The team meets daily to identify problems, programmers work together ("pair"), and the customers are involved frequently during the development of features. In addition, software is delivered and reviewed frequently (usually every two weeks), so the team-- even if there is some huge misunderstanding-- rights the ship before it can get too far off course.&lt;br /&gt;&lt;h2&gt; What we expect from you&lt;br /&gt;&lt;/h2&gt;&lt;span style="font-style: italic;"&gt;Collaborate with us.&lt;/span&gt; We expect to be collectively figuring out, and then building, what you want.&lt;br /&gt;&lt;br /&gt;Here's a typical cycle: We will periodically (every couple weeks), have a planning meeting. Here, we talk about what you want: this will be business goals, product features, and perhaps "the color of that button over there." Once all the ideas are on the table, we will estimate the amount of effort they will take to get done. These are estimates, but help you prioritize the work: a medium priority feature may be worth doing if it's really easy, but should be shelfed if it is a huge undertaking. We keep the developers involved so you can make informed decisions. We need to you to be actively engaged in these meetings and strongly representing your needs. The outcome of these meetings largely shapes what we will do over the next iteration.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;We will expect a daily engagement.&lt;/span&gt; We will need you, or your delegate, to answer product questions throughout the business cycle. By foregoing huge, unmanageable requirements documents and product specifications, we have saved weeks or months of time. But when we do run into a question, like (insert example here), we need the ability to get an answer fairly quickly so that we can move forward.&lt;br /&gt;&lt;br /&gt;Finally, we expect you to make time to look at the working software we deliver and respond to it-- not wait three months until the project is done. Expect from us working software.&lt;br /&gt;&lt;br /&gt;Notes:&lt;br /&gt;&lt;p&gt; &lt;a href="http://www.agilemanifesto.org/" rel="nofollow"&gt;http://www.agilemanifesto.org/&lt;br /&gt; &lt;/a&gt;&lt;br /&gt; &lt;a href="http://www.agilealliance.org/show/2" rel="nofollow"&gt;http://www.agilealliance.org/show/2&lt;br /&gt; &lt;/a&gt;&lt;br /&gt; &lt;a href="http://www.it-cortex.com/Stat_Failure_Rate.htm" rel="nofollow"&gt;http://www.it-cortex.com/Stat_Failure_Rate.htm&lt;/a&gt; This article aggregates several studies on software failure rates.&lt;br /&gt; &lt;a href="http://spectrum.ieee.org/sep05/1685" rel="nofollow"&gt;&lt;br /&gt;  http://spectrum.ieee.org/sep05/1685&lt;/a&gt;  Why Software Fails, Rober N. Charette, September 2005. Summarizes why projects fail.&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-1469049598005908204?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/1469049598005908204/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=1469049598005908204' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1469049598005908204'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/1469049598005908204'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2008/02/why-hire-agile-consultant.html' title='Working with Agile Developers'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-8630700884575191957</id><published>2007-11-01T22:41:00.000-07:00</published><updated>2007-11-01T22:55:28.024-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Date class'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='mock objects'/><title type='text'>Unit testing time</title><content type='html'>We've all run across the problem of trying to unit test some code that is dependent on the current date. Such as:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;   public String elapsedTime(Date d) {&lt;br /&gt;      final Date now = new Date();&lt;br /&gt;      final long ms = now.getTime() - d.getTime();&lt;br /&gt;      return "" + ms / 1000 + " seconds ago";&lt;br /&gt;  }&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;How do you test something like this? (or something more sophisticated and useful)&lt;br /&gt;&lt;br /&gt;I found the solution in a posting about mock objects. The trouble is that this code is depending on the Date class, and its behavior of getting the current time. Imagine a Clock interface:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;  interface Clock {&lt;br /&gt;      Date getNow();&lt;br /&gt;  }&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;and then a rewritten elapsed time:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;  public String elapsedTime(Date d, Clock clock) {&lt;br /&gt;      final Date now = clock.getNow();&lt;br /&gt;      final long ms = now.getTime() - d.getTime();&lt;br /&gt;      return "" + ms / 1000 + " seconds ago";&lt;br /&gt;  }&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Obviously the Clock can be injected in other ways, but now we have a testable and extensible method.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-8630700884575191957?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/8630700884575191957/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=8630700884575191957' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/8630700884575191957'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/8630700884575191957'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2007/11/unit-testing-time.html' title='Unit testing time'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-2619381795768498018</id><published>2006-12-17T23:00:00.000-08:00</published><updated>2007-11-01T23:13:32.069-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='scriptaculous'/><category scheme='http://www.blogger.com/atom/ns#' term='demo'/><title type='text'>Scriptaculous Effects Demo</title><content type='html'>&lt;div class="postentry"&gt;                                        I threw together a demo of Scriptaculous effects:  &lt;a href="http://ndpsoftware.com/ScriptaculousEffectsDemo.php"&gt;http://ndpsoftware.com/ScriptaculousEffectsDemo.php&lt;/a&gt;   Nothing crazy here, but I'd like a flexible and interactive demo. Send me ideas or suggestions, Andy                    &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-2619381795768498018?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/2619381795768498018/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=2619381795768498018' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/2619381795768498018'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/2619381795768498018'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2006/12/i-threw-together-demo-of-scriptaculous.html' title='Scriptaculous Effects Demo'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-6960384923092899678</id><published>2006-05-06T23:04:00.000-07:00</published><updated>2007-11-01T23:13:06.850-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='battery'/><category scheme='http://www.blogger.com/atom/ns#' term='hardware'/><category scheme='http://www.blogger.com/atom/ns#' term='powerbook'/><category scheme='http://www.blogger.com/atom/ns#' term='apple'/><title type='text'>Powerbook Battery Rejuve</title><content type='html'>&lt;div class="postentry"&gt; My aging G4 Powerbook battery slowly died over the last month or so. It just seemed to hold a charge less and less. Last week it wouldn't go on for more than a minute or two. This weekend, unplugging the power cord was equivalent to a shutdown. After that, the battery reported "missing". It seemed like a strange progression-- not as organic, following some sort of decay curve, as I would have expected the material to behave. So I looked on the Internet to see if there were any clues. There were a set of my batteries that were recalled, but (damn), mine didn't qualify. Next, I found a thread about resetting p-ram and other such shenanigans. I was skeptical, but it seemed like there was one trick that worked for people: going into open firmware (Restart with Cmd-option-O-F pressed), and then resetting the chip that manages the battery power (reset-nvram). It was worth a try. So I did this. Wow, what a transformation. Now my battery seems to hold a charge just like it did a month or two ago. I haen't gone through extensive tests, but it's workable again. Perhaps that other dead battery will come to life... -- report from a year later -- Battery life wasn't that great for very long. It decayed to be mostly unworkable after a month or two. After a year it was down to ten minutes or so, but never as bad as before. Yeah, I've bought a new battery. &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-6960384923092899678?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/6960384923092899678/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=6960384923092899678' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/6960384923092899678'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/6960384923092899678'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2006/05/powerbook-battery-rejuve.html' title='Powerbook Battery Rejuve'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-9170062630250012195</id><published>2006-04-09T23:06:00.000-07:00</published><updated>2007-11-01T23:13:51.856-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='html'/><category scheme='http://www.blogger.com/atom/ns#' term='css'/><title type='text'>Page layout options</title><content type='html'>I put together this little experiment to show the options and technique for doing different page alignment strategies: fixed width, stretch to the browser, or various hybrids you see around the web. &lt;p&gt; My question was,&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;can I just wrap a site in a div or two and then decide this later? &lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt; YES! &lt;/p&gt;&lt;p&gt; &lt;a href="http://ndpsoftware.com/experiment/css/stretch.php"&gt;http://ndpsoftware.com/experiment/css/stretch.php&lt;/a&gt;                     &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-9170062630250012195?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/9170062630250012195/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=9170062630250012195' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/9170062630250012195'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/9170062630250012195'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2007/11/page-layout-options.html' title='Page layout options'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-3623286247985003266</id><published>2006-01-06T23:02:00.000-08:00</published><updated>2007-11-01T23:14:37.658-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='style'/><category scheme='http://www.blogger.com/atom/ns#' term='coding'/><title type='text'>First grade first</title><content type='html'>Is using temporary variables was messy? I now advocate writing 1st grade code. By that I mean "See Dick run. See Jane smile." etc. Hey, we're all adults here, you say, why can't I write real code.&lt;br /&gt;&lt;br /&gt;Well, writing really simple code makes debugging easier. What do I mean? I've had a couple occasions in the last week  where debugging would have been easier if I'd writing really simple code. For example, I just got an null pointer exception here:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;          _dataSetsNeedingCleanup.add(new DataSetRef(state, dataSet.getId()));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I can't easily tell where the problem is coming from. It could be one or two places: my member variable _dataSetsNeedingCleanup could be null, or dataSet itself code be null. If I had written it like this,&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;          final DataSetRef dataSetRef = new DataSetRef(state, dataSet.getId());&lt;br /&gt;         _dataSetsNeedingCleanup.add(dataSetRef);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;a stack trace provided by the user tells me exactly what's wrong. This saves me a whole step in the debugging cycle!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-3623286247985003266?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/3623286247985003266/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=3623286247985003266' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/3623286247985003266'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/3623286247985003266'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2006/01/first-grade-first.html' title='First grade first'/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6501871454363042841.post-7331943600852010182</id><published>2005-04-01T23:08:00.000-08:00</published><updated>2007-11-01T23:11:23.629-07:00</updated><title type='text'></title><content type='html'>We posted on craigslist.org for a jr. java engineer and gotten the predictable hundreds of responses. I have seen all sorts of mistakes. Normally I wouldn't repeat this sort of information in a public forum, but what the heck, it's a blog.&lt;br /&gt;COVER LETTER&lt;br /&gt;Follow the directions. If it asks for a cover letter, send one. I kinda like this one: "Hi, Here is my resume. Thanks", but most people would find it too brief. Really long ones show you can't prioritize your work. Generic ones that don't match the listing generally disqualify you. If you're going to try to make the point that the job sounds "interesting", you MUST explain why. Nobody's done it yet. After reading it 100 times, "please email or call me at anytime" seems redundant.&lt;br /&gt;GENERAL&lt;br /&gt;Don't hide behind words. I'm only going to read 20 or so words when I skim your resume. The fewer words you have, the more likely I am to see the important ones. Have someone proofread. I'm waiting for a well-written letter. I don't care if you worked at the cineplex or best buy. Don't attempt more formatting than you can handle. Understand the limitations of MS Words or email before going wild with formatting buttons. It looks unprofessional if certain words or phrases are different sizes. Be aware that not everyone will have the same fonts and OS as you. One person put their resume into pdf, but somehow managed to completely mangle it.&lt;br /&gt;SUBTLETIES&lt;br /&gt;Don't cc lots of companies. Think about the name of your resume. "oldresume.doc" is curious. WORST TRANSLATION&lt;br /&gt;"References available on demand."&lt;br /&gt;BEST FOLLOW-UP&lt;br /&gt;"...Hey, good luck with that. Ya know, you'll probably find someone too, market being what it is. Sucks."&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6501871454363042841-7331943600852010182?l=blog.ndpsoftware.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ndpsoftware.com/feeds/7331943600852010182/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6501871454363042841&amp;postID=7331943600852010182' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/7331943600852010182'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6501871454363042841/posts/default/7331943600852010182'/><link rel='alternate' type='text/html' href='http://blog.ndpsoftware.com/2006/03/we-posted-on-craigslist.html' title=''/><author><name>ndp</name><uri>http://www.blogger.com/profile/03004498802371156834</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://4.bp.blogspot.com/_RZ6yXxZ1RUA/TRtZOxZzDiI/AAAAAAAAAxM/trl68W8dMAQ/S220/IMG_3095bw.jpg'/></author><thr:total>0</thr:total></entry></feed>
